From 5f006949b3f764867f6b3e0dec528a4dca927299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Thu, 15 Feb 2024 18:09:29 -0300 Subject: [PATCH 01/80] fix collapsed --- .../lib/edit/keyboardInput.ts | 8 +++- .../test/edit/keyboardInputTest.ts | 38 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts index 547a362f106..8c2176fd5c2 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts @@ -70,5 +70,11 @@ function shouldInputWithContentModel( } const shouldHandleEnterKey = (selection: DOMSelection | null, rawEvent: KeyboardEvent) => { - return selection && selection.type == 'range' && rawEvent.key == 'Enter' && !rawEvent.shiftKey; + return ( + selection && + selection.type == 'range' && + selection.range.collapsed && + rawEvent.key == 'Enter' && + !rawEvent.shiftKey + ); }; diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts index 1ea3063cea2..6eb707b0f84 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts @@ -430,6 +430,44 @@ describe('keyboardInput', () => { expect(normalizeContentModelSpy).toHaveBeenCalledWith(mockedModel); }); + it('Enter key input on expanded range', () => { + const mockedFormat = 'FORMAT' as any; + getDOMSelectionSpy.and.returnValue({ + type: 'range', + range: { + collapsed: false, + }, + }); + deleteSelectionSpy.and.returnValue({ + deleteResult: 'range', + insertPoint: { + marker: { + format: mockedFormat, + }, + }, + }); + + const rawEvent = { + key: 'Enter', + } as any; + + keyboardInput(editor, rawEvent); + + expect(getDOMSelectionSpy).toHaveBeenCalled(); + expect(takeSnapshotSpy).toHaveBeenCalled(); + expect(formatContentModelSpy).toHaveBeenCalled(); + expect(deleteSelectionSpy).toHaveBeenCalledWith(mockedModel, [], mockedContext); + expect(formatResult).toBeTrue(); + expect(mockedContext).toEqual({ + deletedEntities: [], + newEntities: [], + newImages: [], + clearModelCache: true, + skipUndoSnapshot: true, + newPendingFormat: mockedFormat, + }); + expect(normalizeContentModelSpy).toHaveBeenCalledWith(mockedModel); + }); it('Enter key input with IME', () => { const mockedFormat = 'FORMAT' as any; getDOMSelectionSpy.and.returnValue({ From a45988d0babb353b121d2608f9197050a66c2179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Thu, 15 Feb 2024 18:21:39 -0300 Subject: [PATCH 02/80] remove test --- .../test/edit/keyboardInputTest.ts | 74 ------------------- 1 file changed, 74 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts index 6eb707b0f84..9f1ce692316 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts @@ -468,78 +468,4 @@ describe('keyboardInput', () => { }); expect(normalizeContentModelSpy).toHaveBeenCalledWith(mockedModel); }); - it('Enter key input with IME', () => { - const mockedFormat = 'FORMAT' as any; - getDOMSelectionSpy.and.returnValue({ - type: 'range', - range: { - collapsed: true, - }, - }); - deleteSelectionSpy.and.returnValue({ - deleteResult: 'range', - insertPoint: { - marker: { - format: mockedFormat, - }, - }, - }); - isInIMESpy.and.returnValue(true); - - const rawEvent = { - key: 'Enter', - isComposing: false, - } as any; - - keyboardInput(editor, rawEvent); - - expect(getDOMSelectionSpy).toHaveBeenCalled(); - expect(takeSnapshotSpy).not.toHaveBeenCalled(); - expect(formatContentModelSpy).not.toHaveBeenCalled(); - expect(deleteSelectionSpy).not.toHaveBeenCalled(); - expect(formatResult).toBeUndefined(); - expect(mockedContext).toEqual({ - deletedEntities: [], - newEntities: [], - newImages: [], - }); - expect(normalizeContentModelSpy).not.toHaveBeenCalled(); - }); - - it('Enter key input with isComposing', () => { - const mockedFormat = 'FORMAT' as any; - getDOMSelectionSpy.and.returnValue({ - type: 'range', - range: { - collapsed: true, - }, - }); - deleteSelectionSpy.and.returnValue({ - deleteResult: 'range', - insertPoint: { - marker: { - format: mockedFormat, - }, - }, - }); - - const rawEvent = { - key: 'Enter', - isComposing: true, - } as any; - - keyboardInput(editor, rawEvent); - - expect(getDOMSelectionSpy).toHaveBeenCalled(); - expect(takeSnapshotSpy).not.toHaveBeenCalled(); - expect(formatContentModelSpy).not.toHaveBeenCalled(); - expect(deleteSelectionSpy).not.toHaveBeenCalled(); - expect(formatResult).toBeUndefined(); - expect(mockedContext).toEqual({ - deletedEntities: [], - newEntities: [], - newImages: [], - }); - expect(normalizeContentModelSpy).not.toHaveBeenCalled(); - }); }); From 8e1cc2febed4dbfbb4c63f030e598a29725f310b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Fri, 16 Feb 2024 19:33:41 -0300 Subject: [PATCH 03/80] fix list --- .../lib/edit/inputSteps/handleEnterOnList.ts | 38 +- .../lib/edit/keyboardInput.ts | 8 +- .../edit/inputSteps/handleEnterOnListTest.ts | 374 ++++++++++++++---- .../test/edit/keyboardInputTest.ts | 39 -- 4 files changed, 333 insertions(+), 126 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts index 3e5cca47e50..544162dbcc3 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts @@ -1,9 +1,13 @@ -import { getClosestAncestorBlockGroupIndex } from 'roosterjs-content-model-core'; +import { + getClosestAncestorBlockGroupIndex, + isBlockGroupOfType, +} from 'roosterjs-content-model-core'; import { createBr, createListItem, createListLevel, createParagraph, + normalizeContentModel, normalizeParagraph, setParagraphNotImplicit, } from 'roosterjs-content-model-dom'; @@ -30,13 +34,33 @@ export const handleEnterOnList: DeleteSelectionStep = context => { const index = getClosestAncestorBlockGroupIndex(path, ['ListItem'], ['TableCell']); const listItem = path[index]; + const listParent = path[index + 1]; if (listItem && listItem.blockGroupType === 'ListItem') { - const listParent = path[index + 1]; - if (isEmptyListItem(listItem)) { - listItem.levels.pop(); + const listIndex = listParent.blocks.indexOf(listItem); + const nextBlock = listParent.blocks[listIndex + 1]; + if (context.deleteResult == 'range' && nextBlock) { + normalizeContentModel(listParent); + const nextListItem = listParent.blocks[listIndex + 1]; + if ( + isBlockGroupOfType(nextListItem, 'ListItem') && + nextListItem.levels[0] + ) { + nextListItem.levels.forEach((level, index) => { + level.format.startNumberOverride = undefined; + level.dataset = listItem.levels[index] + ? listItem.levels[index].dataset + : {}; + }); + + context.lastParagraph = undefined; + } } else { - createNewListItem(context, listItem, listParent); + if (isEmptyListItem(listItem)) { + listItem.levels.pop(); + } else { + createNewListItem(context, listItem, listParent); + } } rawEvent?.preventDefault(); context.deleteResult = 'range'; @@ -62,9 +86,12 @@ const createNewListItem = ( const { insertPoint } = context; const listIndex = listParent.blocks.indexOf(listItem); const newParagraph = createNewParagraph(insertPoint); + const levels = createNewListLevel(listItem); const newListItem = createListItem(levels, insertPoint.marker.format); newListItem.blocks.push(newParagraph); + insertPoint.paragraph = newParagraph; + context.lastParagraph = newParagraph; listParent.blocks.splice(listIndex + 1, 0, newListItem); }; @@ -104,5 +131,6 @@ const createNewParagraph = (insertPoint: InsertPoint) => { } normalizeParagraph(newParagraph); + return newParagraph; }; diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts index 0f395f5cac9..b4404bc8db1 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts @@ -66,11 +66,5 @@ function shouldInputWithContentModel(selection: DOMSelection | null, rawEvent: K } const shouldHandleEnterKey = (selection: DOMSelection | null, rawEvent: KeyboardEvent) => { - return ( - selection && - selection.type == 'range' && - selection.range.collapsed && - rawEvent.key == 'Enter' && - !rawEvent.shiftKey - ); + return selection && selection.type == 'range' && rawEvent.key == 'Enter' && !rawEvent.shiftKey; }; diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts index f11408881aa..4277a3c3331 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts @@ -851,8 +851,9 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'fdsfsdf', + text: 'test', format: {}, + isSelected: true, }, ], format: {}, @@ -867,7 +868,7 @@ describe('handleEnterOnList', () => { listStyleType: 'decimal', }, dataset: { - editingInfo: '{"orderedStyleType":1,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -876,9 +877,7 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"1. "', - }, + format: {}, }, { blockType: 'BlockGroup', @@ -889,9 +888,8 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'fsdfsd', + text: 'test', format: {}, - isSelected: true, }, ], format: {}, @@ -906,7 +904,7 @@ describe('handleEnterOnList', () => { listStyleType: 'decimal', }, dataset: { - editingInfo: '{"orderedStyleType":1,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -915,9 +913,53 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"2. "', + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Br', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + marginTop: '0px', + marginBottom: '0px', + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, }, + format: {}, }, { blockType: 'BlockGroup', @@ -928,7 +970,7 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'fsdf', + text: 'test', format: {}, }, ], @@ -942,9 +984,10 @@ describe('handleEnterOnList', () => { marginTop: '0px', marginBottom: '0px', listStyleType: 'decimal', + startNumberOverride: undefined, }, dataset: { - editingInfo: '{"orderedStyleType":1,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -953,14 +996,16 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"3. "', - }, + format: {}, }, ], format: {}, }; - const expectedModel: ContentModelDocument = { + runTest(model, expectedModel, 'range'); + }); + + it('enter on multiple list items with selected text', () => { + const model: ContentModelDocument = { blockGroupType: 'Document', blocks: [ { @@ -972,7 +1017,7 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'fdsfsdf', + text: 'test', format: {}, }, ], @@ -988,7 +1033,7 @@ describe('handleEnterOnList', () => { listStyleType: 'decimal', }, dataset: { - editingInfo: '{"orderedStyleType":1,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -997,9 +1042,49 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"1. "', + format: {}, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + marginTop: '0px', + marginBottom: '0px', + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, }, + format: {}, }, { blockType: 'BlockGroup', @@ -1009,7 +1094,14 @@ describe('handleEnterOnList', () => { blockType: 'Paragraph', segments: [ { - segmentType: 'Br', + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, format: {}, }, ], @@ -1025,7 +1117,7 @@ describe('handleEnterOnList', () => { listStyleType: 'decimal', }, dataset: { - editingInfo: '{"orderedStyleType":1,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -1034,9 +1126,85 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"2. "', + format: {}, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + marginTop: '0px', + marginBottom: '0px', + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, }, + format: {}, + }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + marginTop: '0px', + marginBottom: '0px', + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, }, { blockType: 'BlockGroup', @@ -1065,10 +1233,9 @@ describe('handleEnterOnList', () => { marginTop: '0px', marginBottom: '0px', listStyleType: 'decimal', - startNumberOverride: undefined, }, dataset: { - editingInfo: '{"orderedStyleType":1,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -1088,7 +1255,7 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'fsdf', + text: 'test', format: {}, }, ], @@ -1102,9 +1269,10 @@ describe('handleEnterOnList', () => { marginTop: '0px', marginBottom: '0px', listStyleType: 'decimal', + startNumberOverride: undefined, }, dataset: { - editingInfo: '{"orderedStyleType":1,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -1113,9 +1281,7 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"3. "', - }, + format: {}, }, ], format: {}, @@ -1123,7 +1289,7 @@ describe('handleEnterOnList', () => { runTest(model, expectedModel, 'range'); }); - it('enter on multiple list items with selected text', () => { + it('expanded range mixed list with paragraph', () => { const model: ContentModelDocument = { blockGroupType: 'Document', blocks: [ @@ -1136,9 +1302,15 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'test', + text: 'te', format: {}, }, + { + segmentType: 'Text', + text: 'st', + format: {}, + isSelected: true, + }, ], format: {}, }, @@ -1149,9 +1321,10 @@ describe('handleEnterOnList', () => { format: { marginTop: '0px', marginBottom: '0px', + listStyleType: 'decimal', }, dataset: { - editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -1160,9 +1333,19 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"1) "', - }, + format: {}, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: {}, }, { blockType: 'BlockGroup', @@ -1172,15 +1355,15 @@ describe('handleEnterOnList', () => { blockType: 'Paragraph', segments: [ { - segmentType: 'SelectionMarker', - isSelected: true, + segmentType: 'Text', + text: 'te', format: {}, + isSelected: true, }, { segmentType: 'Text', - text: 'test', + text: 'st', format: {}, - isSelected: true, }, ], format: {}, @@ -1190,11 +1373,13 @@ describe('handleEnterOnList', () => { { listType: 'OL', format: { + startNumberOverride: 1, marginTop: '0px', marginBottom: '0px', + listStyleType: 'decimal', }, dataset: { - editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -1203,10 +1388,14 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"2) "', - }, + format: {}, }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ { blockType: 'BlockGroup', blockGroupType: 'ListItem', @@ -1216,9 +1405,8 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'test', + text: 'te', format: {}, - isSelected: true, }, { segmentType: 'SelectionMarker', @@ -1235,9 +1423,10 @@ describe('handleEnterOnList', () => { format: { marginTop: '0px', marginBottom: '0px', + listStyleType: 'decimal', }, dataset: { - editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -1246,9 +1435,7 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"3) "', - }, + format: {}, }, { blockType: 'BlockGroup', @@ -1259,7 +1446,7 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'test', + text: 'st', format: {}, }, ], @@ -1272,9 +1459,11 @@ describe('handleEnterOnList', () => { format: { marginTop: '0px', marginBottom: '0px', + listStyleType: 'decimal', + startNumberOverride: undefined, }, dataset: { - editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":1}', }, }, ], @@ -1283,14 +1472,16 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"4) "', - }, + format: {}, }, ], format: {}, }; - const expectedModel: ContentModelDocument = { + runTest(model, expectedModel, 'range'); + }); + + it('expanded range with mixed list with paragraph | different styles', () => { + const model: ContentModelDocument = { blockGroupType: 'Document', blocks: [ { @@ -1302,9 +1493,15 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'test', + text: 'te', format: {}, }, + { + segmentType: 'Text', + text: 'st', + format: {}, + isSelected: true, + }, ], format: {}, }, @@ -1317,7 +1514,7 @@ describe('handleEnterOnList', () => { marginBottom: '0px', }, dataset: { - editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":3}', }, }, ], @@ -1330,6 +1527,18 @@ describe('handleEnterOnList', () => { listStyleType: '"1) "', }, }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: {}, + }, { blockType: 'BlockGroup', blockGroupType: 'ListItem', @@ -1338,7 +1547,14 @@ describe('handleEnterOnList', () => { blockType: 'Paragraph', segments: [ { - segmentType: 'Br', + segmentType: 'Text', + text: 'te', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + text: 'st ', format: {}, }, ], @@ -1349,11 +1565,13 @@ describe('handleEnterOnList', () => { { listType: 'OL', format: { + startNumberOverride: 1, marginTop: '0px', marginBottom: '0px', + listStyleType: 'lower-alpha', }, dataset: { - editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":5}', }, }, ], @@ -1362,10 +1580,14 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"2) "', - }, + format: {}, }, + ], + format: {}, + }; + const expectedModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ { blockType: 'BlockGroup', blockGroupType: 'ListItem', @@ -1374,12 +1596,13 @@ describe('handleEnterOnList', () => { blockType: 'Paragraph', segments: [ { - segmentType: 'SelectionMarker', - isSelected: true, + segmentType: 'Text', + text: 'te', format: {}, }, { - segmentType: 'Br', + segmentType: 'SelectionMarker', + isSelected: true, format: {}, }, ], @@ -1392,10 +1615,9 @@ describe('handleEnterOnList', () => { format: { marginTop: '0px', marginBottom: '0px', - startNumberOverride: undefined, }, dataset: { - editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":3}', }, }, ], @@ -1404,7 +1626,9 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: {}, + format: { + listStyleType: '"1) "', + }, }, { blockType: 'BlockGroup', @@ -1415,7 +1639,7 @@ describe('handleEnterOnList', () => { segments: [ { segmentType: 'Text', - text: 'test', + text: 'st', format: {}, }, ], @@ -1428,9 +1652,11 @@ describe('handleEnterOnList', () => { format: { marginTop: '0px', marginBottom: '0px', + startNumberOverride: undefined, + listStyleType: 'lower-alpha', }, dataset: { - editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', + editingInfo: '{"orderedStyleType":3}', }, }, ], @@ -1439,9 +1665,7 @@ describe('handleEnterOnList', () => { isSelected: true, format: {}, }, - format: { - listStyleType: '"4) "', - }, + format: {}, }, ], format: {}, diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts index 9f1ce692316..7db310df950 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts @@ -429,43 +429,4 @@ describe('keyboardInput', () => { }); expect(normalizeContentModelSpy).toHaveBeenCalledWith(mockedModel); }); - - it('Enter key input on expanded range', () => { - const mockedFormat = 'FORMAT' as any; - getDOMSelectionSpy.and.returnValue({ - type: 'range', - range: { - collapsed: false, - }, - }); - deleteSelectionSpy.and.returnValue({ - deleteResult: 'range', - insertPoint: { - marker: { - format: mockedFormat, - }, - }, - }); - - const rawEvent = { - key: 'Enter', - } as any; - - keyboardInput(editor, rawEvent); - - expect(getDOMSelectionSpy).toHaveBeenCalled(); - expect(takeSnapshotSpy).toHaveBeenCalled(); - expect(formatContentModelSpy).toHaveBeenCalled(); - expect(deleteSelectionSpy).toHaveBeenCalledWith(mockedModel, [], mockedContext); - expect(formatResult).toBeTrue(); - expect(mockedContext).toEqual({ - deletedEntities: [], - newEntities: [], - newImages: [], - clearModelCache: true, - skipUndoSnapshot: true, - newPendingFormat: mockedFormat, - }); - expect(normalizeContentModelSpy).toHaveBeenCalledWith(mockedModel); - }); }); From 8f3f0dafcada92059d59b980cc05b85197f02076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Mon, 19 Feb 2024 18:52:59 -0300 Subject: [PATCH 04/80] remove margins --- .../lib/modelApi/list/setListType.ts | 3 +- .../test/modelApi/list/setListTypeTest.ts | 31 +------- .../autoFormat/keyboardListTriggerTest.ts | 76 ++++--------------- 3 files changed, 17 insertions(+), 93 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts b/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts index c824a5a1bbe..a63ec869014 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts @@ -76,8 +76,6 @@ export function setListType(model: ContentModelDocument, listType: 'OL' | 'UL') : 1, direction: block.format.direction, textAlign: block.format.textAlign, - marginTop: '0px', - marginBottom: '0px', }), ], // For list bullet, we only want to carry over these formats from segments: @@ -98,6 +96,7 @@ export function setListType(model: ContentModelDocument, listType: 'OL' | 'UL') newListItem.format.marginRight = block.format.marginRight; block.format.marginRight = undefined; } + if (block.format.marginLeft) { newListItem.format.marginLeft = block.format.marginLeft; block.format.marginLeft = undefined; diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts b/packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts index b457c35abff..0f603ab6287 100644 --- a/packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts @@ -75,8 +75,6 @@ describe('indent', () => { startNumberOverride: 1, direction: undefined, textAlign: undefined, - marginTop: '0px', - marginBottom: '0px', }, dataset: {}, }, @@ -302,8 +300,6 @@ describe('indent', () => { startNumberOverride: undefined, direction: undefined, textAlign: undefined, - marginTop: '0px', - marginBottom: '0px', }, dataset: {}, }, @@ -366,8 +362,6 @@ describe('indent', () => { startNumberOverride: 1, direction: 'rtl', textAlign: 'start', - marginTop: '0px', - marginBottom: '0px', }, }, ], @@ -447,7 +441,6 @@ describe('indent', () => { direction: undefined, textAlign: undefined, marginBottom: '0px', - marginTop: '0px', }, }, ], @@ -475,8 +468,6 @@ describe('indent', () => { direction: undefined, textAlign: undefined, startNumberOverride: undefined, - marginTop: '0px', - marginBottom: '0px', }, }, ], @@ -530,8 +521,6 @@ describe('indent', () => { startNumberOverride: 1, direction: undefined, textAlign: undefined, - marginTop: '0px', - marginBottom: '0px', }, }, ], @@ -587,8 +576,6 @@ describe('indent', () => { startNumberOverride: 1, direction: undefined, textAlign: undefined, - marginTop: '0px', - marginBottom: '0px', }, }, ], @@ -615,8 +602,6 @@ describe('indent', () => { startNumberOverride: undefined, direction: undefined, textAlign: undefined, - marginTop: '0px', - marginBottom: '0px', }, }, ], @@ -643,8 +628,6 @@ describe('indent', () => { direction: undefined, textAlign: undefined, startNumberOverride: undefined, - marginTop: '0px', - marginBottom: '0px', }, }, ], @@ -717,8 +700,6 @@ describe('indent', () => { startNumberOverride: 1, direction: undefined, textAlign: undefined, - marginTop: '0px', - marginBottom: '0px', }, dataset: {}, }, @@ -747,8 +728,6 @@ describe('indent', () => { startNumberOverride: undefined, direction: undefined, textAlign: undefined, - marginTop: '0px', - marginBottom: '0px', }, dataset: {}, }, @@ -799,10 +778,7 @@ describe('indent', () => { levels: [ { listType: 'UL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: {}, }, ], @@ -840,10 +816,7 @@ describe('indent', () => { levels: [ { listType: 'UL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: {}, }, ], diff --git a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts index 2f302ae4d9a..93a7c9c8d1a 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts @@ -95,8 +95,6 @@ describe('keyboardListTrigger', () => { { listType: 'OL', format: { - marginTop: '0px', - marginBottom: '0px', startNumberOverride: 1, direction: undefined, textAlign: undefined, @@ -148,10 +146,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', }, @@ -207,10 +202,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3,"unorderedStyleType":1}', }, @@ -248,8 +240,6 @@ describe('keyboardListTrigger', () => { startNumberOverride: 2, direction: undefined, textAlign: undefined, - marginBottom: '0px', - marginTop: '0px', }, dataset: { editingInfo: '{"orderedStyleType":3}', @@ -373,8 +363,6 @@ describe('keyboardListTrigger', () => { { listType: 'UL', format: { - marginTop: '0px', - marginBottom: '0px', startNumberOverride: 1, direction: undefined, textAlign: undefined, @@ -477,10 +465,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3}', }, @@ -514,10 +499,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3}', }, @@ -579,8 +561,6 @@ describe('keyboardListTrigger', () => { listType: 'OL', format: { startNumberOverride: 1, - marginTop: '0px', - marginBottom: '0px', }, dataset: { editingInfo: '{"orderedStyleType":10}', @@ -615,10 +595,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":10}', }, @@ -659,10 +636,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3}', }, @@ -696,10 +670,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3}', }, @@ -735,8 +706,7 @@ describe('keyboardListTrigger', () => { listType: 'OL', format: { startNumberOverride: 3, - marginTop: '0px', - marginBottom: '0px', + direction: undefined, textAlign: undefined, }, @@ -787,8 +757,6 @@ describe('keyboardListTrigger', () => { listType: 'OL', format: { startNumberOverride: 1, - marginTop: '0px', - marginBottom: '0px', }, dataset: { editingInfo: '{"orderedStyleType":10}', @@ -823,10 +791,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":10}', }, @@ -872,10 +837,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3}', }, @@ -909,10 +871,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3}', }, @@ -978,10 +937,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3}', }, @@ -1015,10 +971,7 @@ describe('keyboardListTrigger', () => { levels: [ { listType: 'OL', - format: { - marginTop: '0px', - marginBottom: '0px', - }, + format: {}, dataset: { editingInfo: '{"orderedStyleType":3}', }, @@ -1064,8 +1017,7 @@ describe('keyboardListTrigger', () => { listType: 'OL', format: { startNumberOverride: 1, - marginTop: '0px', - marginBottom: '0px', + direction: undefined, textAlign: undefined, }, From 41eb153dd9803b245d12387d77afbe7e4f2751df Mon Sep 17 00:00:00 2001 From: Ian Elizondo Date: Mon, 19 Feb 2024 21:10:45 -0600 Subject: [PATCH 05/80] Workaround for no mouseUp event (#2423) --- .../lib/corePlugin/SelectionPlugin.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts index 87a0efb2370..286ef0b57e6 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts @@ -17,6 +17,7 @@ class SelectionPlugin implements PluginWithState { private state: SelectionPluginState; private disposer: (() => void) | null = null; private isSafari = false; + private isMac = false; constructor(options: StandaloneEditorOptions) { this.state = { @@ -43,6 +44,7 @@ class SelectionPlugin implements PluginWithState { const document = this.editor.getDocument(); this.isSafari = !!env.isSafari; + this.isMac = !!env.isMac; if (this.isSafari) { document.addEventListener('selectionchange', this.onSelectionChangeSafari); @@ -91,7 +93,9 @@ class SelectionPlugin implements PluginWithState { (image = this.getClickingImage(event.rawEvent)) && image.isContentEditable && event.rawEvent.button != MouseMiddleButton && - event.isClicking + (event.rawEvent.button == + MouseRightButton /* it's not possible to drag using right click */ || + event.isClicking) ) { this.selectImage(this.editor, image); } @@ -101,7 +105,9 @@ class SelectionPlugin implements PluginWithState { selection = this.editor.getDOMSelection(); if ( event.rawEvent.button === MouseRightButton && - (image = this.getClickingImage(event.rawEvent)) && + (image = + this.getClickingImage(event.rawEvent) ?? + this.getContainedTargetImage(event.rawEvent, selection)) && image.isContentEditable ) { this.selectImage(this.editor, image); @@ -168,6 +174,27 @@ class SelectionPlugin implements PluginWithState { : null; } + //MacOS will not create a mouseUp event if contextMenu event is not prevent defaulted. + //Make sure we capture image target even if image is wrapped + private getContainedTargetImage = ( + event: MouseEvent, + previousSelection: DOMSelection | null + ): HTMLImageElement | null => { + if (!this.isMac || !previousSelection || previousSelection.type !== 'image') { + return null; + } + + const target = event.target as Node; + if ( + isNodeOfType(target, 'ELEMENT_NODE') && + isElementOfType(target, 'span') && + target.firstChild === previousSelection.image + ) { + return previousSelection.image; + } + return null; + }; + private onFocus = () => { if (!this.state.skipReselectOnFocus && this.state.selection) { this.editor?.setDOMSelection(this.state.selection); From 26fd80d270ebf366764e8dfa17c8700e4c451365 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Mon, 19 Feb 2024 22:20:21 -0800 Subject: [PATCH 06/80] Rename StandaloneEditor to Editor (#2416) * Rename StandaloneEditor to Editor * fix build --- .../controls/ContentModelEditorMainPane.tsx | 8 +- .../controls/StandaloneEditorMainPane.tsx | 12 +- .../editor/ContentModelRooster.tsx | 10 +- .../ContentModelFormatPainterPlugin.ts | 8 +- .../contentModel/ContentModelRibbonButton.ts | 4 +- .../contentModel/ContentModelRibbonPlugin.ts | 10 +- .../contentModel/ContentModelPanePlugin.ts | 15 +-- .../insertEntity/InsertEntityPane.tsx | 8 +- .../ContentModelFormatStatePlugin.ts | 6 +- .../snapshot/ContentModelSnapshotPlugin.tsx | 10 +- .../lib/publicApi/block/setAlignment.ts | 4 +- .../lib/publicApi/block/setDirection.ts | 4 +- .../lib/publicApi/block/setHeadingLevel.ts | 10 +- .../lib/publicApi/block/setIndentation.ts | 4 +- .../lib/publicApi/block/setParagraphMargin.ts | 4 +- .../lib/publicApi/block/setSpacing.ts | 4 +- .../lib/publicApi/block/toggleBlockQuote.ts | 7 +- .../lib/publicApi/entity/insertEntity.ts | 12 +- .../lib/publicApi/format/clearFormat.ts | 4 +- .../lib/publicApi/format/getFormatState.ts | 4 +- .../publicApi/image/adjustImageSelection.ts | 4 +- .../lib/publicApi/image/changeImage.ts | 4 +- .../lib/publicApi/image/insertImage.ts | 6 +- .../lib/publicApi/image/setImageAltText.ts | 4 +- .../lib/publicApi/image/setImageBorder.ts | 4 +- .../lib/publicApi/image/setImageBoxShadow.ts | 4 +- .../lib/publicApi/link/adjustLinkSelection.ts | 4 +- .../lib/publicApi/link/insertLink.ts | 4 +- .../lib/publicApi/link/removeLink.ts | 4 +- .../lib/publicApi/list/setListStartNumber.ts | 4 +- .../lib/publicApi/list/setListStyle.ts | 4 +- .../lib/publicApi/list/toggleBullet.ts | 4 +- .../lib/publicApi/list/toggleNumbering.ts | 4 +- .../publicApi/segment/applySegmentFormat.ts | 7 +- .../publicApi/segment/changeCapitalization.ts | 4 +- .../lib/publicApi/segment/changeFontSize.ts | 4 +- .../publicApi/segment/setBackgroundColor.ts | 7 +- .../lib/publicApi/segment/setFontName.ts | 4 +- .../lib/publicApi/segment/setFontSize.ts | 4 +- .../lib/publicApi/segment/setTextColor.ts | 4 +- .../lib/publicApi/segment/toggleBold.ts | 4 +- .../lib/publicApi/segment/toggleCode.ts | 4 +- .../lib/publicApi/segment/toggleItalic.ts | 4 +- .../publicApi/segment/toggleStrikethrough.ts | 4 +- .../lib/publicApi/segment/toggleSubscript.ts | 4 +- .../publicApi/segment/toggleSuperscript.ts | 4 +- .../lib/publicApi/segment/toggleUnderline.ts | 4 +- .../publicApi/table/applyTableBorderFormat.ts | 4 +- .../lib/publicApi/table/editTable.ts | 4 +- .../lib/publicApi/table/formatTable.ts | 4 +- .../lib/publicApi/table/insertTable.ts | 4 +- .../lib/publicApi/table/setTableCellShade.ts | 4 +- .../utils/formatImageWithContentModel.ts | 4 +- .../utils/formatParagraphWithContentModel.ts | 4 +- .../utils/formatSegmentWithContentModel.ts | 4 +- .../utils/formatTableWithContentModel.ts | 8 +- .../publicApi/block/paragraphTestCommon.ts | 6 +- .../test/publicApi/block/setAlignmentTest.ts | 10 +- .../publicApi/block/setIndentationTest.ts | 6 +- .../publicApi/block/toggleBlockQuoteTest.ts | 6 +- .../test/publicApi/entity/insertEntityTest.ts | 4 +- .../test/publicApi/format/clearFormatTest.ts | 4 +- .../publicApi/format/getFormatStateTest.ts | 4 +- .../test/publicApi/image/changeImageTest.ts | 6 +- .../test/publicApi/image/insertImageTest.ts | 6 +- .../publicApi/link/adjustLinkSelectionTest.ts | 6 +- .../test/publicApi/link/insertLinkTest.ts | 10 +- .../test/publicApi/link/removeLinkTest.ts | 6 +- .../test/publicApi/list/toggleBulletTest.ts | 6 +- .../publicApi/list/toggleNumberingTest.ts | 6 +- .../publicApi/segment/changeFontSizeTest.ts | 4 +- .../publicApi/segment/segmentTestCommon.ts | 6 +- .../table/applyTableBorderFormatTest.ts | 6 +- .../test/publicApi/table/editTableTest.ts | 4 +- .../publicApi/table/setTableCellShadeTest.ts | 6 +- .../utils/formatImageWithContentModelTest.ts | 6 +- .../formatParagraphWithContentModelTest.ts | 6 +- .../formatSegmentWithContentModelTest.ts | 6 +- .../utils/formatTableWithContentModelTest.ts | 4 +- .../lib/coreApi/addUndoSnapshot.ts | 2 +- .../lib/coreApi/attachDomEvent.ts | 2 +- .../lib/coreApi/createEditorContext.ts | 8 +- .../lib/coreApi/focus.ts | 2 +- .../lib/coreApi/formatContentModel.ts | 8 +- .../lib/coreApi/getDOMSelection.ts | 8 +- .../lib/coreApi/getVisibleViewport.ts | 2 +- .../lib/coreApi/hasFocus.ts | 2 +- .../lib/coreApi/paste.ts | 6 +- .../lib/coreApi/switchShadowEdit.ts | 2 +- .../lib/coreApi/triggerEvent.ts | 2 +- .../lib/corePlugin/CachePlugin.ts | 14 +-- .../lib/corePlugin/ContextMenuPlugin.ts | 14 +-- .../lib/corePlugin/CopyPastePlugin.ts | 12 +- .../lib/corePlugin/DOMEventPlugin.ts | 12 +- .../lib/corePlugin/EntityPlugin.ts | 16 +-- .../lib/corePlugin/FormatPlugin.ts | 16 ++- .../lib/corePlugin/LifecyclePlugin.ts | 12 +- .../lib/corePlugin/SelectionPlugin.ts | 16 +-- .../lib/corePlugin/UndoPlugin.ts | 22 ++-- ...ePlugins.ts => createEditorCorePlugins.ts} | 13 +-- .../corePlugin/utils/applyDefaultFormat.ts | 11 +- .../corePlugin/utils/applyPendingFormat.ts | 4 +- .../corePlugin/utils/entityDelimiterUtils.ts | 10 +- .../editor/{StandaloneEditor.ts => Editor.ts} | 22 ++-- ...{standaloneCoreApiMap.ts => coreApiMap.ts} | 6 +- ...aloneEditorCore.ts => createEditorCore.ts} | 31 ++--- ...ings.ts => createEditorDefaultSettings.ts} | 10 +- .../roosterjs-content-model-core/lib/index.ts | 2 +- .../lib/publicApi/model/exportContent.ts | 4 +- .../lib/publicApi/undo/redo.ts | 4 +- .../lib/publicApi/undo/undo.ts | 4 +- .../lib/utils/createSnapshotSelection.ts | 4 +- .../paste/generatePasteOptionFromPlugins.ts | 4 +- .../lib/utils/paste/mergePasteContent.ts | 4 +- .../lib/utils/restoreSnapshotColors.ts | 4 +- .../lib/utils/restoreSnapshotHTML.ts | 4 +- .../lib/utils/restoreSnapshotSelection.ts | 4 +- .../test/coreApi/addUndoSnapshotTest.ts | 4 +- .../test/coreApi/attachDomEventTest.ts | 4 +- .../test/coreApi/createContentModelTest.ts | 6 +- .../test/coreApi/createEditorContextTest.ts | 16 +-- .../test/coreApi/focusTest.ts | 4 +- .../test/coreApi/formatContentModelTest.ts | 6 +- .../test/coreApi/getDOMSelectionTest.ts | 4 +- .../test/coreApi/hasFocusTest.ts | 4 +- .../test/coreApi/pasteTest.ts | 16 +-- .../test/coreApi/restoreUndoSnapshotTest.ts | 4 +- .../test/coreApi/setContentModelTest.ts | 6 +- .../test/coreApi/setDOMSelectionTest.ts | 4 +- .../test/coreApi/switchShadowEditTest.ts | 6 +- .../test/coreApi/triggerEventTest.ts | 4 +- .../test/corePlugin/CachePluginTest.ts | 10 +- .../test/corePlugin/ContextMenuPluginTest.ts | 6 +- .../test/corePlugin/CopyPastePluginTest.ts | 6 +- .../test/corePlugin/DomEventPluginTest.ts | 18 ++- .../test/corePlugin/EntityPluginTest.ts | 4 +- .../test/corePlugin/FormatPluginTest.ts | 20 ++-- .../test/corePlugin/LifecyclePluginTest.ts | 16 +-- .../test/corePlugin/SelectionPluginTest.ts | 16 +-- .../test/corePlugin/UndoPluginTest.ts | 4 +- .../utils/applyDefaultFormatTest.ts | 4 +- .../utils/applyPendingFormatTest.ts | 12 +- .../corePlugin/utils/delimiterUtilsTest.ts | 12 +- ...{StandaloneEditorTest.ts => EditorTest.ts} | 61 +++++----- ...torCoreTest.ts => createEditorCoreTest.ts} | 36 +++--- ....ts => createEditorDefaultSettingsTest.ts} | 2 +- .../test/publicApi/model/exportContentTest.ts | 8 +- .../test/publicApi/undo/redoTest.ts | 4 +- .../test/publicApi/undo/undoTest.ts | 4 +- .../test/utils/createSnapshotSelectionTest.ts | 8 +- .../generatePasteOptionFromPluginsTest.ts | 4 +- .../test/utils/paste/mergePasteContentTest.ts | 4 +- .../test/utils/restoreSnapshotColorsTest.ts | 4 +- .../test/utils/restoreSnapshotHTMLTest.ts | 4 +- .../utils/restoreSnapshotSelectionTest.ts | 4 +- .../lib/autoFormat/AutoFormatPlugin.ts | 8 +- .../lib/autoFormat/keyboardListTrigger.ts | 7 +- .../lib/edit/EditPlugin.ts | 12 +- .../lib/edit/handleKeyboardEventCommon.ts | 4 +- .../lib/edit/keyboardDelete.ts | 10 +- .../lib/edit/keyboardInput.ts | 4 +- .../lib/edit/keyboardTab.ts | 4 +- .../lib/paste/PastePlugin.ts | 7 +- .../test/autoFormat/AutoFormatPluginTest.ts | 6 +- .../test/edit/EditPluginTest.ts | 6 +- .../test/edit/editingTestCommon.ts | 6 +- .../edit/handleKeyboardEventCommonTest.ts | 6 +- .../test/edit/keyboardDeleteTest.ts | 8 +- .../test/edit/keyboardInputTest.ts | 4 +- .../test/paste/ContentModelPastePluginTest.ts | 6 +- .../paste/e2e/cmPasteFromExcelOnlineTest.ts | 4 +- .../test/paste/e2e/cmPasteFromExcelTest.ts | 4 +- .../test/paste/e2e/cmPasteFromWacTest.ts | 4 +- .../test/paste/e2e/cmPasteFromWordTest.ts | 4 +- .../test/paste/e2e/cmPasteTest.ts | 4 +- .../test/paste/e2e/testUtils.ts | 14 +-- ...{StandaloneEditorCore.ts => EditorCore.ts} | 108 ++++++++---------- ...torCorePlugins.ts => EditorCorePlugins.ts} | 4 +- ...aloneEditorOptions.ts => EditorOptions.ts} | 8 +- .../lib/editor/EditorPlugin.ts | 4 +- .../{IStandaloneEditor.ts => IEditor.ts} | 5 +- .../lib/index.ts | 12 +- .../lib/pluginState/PluginState.ts | 8 +- .../lib/createEditor.ts | 12 +- .../lib/corePlugins/BridgePlugin.ts | 12 +- .../lib/editor/EditorAdapter.ts | 14 +-- .../lib/publicTypes/EditorAdapterOptions.ts | 4 +- .../test/editor/EditorAdapterTest.ts | 8 +- 188 files changed, 674 insertions(+), 777 deletions(-) rename packages-content-model/roosterjs-content-model-core/lib/corePlugin/{createStandaloneEditorCorePlugins.ts => createEditorCorePlugins.ts} (79%) rename packages-content-model/roosterjs-content-model-core/lib/editor/{StandaloneEditor.ts => Editor.ts} (95%) rename packages-content-model/roosterjs-content-model-core/lib/editor/{standaloneCoreApiMap.ts => coreApiMap.ts} (88%) rename packages-content-model/roosterjs-content-model-core/lib/editor/{createStandaloneEditorCore.ts => createEditorCore.ts} (85%) rename packages-content-model/roosterjs-content-model-core/lib/editor/{createStandaloneEditorDefaultSettings.ts => createEditorDefaultSettings.ts} (91%) rename packages-content-model/roosterjs-content-model-core/test/editor/{StandaloneEditorTest.ts => EditorTest.ts} (94%) rename packages-content-model/roosterjs-content-model-core/test/editor/{createStandaloneEditorCoreTest.ts => createEditorCoreTest.ts} (90%) rename packages-content-model/roosterjs-content-model-core/test/editor/{createStandaloneEditorDefaultSettingsTest.ts => createEditorDefaultSettingsTest.ts} (98%) rename packages-content-model/roosterjs-content-model-types/lib/editor/{StandaloneEditorCore.ts => EditorCore.ts} (79%) rename packages-content-model/roosterjs-content-model-types/lib/editor/{StandaloneEditorCorePlugins.ts => EditorCorePlugins.ts} (96%) rename packages-content-model/roosterjs-content-model-types/lib/editor/{StandaloneEditorOptions.ts => EditorOptions.ts} (94%) rename packages-content-model/roosterjs-content-model-types/lib/editor/{IStandaloneEditor.ts => IEditor.ts} (97%) diff --git a/demo/scripts/controls/ContentModelEditorMainPane.tsx b/demo/scripts/controls/ContentModelEditorMainPane.tsx index c7f0fb2bead..f8999ff58f5 100644 --- a/demo/scripts/controls/ContentModelEditorMainPane.tsx +++ b/demo/scripts/controls/ContentModelEditorMainPane.tsx @@ -80,11 +80,7 @@ import { trustedHTMLHandler } from '../utils/trustedHTMLHandler'; import { underlineButton } from './ribbonButtons/contentModel/underlineButton'; import { undoButton } from './ribbonButtons/contentModel/undoButton'; import { zoom } from './ribbonButtons/contentModel/zoom'; -import { - ContentModelSegmentFormat, - IStandaloneEditor, - Snapshots, -} from 'roosterjs-content-model-types'; +import { ContentModelSegmentFormat, IEditor, Snapshots } from 'roosterjs-content-model-types'; import { spaceAfterButton, spaceBeforeButton, @@ -155,7 +151,7 @@ const DarkTheme: PartialTheme = { }; interface ContentModelMainPaneState extends MainPaneBaseState { - editorCreator: (div: HTMLDivElement, options: EditorAdapterOptions) => IStandaloneEditor; + editorCreator: (div: HTMLDivElement, options: EditorAdapterOptions) => IEditor; } class ContentModelEditorMainPane extends MainPaneBase { diff --git a/demo/scripts/controls/StandaloneEditorMainPane.tsx b/demo/scripts/controls/StandaloneEditorMainPane.tsx index e2bf0f19e7b..a55e13cbe73 100644 --- a/demo/scripts/controls/StandaloneEditorMainPane.tsx +++ b/demo/scripts/controls/StandaloneEditorMainPane.tsx @@ -30,6 +30,7 @@ import { ContentModelRibbonPlugin } from './ribbonButtons/contentModel/ContentMo import { darkMode } from './ribbonButtons/contentModel/darkMode'; import { decreaseFontSizeButton } from './ribbonButtons/contentModel/decreaseFontSizeButton'; import { decreaseIndentButton } from './ribbonButtons/contentModel/decreaseIndentButton'; +import { Editor } from 'roosterjs-content-model-core'; import { exportContent } from './ribbonButtons/contentModel/export'; import { fontButton } from './ribbonButtons/contentModel/fontButton'; import { fontSizeButton } from './ribbonButtons/contentModel/fontSizeButton'; @@ -63,7 +64,6 @@ import { setTableCellShadeButton } from './ribbonButtons/contentModel/setTableCe import { setTableHeaderButton } from './ribbonButtons/contentModel/setTableHeaderButton'; import { Snapshots } from 'roosterjs-editor-types'; import { spacingButton } from './ribbonButtons/contentModel/spacingButton'; -import { StandaloneEditor } from 'roosterjs-content-model-core'; import { strikethroughButton } from './ribbonButtons/contentModel/strikethroughButton'; import { subscriptButton } from './ribbonButtons/contentModel/subscriptButton'; import { superscriptButton } from './ribbonButtons/contentModel/superscriptButton'; @@ -78,9 +78,9 @@ import { undoButton } from './ribbonButtons/contentModel/undoButton'; import { zoom } from './ribbonButtons/contentModel/zoom'; import { ContentModelSegmentFormat, - IStandaloneEditor, + IEditor, Snapshot, - StandaloneEditorOptions, + EditorOptions, } from 'roosterjs-content-model-types'; import { spaceAfterButton, @@ -152,7 +152,7 @@ const DarkTheme: PartialTheme = { }; interface ContentModelMainPaneState extends MainPaneBaseState { - editorCreator: (div: HTMLDivElement, options: StandaloneEditorOptions) => IStandaloneEditor; + editorCreator: (div: HTMLDivElement, options: EditorOptions) => IEditor; } class ContentModelEditorMainPane extends MainPaneBase { @@ -305,8 +305,8 @@ class ContentModelEditorMainPane extends MainPaneBase resetEditor() { this.setState({ - editorCreator: (div: HTMLDivElement, options: StandaloneEditorOptions) => - new StandaloneEditor(div, { + editorCreator: (div: HTMLDivElement, options: EditorOptions) => + new Editor(div, { ...options, cacheModel: this.state.initState.cacheModel, }), diff --git a/demo/scripts/controls/contentModel/editor/ContentModelRooster.tsx b/demo/scripts/controls/contentModel/editor/ContentModelRooster.tsx index a0d5f6cb501..5e76e16ef95 100644 --- a/demo/scripts/controls/contentModel/editor/ContentModelRooster.tsx +++ b/demo/scripts/controls/contentModel/editor/ContentModelRooster.tsx @@ -2,12 +2,8 @@ import * as React from 'react'; import { createUIUtilities, ReactEditorPlugin, UIUtilities } from 'roosterjs-react'; import { divProperties, getNativeProps } from '@fluentui/react/lib/Utilities'; import { EditorAdapter, EditorAdapterOptions } from 'roosterjs-editor-adapter'; +import { EditorOptions, EditorPlugin, IEditor } from 'roosterjs-content-model-types'; import { useTheme } from '@fluentui/react/lib/Theme'; -import { - EditorPlugin, - IStandaloneEditor, - StandaloneEditorOptions, -} from 'roosterjs-content-model-types'; import type { EditorPlugin as LegacyEditorPlugin } from 'roosterjs-editor-types'; /** @@ -20,7 +16,7 @@ export interface ContentModelRoosterProps * Creator function used for creating the instance of roosterjs editor. * Use this callback when you have your own sub class of roosterjs Editor or force trigging a reset of editor */ - editorCreator?: (div: HTMLDivElement, options: StandaloneEditorOptions) => IStandaloneEditor; + editorCreator?: (div: HTMLDivElement, options: EditorOptions) => IEditor; /** * Whether editor should get focus once it is created @@ -36,7 +32,7 @@ export interface ContentModelRoosterProps */ export default function ContentModelRooster(props: ContentModelRoosterProps) { const editorDiv = React.useRef(null); - const editor = React.useRef(null); + const editor = React.useRef(null); const theme = useTheme(); const { focusOnInit, editorCreator, inDarkMode, plugins, legacyPlugins } = props; diff --git a/demo/scripts/controls/contentModel/plugins/ContentModelFormatPainterPlugin.ts b/demo/scripts/controls/contentModel/plugins/ContentModelFormatPainterPlugin.ts index a5ad17cfb36..fe54d377874 100644 --- a/demo/scripts/controls/contentModel/plugins/ContentModelFormatPainterPlugin.ts +++ b/demo/scripts/controls/contentModel/plugins/ContentModelFormatPainterPlugin.ts @@ -3,7 +3,7 @@ import { applySegmentFormat, getFormatState } from 'roosterjs-content-model-api' import { ContentModelSegmentFormat, EditorPlugin, - IStandaloneEditor, + IEditor, PluginEvent, } from 'roosterjs-content-model-types'; @@ -11,7 +11,7 @@ const FORMATPAINTERCURSOR_SVG = require('./formatpaintercursor.svg'); const FORMATPAINTERCURSOR_STYLE = `cursor: url("${FORMATPAINTERCURSOR_SVG}") 8.5 16, auto`; export default class ContentModelFormatPainterPlugin implements EditorPlugin { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private styleNode: HTMLStyleElement | null = null; private painterFormat: ContentModelSegmentFormat | null = null; private static instance: ContentModelFormatPainterPlugin | undefined; @@ -24,7 +24,7 @@ export default class ContentModelFormatPainterPlugin implements EditorPlugin { return 'FormatPainter'; } - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; const doc = this.editor.getDocument(); @@ -77,7 +77,7 @@ export default class ContentModelFormatPainterPlugin implements EditorPlugin { } } -function getSegmentFormat(editor: IStandaloneEditor): ContentModelSegmentFormat { +function getSegmentFormat(editor: IEditor): ContentModelSegmentFormat { const formatState = getFormatState(editor); return { diff --git a/demo/scripts/controls/ribbonButtons/contentModel/ContentModelRibbonButton.ts b/demo/scripts/controls/ribbonButtons/contentModel/ContentModelRibbonButton.ts index a31a7845057..042aeace457 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/ContentModelRibbonButton.ts +++ b/demo/scripts/controls/ribbonButtons/contentModel/ContentModelRibbonButton.ts @@ -1,5 +1,5 @@ import { LocalizedStrings, RibbonButtonDropDown, UIUtilities } from 'roosterjs-react'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; import type { FormatState } from 'roosterjs-editor-types'; import type { ICommandBarItemProps } from '@fluentui/react/lib/CommandBar'; @@ -35,7 +35,7 @@ export default interface ContentModelRibbonButton { * @param uiUtilities a utilities object to help render addition UI elements */ onClick: ( - editor: IStandaloneEditor, + editor: IEditor, key: T, strings: LocalizedStrings | undefined, uiUtilities: UIUtilities diff --git a/demo/scripts/controls/ribbonButtons/contentModel/ContentModelRibbonPlugin.ts b/demo/scripts/controls/ribbonButtons/contentModel/ContentModelRibbonPlugin.ts index 6e27346c751..c8d7c339657 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/ContentModelRibbonPlugin.ts +++ b/demo/scripts/controls/ribbonButtons/contentModel/ContentModelRibbonPlugin.ts @@ -4,14 +4,10 @@ import { FormatState } from 'roosterjs-editor-types'; import { getFormatState } from 'roosterjs-content-model-api'; import { getObjectKeys } from 'roosterjs-editor-dom'; import { LocalizedStrings, UIUtilities } from 'roosterjs-react'; -import { - ContentModelFormatState, - IStandaloneEditor, - PluginEvent, -} from 'roosterjs-content-model-types'; +import { ContentModelFormatState, IEditor, PluginEvent } from 'roosterjs-content-model-types'; export class ContentModelRibbonPlugin implements RibbonPlugin { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private onFormatChanged: ((formatState: FormatState) => void) | null = null; private timer = 0; private formatState: ContentModelFormatState | null = null; @@ -34,7 +30,7 @@ export class ContentModelRibbonPlugin implements RibbonPlugin { * Initialize this plugin * @param editor The editor instance */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; } diff --git a/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts b/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts index b392e4c38cd..1ceebff6902 100644 --- a/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts +++ b/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts @@ -1,8 +1,8 @@ import ContentModelPane, { ContentModelPaneProps } from './ContentModelPane'; import SidePanePluginImpl from '../SidePanePluginImpl'; import { ContentModelRibbonPlugin } from '../../ribbonButtons/contentModel/ContentModelRibbonPlugin'; -import { IEditor, PluginEvent, PluginEventType } from 'roosterjs-editor-types'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor as ILegacyEditor, PluginEvent, PluginEventType } from 'roosterjs-editor-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { setCurrentContentModel } from './currentModel'; import { SidePaneElementProps } from '../SidePaneElement'; @@ -17,10 +17,10 @@ export default class ContentModelPanePlugin extends SidePanePluginImpl< this.contentModelRibbon = new ContentModelRibbonPlugin(); } - initialize(editor: IEditor): void { + initialize(editor: ILegacyEditor): void { super.initialize(editor); - this.contentModelRibbon.initialize(editor as IEditor & IStandaloneEditor); // TODO: Port side pane to use IStandaloneEditor + this.contentModelRibbon.initialize(editor as ILegacyEditor & IEditor); // TODO: Port side pane to use IStandaloneEditor editor.getDocument().addEventListener('selectionchange', this.onModelChangeFromSelection); } @@ -36,8 +36,7 @@ export default class ContentModelPanePlugin extends SidePanePluginImpl< onPluginEvent(e: PluginEvent) { if (e.eventType == PluginEventType.ContentChanged && e.source == 'RefreshModel') { this.getComponent(component => { - // TODO: Port to use IStandaloneEditor and remove type cast here - const model = (this.editor as IEditor & IStandaloneEditor).getContentModelCopy( + const model = (this.editor as ILegacyEditor & IEditor).getContentModelCopy( 'connected' ); component.setContentModel(model); @@ -74,9 +73,7 @@ export default class ContentModelPanePlugin extends SidePanePluginImpl< private onModelChange = () => { this.getComponent(component => { // TODO: Port to use IStandaloneEditor and remove type cast here - const model = (this.editor as IEditor & IStandaloneEditor).getContentModelCopy( - 'connected' - ); + const model = (this.editor as ILegacyEditor & IEditor).getContentModelCopy('connected'); component.setContentModel(model); setCurrentContentModel(model); }); diff --git a/demo/scripts/controls/sidePane/contentModelApiPlayground/insertEntity/InsertEntityPane.tsx b/demo/scripts/controls/sidePane/contentModelApiPlayground/insertEntity/InsertEntityPane.tsx index e380bafbb40..6595fb0ae64 100644 --- a/demo/scripts/controls/sidePane/contentModelApiPlayground/insertEntity/InsertEntityPane.tsx +++ b/demo/scripts/controls/sidePane/contentModelApiPlayground/insertEntity/InsertEntityPane.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import ApiPaneProps from '../ApiPaneProps'; -import { Entity, IEditor } from 'roosterjs-editor-types'; +import { Entity, IEditor as ILegacyEditor } from 'roosterjs-editor-types'; import { getEntityFromElement, getEntitySelector } from 'roosterjs-editor-dom'; +import { IEditor, InsertEntityOptions } from 'roosterjs-content-model-types'; import { insertEntity } from 'roosterjs-content-model-api'; -import { InsertEntityOptions, IStandaloneEditor } from 'roosterjs-content-model-types'; import { trustedHTMLHandler } from '../../../../utils/trustedHTMLHandler'; const styles = require('./InsertEntityPane.scss'); @@ -114,7 +114,7 @@ export default class InsertEntityPane extends React.Component setModelDirection(model, direction), { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts index d77e53f82d6..53e3536ad41 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts @@ -1,8 +1,5 @@ import { formatParagraphWithContentModel } from '../utils/formatParagraphWithContentModel'; -import type { - ContentModelParagraphDecorator, - IStandaloneEditor, -} from 'roosterjs-content-model-types'; +import type { ContentModelParagraphDecorator, IEditor } from 'roosterjs-content-model-types'; type HeadingLevelTags = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; @@ -20,10 +17,7 @@ const HeaderFontSizes: Record = { * @param editor The editor to set heading level to * @param headingLevel Level of heading, from 1 to 6. Set to 0 means set it back to a regular paragraph */ -export default function setHeadingLevel( - editor: IStandaloneEditor, - headingLevel: 0 | 1 | 2 | 3 | 4 | 5 | 6 -) { +export default function setHeadingLevel(editor: IEditor, headingLevel: 0 | 1 | 2 | 3 | 4 | 5 | 6) { editor.focus(); formatParagraphWithContentModel(editor, 'setHeadingLevel', para => { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts index 24266a90f78..8e82effe88d 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts @@ -1,6 +1,6 @@ import { normalizeContentModel } from 'roosterjs-content-model-dom'; import { setModelIndentation } from '../../modelApi/block/setModelIndentation'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Indent or outdent to selected paragraphs @@ -9,7 +9,7 @@ import type { IStandaloneEditor } from 'roosterjs-content-model-types'; * @param length The length of pixel to indent/outdent @default 40 */ export default function setIndentation( - editor: IStandaloneEditor, + editor: IEditor, indentation: 'indent' | 'outdent', length?: number ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts index a2f85af4d5f..3f0b05c0aff 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts @@ -1,6 +1,6 @@ import { createParagraphDecorator } from 'roosterjs-content-model-dom'; import { formatParagraphWithContentModel } from '../utils/formatParagraphWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Toggles the current block(s) margin properties. @@ -10,7 +10,7 @@ import type { IStandaloneEditor } from 'roosterjs-content-model-types'; * @param marginBottom value for bottom margin */ export default function setParagraphMargin( - editor: IStandaloneEditor, + editor: IEditor, marginTop?: string | null, marginBottom?: string | null ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts index dd204e6d253..befed4e3fe0 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts @@ -1,12 +1,12 @@ import { formatParagraphWithContentModel } from '../utils/formatParagraphWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Sets current selected block(s) line-height property and wipes such property from child segments * @param editor The editor to operate on * @param spacing Unitless/px value to set line height */ -export default function setSpacing(editor: IStandaloneEditor, spacing: number | string) { +export default function setSpacing(editor: IEditor, spacing: number | string) { editor.focus(); formatParagraphWithContentModel(editor, 'setSpacing', paragraph => { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts index 87fa8055e0b..f1a4bdddeed 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts @@ -1,8 +1,5 @@ import { toggleModelBlockQuote } from '../../modelApi/block/toggleModelBlockQuote'; -import type { - ContentModelFormatContainerFormat, - IStandaloneEditor, -} from 'roosterjs-content-model-types'; +import type { ContentModelFormatContainerFormat, IEditor } from 'roosterjs-content-model-types'; const DefaultQuoteFormatLtr: ContentModelFormatContainerFormat = { borderLeft: '3px solid rgb(200, 200, 200)', @@ -27,7 +24,7 @@ const BuildInQuoteFormat: ContentModelFormatContainerFormat = { * @param quoteFormat @optional Block format for the new quote object */ export default function toggleBlockQuote( - editor: IStandaloneEditor, + editor: IEditor, quoteFormat?: ContentModelFormatContainerFormat, quoteFormatRtl?: ContentModelFormatContainerFormat ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts index 8282c0fcc9f..4fa758d60ee 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts @@ -6,7 +6,7 @@ import type { DOMSelection, InsertEntityPosition, InsertEntityOptions, - IStandaloneEditor, + IEditor, } from 'roosterjs-content-model-types'; const BlockEntityTag = 'div'; @@ -14,7 +14,7 @@ const InlineEntityTag = 'span'; /** * Insert an entity into editor - * @param editor The Content Model editor + * @param editor The editor object * @param type Type of entity * @param isBlock True to insert a block entity, false to insert an inline entity * @param position Position of the entity to insert. It can be @@ -24,7 +24,7 @@ const InlineEntityTag = 'span'; * @param options Move options to insert. See InsertEntityOptions */ export default function insertEntity( - editor: IStandaloneEditor, + editor: IEditor, type: string, isBlock: boolean, position: 'focus' | 'begin' | 'end' | DOMSelection, @@ -33,7 +33,7 @@ export default function insertEntity( /** * Insert a block entity into editor - * @param editor The Content Model editor + * @param editor The editor object * @param type Type of entity * @param isBlock Must be true for a block entity * @param position Position of the entity to insert. It can be @@ -43,7 +43,7 @@ export default function insertEntity( * @param options Move options to insert. See InsertEntityOptions */ export default function insertEntity( - editor: IStandaloneEditor, + editor: IEditor, type: string, isBlock: true, position: InsertEntityPosition | DOMSelection, @@ -51,7 +51,7 @@ export default function insertEntity( ): ContentModelEntity | null; export default function insertEntity( - editor: IStandaloneEditor, + editor: IEditor, type: string, isBlock: boolean, position?: InsertEntityPosition | DOMSelection, diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts index a890b26dc60..ed1a172d678 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts @@ -1,7 +1,7 @@ import { clearModelFormat } from '../../modelApi/common/clearModelFormat'; import { normalizeContentModel } from 'roosterjs-content-model-dom'; import type { - IStandaloneEditor, + IEditor, ContentModelBlock, ContentModelBlockGroup, ContentModelSegment, @@ -12,7 +12,7 @@ import type { * Clear format of selection * @param editor The editor to clear format from */ -export default function clearFormat(editor: IStandaloneEditor) { +export default function clearFormat(editor: IEditor) { editor.focus(); editor.formatContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts index e97cc925430..9c3af3ddcc8 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts @@ -1,11 +1,11 @@ import { retrieveModelFormatState } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor, ContentModelFormatState } from 'roosterjs-content-model-types'; +import type { IEditor, ContentModelFormatState } from 'roosterjs-content-model-types'; /** * Get current format state * @param editor The editor to get format from */ -export default function getFormatState(editor: IStandaloneEditor): ContentModelFormatState { +export default function getFormatState(editor: IEditor): ContentModelFormatState { const pendingFormat = editor.getPendingFormat(); const model = editor.getContentModelCopy('reduced'); const manager = editor.getSnapshotsManager(); diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts index 83e7e76c6a1..622ca208ce1 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts @@ -1,11 +1,11 @@ import { adjustSegmentSelection } from '../../modelApi/selection/adjustSegmentSelection'; -import type { ContentModelImage, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** * Adjust selection to make sure select an image if any * @return Content Model Image object if an image is select, or null */ -export default function adjustImageSelection(editor: IStandaloneEditor): ContentModelImage | null { +export default function adjustImageSelection(editor: IEditor): ContentModelImage | null { let image: ContentModelImage | null = null; editor.formatContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts index b2bc15d94f1..08872385a14 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts @@ -1,13 +1,13 @@ import formatImageWithContentModel from '../utils/formatImageWithContentModel'; import { readFile, updateImageMetadata } from 'roosterjs-content-model-core'; -import type { ContentModelImage, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** * Change the selected image src * @param editor The editor instance * @param file The image file */ -export default function changeImage(editor: IStandaloneEditor, file: File) { +export default function changeImage(editor: IEditor, file: File) { editor.focus(); const selection = editor.getDOMSelection(); diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts index 2cc4ee92ae1..b63614347c0 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts @@ -1,13 +1,13 @@ import { addSegment, createContentModelDocument, createImage } from 'roosterjs-content-model-dom'; import { mergeModel, readFile } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Insert an image into current selected position * @param editor The editor to operate on * @param file Image Blob file or source string */ -export default function insertImage(editor: IStandaloneEditor, imageFileOrSrc: File | string) { +export default function insertImage(editor: IEditor, imageFileOrSrc: File | string) { editor.focus(); if (typeof imageFileOrSrc == 'string') { @@ -21,7 +21,7 @@ export default function insertImage(editor: IStandaloneEditor, imageFileOrSrc: F } } -function insertImageWithSrc(editor: IStandaloneEditor, src: string) { +function insertImageWithSrc(editor: IEditor, src: string) { editor.formatContentModel( (model, context) => { const image = createImage(src, { backgroundColor: '' }); diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts index c21995e2d3f..aa3779db680 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts @@ -1,5 +1,5 @@ import formatImageWithContentModel from '../utils/formatImageWithContentModel'; -import type { ContentModelImage, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** * Set image alt text for all selected images at selection. If no images is contained @@ -7,7 +7,7 @@ import type { ContentModelImage, IStandaloneEditor } from 'roosterjs-content-mod * @param editor The editor instance * @param altText The image alt text */ -export default function setImageAltText(editor: IStandaloneEditor, altText: string) { +export default function setImageAltText(editor: IEditor, altText: string) { editor.focus(); formatImageWithContentModel(editor, 'setImageAltText', (image: ContentModelImage) => { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts index 71b2d5a0306..8fea90f6f86 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts @@ -1,6 +1,6 @@ import applyImageBorderFormat from '../../modelApi/image/applyImageBorderFormat'; import formatImageWithContentModel from '../utils/formatImageWithContentModel'; -import type { Border, ContentModelImage, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { Border, ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** * Set image border style for all selected images at selection. @@ -10,7 +10,7 @@ import type { Border, ContentModelImage, IStandaloneEditor } from 'roosterjs-con * @param borderRadius the border radius value, if undefined, the border radius will keep the actual value */ export default function setImageBorder( - editor: IStandaloneEditor, + editor: IEditor, border: Border | null, borderRadius?: string ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts index 2f50789079b..4da659632fa 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts @@ -1,5 +1,5 @@ import formatImageWithContentModel from '../utils/formatImageWithContentModel'; -import type { ContentModelImage, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** * Set image box shadow for all selected images at selection. @@ -8,7 +8,7 @@ import type { ContentModelImage, IStandaloneEditor } from 'roosterjs-content-mod * @param margin The image margin for all sides (eg. "4px"), null to remove margin */ export default function setImageBoxShadow( - editor: IStandaloneEditor, + editor: IEditor, boxShadow: string, margin?: string | null ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts index c0e1dc63610..c2b192dbc06 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts @@ -1,13 +1,13 @@ import { adjustSegmentSelection } from '../../modelApi/selection/adjustSegmentSelection'; import { adjustWordSelection } from '../../modelApi/selection/adjustWordSelection'; import { getSelectedSegments, setSelection } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Adjust selection to make sure select a hyperlink if any, or a word if original selection is collapsed * @return A combination of existing link display text and url if any. If there is no existing link, return selected text and null */ -export default function adjustLinkSelection(editor: IStandaloneEditor): [string, string | null] { +export default function adjustLinkSelection(editor: IEditor): [string, string | null] { let text = ''; let url: string | null = null; diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts index 90b16943afe..edf36977355 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts @@ -1,7 +1,7 @@ import { adjustTrailingSpaceSelection } from '../../modelApi/selection/adjustTrailingSpaceSelection'; import { ChangeSource, getSelectedSegments, mergeModel } from 'roosterjs-content-model-core'; import { matchLink } from '../../modelApi/link/matchLink'; -import type { ContentModelLink, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelLink, IEditor } from 'roosterjs-content-model-types'; import { addLink, addSegment, @@ -30,7 +30,7 @@ const FTP_REGEX = /^ftp\./i; * If not specified and there wasn't a link, the link url will be used as display text. */ export default function insertLink( - editor: IStandaloneEditor, + editor: IEditor, link: string, anchorTitle?: string, displayText?: string, diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts index b6c7f38abb6..8b90fa1d68b 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts @@ -1,6 +1,6 @@ import { adjustSegmentSelection } from '../../modelApi/selection/adjustSegmentSelection'; import { getSelectedSegments } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Remove link at selection. If no links at selection, do nothing. @@ -8,7 +8,7 @@ import type { IStandaloneEditor } from 'roosterjs-content-model-types'; * If only part of a link is selected, the whole link style will be removed. * @param editor The editor instance */ -export default function removeLink(editor: IStandaloneEditor) { +export default function removeLink(editor: IEditor) { editor.focus(); editor.formatContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts index 1ce023a263a..37e1091894a 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts @@ -1,12 +1,12 @@ import { getFirstSelectedListItem } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Set start number of a list item * @param editor The editor to operate on * @param value The number to set to, must be equal or greater than 1 */ -export default function setListStartNumber(editor: IStandaloneEditor, value: number) { +export default function setListStartNumber(editor: IEditor, value: number) { editor.focus(); editor.formatContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts index 1a4c075e19f..98b4507d841 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts @@ -1,13 +1,13 @@ import { findListItemsInSameThread } from '../../modelApi/list/findListItemsInSameThread'; import { getFirstSelectedListItem, updateListMetadata } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor, ListMetadataFormat } from 'roosterjs-content-model-types'; +import type { IEditor, ListMetadataFormat } from 'roosterjs-content-model-types'; /** * Set style of list items with in same thread of current item * @param editor The editor to operate on * @param style The target list item style to set */ -export default function setListStyle(editor: IStandaloneEditor, style: ListMetadataFormat) { +export default function setListStyle(editor: IEditor, style: ListMetadataFormat) { editor.focus(); editor.formatContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts index 7729eade0c6..43aefcbb13a 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts @@ -1,5 +1,5 @@ import { setListType } from '../../modelApi/list/setListType'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Toggle bullet list type @@ -7,7 +7,7 @@ import type { IStandaloneEditor } from 'roosterjs-content-model-types'; * - When all blocks are already in bullet list, turn off / outdent there list type * @param editor The editor to operate on */ -export default function toggleBullet(editor: IStandaloneEditor) { +export default function toggleBullet(editor: IEditor) { editor.focus(); editor.formatContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts index b98c6df7b2f..878cc5e5a35 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts @@ -1,5 +1,5 @@ import { setListType } from '../../modelApi/list/setListType'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Toggle numbering list type @@ -7,7 +7,7 @@ import type { IStandaloneEditor } from 'roosterjs-content-model-types'; * - When all blocks are already in numbering list, turn off / outdent there list type * @param editor The editor to operate on */ -export default function toggleNumbering(editor: IStandaloneEditor) { +export default function toggleNumbering(editor: IEditor) { editor.focus(); editor.formatContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts index a3cf335538a..65aaec07a00 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts @@ -1,15 +1,12 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { ContentModelSegmentFormat, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelSegmentFormat, IEditor } from 'roosterjs-content-model-types'; /** * Bulk apply segment format to all selected content. This is usually used for format painter. * @param editor The editor to operate on * @param newFormat The segment format to apply */ -export default function applySegmentFormat( - editor: IStandaloneEditor, - newFormat: ContentModelSegmentFormat -) { +export default function applySegmentFormat(editor: IEditor, newFormat: ContentModelSegmentFormat) { formatSegmentWithContentModel( editor, 'applySegmentFormat', diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts index e5e3e30f96e..4bbe56f8fd8 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts @@ -1,5 +1,5 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Change the capitalization of text in the selection @@ -10,7 +10,7 @@ import type { IStandaloneEditor } from 'roosterjs-content-model-types'; * Default is the host environment’s current locale. */ export default function changeCapitalization( - editor: IStandaloneEditor, + editor: IEditor, capitalization: 'sentence' | 'lowerCase' | 'upperCase' | 'capitalize', language?: string ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts index fcfba224fd0..b1b23c401be 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts @@ -4,7 +4,7 @@ import { setFontSizeInternal } from './setFontSize'; import type { ContentModelParagraph, ContentModelSegmentFormat, - IStandaloneEditor, + IEditor, } from 'roosterjs-content-model-types'; /** @@ -21,7 +21,7 @@ const MAX_FONT_SIZE = 1000; * @param change Whether increase or decrease font size * @param fontSizes A sorted font size array, in pt. Default value is FONT_SIZES */ -export default function changeFontSize(editor: IStandaloneEditor, change: 'increase' | 'decrease') { +export default function changeFontSize(editor: IEditor, change: 'increase' | 'decrease') { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts index 099c2d82ac6..13ba5da55db 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts @@ -1,17 +1,14 @@ import { createSelectionMarker } from 'roosterjs-content-model-dom'; import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; import { setSelection } from 'roosterjs-content-model-core'; -import type { ContentModelParagraph, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelParagraph, IEditor } from 'roosterjs-content-model-types'; /** * Set background color * @param editor The editor to operate on * @param backgroundColor The color to set. Pass null to remove existing color. */ -export default function setBackgroundColor( - editor: IStandaloneEditor, - backgroundColor: string | null -) { +export default function setBackgroundColor(editor: IEditor, backgroundColor: string | null) { editor.focus(); let lastParagraph: ContentModelParagraph | null = null; diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts index bef80eaed7e..3acaeeaa37c 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts @@ -1,12 +1,12 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Set font name * @param editor The editor to operate on * @param fontName The font name to set */ -export default function setFontName(editor: IStandaloneEditor, fontName: string) { +export default function setFontName(editor: IEditor, fontName: string) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts index f39104ecd1a..ccfa9194f32 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts @@ -2,7 +2,7 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContent import type { ContentModelParagraph, ContentModelSegmentFormat, - IStandaloneEditor, + IEditor, } from 'roosterjs-content-model-types'; /** @@ -10,7 +10,7 @@ import type { * @param editor The editor to operate on * @param fontSize The font size to set */ -export default function setFontSize(editor: IStandaloneEditor, fontSize: string) { +export default function setFontSize(editor: IEditor, fontSize: string) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts index edf70181d89..0c6c4c03e0f 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts @@ -1,12 +1,12 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Set text color * @param editor The editor to operate on * @param textColor The text color to set. Pass null to remove existing color. */ -export default function setTextColor(editor: IStandaloneEditor, textColor: string | null) { +export default function setTextColor(editor: IEditor, textColor: string | null) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts index 94440c2c7d0..5fc881ce9d1 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts @@ -1,12 +1,12 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; import { isBold } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Toggle bold style * @param editor The editor to operate on */ -export default function toggleBold(editor: IStandaloneEditor) { +export default function toggleBold(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts index 4269bcd762a..06526e8aee2 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts @@ -1,6 +1,6 @@ import { addCode } from 'roosterjs-content-model-dom'; import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { ContentModelCode, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelCode, IEditor } from 'roosterjs-content-model-types'; const DefaultCode: ContentModelCode = { format: { @@ -12,7 +12,7 @@ const DefaultCode: ContentModelCode = { * Toggle italic style * @param editor The editor to operate on */ -export default function toggleCode(editor: IStandaloneEditor) { +export default function toggleCode(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts index b3c25de6524..2be06d9b899 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts @@ -1,11 +1,11 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Toggle italic style * @param editor The editor to operate on */ -export default function toggleItalic(editor: IStandaloneEditor) { +export default function toggleItalic(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts index 93e2df84d2c..3794ea99d27 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts @@ -1,11 +1,11 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Toggle strikethrough style * @param editor The editor to operate on */ -export default function toggleStrikethrough(editor: IStandaloneEditor) { +export default function toggleStrikethrough(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts index 28c9b6da37b..4396b9e1d47 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts @@ -1,11 +1,11 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Toggle subscript style * @param editor The editor to operate on */ -export default function toggleSubscript(editor: IStandaloneEditor) { +export default function toggleSubscript(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts index 2e84d4d5059..74d37665778 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts @@ -1,11 +1,11 @@ import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Toggle superscript style * @param editor The editor to operate on */ -export default function toggleSuperscript(editor: IStandaloneEditor) { +export default function toggleSuperscript(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts index b58ef453783..f1aa57e86c9 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts @@ -1,12 +1,12 @@ import { adjustTrailingSpaceSelection } from '../../modelApi/selection/adjustTrailingSpaceSelection'; import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Toggle underline style * @param editor The editor to operate on */ -export default function toggleUnderline(editor: IStandaloneEditor) { +export default function toggleUnderline(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts index f7c92cc7069..ffc0d4026a4 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts @@ -6,7 +6,7 @@ import { updateTableCellMetadata, } from 'roosterjs-content-model-core'; import type { - IStandaloneEditor, + IEditor, Border, ContentModelTable, ContentModelTableCell, @@ -39,7 +39,7 @@ type Perimeter = { * @param operation The operation to apply */ export default function applyTableBorderFormat( - editor: IStandaloneEditor, + editor: IEditor, border: Border, operation: BorderOperations ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/editTable.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/editTable.ts index 5f63f760b54..543110c135f 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/editTable.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/editTable.ts @@ -10,7 +10,7 @@ import { mergeTableColumn } from '../../modelApi/table/mergeTableColumn'; import { mergeTableRow } from '../../modelApi/table/mergeTableRow'; import { splitTableCellHorizontally } from '../../modelApi/table/splitTableCellHorizontally'; import { splitTableCellVertically } from '../../modelApi/table/splitTableCellVertically'; -import type { TableOperation, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { TableOperation, IEditor } from 'roosterjs-content-model-types'; import { alignTableCellHorizontally, alignTableCellVertically, @@ -21,7 +21,7 @@ import { * @param editor The editor instance * @param operation The table operation to apply */ -export default function editTable(editor: IStandaloneEditor, operation: TableOperation) { +export default function editTable(editor: IEditor, operation: TableOperation) { editor.focus(); formatTableWithContentModel(editor, 'editTable', tableModel => { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts index 411f229bedb..8fd4d6235a4 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts @@ -3,7 +3,7 @@ import { getFirstSelectedTable, updateTableCellMetadata, } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor, TableMetadataFormat } from 'roosterjs-content-model-types'; +import type { IEditor, TableMetadataFormat } from 'roosterjs-content-model-types'; /** * Format current focused table with the given format @@ -12,7 +12,7 @@ import type { IStandaloneEditor, TableMetadataFormat } from 'roosterjs-content-m * @param keepCellShade Whether keep existing shade color when apply format if there is a manually set shade color */ export default function formatTable( - editor: IStandaloneEditor, + editor: IEditor, format: TableMetadataFormat, keepCellShade?: boolean ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts index 0128b7913a2..8c1072c94ae 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts @@ -7,7 +7,7 @@ import { normalizeTable, setSelection, } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor, TableMetadataFormat } from 'roosterjs-content-model-types'; +import type { IEditor, TableMetadataFormat } from 'roosterjs-content-model-types'; /** * Insert table into editor at current selection @@ -19,7 +19,7 @@ import type { IStandaloneEditor, TableMetadataFormat } from 'roosterjs-content-m * background color: #FFF; border color: #ABABAB */ export default function insertTable( - editor: IStandaloneEditor, + editor: IEditor, columns: number, rows: number, format?: Partial diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts index fee572ec8bf..8c54f2c595e 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts @@ -4,14 +4,14 @@ import { normalizeTable, setTableCellBackgroundColor, } from 'roosterjs-content-model-core'; -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Set table cell shade color * @param editor The editor instance * @param color The color to set. Pass null to remove existing shade color */ -export default function setTableCellShade(editor: IStandaloneEditor, color: string | null) { +export default function setTableCellShade(editor: IEditor, color: string | null) { editor.focus(); editor.formatContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts index df35d9b8e74..1bda429eeed 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts @@ -1,11 +1,11 @@ import { formatSegmentWithContentModel } from './formatSegmentWithContentModel'; -import type { ContentModelImage, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** * @internal */ export default function formatImageWithContentModel( - editor: IStandaloneEditor, + editor: IEditor, apiName: string, callback: (segment: ContentModelImage) => void ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts index a54e2e8deca..951e26f7748 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts @@ -1,11 +1,11 @@ import { getSelectedParagraphs } from 'roosterjs-content-model-core'; -import type { ContentModelParagraph, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelParagraph, IEditor } from 'roosterjs-content-model-types'; /** * @internal */ export function formatParagraphWithContentModel( - editor: IStandaloneEditor, + editor: IEditor, apiName: string, setStyleCallback: (paragraph: ContentModelParagraph) => void ) { diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts index 45f9653674e..727615732e5 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts @@ -5,14 +5,14 @@ import type { ContentModelParagraph, ContentModelSegment, ContentModelSegmentFormat, - IStandaloneEditor, + IEditor, } from 'roosterjs-content-model-types'; /** * @internal */ export function formatSegmentWithContentModel( - editor: IStandaloneEditor, + editor: IEditor, apiName: string, toggleStyleCallback: ( format: ContentModelSegmentFormat, diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatTableWithContentModel.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatTableWithContentModel.ts index fa33f6ed282..b9f26ac9c35 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatTableWithContentModel.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatTableWithContentModel.ts @@ -11,11 +11,7 @@ import { normalizeTable, setSelection, } from 'roosterjs-content-model-core'; -import type { - ContentModelTable, - IStandaloneEditor, - TableSelection, -} from 'roosterjs-content-model-types'; +import type { ContentModelTable, IEditor, TableSelection } from 'roosterjs-content-model-types'; /** * Invoke a callback to format the selected table using Content Model @@ -25,7 +21,7 @@ import type { * @param selectionOverride Override the current selection. If we want to format a table even currently it is not selected, we can use this parameter to override current selection */ export function formatTableWithContentModel( - editor: IStandaloneEditor, + editor: IEditor, apiName: string, callback: (tableModel: ContentModelTable) => void, selectionOverride?: TableSelection diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/paragraphTestCommon.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/block/paragraphTestCommon.ts index fff6d7b07d2..87f94541614 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/paragraphTestCommon.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/block/paragraphTestCommon.ts @@ -1,4 +1,4 @@ -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelFormatter, @@ -7,7 +7,7 @@ import { export function paragraphTestCommon( apiName: string, - executionCallback: (editor: IStandaloneEditor) => void, + executionCallback: (editor: IEditor) => void, model: ContentModelDocument, result: ContentModelDocument, calledTimes: number @@ -26,7 +26,7 @@ export function paragraphTestCommon( focus: jasmine.createSpy(), getFocusedPosition: () => ({}), formatContentModel, - } as any) as IStandaloneEditor; + } as any) as IEditor; executionCallback(editor); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts index a2bc884ffc4..011584e2754 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts @@ -1,7 +1,7 @@ import * as normalizeTable from 'roosterjs-content-model-core/lib/publicApi/table/normalizeTable'; import setAlignment from '../../../lib/publicApi/block/setAlignment'; import { createContentModelDocument } from 'roosterjs-content-model-dom'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { paragraphTestCommon } from './paragraphTestCommon'; import { ContentModelDocument, @@ -415,7 +415,7 @@ describe('setAlignment', () => { }); describe('setAlignment in table', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let triggerEvent: jasmine.Spy; let getVisibleViewport: jasmine.Spy; @@ -431,7 +431,7 @@ describe('setAlignment in table', () => { isDarkMode: () => false, triggerEvent, getVisibleViewport, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); function runTest( @@ -813,7 +813,7 @@ describe('setAlignment in table', () => { }); describe('setAlignment in list', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let triggerEvent: jasmine.Spy; let getVisibleViewport: jasmine.Spy; @@ -827,7 +827,7 @@ describe('setAlignment in list', () => { isDarkMode: () => false, triggerEvent, getVisibleViewport, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); function runTest( diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts index a016d66f7db..a72c0e02f21 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts @@ -1,11 +1,11 @@ import * as setModelIndentation from '../../../lib/modelApi/block/setModelIndentation'; import setIndentation from '../../../lib/publicApi/block/setIndentation'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; import { ContentModelFormatter, FormatContentModelContext } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; describe('setIndentation', () => { const fakeModel: any = { a: 'b' }; - let editor: IStandaloneEditor; + let editor: IEditor; let formatContentModelSpy: jasmine.Spy; let context: FormatContentModelContext; @@ -26,7 +26,7 @@ describe('setIndentation', () => { formatContentModel: formatContentModelSpy, focus: jasmine.createSpy('focus'), getPendingFormat: () => null as any, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); it('indent', () => { diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts index 5479f7988f0..76ebbef70e5 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts @@ -1,11 +1,11 @@ import * as toggleModelBlockQuote from '../../../lib/modelApi/block/toggleModelBlockQuote'; import toggleBlockQuote from '../../../lib/publicApi/block/toggleBlockQuote'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; import { ContentModelFormatter, FormatContentModelContext } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; describe('toggleBlockQuote', () => { const fakeModel: any = { a: 'b' }; - let editor: IStandaloneEditor; + let editor: IEditor; let formatContentModelSpy: jasmine.Spy; let context: FormatContentModelContext; @@ -26,7 +26,7 @@ describe('toggleBlockQuote', () => { editor = ({ focus: jasmine.createSpy('focus'), formatContentModel: formatContentModelSpy, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); it('toggleBlockQuote', () => { diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts index 2753aaaeb2f..f8a8f3619e2 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts @@ -3,14 +3,14 @@ import * as insertEntityModel from '../../../lib/modelApi/entity/insertEntityMod import * as normalizeContentModel from 'roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel'; import insertEntity from '../../../lib/publicApi/entity/insertEntity'; import { ChangeSource } from 'roosterjs-content-model-core'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { FormatContentModelContext, FormatContentModelOptions, } from 'roosterjs-content-model-types'; describe('insertEntity', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let context: FormatContentModelContext; let wrapper: HTMLElement; const model = 'MockedModel' as any; diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts index 2ab4ed941fe..f98777a04e8 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts @@ -1,7 +1,7 @@ import * as clearModelFormat from '../../../lib/modelApi/common/clearModelFormat'; import * as normalizeContentModel from 'roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel'; import clearFormat from '../../../lib/publicApi/format/clearFormat'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelFormatter, @@ -21,7 +21,7 @@ describe('clearFormat', () => { const editor = ({ focus: () => {}, formatContentModel: formatContentModelSpy, - } as any) as IStandaloneEditor; + } as any) as IEditor; spyOn(clearModelFormat, 'clearModelFormat'); spyOn(normalizeContentModel, 'normalizeContentModel'); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts index 5ba4c29395c..a9e90949ed8 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts @@ -2,7 +2,7 @@ import * as retrieveModelFormatState from 'roosterjs-content-model-core/lib/publ import getFormatState from '../../../lib/publicApi/format/getFormatState'; import { ContentModelDocument, ContentModelSegmentFormat } from 'roosterjs-content-model-types'; import { ContentModelFormatState } from 'roosterjs-content-model-types'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { reducedModelChildProcessor } from 'roosterjs-content-model-core/lib/override/reducedModelChildProcessor'; import { createContentModelDocument, @@ -60,7 +60,7 @@ describe('getFormatState', () => { return model; }, - } as any) as IStandaloneEditor; + } as any) as IEditor; const result = getFormatState(editor); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts index 65aa6f3d944..4b4593d7513 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts @@ -1,6 +1,6 @@ import * as readFile from 'roosterjs-content-model-core/lib/publicApi/domUtils/readFile'; import changeImage from '../../../lib/publicApi/image/changeImage'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelFormatter, @@ -21,7 +21,7 @@ describe('changeImage', () => { function runTest( model: ContentModelDocument, - executionCallback: (editor: IStandaloneEditor) => void, + executionCallback: (editor: IEditor) => void, result: ContentModelDocument, calledTimes: number ) { @@ -49,7 +49,7 @@ describe('changeImage', () => { getDOMSelection, triggerEvent, formatContentModel, - } as any) as IStandaloneEditor; + } as any) as IEditor; executionCallback(editor); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts index fb061333a62..f381c98ac82 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts @@ -1,6 +1,6 @@ import * as readFile from 'roosterjs-content-model-core/lib/publicApi/domUtils/readFile'; import insertImage from '../../../lib/publicApi/image/insertImage'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelFormatter, @@ -17,7 +17,7 @@ describe('insertImage', () => { function runTest( apiName: string, - executionCallback: (editor: IStandaloneEditor) => void, + executionCallback: (editor: IEditor) => void, model: ContentModelDocument, result: ContentModelDocument, calledTimes: number @@ -37,7 +37,7 @@ describe('insertImage', () => { focus: jasmine.createSpy(), isDisposed: () => false, formatContentModel, - } as any) as IStandaloneEditor; + } as any) as IEditor; executionCallback(editor); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts index ea34577e818..ae1d7127f80 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts @@ -1,5 +1,5 @@ import adjustLinkSelection from '../../../lib/publicApi/link/adjustLinkSelection'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelLink, @@ -17,7 +17,7 @@ import { } from 'roosterjs-content-model-dom'; describe('adjustLinkSelection', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let formatContentModel: jasmine.Spy; let formatResult: boolean | undefined; let mockedModel: ContentModelDocument; @@ -38,7 +38,7 @@ describe('adjustLinkSelection', () => { editor = ({ formatContentModel, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); function runTest( diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts index 7d12c8d9c99..18924cb5ae9 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts @@ -1,6 +1,6 @@ import insertLink from '../../../lib/publicApi/link/insertLink'; -import { ChangeSource, StandaloneEditor } from 'roosterjs-content-model-core'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { ChangeSource, Editor } from 'roosterjs-content-model-core'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelLink, @@ -16,13 +16,13 @@ import { } from 'roosterjs-content-model-dom'; describe('insertLink', () => { - let editor: IStandaloneEditor; + let editor: IEditor; beforeEach(() => { editor = ({ focus: () => {}, getPendingFormat: () => null as any, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); function runTest( @@ -326,7 +326,7 @@ describe('insertLink', () => { getName: () => 'mock', onPluginEvent: onPluginEvent, }; - const editor = new StandaloneEditor(div, { + const editor = new Editor(div, { plugins: [mockedPlugin], }); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts index 7ee4421aa21..3495cdb9f05 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts @@ -1,5 +1,5 @@ import removeLink from '../../../lib/publicApi/link/removeLink'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelLink, @@ -15,12 +15,12 @@ import { } from 'roosterjs-content-model-dom'; describe('removeLink', () => { - let editor: IStandaloneEditor; + let editor: IEditor; beforeEach(() => { editor = ({ focus: () => {}, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); function runTest(model: ContentModelDocument, expectedModel: ContentModelDocument | null) { diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts index aff7fbd3260..da35f12f827 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts @@ -1,6 +1,6 @@ import * as setListType from '../../../lib/modelApi/list/setListType'; import toggleBullet from '../../../lib/publicApi/list/toggleBullet'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelFormatter, @@ -9,7 +9,7 @@ import { } from 'roosterjs-content-model-types'; describe('toggleBullet', () => { - let editor = ({} as any) as IStandaloneEditor; + let editor = ({} as any) as IEditor; let formatContentModel: jasmine.Spy; let focus: jasmine.Spy; let mockedModel: ContentModelDocument; @@ -36,7 +36,7 @@ describe('toggleBullet', () => { focus, formatContentModel, getFocusedPosition: () => ({}), - } as any) as IStandaloneEditor; + } as any) as IEditor; spyOn(setListType, 'setListType').and.returnValue(true); }); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts index 131a7d011cc..b19a99b5374 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts @@ -1,6 +1,6 @@ import * as setListType from '../../../lib/modelApi/list/setListType'; import toggleNumbering from '../../../lib/publicApi/list/toggleNumbering'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelFormatter, @@ -9,7 +9,7 @@ import { } from 'roosterjs-content-model-types'; describe('toggleNumbering', () => { - let editor = ({} as any) as IStandaloneEditor; + let editor = ({} as any) as IEditor; let focus: jasmine.Spy; let mockedModel: ContentModelDocument; let context: FormatContentModelContext; @@ -35,7 +35,7 @@ describe('toggleNumbering', () => { editor = ({ focus, formatContentModel, - } as any) as IStandaloneEditor; + } as any) as IEditor; spyOn(setListType, 'setListType').and.returnValue(true); }); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts index 647b6db1e06..268d8cfc6b1 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts @@ -1,7 +1,7 @@ import changeFontSize from '../../../lib/publicApi/segment/changeFontSize'; import { createDomToModelContext, domToContentModel } from 'roosterjs-content-model-dom'; import { createRange } from 'roosterjs-content-model-dom/test/testUtils'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; import { ContentModelDocument, @@ -361,7 +361,7 @@ describe('changeFontSize', () => { formatContentModel, focus: jasmine.createSpy(), getPendingFormat: () => null as ContentModelSegmentFormat, - } as any) as IStandaloneEditor; + } as any) as IEditor; changeFontSize(editor, 'increase'); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/segmentTestCommon.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/segmentTestCommon.ts index fcb001b9300..f0c17cd84cb 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/segmentTestCommon.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/segmentTestCommon.ts @@ -1,4 +1,4 @@ -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelFormatter, @@ -7,7 +7,7 @@ import { export function segmentTestCommon( apiName: string, - executionCallback: (editor: IStandaloneEditor) => void, + executionCallback: (editor: IEditor) => void, model: ContentModelDocument, result: ContentModelDocument, calledTimes: number @@ -27,7 +27,7 @@ export function segmentTestCommon( focus: jasmine.createSpy(), getPendingFormat: () => null as any, formatContentModel, - } as any) as IStandaloneEditor; + } as any) as IEditor; executionCallback(editor); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts index 17208920825..2e69d1b483e 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts @@ -2,7 +2,7 @@ import * as normalizeTable from 'roosterjs-content-model-core/lib/publicApi/tabl import applyTableBorderFormat from '../../../lib/publicApi/table/applyTableBorderFormat'; import { createContentModelDocument } from 'roosterjs-content-model-dom'; import { createTable, createTableCell } from 'roosterjs-content-model-dom'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { Border, BorderOperations, @@ -13,7 +13,7 @@ import { } from 'roosterjs-content-model-types'; describe('applyTableBorderFormat', () => { - let editor: IStandaloneEditor; + let editor: IEditor; const width = '3px'; const style = 'double'; const color = '#AABBCC'; @@ -43,7 +43,7 @@ describe('applyTableBorderFormat', () => { beforeEach(() => { spyOn(normalizeTable, 'normalizeTable'); - editor = ({} as any) as IStandaloneEditor; + editor = ({} as any) as IEditor; }); function runTest( diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts index 7593be0a2ed..841b50cc468 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts @@ -12,10 +12,10 @@ import * as mergeTableRow from '../../../lib/modelApi/table/mergeTableRow'; import * as splitTableCellHorizontally from '../../../lib/modelApi/table/splitTableCellHorizontally'; import * as splitTableCellVertically from '../../../lib/modelApi/table/splitTableCellVertically'; import editTable from '../../../lib/publicApi/table/editTable'; -import { IStandaloneEditor, TableOperation } from 'roosterjs-content-model-types'; +import { IEditor, TableOperation } from 'roosterjs-content-model-types'; describe('editTable', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let focusSpy: jasmine.Spy; let formatTableWithContentModelSpy: jasmine.Spy; const mockedTable = 'TABLE' as any; diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts index c3ae1bbbaf6..9869155565a 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts @@ -1,7 +1,7 @@ import * as normalizeTable from 'roosterjs-content-model-core/lib/publicApi/table/normalizeTable'; import setTableCellShade from '../../../lib/publicApi/table/setTableCellShade'; import { createContentModelDocument } from 'roosterjs-content-model-dom'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelTable, ContentModelFormatter, @@ -9,14 +9,14 @@ import { } from 'roosterjs-content-model-types'; describe('setTableCellShade', () => { - let editor: IStandaloneEditor; + let editor: IEditor; beforeEach(() => { spyOn(normalizeTable, 'normalizeTable'); editor = ({ focus: () => {}, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); function runTest( diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts index 987776d7e06..f15a296691b 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts @@ -1,5 +1,5 @@ import formatImageWithContentModel from '../../../lib/publicApi/utils/formatImageWithContentModel'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelImage, @@ -196,7 +196,7 @@ describe('formatImageWithContentModel', () => { function segmentTestForPluginEvent( apiName: string, - executionCallback: (editor: IStandaloneEditor) => void, + executionCallback: (editor: IEditor) => void, model: ContentModelDocument, result: ContentModelDocument, calledTimes: number @@ -215,7 +215,7 @@ function segmentTestForPluginEvent( const editor = ({ formatContentModel, getPendingFormat: () => null as any, - } as any) as IStandaloneEditor; + } as any) as IEditor; executionCallback(editor); diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatParagraphWithContentModelTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatParagraphWithContentModelTest.ts index 341e6d3a259..1d9f532be78 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatParagraphWithContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatParagraphWithContentModelTest.ts @@ -1,5 +1,5 @@ import { formatParagraphWithContentModel } from '../../../lib/publicApi/utils/formatParagraphWithContentModel'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelParagraph, @@ -14,7 +14,7 @@ import { } from 'roosterjs-content-model-dom'; describe('formatParagraphWithContentModel', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let model: ContentModelDocument; let context: FormatContentModelContext; @@ -42,7 +42,7 @@ describe('formatParagraphWithContentModel', () => { editor = ({ getFocusedPosition: () => ({ node: mockedContainer, offset: mockedOffset }), formatContentModel, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); it('empty doc', () => { diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatSegmentWithContentModelTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatSegmentWithContentModelTest.ts index 3b87fecf7b5..e6be395e9d3 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatSegmentWithContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatSegmentWithContentModelTest.ts @@ -1,5 +1,5 @@ import { formatSegmentWithContentModel } from '../../../lib/publicApi/utils/formatSegmentWithContentModel'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, ContentModelSegmentFormat, @@ -15,7 +15,7 @@ import { } from 'roosterjs-content-model-dom'; describe('formatSegment', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let focus: jasmine.Spy; let model: ContentModelDocument; let formatContentModel: jasmine.Spy; @@ -43,7 +43,7 @@ describe('formatSegment', () => { editor = ({ focus, formatContentModel, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); it('empty doc', () => { diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts index 377317b6d43..ef3b022d22c 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts @@ -2,7 +2,7 @@ import * as applyTableFormat from 'roosterjs-content-model-core/lib/publicApi/ta import * as ensureFocusableParagraphForTable from '../../../lib/modelApi/table/ensureFocusableParagraphForTable'; import * as hasSelectionInBlock from 'roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock'; import * as normalizeTable from 'roosterjs-content-model-core/lib/publicApi/table/normalizeTable'; -import { ContentModelDocument, IStandaloneEditor } from 'roosterjs-content-model-types'; +import { ContentModelDocument, IEditor } from 'roosterjs-content-model-types'; import { formatTableWithContentModel } from '../../../lib/publicApi/utils/formatTableWithContentModel'; import { createContentModelDocument, @@ -11,7 +11,7 @@ import { } from 'roosterjs-content-model-dom'; describe('formatTableWithContentModel', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let formatContentModelSpy: jasmine.Spy; let model: ContentModelDocument; let formatResult: boolean | undefined; diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts index c2bcdcf9eb2..1e5f6e33724 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts @@ -4,7 +4,7 @@ import type { AddUndoSnapshot, Snapshot } from 'roosterjs-content-model-types'; /** * @internal * Add an undo snapshot to current undo snapshot stack - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param canUndoByBackspace True if this action can be undone when user press Backspace key (aka Auto Complete). * @param entityStates @optional Entity states related to this snapshot. * Each entity state will cause an EntityOperation event with operation = EntityOperation.UpdateEntityState diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts index 833989ebee0..b93b02f5168 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts @@ -4,7 +4,7 @@ import type { AttachDomEvent, PluginEvent } from 'roosterjs-content-model-types' /** * @internal * Attach a DOM event to the editor content DIV - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param eventName The DOM event name * @param pluginEventType Optional event type. When specified, editor will trigger a plugin event with this name when the DOM event is triggered * @param beforeDispatch Optional callback function to be invoked when the DOM event is triggered before trigger plugin event diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts index 34973eb1afb..bf0a54b7e33 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts @@ -1,9 +1,5 @@ import { parseValueWithUnit } from 'roosterjs-content-model-dom'; -import type { - EditorContext, - CreateEditorContext, - StandaloneEditorCore, -} from 'roosterjs-content-model-types'; +import type { EditorContext, CreateEditorContext, EditorCore } from 'roosterjs-content-model-types'; const DefaultRootFontSize = 16; @@ -40,7 +36,7 @@ function checkRootRtl(element: HTMLElement, context: EditorContext) { } } -function getRootComputedStyle(core: StandaloneEditorCore) { +function getRootComputedStyle(core: EditorCore) { const document = core.contentDiv.ownerDocument; const rootComputedStyle = document.defaultView?.getComputedStyle(document.documentElement); return rootComputedStyle; diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts index b5b5c8e69f5..8e6104f7bda 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts @@ -3,7 +3,7 @@ import type { Focus } from 'roosterjs-content-model-types'; /** * @internal * Focus to editor. If there is a cached selection range, use it as current selection - * @param core The StandaloneEditorCore object + * @param core The EditorCore object */ export const focus: Focus = core => { if (!core.lifecycle.shadowEditFragment) { diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts index a75c9ab498c..d7d1d2c5f6e 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts @@ -5,7 +5,7 @@ import type { DOMSelection, FormatContentModel, FormatContentModelContext, - StandaloneEditorCore, + EditorCore, } from 'roosterjs-content-model-types'; /** @@ -14,7 +14,7 @@ import type { * It will grab a Content Model for current editor content, and invoke a callback function * to do format change. Then according to the return value, write back the modified content model into editor. * If there is cached model, it will be used and updated. - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param formatter Formatter function, see ContentModelFormatter * @param options More options, see FormatContentModelOptions */ @@ -97,7 +97,7 @@ export const formatContentModel: FormatContentModel = (core, formatter, options) } }; -function handleImages(core: StandaloneEditorCore, context: FormatContentModelContext) { +function handleImages(core: EditorCore, context: FormatContentModelContext) { if (context.newImages.length > 0) { const viewport = core.api.getVisibleViewport(core); @@ -113,7 +113,7 @@ function handleImages(core: StandaloneEditorCore, context: FormatContentModelCon } function handlePendingFormat( - core: StandaloneEditorCore, + core: EditorCore, context: FormatContentModelContext, selection?: DOMSelection | null ) { diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts index ee4c7f8e26a..fddc26c9d93 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts @@ -1,8 +1,4 @@ -import type { - DOMSelection, - GetDOMSelection, - StandaloneEditorCore, -} from 'roosterjs-content-model-types'; +import type { DOMSelection, GetDOMSelection, EditorCore } from 'roosterjs-content-model-types'; /** * @internal @@ -19,7 +15,7 @@ export const getDOMSelection: GetDOMSelection = core => { } }; -function getNewSelection(core: StandaloneEditorCore): DOMSelection | null { +function getNewSelection(core: EditorCore): DOMSelection | null { const selection = core.contentDiv.ownerDocument.defaultView?.getSelection(); const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts index deadbe89f1a..bd3f8b00fc8 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts @@ -3,7 +3,7 @@ import type { GetVisibleViewport, Rect } from 'roosterjs-content-model-types'; /** * @internal * Retrieves the rect of the visible viewport of the editor. - * @param core The StandaloneEditorCore object + * @param core The EditorCore object */ export const getVisibleViewport: GetVisibleViewport = core => { const scrollContainer = core.domEvent.scrollContainer; diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts index 5026e5f17eb..9f4cc0cc4b9 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts @@ -3,7 +3,7 @@ import type { HasFocus } from 'roosterjs-content-model-types'; /** * @internal * Check if the editor has focus now - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @returns True if the editor has focus, otherwise false */ export const hasFocus: HasFocus = core => { diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts index d229c830a10..44258f6ff84 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts @@ -9,7 +9,7 @@ import type { PasteType, ClipboardData, Paste, - StandaloneEditorCore, + EditorCore, TrustedHTMLHandler, } from 'roosterjs-content-model-types'; @@ -20,12 +20,12 @@ const CloneOption: CloneModelOptions = { /** * @internal * Paste into editor using a clipboardData object - * @param core The StandaloneEditorCore object. + * @param core The EditorCore object. * @param clipboardData Clipboard data retrieved from clipboard * @param pasteType Type of content to paste. @default normal */ export const paste: Paste = ( - core: StandaloneEditorCore, + core: EditorCore, clipboardData: ClipboardData, pasteType: PasteType = 'normal' ) => { diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts index c2053bd4351..818b35e71a7 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts @@ -5,7 +5,7 @@ import type { SwitchShadowEdit } from 'roosterjs-content-model-types'; /** * @internal * Switch the Shadow Edit mode of editor On/Off - * @param editorCore The StandaloneEditorCore object + * @param editorCore The EditorCore object * @param isOn True to switch On, False to switch Off */ export const switchShadowEdit: SwitchShadowEdit = (editorCore, isOn): void => { diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/triggerEvent.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/triggerEvent.ts index a4dc7587f09..c2553fec493 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/triggerEvent.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/triggerEvent.ts @@ -15,7 +15,7 @@ const allowedEventsInShadowEdit: PluginEventType[] = [ /** * @internal * Trigger a plugin event - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param pluginEvent The event object to trigger * @param broadcast Set to true to skip the shouldHandleEventExclusively check */ diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CachePlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CachePlugin.ts index 90c2c28980b..8b9cf407e7d 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CachePlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CachePlugin.ts @@ -3,17 +3,17 @@ import { createTextMutationObserver } from './utils/textMutationObserver'; import { domIndexerImpl } from './utils/domIndexerImpl'; import type { CachePluginState, - IStandaloneEditor, + IEditor, PluginEvent, PluginWithState, - StandaloneEditorOptions, + EditorOptions, } from 'roosterjs-content-model-types'; /** * ContentModel cache plugin manages cached Content Model, and refresh the cache when necessary */ class CachePlugin implements PluginWithState { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private state: CachePluginState; /** @@ -21,7 +21,7 @@ class CachePlugin implements PluginWithState { * @param option The editor option * @param contentDiv The editor content DIV */ - constructor(option: StandaloneEditorOptions, contentDiv: HTMLDivElement) { + constructor(option: EditorOptions, contentDiv: HTMLDivElement) { this.state = option.cacheModel ? { domIndexer: domIndexerImpl, @@ -43,7 +43,7 @@ class CachePlugin implements PluginWithState { * editor reference so that it can call to any editor method or format API later. * @param editor The editor object */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; this.editor.getDocument().addEventListener('selectionchange', this.onNativeSelectionChange); @@ -134,7 +134,7 @@ class CachePlugin implements PluginWithState { } } - private updateCachedModel(editor: IStandaloneEditor, forceUpdate?: boolean) { + private updateCachedModel(editor: IEditor, forceUpdate?: boolean) { const cachedSelection = this.state.cachedSelection; this.state.cachedSelection = undefined; // Clear it to force getDOMSelection() retrieve the latest selection range @@ -169,7 +169,7 @@ class CachePlugin implements PluginWithState { * @param contentDiv The editor content DIV */ export function createCachePlugin( - option: StandaloneEditorOptions, + option: EditorOptions, contentDiv: HTMLDivElement ): PluginWithState { return new CachePlugin(option, contentDiv); diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/ContextMenuPlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/ContextMenuPlugin.ts index ed433c9d890..e1d584ebd05 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/ContextMenuPlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/ContextMenuPlugin.ts @@ -2,9 +2,9 @@ import { getSelectionRootNode } from '../publicApi/selection/getSelectionRootNod import type { ContextMenuPluginState, ContextMenuProvider, - IStandaloneEditor, + IEditor, PluginWithState, - StandaloneEditorOptions, + EditorOptions, } from 'roosterjs-content-model-types'; const ContextMenuButton = 2; @@ -13,7 +13,7 @@ const ContextMenuButton = 2; * Edit Component helps handle Content edit features */ class ContextMenuPlugin implements PluginWithState { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private state: ContextMenuPluginState; private disposer: (() => void) | null = null; @@ -21,7 +21,7 @@ class ContextMenuPlugin implements PluginWithState { * Construct a new instance of EditPlugin * @param options The editor options */ - constructor(options: StandaloneEditorOptions) { + constructor(options: EditorOptions) { this.state = { contextMenuProviders: options.plugins?.filter>(isContextMenuProvider) || [], @@ -39,7 +39,7 @@ class ContextMenuPlugin implements PluginWithState { * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; this.disposer = this.editor.attachDomEvent({ contextmenu: { @@ -97,7 +97,7 @@ class ContextMenuPlugin implements PluginWithState { } }; - private getFocusedNode(editor: IStandaloneEditor) { + private getFocusedNode(editor: IEditor) { const selection = editor.getDOMSelection(); if (selection) { @@ -121,7 +121,7 @@ function isContextMenuProvider(source: unknown): source is ContextMenuProvider { return new ContextMenuPlugin(options); } diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts index 45834288230..6946d36daee 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts @@ -22,9 +22,9 @@ import type { CopyPastePluginState, ContentModelTable, DOMSelection, - IStandaloneEditor, + IEditor, OnNodeCreated, - StandaloneEditorOptions, + EditorOptions, PluginWithState, ContentModelDocument, ContentModelParagraph, @@ -36,7 +36,7 @@ import type { * Copy and paste plugin for handling onCopy and onPaste event */ class CopyPastePlugin implements PluginWithState { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private disposer: (() => void) | null = null; private state: CopyPastePluginState; @@ -44,7 +44,7 @@ class CopyPastePlugin implements PluginWithState { * Construct a new instance of CopyPastePlugin * @param option The editor option */ - constructor(option: StandaloneEditorOptions) { + constructor(option: EditorOptions) { this.state = { allowedCustomPasteType: option.allowedCustomPasteType || [], tempDiv: null, @@ -62,7 +62,7 @@ class CopyPastePlugin implements PluginWithState { * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; this.disposer = this.editor.attachDomEvent({ paste: { @@ -338,7 +338,7 @@ export function preprocessTable(table: ContentModelTable) { * @param option The editor option */ export function createCopyPastePlugin( - option: StandaloneEditorOptions + option: EditorOptions ): PluginWithState { return new CopyPastePlugin(option); } diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/DOMEventPlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/DOMEventPlugin.ts index f3f08524a5f..2cf35b040c7 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/DOMEventPlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/DOMEventPlugin.ts @@ -3,9 +3,9 @@ import { isCharacterValue, isCursorMovingKey } from '../publicApi/domUtils/event import { isNodeOfType } from 'roosterjs-content-model-dom'; import type { DOMEventPluginState, - IStandaloneEditor, + IEditor, DOMEventRecord, - StandaloneEditorOptions, + EditorOptions, PluginWithState, } from 'roosterjs-content-model-types'; @@ -27,7 +27,7 @@ const EventTypeMap: Record = { * It contains special handling for Safari since Safari cannot get correct selection when onBlur event is triggered in editor. */ class DOMEventPlugin implements PluginWithState { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private disposer: (() => void) | null = null; private state: DOMEventPluginState; @@ -36,7 +36,7 @@ class DOMEventPlugin implements PluginWithState { * @param options The editor options * @param contentDiv The editor content DIV */ - constructor(options: StandaloneEditorOptions, contentDiv: HTMLDivElement) { + constructor(options: EditorOptions, contentDiv: HTMLDivElement) { this.state = { isInIME: false, scrollContainer: options.scrollContainer || contentDiv, @@ -57,7 +57,7 @@ class DOMEventPlugin implements PluginWithState { * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; const document = this.editor.getDocument(); @@ -228,7 +228,7 @@ class DOMEventPlugin implements PluginWithState { * @param contentDiv The editor content DIV element */ export function createDOMEventPlugin( - option: StandaloneEditorOptions, + option: EditorOptions, contentDiv: HTMLDivElement ): PluginWithState { return new DOMEventPlugin(option, contentDiv); diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts index b49687c2f07..ec0cf75cb08 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts @@ -19,7 +19,7 @@ import type { ContentModelEntityFormat, EntityOperation, EntityPluginState, - IStandaloneEditor, + IEditor, MouseUpEvent, PluginEvent, PluginWithState, @@ -31,7 +31,7 @@ const ENTITY_ID_REGEX = /_(\d{1,8})$/; * Entity Plugin helps handle all operations related to an entity and generate entity specified events */ class EntityPlugin implements PluginWithState { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private state: EntityPluginState; /** @@ -54,7 +54,7 @@ class EntityPlugin implements PluginWithState { * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; } @@ -102,7 +102,7 @@ class EntityPlugin implements PluginWithState { } } - private handleMouseUpEvent(editor: IStandaloneEditor, event: MouseUpEvent) { + private handleMouseUpEvent(editor: IEditor, event: MouseUpEvent) { const { rawEvent, isClicking } = event; let node: Node | null = rawEvent.target as Node; @@ -118,7 +118,7 @@ class EntityPlugin implements PluginWithState { } } - private handleContentChangedEvent(editor: IStandaloneEditor, event?: ContentChangedEvent) { + private handleContentChangedEvent(editor: IEditor, event?: ContentChangedEvent) { const modifiedEntities: ChangedEntity[] = event?.changedEntities ?? this.getChangedEntities(editor); const entityStates = event?.entityStates; @@ -184,7 +184,7 @@ class EntityPlugin implements PluginWithState { handleDelimiterContentChangedEvent(editor); } - private getChangedEntities(editor: IStandaloneEditor): ChangedEntity[] { + private getChangedEntities(editor: IEditor): ChangedEntity[] { const result: ChangedEntity[] = []; editor.formatContentModel(model => { @@ -229,7 +229,7 @@ class EntityPlugin implements PluginWithState { return result; } - private handleExtractContentWithDomEvent(editor: IStandaloneEditor, root: HTMLElement) { + private handleExtractContentWithDomEvent(editor: IEditor, root: HTMLElement) { getAllEntityWrappers(root).forEach(element => { element.removeAttribute('contentEditable'); @@ -238,7 +238,7 @@ class EntityPlugin implements PluginWithState { } private triggerEvent( - editor: IStandaloneEditor, + editor: IEditor, wrapper: HTMLElement, operation: EntityOperation, rawEvent?: Event, diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/FormatPlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/FormatPlugin.ts index 1fd6c7164a1..812a1ab5fd7 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/FormatPlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/FormatPlugin.ts @@ -7,10 +7,10 @@ import type { FontFamilyFormat, FontSizeFormat, FormatPluginState, - IStandaloneEditor, + IEditor, PluginEvent, PluginWithState, - StandaloneEditorOptions, + EditorOptions, TextColorFormat, } from 'roosterjs-content-model-types'; @@ -32,7 +32,7 @@ const DefaultStyleKeyMap: Record< * 1. Handle pending format changes when selection is collapsed */ class FormatPlugin implements PluginWithState { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private defaultFormatKeys: Set; private state: FormatPluginState; private lastCheckedNode: Node | null = null; @@ -41,7 +41,7 @@ class FormatPlugin implements PluginWithState { * Construct a new instance of FormatPlugin class * @param option The editor option */ - constructor(option: StandaloneEditorOptions) { + constructor(option: EditorOptions) { this.state = { defaultFormat: { ...option.defaultSegmentFormat }, pendingFormat: null, @@ -69,7 +69,7 @@ class FormatPlugin implements PluginWithState { * editor reference so that it can call to any editor method or format API later. * @param editor The editor object */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; } @@ -168,7 +168,7 @@ class FormatPlugin implements PluginWithState { return result; } - private shouldApplyDefaultFormat(editor: IStandaloneEditor): boolean { + private shouldApplyDefaultFormat(editor: IEditor): boolean { const selection = editor.getDOMSelection(); const range = selection?.type == 'range' ? selection.range : null; const posContainer = range?.startContainer ?? null; @@ -216,8 +216,6 @@ class FormatPlugin implements PluginWithState { * Create a new instance of FormatPlugin. * @param option The editor option */ -export function createFormatPlugin( - option: StandaloneEditorOptions -): PluginWithState { +export function createFormatPlugin(option: EditorOptions): PluginWithState { return new FormatPlugin(option); } diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/LifecyclePlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/LifecyclePlugin.ts index 77875155d38..e98d81313d4 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/LifecyclePlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/LifecyclePlugin.ts @@ -1,11 +1,11 @@ import { ChangeSource } from '../constants/ChangeSource'; import { setColor } from 'roosterjs-content-model-dom'; import type { - IStandaloneEditor, + IEditor, LifecyclePluginState, PluginEvent, PluginWithState, - StandaloneEditorOptions, + EditorOptions, } from 'roosterjs-content-model-types'; const ContentEditableAttributeName = 'contenteditable'; @@ -16,7 +16,7 @@ const DefaultBackColor = '#ffffff'; * Lifecycle plugin handles editor initialization and disposing */ class LifecyclePlugin implements PluginWithState { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private state: LifecyclePluginState; private initializer: (() => void) | null = null; private disposer: (() => void) | null = null; @@ -27,7 +27,7 @@ class LifecyclePlugin implements PluginWithState { * @param options The editor options * @param contentDiv The editor content DIV */ - constructor(options: StandaloneEditorOptions, contentDiv: HTMLDivElement) { + constructor(options: EditorOptions, contentDiv: HTMLDivElement) { // Make the container editable and set its selection styles if (contentDiv.getAttribute(ContentEditableAttributeName) === null) { this.initializer = () => { @@ -62,7 +62,7 @@ class LifecyclePlugin implements PluginWithState { * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; // Set content DIV to be editable @@ -141,7 +141,7 @@ class LifecyclePlugin implements PluginWithState { * @param contentDiv The editor content DIV element */ export function createLifecyclePlugin( - option: StandaloneEditorOptions, + option: EditorOptions, contentDiv: HTMLDivElement ): PluginWithState { return new LifecyclePlugin(option, contentDiv); diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts index 286ef0b57e6..535c686a52f 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts @@ -2,24 +2,24 @@ import { isElementOfType, isNodeOfType, toArray } from 'roosterjs-content-model- import { isModifierKey } from '../publicApi/domUtils/eventUtils'; import type { DOMSelection, - IStandaloneEditor, + IEditor, PluginEvent, PluginWithState, SelectionPluginState, - StandaloneEditorOptions, + EditorOptions, } from 'roosterjs-content-model-types'; const MouseMiddleButton = 1; const MouseRightButton = 2; class SelectionPlugin implements PluginWithState { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private state: SelectionPluginState; private disposer: (() => void) | null = null; private isSafari = false; private isMac = false; - constructor(options: StandaloneEditorOptions) { + constructor(options: EditorOptions) { this.state = { selection: null, selectionStyleNode: null, @@ -31,7 +31,7 @@ class SelectionPlugin implements PluginWithState { return 'Selection'; } - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; const doc = this.editor.getDocument(); @@ -141,14 +141,14 @@ class SelectionPlugin implements PluginWithState { } } - private selectImage(editor: IStandaloneEditor, image: HTMLImageElement) { + private selectImage(editor: IEditor, image: HTMLImageElement) { editor.setDOMSelection({ type: 'image', image: image, }); } - private selectBeforeImage(editor: IStandaloneEditor, image: HTMLImageElement) { + private selectBeforeImage(editor: IEditor, image: HTMLImageElement) { const doc = editor.getDocument(); const parent = image.parentNode; const index = parent && toArray(parent.childNodes).indexOf(image); @@ -231,7 +231,7 @@ class SelectionPlugin implements PluginWithState { * @param option The editor option */ export function createSelectionPlugin( - options: StandaloneEditorOptions + options: EditorOptions ): PluginWithState { return new SelectionPlugin(options); } diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/UndoPlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/UndoPlugin.ts index 7e35ff5c0f1..600ed50a63b 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/UndoPlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/UndoPlugin.ts @@ -4,10 +4,10 @@ import { isCursorMovingKey } from '../publicApi/domUtils/eventUtils'; import { undo } from '../publicApi/undo/undo'; import type { ContentChangedEvent, - IStandaloneEditor, + IEditor, PluginEvent, PluginWithState, - StandaloneEditorOptions, + EditorOptions, UndoPluginState, } from 'roosterjs-content-model-types'; @@ -19,14 +19,14 @@ const Enter = 'Enter'; * Provides snapshot based undo service for Editor */ class UndoPlugin implements PluginWithState { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private state: UndoPluginState; /** * Construct a new instance of UndoPlugin * @param options The wrapper of the state object */ - constructor(options: StandaloneEditorOptions) { + constructor(options: EditorOptions) { this.state = { snapshotsManager: createSnapshotsManager(options.snapshots), isRestoring: false, @@ -48,7 +48,7 @@ class UndoPlugin implements PluginWithState { * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ - initialize(editor: IStandaloneEditor): void { + initialize(editor: IEditor): void { this.editor = editor; } @@ -120,7 +120,7 @@ class UndoPlugin implements PluginWithState { } } - private onKeyDown(editor: IStandaloneEditor, evt: KeyboardEvent): void { + private onKeyDown(editor: IEditor, evt: KeyboardEvent): void { const { snapshotsManager } = this.state; // Handle backspace/delete when there is a selection to take a snapshot @@ -167,7 +167,7 @@ class UndoPlugin implements PluginWithState { } } - private onKeyPress(editor: IStandaloneEditor, evt: KeyboardEvent): void { + private onKeyPress(editor: IEditor, evt: KeyboardEvent): void { if (evt.metaKey) { // if metaKey is pressed, simply return since no actual effect will be taken on the editor. // this is to prevent changing hasNewContent to true when meta + v to paste on Safari. @@ -226,7 +226,7 @@ class UndoPlugin implements PluginWithState { this.state.snapshotsManager.hasNewContent = true; } - private canUndoAutoComplete(editor: IStandaloneEditor) { + private canUndoAutoComplete(editor: IEditor) { const selection = editor.getDOMSelection(); return ( @@ -244,7 +244,7 @@ class UndoPlugin implements PluginWithState { this.state.posOffset = null; } - private isCtrlOrMetaPressed(editor: IStandaloneEditor, event: KeyboardEvent) { + private isCtrlOrMetaPressed(editor: IEditor, event: KeyboardEvent) { const env = editor.getEnvironment(); return env.isMac ? event.metaKey : event.ctrlKey; @@ -256,8 +256,6 @@ class UndoPlugin implements PluginWithState { * Create a new instance of UndoPlugin. * @param option The editor option */ -export function createUndoPlugin( - option: StandaloneEditorOptions -): PluginWithState { +export function createUndoPlugin(option: EditorOptions): PluginWithState { return new UndoPlugin(option); } diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/createStandaloneEditorCorePlugins.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/createEditorCorePlugins.ts similarity index 79% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/createStandaloneEditorCorePlugins.ts rename to packages-content-model/roosterjs-content-model-core/lib/corePlugin/createEditorCorePlugins.ts index eb8692305e5..df38c0e2572 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/createStandaloneEditorCorePlugins.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/createEditorCorePlugins.ts @@ -7,20 +7,17 @@ import { createFormatPlugin } from './FormatPlugin'; import { createLifecyclePlugin } from './LifecyclePlugin'; import { createSelectionPlugin } from './SelectionPlugin'; import { createUndoPlugin } from './UndoPlugin'; -import type { - StandaloneEditorCorePlugins, - StandaloneEditorOptions, -} from 'roosterjs-content-model-types'; +import type { EditorCorePlugins, EditorOptions } from 'roosterjs-content-model-types'; /** * @internal - * Create core plugins for standalone editor + * Create core plugins for editor * @param options Options of editor */ -export function createStandaloneEditorCorePlugins( - options: StandaloneEditorOptions, +export function createEditorCorePlugins( + options: EditorOptions, contentDiv: HTMLDivElement -): StandaloneEditorCorePlugins { +): EditorCorePlugins { return { cache: createCachePlugin(options, contentDiv), format: createFormatPlugin(options), diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyDefaultFormat.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyDefaultFormat.ts index 263848843b7..145a9dc2325 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyDefaultFormat.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyDefaultFormat.ts @@ -1,17 +1,14 @@ import { deleteSelection } from '../../publicApi/selection/deleteSelection'; import { normalizeContentModel } from 'roosterjs-content-model-dom'; -import type { ContentModelSegmentFormat, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelSegmentFormat, IEditor } from 'roosterjs-content-model-types'; /** * @internal * When necessary, set default format as current pending format so it will be applied when Input event is fired - * @param editor The Content Model Editor + * @param editor The editor object * @param defaultFormat The default segment format to apply */ -export function applyDefaultFormat( - editor: IStandaloneEditor, - defaultFormat: ContentModelSegmentFormat -) { +export function applyDefaultFormat(editor: IEditor, defaultFormat: ContentModelSegmentFormat) { editor.formatContentModel((model, context) => { const result = deleteSelection(model, [], context); @@ -64,7 +61,7 @@ export function applyDefaultFormat( } function getNewPendingFormat( - editor: IStandaloneEditor, + editor: IEditor, defaultFormat: ContentModelSegmentFormat, markerFormat: ContentModelSegmentFormat ): ContentModelSegmentFormat { diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyPendingFormat.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyPendingFormat.ts index d16e0c501f8..8113912ad09 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyPendingFormat.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyPendingFormat.ts @@ -1,5 +1,5 @@ import { iterateSelections } from '../../publicApi/selection/iterateSelections'; -import type { ContentModelSegmentFormat, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelSegmentFormat, IEditor } from 'roosterjs-content-model-types'; import { createText, normalizeContentModel, @@ -16,7 +16,7 @@ const NON_BREAK_SPACE = '\u00A0'; * @param data The text user just input */ export function applyPendingFormat( - editor: IStandaloneEditor, + editor: IEditor, data: string, format: ContentModelSegmentFormat ) { diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/entityDelimiterUtils.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/entityDelimiterUtils.ts index e2f8f1953c1..53d84e232b2 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/entityDelimiterUtils.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/entityDelimiterUtils.ts @@ -6,7 +6,7 @@ import type { ContentModelFormatter, ContentModelParagraph, ContentModelSegmentFormat, - IStandaloneEditor, + IEditor, KeyDownEvent, RangeSelection, } from 'roosterjs-content-model-types'; @@ -32,7 +32,7 @@ const BlockEntityContainerSelector = '.' + BlockEntityContainer; /** * @internal exported only for unit test */ -export function preventTypeInDelimiter(node: HTMLElement, editor: IStandaloneEditor) { +export function preventTypeInDelimiter(node: HTMLElement, editor: IEditor) { const isAfter = node.classList.contains(DelimiterAfter); const entitySibling = isAfter ? node.previousElementSibling : node.nextElementSibling; if (entitySibling && isEntityElement(entitySibling)) { @@ -164,7 +164,7 @@ function getFocusedElement( /** * @internal */ -export function handleDelimiterContentChangedEvent(editor: IStandaloneEditor) { +export function handleDelimiterContentChangedEvent(editor: IEditor) { const helper = editor.getDOMHelper(); removeInvalidDelimiters(helper.queryElements(DelimiterSelector)); addDelimitersIfNeeded(helper.queryElements(InlineEntitySelector), editor.getPendingFormat()); @@ -173,7 +173,7 @@ export function handleDelimiterContentChangedEvent(editor: IStandaloneEditor) { /** * @internal */ -export function handleCompositionEndEvent(editor: IStandaloneEditor, event: CompositionEndEvent) { +export function handleCompositionEndEvent(editor: IEditor, event: CompositionEndEvent) { const selection = editor.getDOMSelection(); if (selection?.type == 'range' && selection.range.collapsed) { @@ -193,7 +193,7 @@ export function handleCompositionEndEvent(editor: IStandaloneEditor, event: Comp /** * @internal */ -export function handleDelimiterKeyDownEvent(editor: IStandaloneEditor, event: KeyDownEvent) { +export function handleDelimiterKeyDownEvent(editor: IEditor, event: KeyDownEvent) { const selection = editor.getDOMSelection(); const { rawEvent } = event; diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/StandaloneEditor.ts b/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts similarity index 95% rename from packages-content-model/roosterjs-content-model-core/lib/editor/StandaloneEditor.ts rename to packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts index 37b6ba45a90..acd1c29e800 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/editor/StandaloneEditor.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts @@ -1,7 +1,7 @@ import { ChangeSource } from '../constants/ChangeSource'; import { cloneModel } from '../publicApi/model/cloneModel'; +import { createEditorCore } from './createEditorCore'; import { createEmptyModel, tableProcessor } from 'roosterjs-content-model-dom'; -import { createStandaloneEditorCore } from './createStandaloneEditorCore'; import { reducedModelChildProcessor } from '../override/reducedModelChildProcessor'; import { transformColor } from '../publicApi/color/transformColor'; import type { CachedElementHandler } from '../publicApi/model/cloneModel'; @@ -16,32 +16,32 @@ import type { DOMSelection, EditorEnvironment, FormatContentModelOptions, - IStandaloneEditor, + IEditor, PasteType, PluginEventData, PluginEventFromType, PluginEventType, Snapshot, SnapshotsManager, - StandaloneEditorCore, - StandaloneEditorOptions, + EditorCore, + EditorOptions, TrustedHTMLHandler, Rect, } from 'roosterjs-content-model-types'; /** - * The standalone editor class based on Content Model + * The main editor class based on Content Model */ -export class StandaloneEditor implements IStandaloneEditor { - private core: StandaloneEditorCore | null = null; +export class Editor implements IEditor { + private core: EditorCore | null = null; /** * Creates an instance of Editor * @param contentDiv The DIV HTML element which will be the container element of editor * @param options An optional options object to customize the editor */ - constructor(contentDiv: HTMLDivElement, options: StandaloneEditorOptions = {}) { - this.core = createStandaloneEditorCore(contentDiv, options); + constructor(contentDiv: HTMLDivElement, options: EditorOptions = {}) { + this.core = createEditorCore(contentDiv, options); const initialModel = options.initialModel ?? createEmptyModel(options.defaultSegmentFormat); @@ -367,10 +367,10 @@ export class StandaloneEditor implements IStandaloneEditor { } /** - * @returns the current StandaloneEditorCore object + * @returns the current EditorCore object * @throws a standard Error if there's no core object */ - protected getCore(): StandaloneEditorCore { + protected getCore(): EditorCore { if (!this.core) { throw new Error('Editor is already disposed'); } diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/standaloneCoreApiMap.ts b/packages-content-model/roosterjs-content-model-core/lib/editor/coreApiMap.ts similarity index 88% rename from packages-content-model/roosterjs-content-model-core/lib/editor/standaloneCoreApiMap.ts rename to packages-content-model/roosterjs-content-model-core/lib/editor/coreApiMap.ts index fadf02808ff..8626ea508eb 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/editor/standaloneCoreApiMap.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/editor/coreApiMap.ts @@ -13,13 +13,13 @@ import { setContentModel } from '../coreApi/setContentModel'; import { setDOMSelection } from '../coreApi/setDOMSelection'; import { switchShadowEdit } from '../coreApi/switchShadowEdit'; import { triggerEvent } from '../coreApi/triggerEvent'; -import type { StandaloneCoreApiMap } from 'roosterjs-content-model-types'; +import type { CoreApiMap } from 'roosterjs-content-model-types'; /** * @internal - * Core API map for Standalone Content Model Editor + * Core API map for Editor */ -export const standaloneCoreApiMap: StandaloneCoreApiMap = { +export const coreApiMap: CoreApiMap = { createContentModel: createContentModel, createEditorContext: createEditorContext, formatContentModel: formatContentModel, diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/createStandaloneEditorCore.ts b/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorCore.ts similarity index 85% rename from packages-content-model/roosterjs-content-model-core/lib/editor/createStandaloneEditorCore.ts rename to packages-content-model/roosterjs-content-model-core/lib/editor/createEditorCore.ts index 53e629732dd..c222dacebce 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/editor/createStandaloneEditorCore.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorCore.ts @@ -1,35 +1,28 @@ +import { coreApiMap } from './coreApiMap'; import { createDarkColorHandler } from './DarkColorHandlerImpl'; import { createDOMHelper } from './DOMHelperImpl'; -import { createStandaloneEditorCorePlugins } from '../corePlugin/createStandaloneEditorCorePlugins'; -import { standaloneCoreApiMap } from './standaloneCoreApiMap'; -import { - createDomToModelSettings, - createModelToDomSettings, -} from './createStandaloneEditorDefaultSettings'; +import { createEditorCorePlugins } from '../corePlugin/createEditorCorePlugins'; +import { createDomToModelSettings, createModelToDomSettings } from './createEditorDefaultSettings'; import type { EditorEnvironment, PluginState, - StandaloneEditorCore, - StandaloneEditorCorePlugins, - StandaloneEditorOptions, + EditorCore, + EditorCorePlugins, + EditorOptions, } from 'roosterjs-content-model-types'; /** - * @internal - * A temporary function to create Standalone Editor core + * @internal Create core object for editor * @param contentDiv Editor content DIV * @param options Editor options */ -export function createStandaloneEditorCore( - contentDiv: HTMLDivElement, - options: StandaloneEditorOptions -): StandaloneEditorCore { - const corePlugins = createStandaloneEditorCorePlugins(options, contentDiv); +export function createEditorCore(contentDiv: HTMLDivElement, options: EditorOptions): EditorCore { + const corePlugins = createEditorCorePlugins(options, contentDiv); return { contentDiv, - api: { ...standaloneCoreApiMap, ...options.coreApiOverride }, - originalApi: { ...standaloneCoreApiMap }, + api: { ...coreApiMap, ...options.coreApiOverride }, + originalApi: { ...coreApiMap }, plugins: [ corePlugins.cache, corePlugins.format, @@ -97,7 +90,7 @@ export function defaultTrustHtmlHandler(html: string) { return html; } -function getPluginState(corePlugins: StandaloneEditorCorePlugins): PluginState { +function getPluginState(corePlugins: EditorCorePlugins): PluginState { return { domEvent: corePlugins.domEvent.getState(), copyPaste: corePlugins.copyPaste.getState(), diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/createStandaloneEditorDefaultSettings.ts b/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorDefaultSettings.ts similarity index 91% rename from packages-content-model/roosterjs-content-model-core/lib/editor/createStandaloneEditorDefaultSettings.ts rename to packages-content-model/roosterjs-content-model-core/lib/editor/createEditorDefaultSettings.ts index bbba2c45b6a..1340cb9d058 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/editor/createStandaloneEditorDefaultSettings.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorDefaultSettings.ts @@ -7,16 +7,16 @@ import type { DomToModelSettings, ModelToDomOption, ModelToDomSettings, - StandaloneEditorOptions, + EditorOptions, } from 'roosterjs-content-model-types'; /** * @internal - * Create default DOM to Content Model conversion settings for a standalone editor + * Create default DOM to Content Model conversion settings for an editor * @param options The editor options */ export function createDomToModelSettings( - options: StandaloneEditorOptions + options: EditorOptions ): ContentModelSettings { const builtIn: DomToModelOption = { processorOverride: { @@ -34,11 +34,11 @@ export function createDomToModelSettings( /** * @internal - * Create default Content Model to DOM conversion settings for a standalone editor + * Create default Content Model to DOM conversion settings for an editor * @param options The editor options */ export function createModelToDomSettings( - options: StandaloneEditorOptions + options: EditorOptions ): ContentModelSettings { const builtIn: ModelToDomOption = { metadataAppliers: { diff --git a/packages-content-model/roosterjs-content-model-core/lib/index.ts b/packages-content-model/roosterjs-content-model-core/lib/index.ts index 743e4314697..9a2034e3450 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/index.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/index.ts @@ -60,4 +60,4 @@ export { BulletListType } from './constants/BulletListType'; export { NumberingListType } from './constants/NumberingListType'; export { TableBorderFormat } from './constants/TableBorderFormat'; -export { StandaloneEditor } from './editor/StandaloneEditor'; +export { Editor } from './editor/Editor'; diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts index 83e3c8c6184..e7a021d55ef 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts @@ -3,7 +3,7 @@ import { contentModelToText, createModelToDomContext, } from 'roosterjs-content-model-dom'; -import type { ExportContentMode, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ExportContentMode, IEditor } from 'roosterjs-content-model-types'; /** * Export string content of editor @@ -13,7 +13,7 @@ import type { ExportContentMode, IStandaloneEditor } from 'roosterjs-content-mod * - PlainText: Export plain text content * - PlainTextFast: Export plain text using editor's textContent property directly */ -export function exportContent(editor: IStandaloneEditor, mode: ExportContentMode = 'HTML'): string { +export function exportContent(editor: IEditor, mode: ExportContentMode = 'HTML'): string { if (mode == 'PlainTextFast') { return editor.getDOMHelper().getTextContent(); } else { diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/redo.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/redo.ts index 1ae09d3b1a0..6255b1aed07 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/redo.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/redo.ts @@ -1,10 +1,10 @@ -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Redo to next undo snapshot * @param editor The editor to undo with */ -export function redo(editor: IStandaloneEditor): void { +export function redo(editor: IEditor): void { editor.focus(); const manager = editor.getSnapshotsManager(); diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/undo.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/undo.ts index c67714a07fd..bac557a39ac 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/undo.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/undo.ts @@ -1,10 +1,10 @@ -import type { IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * Undo to last undo snapshot * @param editor The editor to undo with */ -export function undo(editor: IStandaloneEditor): void { +export function undo(editor: IEditor): void { editor.focus(); const manager = editor.getSnapshotsManager(); diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts index b15da47dd43..fd32fa44096 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts @@ -1,10 +1,10 @@ import { isElementOfType, isNodeOfType, moveChildNodes } from 'roosterjs-content-model-dom'; -import type { SnapshotSelection, StandaloneEditorCore } from 'roosterjs-content-model-types'; +import type { SnapshotSelection, EditorCore } from 'roosterjs-content-model-types'; /** * @internal */ -export function createSnapshotSelection(core: StandaloneEditorCore): SnapshotSelection { +export function createSnapshotSelection(core: EditorCore): SnapshotSelection { const { contentDiv, api } = core; const selection = api.getDOMSelection(core); diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts index 0d73aace66e..2d85729aac1 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts @@ -4,14 +4,14 @@ import type { ClipboardData, DomToModelOptionForSanitizing, PasteType, - StandaloneEditorCore, + EditorCore, } from 'roosterjs-content-model-types'; /** * @internal */ export function generatePasteOptionFromPlugins( - core: StandaloneEditorCore, + core: EditorCore, clipboardData: ClipboardData, fragment: DocumentFragment, htmlFromClipboard: HtmlFromClipboard, diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts index 905ac41b5c4..5ba9d88bed4 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts @@ -11,7 +11,7 @@ import type { ClipboardData, ContentModelDocument, ContentModelSegmentFormat, - StandaloneEditorCore, + EditorCore, } from 'roosterjs-content-model-types'; const EmptySegmentFormat: Required = { @@ -32,7 +32,7 @@ const EmptySegmentFormat: Required = { * @internal */ export function mergePasteContent( - core: StandaloneEditorCore, + core: EditorCore, eventResult: BeforePasteEvent, clipboardData: ClipboardData ) { diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts index 2bae196159d..768d2a1797a 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts @@ -1,10 +1,10 @@ import { transformColor } from '../publicApi/color/transformColor'; -import type { StandaloneEditorCore, Snapshot } from 'roosterjs-content-model-types'; +import type { EditorCore, Snapshot } from 'roosterjs-content-model-types'; /** * @internal */ -export function restoreSnapshotColors(core: StandaloneEditorCore, snapshot: Snapshot) { +export function restoreSnapshotColors(core: EditorCore, snapshot: Snapshot) { const isDarkMode = core.lifecycle.isDarkMode; core.darkColorHandler.updateKnownColor(isDarkMode); // Pass no parameter to force update all colors diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts index 3b05a30be00..b70474d9446 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts @@ -7,7 +7,7 @@ import { } from 'roosterjs-content-model-dom'; import type { Snapshot, - StandaloneEditorCore, + EditorCore, KnownEntityItem, ContentModelEntityFormat, } from 'roosterjs-content-model-types'; @@ -17,7 +17,7 @@ const BlockEntityContainer = '_E_EBlockEntityContainer'; /** * @internal */ -export function restoreSnapshotHTML(core: StandaloneEditorCore, snapshot: Snapshot) { +export function restoreSnapshotHTML(core: EditorCore, snapshot: Snapshot) { const { contentDiv, entity: { entityMap }, diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts index 79e2e4e2b70..9c835d72b6f 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts @@ -1,10 +1,10 @@ import { isNodeOfType } from 'roosterjs-content-model-dom'; -import type { DOMSelection, StandaloneEditorCore, Snapshot } from 'roosterjs-content-model-types'; +import type { DOMSelection, EditorCore, Snapshot } from 'roosterjs-content-model-types'; /** * @internal */ -export function restoreSnapshotSelection(core: StandaloneEditorCore, snapshot: Snapshot) { +export function restoreSnapshotSelection(core: EditorCore, snapshot: Snapshot) { const snapshotSelection = snapshot.selection; const { contentDiv } = core; let domSelection: DOMSelection | null = null; diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts index f1a579518e5..718b1b1fff5 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts @@ -1,9 +1,9 @@ import * as createSnapshotSelection from '../../lib/utils/createSnapshotSelection'; import { addUndoSnapshot } from '../../lib/coreApi/addUndoSnapshot'; -import { SnapshotsManager, StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { EditorCore, SnapshotsManager } from 'roosterjs-content-model-types'; describe('addUndoSnapshot', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let contentDiv: HTMLDivElement; let addSnapshotSpy: jasmine.Spy; let getKnownColorsCopySpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts index 92163c4f6ac..15e48826b3a 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts @@ -1,9 +1,9 @@ import { attachDomEvent } from '../../lib/coreApi/attachDomEvent'; -import { StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { EditorCore } from 'roosterjs-content-model-types'; describe('attachDomEvent', () => { let div: HTMLDivElement; - let core: StandaloneEditorCore; + let core: EditorCore; beforeEach(() => { div = document.createElement('div'); diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts index 526e3991776..3353d9c6a99 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts @@ -2,7 +2,7 @@ import * as cloneModel from '../../lib/publicApi/model/cloneModel'; import * as createDomToModelContext from 'roosterjs-content-model-dom/lib/domToModel/context/createDomToModelContext'; import * as domToContentModel from 'roosterjs-content-model-dom/lib/domToModel/domToContentModel'; import { createContentModel } from '../../lib/coreApi/createContentModel'; -import { StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { EditorCore } from 'roosterjs-content-model-types'; const mockedEditorContext = 'EDITORCONTEXT' as any; const mockedContext = 'CONTEXT' as any; @@ -12,7 +12,7 @@ const mockedCachedMode = 'CACHEDMODEL' as any; const mockedClonedModel = 'CLONEDMODEL' as any; describe('createContentModel', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let createEditorContext: jasmine.Spy; let getDOMSelection: jasmine.Spy; let domToContentModelSpy: jasmine.Spy; @@ -44,7 +44,7 @@ describe('createContentModel', () => { }, lifecycle: {}, domToModelSettings: {}, - } as any) as StandaloneEditorCore; + } as any) as EditorCore; }); it('Reuse model, no cache, no shadow edit', () => { diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts index bdf2f92ba06..0768c3ad517 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts @@ -1,5 +1,5 @@ import { createEditorContext } from '../../lib/coreApi/createEditorContext'; -import { StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { EditorCore } from 'roosterjs-content-model-types'; describe('createEditorContext', () => { it('create a normal context', () => { @@ -33,7 +33,7 @@ describe('createEditorContext', () => { domHelper: { calculateZoomScale: calculateZoomScaleSpy, }, - } as any) as StandaloneEditorCore; + } as any) as EditorCore; const context = createEditorContext(core, false); @@ -81,7 +81,7 @@ describe('createEditorContext', () => { domHelper: { calculateZoomScale: calculateZoomScaleSpy, }, - } as any) as StandaloneEditorCore; + } as any) as EditorCore; const context = createEditorContext(core, true); @@ -128,7 +128,7 @@ describe('createEditorContext', () => { domHelper: { calculateZoomScale: calculateZoomScaleSpy, }, - } as any) as StandaloneEditorCore; + } as any) as EditorCore; const context = createEditorContext(core, false); @@ -147,7 +147,7 @@ describe('createEditorContext', () => { }); describe('createEditorContext - checkZoomScale', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let div: any; let getComputedStyleSpy: jasmine.Spy; let calculateZoomScaleSpy: jasmine.Spy; @@ -179,7 +179,7 @@ describe('createEditorContext - checkZoomScale', () => { domHelper: { calculateZoomScale: calculateZoomScaleSpy, }, - } as any) as StandaloneEditorCore; + } as any) as EditorCore; }); it('Zoom scale = 2', () => { @@ -202,7 +202,7 @@ describe('createEditorContext - checkZoomScale', () => { }); describe('createEditorContext - checkRootDir', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let div: any; let getComputedStyleSpy: jasmine.Spy; let calculateZoomScaleSpy: jasmine.Spy; @@ -233,7 +233,7 @@ describe('createEditorContext - checkRootDir', () => { domHelper: { calculateZoomScale: calculateZoomScaleSpy, }, - } as any) as StandaloneEditorCore; + } as any) as EditorCore; }); it('LTR CSS', () => { diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts index af96e16dd14..52f2ef37489 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts @@ -1,9 +1,9 @@ +import { EditorCore } from 'roosterjs-content-model-types'; import { focus } from '../../lib/coreApi/focus'; -import { StandaloneEditorCore } from 'roosterjs-content-model-types'; describe('focus', () => { let div: HTMLDivElement; - let core: StandaloneEditorCore; + let core: EditorCore; let hasFocusSpy: jasmine.Spy; let setDOMSelectionSpy: jasmine.Spy; let nativeFocusSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts index 96aaf2b74c3..40a382043e4 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts @@ -6,11 +6,11 @@ import { ContentModelDocument, ContentModelSegmentFormat, FormatContentModelContext, - StandaloneEditorCore, + EditorCore, } from 'roosterjs-content-model-types'; describe('formatContentModel', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let addUndoSnapshot: jasmine.Spy; let createContentModel: jasmine.Spy; let setContentModel: jasmine.Spy; @@ -56,7 +56,7 @@ describe('formatContentModel', () => { undo: { snapshotsManager: {}, }, - } as any) as StandaloneEditorCore; + } as any) as EditorCore; }); describe('Editor has focus', () => { diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts index 952a2af9fa2..3feabab9692 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts @@ -1,8 +1,8 @@ +import { EditorCore } from 'roosterjs-content-model-types'; import { getDOMSelection } from '../../lib/coreApi/getDOMSelection'; -import { StandaloneEditorCore } from 'roosterjs-content-model-types'; describe('getDOMSelection', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let getSelectionSpy: jasmine.Spy; let hasFocusSpy: jasmine.Spy; let containsSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts index 3eff77e4d38..62ceda15096 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts @@ -1,8 +1,8 @@ +import { EditorCore } from 'roosterjs-content-model-types'; import { hasFocus } from '../../lib/coreApi/hasFocus'; -import { StandaloneEditorCore } from 'roosterjs-content-model-types'; describe('hasFocus', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let containsSpy: jasmine.Spy; let mockedElement = 'ELEMENT' as any; diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts index 3592b897491..fcb8f1e13d4 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts @@ -9,13 +9,13 @@ import * as PPT from 'roosterjs-content-model-plugins/lib/paste/PowerPoint/proce import * as setProcessorF from 'roosterjs-content-model-plugins/lib/paste/utils/setProcessor'; import * as WacComponents from 'roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents'; import * as WordDesktopFile from 'roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop'; +import { Editor } from '../../lib/editor/Editor'; import { expectEqual, initEditor } from 'roosterjs-content-model-plugins/test/paste/e2e/testUtils'; import { PastePlugin } from 'roosterjs-content-model-plugins/lib/paste/PastePlugin'; -import { StandaloneEditor } from '../../lib/editor/StandaloneEditor'; import { ClipboardData, ContentModelDocument, - IStandaloneEditor, + IEditor, BeforePasteEvent, PluginEvent, } from 'roosterjs-content-model-types'; @@ -25,7 +25,7 @@ let clipboardData: ClipboardData; const DEFAULT_TIMES_ADD_PARSER_CALLED = 4; describe('Paste ', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let createContentModel: jasmine.Spy; let focus: jasmine.Spy; let mockedModel: ContentModelDocument; @@ -72,7 +72,7 @@ describe('Paste ', () => { } as any, ]); - editor = new StandaloneEditor(div, { + editor = new Editor(div, { plugins: [new PastePlugin()], coreApiOverride: { focus, @@ -105,13 +105,13 @@ describe('Paste ', () => { }); describe('paste with content model & paste plugin', () => { - let editor: StandaloneEditor | undefined; + let editor: Editor | undefined; let div: HTMLDivElement | undefined; beforeEach(() => { div = document.createElement('div'); document.body.appendChild(div); - editor = new StandaloneEditor(div, { + editor = new Editor(div, { plugins: [new PastePlugin()], }); spyOn(addParserF, 'default').and.callThrough(); @@ -258,7 +258,7 @@ describe('paste with content model & paste plugin', () => { }; let eventChecker: BeforePasteEvent = {}; - editor = new StandaloneEditor(div!, { + editor = new Editor(div!, { plugins: [ { initialize: () => {}, @@ -283,7 +283,7 @@ describe('paste with content model & paste plugin', () => { }); describe('Paste with clipboardData', () => { - let editor: IStandaloneEditor = undefined!; + let editor: IEditor = undefined!; const ID = 'EDITOR_ID'; beforeEach(() => { diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshotTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshotTest.ts index 81707e39596..b1c2db93602 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshotTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshotTest.ts @@ -2,11 +2,11 @@ import * as restoreSnapshotColors from '../../lib/utils/restoreSnapshotColors'; import * as restoreSnapshotHTML from '../../lib/utils/restoreSnapshotHTML'; import * as restoreSnapshotSelection from '../../lib/utils/restoreSnapshotSelection'; import { ChangeSource } from '../../lib/constants/ChangeSource'; +import { EditorCore, Snapshot } from 'roosterjs-content-model-types'; import { restoreUndoSnapshot } from '../../lib/coreApi/restoreUndoSnapshot'; -import { Snapshot, StandaloneEditorCore } from 'roosterjs-content-model-types'; describe('restoreUndoSnapshot', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let triggerEventSpy: jasmine.Spy; let restoreSnapshotColorsSpy: jasmine.Spy; let restoreSnapshotHTMLSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts index 31a0cefe234..62fbe1044da 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts @@ -1,7 +1,7 @@ import * as contentModelToDom from 'roosterjs-content-model-dom/lib/modelToDom/contentModelToDom'; import * as createModelToDomContext from 'roosterjs-content-model-dom/lib/modelToDom/context/createModelToDomContext'; +import { EditorCore } from 'roosterjs-content-model-types'; import { setContentModel } from '../../lib/coreApi/setContentModel'; -import { StandaloneEditorCore } from 'roosterjs-content-model-types'; const mockedDoc = 'DOCUMENT' as any; const mockedModel = 'MODEL' as any; @@ -11,7 +11,7 @@ const mockedDiv = { ownerDocument: mockedDoc } as any; const mockedConfig = 'CONFIG' as any; describe('setContentModel', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let contentModelToDomSpy: jasmine.Spy; let createEditorContext: jasmine.Spy; let createModelToDomContextSpy: jasmine.Spy; @@ -47,7 +47,7 @@ describe('setContentModel', () => { modelToDomSettings: { calculated: mockedConfig, }, - } as any) as StandaloneEditorCore; + } as any) as EditorCore; }); it('no default option, no shadow edit', () => { diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts index d97267cd9a6..3e631e7fb49 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts @@ -1,9 +1,9 @@ import * as addRangeToSelection from '../../lib/corePlugin/utils/addRangeToSelection'; -import { DOMSelection, StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { DOMSelection, EditorCore } from 'roosterjs-content-model-types'; import { setDOMSelection } from '../../lib/coreApi/setDOMSelection'; describe('setDOMSelection', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let querySelectorAllSpy: jasmine.Spy; let hasFocusSpy: jasmine.Spy; let triggerEventSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts index 40c40ba5006..3f707622c82 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts @@ -1,12 +1,12 @@ import * as iterateSelections from '../../lib/publicApi/selection/iterateSelections'; -import { StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { EditorCore } from 'roosterjs-content-model-types'; import { switchShadowEdit } from '../../lib/coreApi/switchShadowEdit'; const mockedModel = 'MODEL' as any; const mockedCachedModel = 'CACHEMODEL' as any; describe('switchShadowEdit', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let createContentModel: jasmine.Spy; let setContentModel: jasmine.Spy; let getSelectionRange: jasmine.Spy; @@ -28,7 +28,7 @@ describe('switchShadowEdit', () => { lifecycle: {}, contentDiv: document.createElement('div'), cache: {}, - } as any) as StandaloneEditorCore; + } as any) as EditorCore; }); describe('was off', () => { diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts index ed9197ee6c7..bfe18c4001f 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts @@ -1,9 +1,9 @@ -import { EditorPlugin, PluginEvent, StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { EditorCore, EditorPlugin, PluginEvent } from 'roosterjs-content-model-types'; import { triggerEvent } from '../../lib/coreApi/triggerEvent'; describe('triggerEvent', () => { let div: HTMLDivElement; - let core: StandaloneEditorCore; + let core: EditorCore; beforeEach(() => { div = document.createElement('div'); diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/CachePluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/CachePluginTest.ts index 29463824edc..f5f76c1aab5 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/CachePluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/CachePluginTest.ts @@ -4,14 +4,14 @@ import { domIndexerImpl } from '../../lib/corePlugin/utils/domIndexerImpl'; import { CachePluginState, DomIndexer, - IStandaloneEditor, + IEditor, PluginWithState, - StandaloneEditorOptions, + EditorOptions, } from 'roosterjs-content-model-types'; describe('CachePlugin', () => { let plugin: PluginWithState; - let editor: IStandaloneEditor; + let editor: IEditor; let addEventListenerSpy: jasmine.Spy; let removeEventListenerSpy: jasmine.Spy; @@ -21,7 +21,7 @@ describe('CachePlugin', () => { let domIndexer: DomIndexer; let contentDiv: HTMLDivElement; - function init(option: StandaloneEditorOptions) { + function init(option: EditorOptions) { addEventListenerSpy = jasmine.createSpy('addEventListenerSpy'); removeEventListenerSpy = jasmine.createSpy('removeEventListener'); getDOMSelectionSpy = jasmine.createSpy('getDOMSelection'); @@ -43,7 +43,7 @@ describe('CachePlugin', () => { removeEventListener: removeEventListenerSpy, }; }, - } as any) as IStandaloneEditor; + } as any) as IEditor; plugin = createCachePlugin(option, contentDiv); plugin.initialize(editor); diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/ContextMenuPluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/ContextMenuPluginTest.ts index 92fac47a729..42e3282e5ef 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/ContextMenuPluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/ContextMenuPluginTest.ts @@ -3,7 +3,7 @@ import { createContextMenuPlugin } from '../../lib/corePlugin/ContextMenuPlugin' import { ContextMenuPluginState, DOMEventRecord, - IStandaloneEditor, + IEditor, PluginWithState, } from 'roosterjs-content-model-types'; @@ -14,7 +14,7 @@ describe('ContextMenu handle other event', () => { let getDOMSelectionSpy: jasmine.Spy; let attachDOMEventSpy: jasmine.Spy; let getSelectionRootNodeSpy: jasmine.Spy; - let editor: IStandaloneEditor; + let editor: IEditor; beforeEach(() => { triggerEventSpy = jasmine.createSpy('triggerEvent'); @@ -26,7 +26,7 @@ describe('ContextMenu handle other event', () => { eventMap = handlers; }); - editor = ({ + editor = ({ getDOMSelection: getDOMSelectionSpy, attachDomEvent: attachDOMEventSpy, triggerEvent: triggerEventSpy, diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts index cf529029dd4..2337b8dbe5f 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts @@ -13,7 +13,7 @@ import { DOMSelection, ContentModelFormatter, FormatContentModelOptions, - IStandaloneEditor, + IEditor, DOMEventRecord, ClipboardData, CopyPastePluginState, @@ -59,7 +59,7 @@ describe('CopyPastePlugin.Ctor', () => { }); describe('CopyPastePlugin |', () => { - let editor: IStandaloneEditor = null!; + let editor: IEditor = null!; let plugin: PluginWithState; let domEvents: Record = {}; let div: HTMLDivElement; @@ -115,7 +115,7 @@ describe('CopyPastePlugin |', () => { allowedCustomPasteType, }); plugin.getState().tempDiv = div; - editor = ({ + editor = ({ attachDomEvent: (eventMap: Record) => { domEvents = eventMap; }, diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/DomEventPluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/DomEventPluginTest.ts index e65bbddcf5c..7f4890e0fe8 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/DomEventPluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/DomEventPluginTest.ts @@ -1,11 +1,7 @@ import * as eventUtils from '../../lib/publicApi/domUtils/eventUtils'; import { ChangeSource } from '../../lib/constants/ChangeSource'; import { createDOMEventPlugin } from '../../lib/corePlugin/DOMEventPlugin'; -import { - DOMEventPluginState, - IStandaloneEditor, - PluginWithState, -} from 'roosterjs-content-model-types'; +import { DOMEventPluginState, IEditor, PluginWithState } from 'roosterjs-content-model-types'; const getDocument = () => document; @@ -25,7 +21,7 @@ describe('DOMEventPlugin', () => { getDocument, attachDomEvent, getEnvironment: () => ({}), - } as any) as IStandaloneEditor; + } as any) as IEditor; plugin.initialize(editor); @@ -70,7 +66,7 @@ describe('DOMEventPlugin', () => { const attachDomEvent = jasmine .createSpy('attachDomEvent') .and.returnValue(jasmine.createSpy('disposer')); - plugin.initialize(({ + plugin.initialize(({ getDocument, attachDomEvent, getEnvironment: () => ({}), @@ -108,7 +104,7 @@ describe('DOMEventPlugin verify event handlers while disallow keyboard event pro triggerEventSpy = jasmine.createSpy('triggerEvent'); plugin = createDOMEventPlugin({}, div); - plugin.initialize(({ + plugin.initialize(({ getDocument, attachDomEvent: (map: Record) => { eventMap = map; @@ -278,7 +274,7 @@ describe('DOMEventPlugin handle mouse down and mouse up event', () => { }, null! ); - plugin.initialize(({ + plugin.initialize(({ getDocument: () => ({ addEventListener, removeEventListener, @@ -395,7 +391,7 @@ describe('DOMEventPlugin handle other event', () => { let eventMap: Record; let scrollContainer: HTMLElement; let addEventListenerSpy: jasmine.Spy; - let editor: IStandaloneEditor; + let editor: IEditor; beforeEach(() => { addEventListener = jasmine.createSpy('addEventListener'); @@ -414,7 +410,7 @@ describe('DOMEventPlugin handle other event', () => { null! ); - editor = ({ + editor = ({ getDocument: () => ({ addEventListener, removeEventListener, diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts index bb723cfc8d2..010938c9869 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts @@ -7,12 +7,12 @@ import { ContentModelDocument, DarkColorHandler, EntityPluginState, - IStandaloneEditor, + IEditor, PluginWithState, } from 'roosterjs-content-model-types'; describe('EntityPlugin', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let plugin: PluginWithState; let formatContentModelSpy: jasmine.Spy; let triggerPluginEventSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/FormatPluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/FormatPluginTest.ts index dd5a92fc80b..9fd485fdc12 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/FormatPluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/FormatPluginTest.ts @@ -2,7 +2,7 @@ import * as applyDefaultFormat from '../../lib/corePlugin/utils/applyDefaultForm import * as applyPendingFormat from '../../lib/corePlugin/utils/applyPendingFormat'; import { createContentModelDocument } from 'roosterjs-content-model-dom'; import { createFormatPlugin } from '../../lib/corePlugin/FormatPlugin'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; describe('FormatPlugin', () => { const mockedFormat = { @@ -18,7 +18,7 @@ describe('FormatPlugin', () => { const editor = ({ cacheContentModel: () => {}, isDarkMode: () => false, - } as any) as IStandaloneEditor; + } as any) as IEditor; const plugin = createFormatPlugin({}); plugin.initialize(editor); @@ -40,7 +40,7 @@ describe('FormatPlugin', () => { isInIME: () => false, cacheContentModel: () => {}, getEnvironment: () => ({}), - } as any) as IStandaloneEditor; + } as any) as IEditor; const plugin = createFormatPlugin({}); const model = createContentModelDocument(); @@ -76,7 +76,7 @@ describe('FormatPlugin', () => { isDarkMode: () => false, triggerEvent, getVisibleViewport, - } as any) as IStandaloneEditor; + } as any) as IEditor; const plugin = createFormatPlugin({}); const state = plugin.getState(); @@ -101,7 +101,7 @@ describe('FormatPlugin', () => { const editor = ({ createContentModel: () => model, cacheContentModel: () => {}, - } as any) as IStandaloneEditor; + } as any) as IEditor; const plugin = createFormatPlugin({}); plugin.initialize(editor); @@ -133,7 +133,7 @@ describe('FormatPlugin', () => { callback(); }, cacheContentModel: () => {}, - } as any) as IStandaloneEditor; + } as any) as IEditor; const plugin = createFormatPlugin({}); const state = plugin.getState(); @@ -162,7 +162,7 @@ describe('FormatPlugin', () => { const editor = ({ createContentModel: () => model, cacheContentModel: () => {}, - } as any) as IStandaloneEditor; + } as any) as IEditor; const plugin = createFormatPlugin({}); const state = plugin.getState(); @@ -192,7 +192,7 @@ describe('FormatPlugin', () => { createContentModel: () => model, cacheContentModel: () => {}, getEnvironment: () => ({}), - } as any) as IStandaloneEditor; + } as any) as IEditor; const plugin = createFormatPlugin({}); const state = plugin.getState(); @@ -218,7 +218,7 @@ describe('FormatPlugin', () => { }); describe('FormatPlugin for default format', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let contentDiv: HTMLDivElement; let getDOMSelection: jasmine.Spy; let getPendingFormatSpy: jasmine.Spy; @@ -243,7 +243,7 @@ describe('FormatPlugin for default format', () => { cacheContentModel: cacheContentModelSpy, takeSnapshot: takeSnapshotSpy, formatContentModel: formatContentModelSpy, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); it('Collapsed range, text input, under editor directly', () => { diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/LifecyclePluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/LifecyclePluginTest.ts index d4a3e3bb258..7e76078b19b 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/LifecyclePluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/LifecyclePluginTest.ts @@ -1,7 +1,7 @@ import * as color from 'roosterjs-content-model-dom/lib/formatHandlers/utils/color'; import { ChangeSource } from '../../lib/constants/ChangeSource'; import { createLifecyclePlugin } from '../../lib/corePlugin/LifecyclePlugin'; -import { DarkColorHandler, IStandaloneEditor } from 'roosterjs-content-model-types'; +import { DarkColorHandler, IEditor } from 'roosterjs-content-model-types'; describe('LifecyclePlugin', () => { it('init', () => { @@ -10,7 +10,7 @@ describe('LifecyclePlugin', () => { const triggerEvent = jasmine.createSpy('triggerEvent'); const state = plugin.getState(); - plugin.initialize(({ + plugin.initialize(({ triggerEvent, getFocusedPosition: () => null, getColorManager: () => null, @@ -47,7 +47,7 @@ describe('LifecyclePlugin', () => { const triggerEvent = jasmine.createSpy('triggerEvent'); const state = plugin.getState(); - plugin.initialize(({ + plugin.initialize(({ triggerEvent, getFocusedPosition: () => null, getColorManager: () => null, @@ -74,7 +74,7 @@ describe('LifecyclePlugin', () => { const plugin = createLifecyclePlugin({}, div); const triggerEvent = jasmine.createSpy('triggerEvent'); - plugin.initialize(({ + plugin.initialize(({ triggerEvent, getFocusedPosition: () => null, getColorManager: () => null, @@ -96,7 +96,7 @@ describe('LifecyclePlugin', () => { const plugin = createLifecyclePlugin({}, div); const triggerEvent = jasmine.createSpy('triggerEvent'); - plugin.initialize(({ + plugin.initialize(({ triggerEvent, getFocusedPosition: () => null, getColorManager: () => null, @@ -121,7 +121,7 @@ describe('LifecyclePlugin', () => { const setColorSpy = spyOn(color, 'setColor'); - plugin.initialize(({ + plugin.initialize(({ triggerEvent, getColorManager: () => mockedDarkColorHandler, })); @@ -151,7 +151,7 @@ describe('LifecyclePlugin', () => { const setColorSpy = spyOn(color, 'setColor'); - plugin.initialize(({ + plugin.initialize(({ triggerEvent, getColorManager: () => mockedDarkColorHandler, })); @@ -203,7 +203,7 @@ describe('LifecyclePlugin', () => { const setColorSpy = spyOn(color, 'setColor'); - plugin.initialize(({ + plugin.initialize(({ triggerEvent, getDarkColorHandler: () => mockedDarkColorHandler, })); diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/SelectionPluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/SelectionPluginTest.ts index e73cac4e98b..e5b624c4027 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/SelectionPluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/SelectionPluginTest.ts @@ -1,7 +1,7 @@ import { createSelectionPlugin } from '../../lib/corePlugin/SelectionPlugin'; import { EditorPlugin, - IStandaloneEditor, + IEditor, PluginWithState, SelectionPluginState, } from 'roosterjs-content-model-types'; @@ -30,7 +30,7 @@ describe('SelectionPlugin', () => { getDocument: getDocumentSpy, attachDomEvent, getEnvironment: () => ({}), - } as any) as IStandaloneEditor; + } as any) as IEditor; plugin.initialize(editor); @@ -71,7 +71,7 @@ describe('SelectionPlugin', () => { removeEventListener: removeEventListenerSpy, }); - plugin.initialize(({ + plugin.initialize(({ getDocument: getDocumentSpy, attachDomEvent, getEnvironment: () => ({}), @@ -100,7 +100,7 @@ describe('SelectionPlugin handle onFocus and onBlur event', () => { let setDOMSelectionSpy: jasmine.Spy; let removeEventListenerSpy: jasmine.Spy; - let editor: IStandaloneEditor; + let editor: IEditor; beforeEach(() => { triggerEvent = jasmine.createSpy('triggerEvent'); @@ -119,7 +119,7 @@ describe('SelectionPlugin handle onFocus and onBlur event', () => { plugin = createSelectionPlugin({}); - editor = ({ + editor = ({ getDocument: getDocumentSpy, triggerEvent, getEnvironment: () => ({}), @@ -172,7 +172,7 @@ describe('SelectionPlugin handle onFocus and onBlur event', () => { describe('SelectionPlugin handle image selection', () => { let plugin: EditorPlugin; - let editor: IStandaloneEditor; + let editor: IEditor; let getDOMSelectionSpy: jasmine.Spy; let setDOMSelectionSpy: jasmine.Spy; let getDocumentSpy: jasmine.Spy; @@ -584,7 +584,7 @@ describe('SelectionPlugin on Safari', () => { let hasFocusSpy: jasmine.Spy; let isInShadowEditSpy: jasmine.Spy; let getDOMSelectionSpy: jasmine.Spy; - let editor: IStandaloneEditor; + let editor: IEditor; beforeEach(() => { disposer = jasmine.createSpy('disposer'); @@ -614,7 +614,7 @@ describe('SelectionPlugin on Safari', () => { hasFocus: hasFocusSpy, isInShadowEdit: isInShadowEditSpy, getDOMSelection: getDOMSelectionSpy, - } as any) as IStandaloneEditor; + } as any) as IEditor; }); it('init and dispose', () => { diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/UndoPluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/UndoPluginTest.ts index ca29db4c828..b487e57e37e 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/UndoPluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/UndoPluginTest.ts @@ -3,14 +3,14 @@ import * as undo from '../../lib/publicApi/undo/undo'; import { ChangeSource } from '../../lib/constants/ChangeSource'; import { createUndoPlugin } from '../../lib/corePlugin/UndoPlugin'; import { - IStandaloneEditor, + IEditor, PluginWithState, SnapshotsManager, UndoPluginState, } from 'roosterjs-content-model-types'; describe('UndoPlugin', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let createSnapshotsManagerSpy: jasmine.Spy; let getDOMSelectionSpy: jasmine.Spy; let canUndoAutoCompleteSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyDefaultFormatTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyDefaultFormatTest.ts index c6f5823f1e1..fcc9944b030 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyDefaultFormatTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyDefaultFormatTest.ts @@ -7,7 +7,7 @@ import { ContentModelSegmentFormat, FormatContentModelContext, FormatContentModelOptions, - IStandaloneEditor, + IEditor, InsertPoint, } from 'roosterjs-content-model-types'; import { @@ -20,7 +20,7 @@ import { } from 'roosterjs-content-model-dom'; describe('applyDefaultFormat', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let getDOMSelectionSpy: jasmine.Spy; let formatContentModelSpy: jasmine.Spy; let deleteSelectionSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyPendingFormatTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyPendingFormatTest.ts index 78bbab041f0..6eabe6aa0c3 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyPendingFormatTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyPendingFormatTest.ts @@ -8,7 +8,7 @@ import { ContentModelText, ContentModelFormatter, FormatContentModelOptions, - IStandaloneEditor, + IEditor, } from 'roosterjs-content-model-types'; import { createContentModelDocument, @@ -52,7 +52,7 @@ describe('applyPendingFormat', () => { const editor = ({ formatContentModel: formatContentModelSpy, - } as any) as IStandaloneEditor; + } as any) as IEditor; spyOn(iterateSelections, 'iterateSelections').and.callFake((_, callback) => { callback([model], undefined, paragraph, [marker]); @@ -124,7 +124,7 @@ describe('applyPendingFormat', () => { const editor = ({ formatContentModel: formatContentModelSpy, - } as any) as IStandaloneEditor; + } as any) as IEditor; spyOn(iterateSelections, 'iterateSelections').and.callFake((_, callback) => { callback([model], undefined, paragraph, [marker]); @@ -183,7 +183,7 @@ describe('applyPendingFormat', () => { const formatContentModelSpy = jasmine.createSpy('formatContentModel'); const editor = ({ formatContentModel: formatContentModelSpy, - } as any) as IStandaloneEditor; + } as any) as IEditor; spyOn(iterateSelections, 'iterateSelections').and.callFake((_, callback) => { callback([model], undefined, paragraph, [marker]); @@ -241,7 +241,7 @@ describe('applyPendingFormat', () => { const editor = ({ formatContentModel: formatContentModelSpy, - } as any) as IStandaloneEditor; + } as any) as IEditor; spyOn(iterateSelections, 'iterateSelections').and.callFake((_, callback) => { callback([model], undefined, paragraph, [text]); @@ -290,7 +290,7 @@ describe('applyPendingFormat', () => { const editor = ({ formatContentModel: formatContentModelSpy, - } as any) as IStandaloneEditor; + } as any) as IEditor; spyOn(iterateSelections, 'iterateSelections').and.callFake((_, callback) => { callback([model], undefined, paragraph, [marker]); diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/delimiterUtilsTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/delimiterUtilsTest.ts index 8b2a2fb17da..203c28c4e4f 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/delimiterUtilsTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/delimiterUtilsTest.ts @@ -1,5 +1,6 @@ import * as DelimiterFile from '../../../lib/corePlugin/utils/entityDelimiterUtils'; import * as entityUtils from 'roosterjs-content-model-dom/lib/domUtils/entityUtils'; +import { ContentModelDocument, DOMSelection, IEditor } from 'roosterjs-content-model-types'; import { handleDelimiterContentChangedEvent, handleDelimiterKeyDownEvent, @@ -9,11 +10,6 @@ import { createEntity, createModelToDomContext, } from 'roosterjs-content-model-dom'; -import { - ContentModelDocument, - DOMSelection, - IStandaloneEditor, -} from 'roosterjs-content-model-types'; const ZeroWidthSpace = '\u200B'; const BlockEntityContainer = '_E_EBlockEntityContainer'; @@ -29,7 +25,7 @@ describe('EntityDelimiterUtils |', () => { isNodeInEditor: () => true, }), getPendingFormat: ((): any => null), - }) as Partial; + }) as Partial; }); describe('contentChanged |', () => { @@ -182,7 +178,7 @@ describe('EntityDelimiterUtils |', () => { isNodeInEditor: () => true, }), takeSnapshot: takeSnapshotSpy, - }) as Partial; + }) as Partial; spyOn(DelimiterFile, 'preventTypeInDelimiter').and.callThrough(); }); @@ -576,7 +572,7 @@ describe('preventTypeInDelimiter', () => { formatContentModel: formatter => { formatter(mockedModel, context); }, - } as Partial; + } as Partial; }); it('handle delimiter after entity', () => { diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/StandaloneEditorTest.ts b/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts similarity index 94% rename from packages-content-model/roosterjs-content-model-core/test/editor/StandaloneEditorTest.ts rename to packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts index 21f333d1427..a16b0025f2f 100644 --- a/packages-content-model/roosterjs-content-model-core/test/editor/StandaloneEditorTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts @@ -1,14 +1,14 @@ import * as cloneModel from '../../lib/publicApi/model/cloneModel'; +import * as createEditorCore from '../../lib/editor/createEditorCore'; import * as createEmptyModel from 'roosterjs-content-model-dom/lib/modelApi/creators/createEmptyModel'; -import * as createStandaloneEditorCore from '../../lib/editor/createStandaloneEditorCore'; import * as transformColor from '../../lib/publicApi/color/transformColor'; import { ChangeSource } from '../../lib/constants/ChangeSource'; -import { Rect, StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { Editor } from '../../lib/editor/Editor'; +import { EditorCore, Rect } from 'roosterjs-content-model-types'; import { reducedModelChildProcessor } from '../../lib/override/reducedModelChildProcessor'; -import { StandaloneEditor } from '../../lib/editor/StandaloneEditor'; import { tableProcessor } from 'roosterjs-content-model-dom'; -describe('StandaloneEditor', () => { +describe('Editor', () => { let createEditorCoreSpy: jasmine.Spy; let updateKnownColorSpy: jasmine.Spy; let setContentModelSpy: jasmine.Spy; @@ -16,10 +16,7 @@ describe('StandaloneEditor', () => { beforeEach(() => { updateKnownColorSpy = jasmine.createSpy('updateKnownColor'); - createEditorCoreSpy = spyOn( - createStandaloneEditorCore, - 'createStandaloneEditorCore' - ).and.callThrough(); + createEditorCoreSpy = spyOn(createEditorCore, 'createEditorCore').and.callThrough(); setContentModelSpy = jasmine.createSpy('setContentModel'); createEmptyModelSpy = spyOn(createEmptyModel, 'createEmptyModel'); }); @@ -29,7 +26,7 @@ describe('StandaloneEditor', () => { createEmptyModelSpy.and.callThrough(); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); expect(createEditorCoreSpy).toHaveBeenCalledWith(div, {}); expect(editor.isDisposed()).toBeFalse(); @@ -75,7 +72,7 @@ describe('StandaloneEditor', () => { createEmptyModelSpy.and.callThrough(); - const editor = new StandaloneEditor(div, options); + const editor = new Editor(div, options); expect(createEditorCoreSpy).toHaveBeenCalledWith(div, options); expect(editor.isDisposed()).toBeFalse(); @@ -129,7 +126,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const model1 = editor.getContentModelCopy('connected'); @@ -180,7 +177,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const cloneModelSpy = spyOn(cloneModel, 'cloneModel').and.returnValue(mockedClonedModel); @@ -256,7 +253,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const result = editor.getEnvironment(); @@ -288,7 +285,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const result = editor.getDOMSelection(); @@ -319,7 +316,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); editor.setDOMSelection(null); @@ -354,7 +351,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); editor.formatContentModel(mockedFormatter); @@ -391,7 +388,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const result1 = editor.getPendingFormat(); @@ -430,7 +427,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const snapshot = editor.takeSnapshot(); @@ -458,7 +455,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const domHelper = editor.getDOMHelper(); expect(domHelper).toBe(mockedDOMHelper); @@ -487,7 +484,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); editor.restoreSnapshot(mockedSnapshot); @@ -516,7 +513,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); editor.focus(); expect(focusSpy).toHaveBeenCalledWith(mockedCore); @@ -545,7 +542,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const result = editor.hasFocus(); @@ -580,7 +577,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const mockedEventType = 'EVENTTYPE' as any; const result = editor.triggerEvent(mockedEventType, mockedEventData, true); @@ -627,7 +624,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); const mockedEventMap = 'EVENTMAP' as any; - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const result = editor.attachDomEvent(mockedEventMap); @@ -658,7 +655,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const result = editor.getSnapshotsManager(); @@ -692,7 +689,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); expect(editor.isInShadowEdit()).toBeFalse(); @@ -736,7 +733,7 @@ describe('StandaloneEditor', () => { const mockedClipboardData = 'ClipboardData' as any; const mockedPasteType = 'PASTETYPE' as any; - const editor = new StandaloneEditor(div); + const editor = new Editor(div); editor.pasteFromClipboard(mockedClipboardData); @@ -766,7 +763,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const result = editor.getColorManager(); @@ -805,7 +802,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); expect(editor.isDarkMode()).toBeFalse(); @@ -879,7 +876,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const result = editor.getScrollContainer(); @@ -902,7 +899,7 @@ describe('StandaloneEditor', () => { }, api: { setContentModel: setContentModelSpy, - getVisibleViewport: (core: StandaloneEditorCore) => { + getVisibleViewport: (core: EditorCore) => { return mockedScrollContainer; }, }, @@ -911,7 +908,7 @@ describe('StandaloneEditor', () => { createEditorCoreSpy.and.returnValue(mockedCore); - const editor = new StandaloneEditor(div); + const editor = new Editor(div); const result = editor.getVisibleViewport(); diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/createStandaloneEditorCoreTest.ts b/packages-content-model/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts similarity index 90% rename from packages-content-model/roosterjs-content-model-core/test/editor/createStandaloneEditorCoreTest.ts rename to packages-content-model/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts index 3d6a523dd40..a0332204118 100644 --- a/packages-content-model/roosterjs-content-model-core/test/editor/createStandaloneEditorCoreTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts @@ -1,14 +1,14 @@ -import * as createDefaultSettings from '../../lib/editor/createStandaloneEditorDefaultSettings'; -import * as createStandaloneEditorCorePlugins from '../../lib/corePlugin/createStandaloneEditorCorePlugins'; +import * as createDefaultSettings from '../../lib/editor/createEditorDefaultSettings'; +import * as createEditorCorePlugins from '../../lib/corePlugin/createEditorCorePlugins'; import * as DarkColorHandlerImpl from '../../lib/editor/DarkColorHandlerImpl'; import * as DOMHelperImpl from '../../lib/editor/DOMHelperImpl'; -import { standaloneCoreApiMap } from '../../lib/editor/standaloneCoreApiMap'; -import { StandaloneEditorCore, StandaloneEditorOptions } from 'roosterjs-content-model-types'; +import { coreApiMap } from '../../lib/editor/coreApiMap'; +import { EditorCore, EditorOptions } from 'roosterjs-content-model-types'; import { - createStandaloneEditorCore, + createEditorCore, defaultTrustHtmlHandler, getDarkColorFallback, -} from '../../lib/editor/createStandaloneEditorCore'; +} from '../../lib/editor/createEditorCore'; describe('createEditorCore', () => { function createMockedPlugin(stateName: string): any { @@ -43,10 +43,7 @@ describe('createEditorCore', () => { const mockedDOMHelper = 'DOMHELPER' as any; beforeEach(() => { - spyOn( - createStandaloneEditorCorePlugins, - 'createStandaloneEditorCorePlugins' - ).and.returnValue(mockedPlugins); + spyOn(createEditorCorePlugins, 'createEditorCorePlugins').and.returnValue(mockedPlugins); spyOn(DarkColorHandlerImpl, 'createDarkColorHandler').and.returnValue( mockedDarkColorHandler ); @@ -61,15 +58,15 @@ describe('createEditorCore', () => { function runTest( contentDiv: HTMLDivElement, - options: StandaloneEditorOptions, - additionalResult: Partial + options: EditorOptions, + additionalResult: Partial ) { - const core = createStandaloneEditorCore(contentDiv, options); + const core = createEditorCore(contentDiv, options); expect(core).toEqual({ contentDiv: contentDiv, - api: standaloneCoreApiMap, - originalApi: standaloneCoreApiMap, + api: coreApiMap, + originalApi: coreApiMap, plugins: [ mockedCachePlugin, mockedFormatPlugin, @@ -105,9 +102,10 @@ describe('createEditorCore', () => { ...additionalResult, }); - expect( - createStandaloneEditorCorePlugins.createStandaloneEditorCorePlugins - ).toHaveBeenCalledWith(options, contentDiv); + expect(createEditorCorePlugins.createEditorCorePlugins).toHaveBeenCalledWith( + options, + contentDiv + ); expect(createDefaultSettings.createDomToModelSettings).toHaveBeenCalledWith(options); expect(createDefaultSettings.createModelToDomSettings).toHaveBeenCalledWith(options); } @@ -158,7 +156,7 @@ describe('createEditorCore', () => { runTest(mockedDiv, mockedOptions, { contentDiv: mockedDiv, - api: { ...standaloneCoreApiMap, a: 'b' } as any, + api: { ...coreApiMap, a: 'b' } as any, plugins: [ mockedCachePlugin, mockedFormatPlugin, diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/createStandaloneEditorDefaultSettingsTest.ts b/packages-content-model/roosterjs-content-model-core/test/editor/createEditorDefaultSettingsTest.ts similarity index 98% rename from packages-content-model/roosterjs-content-model-core/test/editor/createStandaloneEditorDefaultSettingsTest.ts rename to packages-content-model/roosterjs-content-model-core/test/editor/createEditorDefaultSettingsTest.ts index fad2b2eb099..fee3355d279 100644 --- a/packages-content-model/roosterjs-content-model-core/test/editor/createStandaloneEditorDefaultSettingsTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/editor/createEditorDefaultSettingsTest.ts @@ -8,7 +8,7 @@ import { import { createDomToModelSettings, createModelToDomSettings, -} from '../../lib/editor/createStandaloneEditorDefaultSettings'; +} from '../../lib/editor/createEditorDefaultSettings'; describe('createDomToModelSettings', () => { const mockedCalculatedConfig = 'CONFIG' as any; diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts index c944d4f93de..d525421cf41 100644 --- a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts @@ -2,7 +2,7 @@ import * as contentModelToDom from 'roosterjs-content-model-dom/lib/modelToDom/c import * as contentModelToText from 'roosterjs-content-model-dom/lib/modelToText/contentModelToText'; import * as createModelToDomContext from 'roosterjs-content-model-dom/lib/modelToDom/context/createModelToDomContext'; import { exportContent } from '../../../lib/publicApi/model/exportContent'; -import { IStandaloneEditor } from 'roosterjs-content-model-types'; +import { IEditor } from 'roosterjs-content-model-types'; describe('exportContent', () => { it('PlainTextFast', () => { @@ -10,7 +10,7 @@ describe('exportContent', () => { const getTextContentSpy = jasmine .createSpy('getTextContent') .and.returnValue(mockedTextContent); - const editor: IStandaloneEditor = { + const editor: IEditor = { getDOMHelper: () => ({ getTextContent: getTextContentSpy, }), @@ -27,7 +27,7 @@ describe('exportContent', () => { const getContentModelCopySpy = jasmine .createSpy('getContentModelCopy') .and.returnValue(mockedModel); - const editor: IStandaloneEditor = { + const editor: IEditor = { getContentModelCopy: getContentModelCopySpy, } as any; const mockedText = 'TEXT'; @@ -56,7 +56,7 @@ describe('exportContent', () => { createElement: () => mockedDiv, } as any; const triggerEventSpy = jasmine.createSpy('triggerEvent'); - const editor: IStandaloneEditor = { + const editor: IEditor = { getContentModelCopy: getContentModelCopySpy, getDocument: () => mockedDoc, triggerEvent: triggerEventSpy, diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/redoTest.ts b/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/redoTest.ts index 90976936d2d..c1b24e82b77 100644 --- a/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/redoTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/redoTest.ts @@ -1,8 +1,8 @@ -import { IStandaloneEditor, SnapshotsManager } from 'roosterjs-content-model-types'; +import { IEditor, SnapshotsManager } from 'roosterjs-content-model-types'; import { redo } from '../../../lib/publicApi/undo/redo'; describe('redo', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let getSnapshotsManagerSpy: jasmine.Spy; let takeSnapshotSpy: jasmine.Spy; let restoreSnapshotSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/undoTest.ts b/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/undoTest.ts index 251f8a63788..86dee0b8980 100644 --- a/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/undoTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/undoTest.ts @@ -1,8 +1,8 @@ -import { IStandaloneEditor, SnapshotsManager } from 'roosterjs-content-model-types'; +import { IEditor, SnapshotsManager } from 'roosterjs-content-model-types'; import { undo } from '../../../lib/publicApi/undo/undo'; describe('undo', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let getSnapshotsManagerSpy: jasmine.Spy; let takeSnapshotSpy: jasmine.Spy; let restoreSnapshotSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts index d6261cf5be8..c2dbb03a726 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts @@ -1,9 +1,9 @@ import { createSnapshotSelection } from '../../lib/utils/createSnapshotSelection'; -import { DOMSelection, StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { DOMSelection, EditorCore } from 'roosterjs-content-model-types'; describe('createSnapshotSelection', () => { let div: HTMLDivElement; - let core: StandaloneEditorCore; + let core: EditorCore; let getDOMSelectionSpy: jasmine.Spy; beforeEach(() => { @@ -66,7 +66,7 @@ describe('createSnapshotSelection', () => { describe('createSnapshotSelection - Range selection', () => { let div: HTMLDivElement; - let core: StandaloneEditorCore; + let core: EditorCore; let getDOMSelectionSpy: jasmine.Spy; beforeEach(() => { @@ -227,7 +227,7 @@ describe('createSnapshotSelection - Normalize Table', () => { const TABLE_ID1 = 't1'; const TABLE_ID2 = 't2'; let div: HTMLDivElement; - let core: StandaloneEditorCore; + let core: EditorCore; let getDOMSelectionSpy: jasmine.Spy; let setDOMSelectionSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts index 5ad2a7b4304..0ab1e87e81d 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts @@ -1,8 +1,8 @@ +import { EditorCore } from 'roosterjs-content-model-types'; import { generatePasteOptionFromPlugins } from '../../../lib/utils/paste/generatePasteOptionFromPlugins'; -import { StandaloneEditorCore } from 'roosterjs-content-model-types'; describe('generatePasteOptionFromPlugins', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let triggerPluginEventSpy: jasmine.Spy; const mockedClipboardData = 'CLIPBOARDDATA' as any; diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts index b3b2c51feac..0bf16378888 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts @@ -10,7 +10,7 @@ import { FormatContentModelContext, FormatContentModelOptions, InsertPoint, - StandaloneEditorCore, + EditorCore, } from 'roosterjs-content-model-types'; describe('mergePasteContent', () => { @@ -18,7 +18,7 @@ describe('mergePasteContent', () => { let context: FormatContentModelContext | undefined; let formatContentModel: jasmine.Spy; let sourceModel: ContentModelDocument; - let core: StandaloneEditorCore; + let core: EditorCore; const mockedClipboard = 'CLIPBOARD' as any; beforeEach(() => { diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts index f5c7f5537d9..1de6d608eb9 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts @@ -1,9 +1,9 @@ import * as transformColor from '../../lib/publicApi/color/transformColor'; -import { DarkColorHandler, Snapshot, StandaloneEditorCore } from 'roosterjs-content-model-types'; +import { DarkColorHandler, EditorCore, Snapshot } from 'roosterjs-content-model-types'; import { restoreSnapshotColors } from '../../lib/utils/restoreSnapshotColors'; describe('restoreSnapshotColors', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let updateKnownColorSpy: jasmine.Spy; let transformColorSpy: jasmine.Spy; let darkColorHandler: DarkColorHandler; diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts index 0fba26ea47e..fdaadb8b488 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts @@ -1,9 +1,9 @@ +import { EditorCore, Snapshot } from 'roosterjs-content-model-types'; import { restoreSnapshotHTML } from '../../lib/utils/restoreSnapshotHTML'; -import { Snapshot, StandaloneEditorCore } from 'roosterjs-content-model-types'; import { wrap } from 'roosterjs-content-model-dom'; describe('restoreSnapshotHTML', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let div: HTMLDivElement; beforeEach(() => { diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts index db6196e2e1c..c0425cc4770 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts @@ -1,8 +1,8 @@ +import { EditorCore, Snapshot, SnapshotSelection } from 'roosterjs-content-model-types'; import { restoreSnapshotSelection } from '../../lib/utils/restoreSnapshotSelection'; -import { Snapshot, SnapshotSelection, StandaloneEditorCore } from 'roosterjs-content-model-types'; describe('restoreSnapshotSelection', () => { - let core: StandaloneEditorCore; + let core: EditorCore; let div: HTMLDivElement; let setDOMSelectionSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts b/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts index b4f1a4bd98b..5308866f429 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts @@ -1,7 +1,7 @@ import { keyboardListTrigger } from './keyboardListTrigger'; import type { EditorPlugin, - IStandaloneEditor, + IEditor, KeyDownEvent, PluginEvent, } from 'roosterjs-content-model-types'; @@ -34,7 +34,7 @@ const DefaultOptions: Required = { * It can be customized with options to enable or disable auto list features. */ export class AutoFormatPlugin implements EditorPlugin { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; /** * @param options An optional parameter that takes in an object of type AutoFormatOptions, which includes the following properties: @@ -56,7 +56,7 @@ export class AutoFormatPlugin implements EditorPlugin { * editor reference so that it can call to any editor method or format API later. * @param editor The editor object */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; } @@ -85,7 +85,7 @@ export class AutoFormatPlugin implements EditorPlugin { } } - private handleKeyDownEvent(editor: IStandaloneEditor, event: KeyDownEvent) { + private handleKeyDownEvent(editor: IEditor, event: KeyDownEvent) { const rawEvent = event.rawEvent; if (!rawEvent.defaultPrevented && !event.handledByEditFeature) { switch (rawEvent.key) { diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts b/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts index 900c0c10900..78fea6df5db 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts @@ -2,14 +2,13 @@ import { getListTypeStyle } from './utils/getListTypeStyle'; import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import { normalizeContentModel } from 'roosterjs-content-model-dom'; import { setListStartNumber, setListStyle, setListType } from 'roosterjs-content-model-api'; - -import type { ContentModelDocument, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ContentModelDocument, IEditor } from 'roosterjs-content-model-types'; /** * @internal */ export function keyboardListTrigger( - editor: IStandaloneEditor, + editor: IEditor, rawEvent: KeyboardEvent, shouldSearchForBullet: boolean = true, shouldSearchForNumbering: boolean = true @@ -37,7 +36,7 @@ export function keyboardListTrigger( } const triggerList = ( - editor: IStandaloneEditor, + editor: IEditor, model: ContentModelDocument, listType: 'OL' | 'UL', styleType: number, diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts index 43b5c939612..73a994c473b 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts @@ -3,7 +3,7 @@ import { keyboardInput } from './keyboardInput'; import { keyboardTab } from './keyboardTab'; import type { EditorPlugin, - IStandaloneEditor, + IEditor, KeyDownEvent, PluginEvent, } from 'roosterjs-content-model-types'; @@ -12,14 +12,14 @@ const BACKSPACE_KEY = 8; const DELETE_KEY = 46; /** - * ContentModel edit plugins helps editor to do editing operation on top of content model. + * Edit plugins helps editor to do editing operation on top of content model. * This includes: * 1. Delete Key * 2. Backspace Key * 3. Tab Key */ export class EditPlugin implements EditorPlugin { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; private disposer: (() => void) | null = null; private shouldHandleNextInputEvent = false; @@ -36,7 +36,7 @@ export class EditPlugin implements EditorPlugin { * editor reference so that it can call to any editor method or format API later. * @param editor The editor object */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; if (editor.getEnvironment().isAndroid) { this.disposer = this.editor.attachDomEvent({ @@ -74,7 +74,7 @@ export class EditPlugin implements EditorPlugin { } } - private handleKeyDownEvent(editor: IStandaloneEditor, event: KeyDownEvent) { + private handleKeyDownEvent(editor: IEditor, event: KeyDownEvent) { const rawEvent = event.rawEvent; if (!rawEvent.defaultPrevented && !event.handledByEditFeature) { @@ -103,7 +103,7 @@ export class EditPlugin implements EditorPlugin { } } - private handleBeforeInputEvent(editor: IStandaloneEditor, rawEvent: Event) { + private handleBeforeInputEvent(editor: IEditor, rawEvent: Event) { // Some Android IMEs doesn't fire correct keydown event for BACKSPACE/DELETE key // Here we translate input event to BACKSPACE/DELETE keydown event to be compatible with existing logic if ( diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/handleKeyboardEventCommon.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/handleKeyboardEventCommon.ts index 5520d6f566a..a1b6e73c04e 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/handleKeyboardEventCommon.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/handleKeyboardEventCommon.ts @@ -3,7 +3,7 @@ import type { ContentModelDocument, DeleteResult, FormatContentModelContext, - IStandaloneEditor, + IEditor, } from 'roosterjs-content-model-types'; /** @@ -11,7 +11,7 @@ import type { * @return True means content is changed, so need to rewrite content model to editor. Otherwise false */ export function handleKeyboardEventResult( - editor: IStandaloneEditor, + editor: IEditor, model: ContentModelDocument, rawEvent: KeyboardEvent, result: DeleteResult, diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts index 59f27617d77..0fd7522eba3 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts @@ -15,20 +15,16 @@ import { backwardDeleteCollapsedSelection, forwardDeleteCollapsedSelection, } from './deleteSteps/deleteCollapsedSelection'; -import type { - DOMSelection, - DeleteSelectionStep, - IStandaloneEditor, -} from 'roosterjs-content-model-types'; +import type { DOMSelection, DeleteSelectionStep, IEditor } from 'roosterjs-content-model-types'; /** * @internal * Do keyboard event handling for DELETE/BACKSPACE key - * @param editor The Content Model Editor + * @param editor The editor object * @param rawEvent DOM keyboard event * @returns True if the event is handled by content model, otherwise false */ -export function keyboardDelete(editor: IStandaloneEditor, rawEvent: KeyboardEvent) { +export function keyboardDelete(editor: IEditor, rawEvent: KeyboardEvent) { let handled = false; const selection = editor.getDOMSelection(); diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts index b4404bc8db1..f8c3219c12e 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts @@ -1,12 +1,12 @@ import { deleteSelection, isModifierKey } from 'roosterjs-content-model-core'; import { handleEnterOnList } from './inputSteps/handleEnterOnList'; import { normalizeContentModel } from 'roosterjs-content-model-dom'; -import type { DOMSelection, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { DOMSelection, IEditor } from 'roosterjs-content-model-types'; /** * @internal */ -export function keyboardInput(editor: IStandaloneEditor, rawEvent: KeyboardEvent) { +export function keyboardInput(editor: IEditor, rawEvent: KeyboardEvent) { const selection = editor.getDOMSelection(); if (shouldInputWithContentModel(selection, rawEvent)) { diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts index 733dc7ef582..da530533f73 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts @@ -3,13 +3,13 @@ import { setModelIndentation } from 'roosterjs-content-model-api'; import type { ContentModelDocument, ContentModelListItem, - IStandaloneEditor, + IEditor, } from 'roosterjs-content-model-types'; /** * @internal */ -export function keyboardTab(editor: IStandaloneEditor, rawEvent: KeyboardEvent) { +export function keyboardTab(editor: IEditor, rawEvent: KeyboardEvent) { const selection = editor.getDOMSelection(); if (selection?.type == 'range') { diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts b/packages-content-model/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts index dac08441684..4da8cf902b4 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts @@ -14,7 +14,7 @@ import type { ContentModelTableCellFormat, EditorPlugin, FormatParser, - IStandaloneEditor, + IEditor, PluginEvent, } from 'roosterjs-content-model-types'; @@ -24,10 +24,9 @@ import type { * 2. Content copied from Excel * 3. Content copied from Word Online or OneNote Online * 4. Content copied from Power Point - * (This class is still under development, and may still be changed in the future with some breaking changes) */ export class PastePlugin implements EditorPlugin { - private editor: IStandaloneEditor | null = null; + private editor: IEditor | null = null; /** * Construct a new instance of Paste class @@ -49,7 +48,7 @@ export class PastePlugin implements EditorPlugin { * editor reference so that it can call to any editor method or format API later. * @param editor The editor object */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { this.editor = editor; } diff --git a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts index 1e762489d3c..573b3411804 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts @@ -1,9 +1,9 @@ import * as keyboardTrigger from '../../lib/autoFormat/keyboardListTrigger'; import { AutoFormatPlugin } from '../../lib/autoFormat/AutoFormatPlugin'; -import { IStandaloneEditor, KeyDownEvent } from 'roosterjs-content-model-types'; +import { IEditor, KeyDownEvent } from 'roosterjs-content-model-types'; describe('Content Model Auto Format Plugin Test', () => { - let editor: IStandaloneEditor; + let editor: IEditor; beforeEach(() => { editor = ({ @@ -12,7 +12,7 @@ describe('Content Model Auto Format Plugin Test', () => { ({ type: -1, } as any), // Force return invalid range to go through content model code - } as any) as IStandaloneEditor; + } as any) as IEditor; }); describe('onPluginEvent', () => { diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts index 7c0c2c5ac93..0ccc3ad7e6a 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts @@ -1,12 +1,12 @@ import * as keyboardDelete from '../../lib/edit/keyboardDelete'; import * as keyboardInput from '../../lib/edit/keyboardInput'; import * as keyboardTab from '../../lib/edit/keyboardTab'; -import { DOMEventRecord, IStandaloneEditor } from 'roosterjs-content-model-types'; +import { DOMEventRecord, IEditor } from 'roosterjs-content-model-types'; import { EditPlugin } from '../../lib/edit/EditPlugin'; describe('EditPlugin', () => { let plugin: EditPlugin; - let editor: IStandaloneEditor; + let editor: IEditor; let eventMap: Record; let attachDOMEventSpy: jasmine.Spy; let getEnvironmentSpy: jasmine.Spy; @@ -29,7 +29,7 @@ describe('EditPlugin', () => { ({ type: -1, } as any), // Force return invalid range to go through content model code - } as any) as IStandaloneEditor; + } as any) as IEditor; }); afterEach(() => { diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/editingTestCommon.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/editingTestCommon.ts index 1ff6f52eb16..7aad9ce2154 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/editingTestCommon.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/editingTestCommon.ts @@ -2,12 +2,12 @@ import { ContentModelDocument, ContentModelFormatter, FormatContentModelOptions, - IStandaloneEditor, + IEditor, } from 'roosterjs-content-model-types'; export function editingTestCommon( apiName: string | undefined, - executionCallback: (editor: IStandaloneEditor) => void, + executionCallback: (editor: IEditor) => void, model: ContentModelDocument, result: ContentModelDocument, calledTimes: number, @@ -35,7 +35,7 @@ export function editingTestCommon( isInIME: () => false, getEnvironment: () => ({}), formatContentModel, - } as any) as IStandaloneEditor; + } as any) as IEditor; executionCallback(editor); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/handleKeyboardEventCommonTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/handleKeyboardEventCommonTest.ts index 5c7f56a94f0..b3c10b86672 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/handleKeyboardEventCommonTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/handleKeyboardEventCommonTest.ts @@ -1,5 +1,5 @@ import * as normalizeContentModel from 'roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel'; -import { FormatContentModelContext, IStandaloneEditor } from 'roosterjs-content-model-types'; +import { FormatContentModelContext, IEditor } from 'roosterjs-content-model-types'; import { handleKeyboardEventResult, shouldDeleteAllSegmentsBefore, @@ -7,7 +7,7 @@ import { } from '../../lib/edit/handleKeyboardEventCommon'; describe('handleKeyboardEventResult', () => { - let mockedEditor: IStandaloneEditor; + let mockedEditor: IEditor; let mockedEvent: KeyboardEvent; let cacheContentModel: jasmine.Spy; let preventDefault: jasmine.Spy; @@ -27,7 +27,7 @@ describe('handleKeyboardEventResult', () => { triggerContentChangedEvent, triggerEvent, addUndoSnapshot, - } as any) as IStandaloneEditor; + } as any) as IEditor; mockedEvent = ({ preventDefault, } as any) as KeyboardEvent; diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts index be70773518d..6da4cb1964d 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts @@ -6,11 +6,7 @@ import { deleteList } from '../../lib/edit/deleteSteps/deleteList'; import { DeleteResult, DeleteSelectionStep } from 'roosterjs-content-model-types'; import { editingTestCommon } from './editingTestCommon'; import { keyboardDelete } from '../../lib/edit/keyboardDelete'; -import { - ContentModelDocument, - DOMSelection, - IStandaloneEditor, -} from 'roosterjs-content-model-types'; +import { ContentModelDocument, DOMSelection, IEditor } from 'roosterjs-content-model-types'; import { backwardDeleteWordSelection, forwardDeleteWordSelection, @@ -489,7 +485,7 @@ describe('keyboardDelete', () => { type: 'range', range: { collapsed: false }, }), - } as any) as IStandaloneEditor; + } as any) as IEditor; const event = { which: Delete, key: 'Delete', diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts index 7db310df950..ae2a67ce817 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts @@ -6,11 +6,11 @@ import { ContentModelDocument, ContentModelFormatter, FormatContentModelContext, - IStandaloneEditor, + IEditor, } from 'roosterjs-content-model-types'; describe('keyboardInput', () => { - let editor: IStandaloneEditor; + let editor: IEditor; let takeSnapshotSpy: jasmine.Spy; let formatContentModelSpy: jasmine.Spy; let getDOMSelectionSpy: jasmine.Spy; diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts index 5af29820fa0..f016f1a0e15 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts @@ -4,7 +4,7 @@ import * as getPasteSource from '../../lib/paste/pasteSourceValidations/getPaste import * as PowerPointFile from '../../lib/paste/PowerPoint/processPastedContentFromPowerPoint'; import * as setProcessor from '../../lib/paste/utils/setProcessor'; import * as WacFile from '../../lib/paste/WacComponents/processPastedContentWacComponents'; -import { BeforePasteEvent, IStandaloneEditor } from 'roosterjs-content-model-types'; +import { BeforePasteEvent, IEditor } from 'roosterjs-content-model-types'; import { PastePlugin } from '../../lib/paste/PastePlugin'; import { PastePropertyNames } from '../../lib/paste/pasteSourceValidations/constants'; @@ -12,12 +12,12 @@ const trustedHTMLHandler = (val: string) => val; const DEFAULT_TIMES_ADD_PARSER_CALLED = 4; describe('Content Model Paste Plugin Test', () => { - let editor: IStandaloneEditor; + let editor: IEditor; beforeEach(() => { editor = ({ getTrustedHTMLHandler: () => trustedHTMLHandler, - } as any) as IStandaloneEditor; + } as any) as IEditor; spyOn(addParser, 'default').and.callThrough(); spyOn(setProcessor, 'setProcessor').and.callThrough(); }); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts index 9bb91be927e..d7fd8e61f1f 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts @@ -1,6 +1,6 @@ import * as processPastedContentFromExcel from '../../../lib/paste/Excel/processPastedContentFromExcel'; import { expectEqual, initEditor } from './testUtils'; -import type { ClipboardData, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ClipboardData, IEditor } from 'roosterjs-content-model-types'; const ID = 'CM_Paste_From_ExcelOnline_E2E'; const clipboardData = ({ @@ -18,7 +18,7 @@ const clipboardData = ({ }); describe(ID, () => { - let editor: IStandaloneEditor = undefined!; + let editor: IEditor = undefined!; beforeEach(() => { editor = initEditor(ID); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts index 31c4ca31710..75592382d73 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts @@ -1,7 +1,7 @@ import * as processPastedContentFromExcel from '../../../lib/paste/Excel/processPastedContentFromExcel'; import { expectEqual, initEditor } from './testUtils'; import { itChromeOnly } from 'roosterjs-content-model-dom/test/testUtils'; -import type { ClipboardData, IStandaloneEditor } from 'roosterjs-content-model-types'; +import type { ClipboardData, IEditor } from 'roosterjs-content-model-types'; const ID = 'CM_Paste_From_Excel_E2E'; const clipboardData = ({ @@ -19,7 +19,7 @@ const clipboardData = ({ }); describe(ID, () => { - let editor: IStandaloneEditor = undefined!; + let editor: IEditor = undefined!; beforeEach(() => { editor = initEditor(ID); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts index 19986e9eb1a..3836607d3b5 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts @@ -1,11 +1,11 @@ import * as processPastedContentWacComponents from '../../../lib/paste/WacComponents/processPastedContentWacComponents'; -import { ClipboardData, IStandaloneEditor } from 'roosterjs-content-model-types'; +import { ClipboardData, IEditor } from 'roosterjs-content-model-types'; import { expectEqual, initEditor } from './testUtils'; const ID = 'CM_Paste_From_WORD_Online_E2E'; describe(ID, () => { - let editor: IStandaloneEditor = undefined!; + let editor: IEditor = undefined!; let clipboardData: ClipboardData; beforeEach(() => { diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts index 00865771548..35c259ccc68 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts @@ -1,5 +1,5 @@ import * as wordFile from '../../../lib/paste/WordDesktop/processPastedContentFromWordDesktop'; -import { ClipboardData, IStandaloneEditor } from 'roosterjs-content-model-types'; +import { ClipboardData, IEditor } from 'roosterjs-content-model-types'; import { cloneModel } from 'roosterjs-content-model-core'; import { expectEqual, initEditor } from './testUtils'; import { itChromeOnly } from 'roosterjs-content-model-dom/test/testUtils'; @@ -18,7 +18,7 @@ const clipboardData = ({ }); describe(ID, () => { - let editor: IStandaloneEditor = undefined!; + let editor: IEditor = undefined!; beforeEach(() => { editor = initEditor(ID); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts index 6fb885a9889..d0e38ab9dda 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts @@ -1,12 +1,12 @@ import * as wordFile from '../../../lib/paste/WordDesktop/processPastedContentFromWordDesktop'; -import { ClipboardData, IStandaloneEditor } from 'roosterjs-content-model-types'; +import { ClipboardData, IEditor } from 'roosterjs-content-model-types'; import { expectEqual, initEditor } from './testUtils'; import { itChromeOnly } from 'roosterjs-content-model-dom/test/testUtils'; const ID = 'CM_Paste_E2E'; describe(ID, () => { - let editor: IStandaloneEditor = undefined!; + let editor: IEditor = undefined!; beforeEach(() => { editor = initEditor(ID); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/testUtils.ts b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/testUtils.ts index 7cdc51cda32..f9663cf89e1 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/testUtils.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/testUtils.ts @@ -1,17 +1,13 @@ -import { cloneModel, StandaloneEditor } from 'roosterjs-content-model-core'; +import { cloneModel, Editor } from 'roosterjs-content-model-core'; +import { ContentModelDocument, EditorOptions, IEditor } from 'roosterjs-content-model-types'; import { PastePlugin } from '../../../lib/paste/PastePlugin'; -import { - ContentModelDocument, - IStandaloneEditor, - StandaloneEditorOptions, -} from 'roosterjs-content-model-types'; -export function initEditor(id: string): IStandaloneEditor { +export function initEditor(id: string): IEditor { let node = document.createElement('div'); node.id = id; document.body.insertBefore(node, document.body.childNodes[0]); - let options: StandaloneEditorOptions = { + let options: EditorOptions = { plugins: [new PastePlugin()], coreApiOverride: { getVisibleViewport: () => { @@ -25,7 +21,7 @@ export function initEditor(id: string): IStandaloneEditor { }, }; - let editor = new StandaloneEditor(node as HTMLDivElement, options); + let editor = new Editor(node as HTMLDivElement, options); return editor; } diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/StandaloneEditorCore.ts b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCore.ts similarity index 79% rename from packages-content-model/roosterjs-content-model-types/lib/editor/StandaloneEditorCore.ts rename to packages-content-model/roosterjs-content-model-types/lib/editor/EditorCore.ts index a8450f864f5..f0145320be1 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/editor/StandaloneEditorCore.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCore.ts @@ -25,38 +25,38 @@ import type { /** * Create a EditorContext object used by ContentModel API - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param saveIndex True to allow saving index info into node using domIndexer, otherwise false */ -export type CreateEditorContext = (core: StandaloneEditorCore, saveIndex: boolean) => EditorContext; +export type CreateEditorContext = (core: EditorCore, saveIndex: boolean) => EditorContext; /** * Create Content Model from DOM tree in this editor - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param option The option to customize the behavior of DOM to Content Model conversion * @param selectionOverride When passed, use this selection range instead of current selection in editor */ export type CreateContentModel = ( - core: StandaloneEditorCore, + core: EditorCore, option?: DomToModelOption, selectionOverride?: DOMSelection ) => ContentModelDocument; /** * Get current DOM selection from editor - * @param core The StandaloneEditorCore object + * @param core The EditorCore object */ -export type GetDOMSelection = (core: StandaloneEditorCore) => DOMSelection | null; +export type GetDOMSelection = (core: EditorCore) => DOMSelection | null; /** * Set content with content model. This is the replacement of core API getSelectionRangeEx - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param model The content model to set * @param option Additional options to customize the behavior of Content Model to DOM conversion * @param onNodeCreated An optional callback that will be called when a DOM node is created */ export type SetContentModel = ( - core: StandaloneEditorCore, + core: EditorCore, model: ContentModelDocument, option?: ModelToDomOption, onNodeCreated?: OnNodeCreated @@ -64,12 +64,12 @@ export type SetContentModel = ( /** * Set current DOM selection from editor. This is the replacement of core API select - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param selection The selection to set * @param skipSelectionChangedEvent @param Pass true to skip triggering a SelectionChangedEvent */ export type SetDOMSelection = ( - core: StandaloneEditorCore, + core: EditorCore, selection: DOMSelection | null, skipSelectionChangedEvent?: boolean ) => void; @@ -79,125 +79,117 @@ export type SetDOMSelection = ( * It will grab a Content Model for current editor content, and invoke a callback function * to do format change. Then according to the return value, write back the modified content model into editor. * If there is cached model, it will be used and updated. - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param formatter Formatter function, see ContentModelFormatter * @param options More options, see FormatContentModelOptions */ export type FormatContentModel = ( - core: StandaloneEditorCore, + core: EditorCore, formatter: ContentModelFormatter, options?: FormatContentModelOptions ) => void; /** * Switch the Shadow Edit mode of editor On/Off - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param isOn True to switch On, False to switch Off */ -export type SwitchShadowEdit = (core: StandaloneEditorCore, isOn: boolean) => void; +export type SwitchShadowEdit = (core: EditorCore, isOn: boolean) => void; /** * Trigger a plugin event - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param pluginEvent The event object to trigger * @param broadcast Set to true to skip the shouldHandleEventExclusively check */ -export type TriggerEvent = ( - core: StandaloneEditorCore, - pluginEvent: PluginEvent, - broadcast: boolean -) => void; +export type TriggerEvent = (core: EditorCore, pluginEvent: PluginEvent, broadcast: boolean) => void; /** * Add an undo snapshot to current undo snapshot stack - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param canUndoByBackspace True if this action can be undone when user press Backspace key (aka Auto Complete). * @param entityStates @optional Entity states related to this snapshot. * Each entity state will cause an EntityOperation event with operation = EntityOperation.UpdateEntityState * when undo/redo to this snapshot */ export type AddUndoSnapshot = ( - core: StandaloneEditorCore, + core: EditorCore, canUndoByBackspace: boolean, entityStates?: EntityState[] ) => Snapshot | null; /** * Retrieves the rect of the visible viewport of the editor. - * @param core The StandaloneEditorCore object + * @param core The EditorCore object */ -export type GetVisibleViewport = (core: StandaloneEditorCore) => Rect | null; +export type GetVisibleViewport = (core: EditorCore) => Rect | null; /** * Check if the editor has focus now - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @returns True if the editor has focus, otherwise false */ -export type HasFocus = (core: StandaloneEditorCore) => boolean; +export type HasFocus = (core: EditorCore) => boolean; /** * Focus to editor. If there is a cached selection range, use it as current selection - * @param core The StandaloneEditorCore object + * @param core The EditorCore object */ -export type Focus = (core: StandaloneEditorCore) => void; +export type Focus = (core: EditorCore) => void; /** * Attach a DOM event to the editor content DIV - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param eventMap A map from event name to its handler */ export type AttachDomEvent = ( - core: StandaloneEditorCore, + core: EditorCore, eventMap: Record ) => () => void; /** * Restore an undo snapshot into editor - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param step Steps to move, can be 0, positive or negative */ -export type RestoreUndoSnapshot = (core: StandaloneEditorCore, snapshot: Snapshot) => void; +export type RestoreUndoSnapshot = (core: EditorCore, snapshot: Snapshot) => void; /** * Paste into editor using a clipboardData object - * @param core The StandaloneEditorCore object. + * @param core The EditorCore object. * @param clipboardData Clipboard data retrieved from clipboard * @param pasteType Type of content to paste. @default normal */ -export type Paste = ( - core: StandaloneEditorCore, - clipboardData: ClipboardData, - pasteType: PasteType -) => void; +export type Paste = (core: EditorCore, clipboardData: ClipboardData, pasteType: PasteType) => void; /** - * The interface for the map of core API for Content Model editor. - * Editor can call call API from this map under StandaloneEditorCore object + * The interface for the map of core API for Editor. + * Editor can call call API from this map under EditorCore object */ -export interface StandaloneCoreApiMap { +export interface CoreApiMap { /** * Create a EditorContext object used by ContentModel API - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param saveIndex True to allow saving index info into node using domIndexer, otherwise false */ createEditorContext: CreateEditorContext; /** * Create Content Model from DOM tree in this editor - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param option The option to customize the behavior of DOM to Content Model conversion */ createContentModel: CreateContentModel; /** * Get current DOM selection from editor - * @param core The StandaloneEditorCore object + * @param core The EditorCore object */ getDOMSelection: GetDOMSelection; /** * Set content with content model - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param model The content model to set * @param option Additional options to customize the behavior of Content Model to DOM conversion */ @@ -205,7 +197,7 @@ export interface StandaloneCoreApiMap { /** * Set current DOM selection from editor. This is the replacement of core API select - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param selection The selection to set * @param skipSelectionChangedEvent @param Pass true to skip triggering a SelectionChangedEvent */ @@ -216,7 +208,7 @@ export interface StandaloneCoreApiMap { * It will grab a Content Model for current editor content, and invoke a callback function * to do format change. Then according to the return value, write back the modified content model into editor. * If there is cached model, it will be used and updated. - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param formatter Formatter function, see ContentModelFormatter * @param options More options, see FormatContentModelOptions */ @@ -224,33 +216,33 @@ export interface StandaloneCoreApiMap { /** * Switch the Shadow Edit mode of editor On/Off - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param isOn True to switch On, False to switch Off */ switchShadowEdit: SwitchShadowEdit; /** * Retrieves the rect of the visible viewport of the editor. - * @param core The StandaloneEditorCore object + * @param core The EditorCore object */ getVisibleViewport: GetVisibleViewport; /** * Check if the editor has focus now - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @returns True if the editor has focus, otherwise false */ hasFocus: HasFocus; /** * Focus to editor. If there is a cached selection range, use it as current selection - * @param core The StandaloneEditorCore object + * @param core The EditorCore object */ focus: Focus; /** * Add an undo snapshot to current undo snapshot stack - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param canUndoByBackspace True if this action can be undone when user press Backspace key (aka Auto Complete). * @param entityStates @optional Entity states related to this snapshot. * Each entity state will cause an EntityOperation event with operation = EntityOperation.UpdateEntityState @@ -267,14 +259,14 @@ export interface StandaloneCoreApiMap { /** * Attach a DOM event to the editor content DIV - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param eventMap A map from event name to its handler */ attachDomEvent: AttachDomEvent; /** * Trigger a plugin event - * @param core The StandaloneEditorCore object + * @param core The EditorCore object * @param pluginEvent The event object to trigger * @param broadcast Set to true to skip the shouldHandleEventExclusively check */ @@ -290,9 +282,9 @@ export interface StandaloneCoreApiMap { } /** - * Represents the core data structure of a Content Model editor + * Represents the core data structure of an editor */ -export interface StandaloneEditorCore extends PluginState { +export interface EditorCore extends PluginState { /** * The content DIV element of this editor */ @@ -301,12 +293,12 @@ export interface StandaloneEditorCore extends PluginState { /** * Core API map of this editor */ - readonly api: StandaloneCoreApiMap; + readonly api: CoreApiMap; /** * Original API map of this editor. Overridden core API can use API from this map to call the original version of core API. */ - readonly originalApi: StandaloneCoreApiMap; + readonly originalApi: CoreApiMap; /** * An array of editor plugins. diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/StandaloneEditorCorePlugins.ts b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCorePlugins.ts similarity index 96% rename from packages-content-model/roosterjs-content-model-types/lib/editor/StandaloneEditorCorePlugins.ts rename to packages-content-model/roosterjs-content-model-types/lib/editor/EditorCorePlugins.ts index 9b3394c214f..e5430a822a8 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/editor/StandaloneEditorCorePlugins.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCorePlugins.ts @@ -10,9 +10,9 @@ import type { CachePluginState } from '../pluginState/CachePluginState'; import type { FormatPluginState } from '../pluginState/FormatPluginState'; /** - * Core plugins for standalone editor + * Core plugins for editor */ -export interface StandaloneEditorCorePlugins { +export interface EditorCorePlugins { /** * ContentModel cache plugin manages cached Content Model, and refresh the cache when necessary */ diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/StandaloneEditorOptions.ts b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorOptions.ts similarity index 94% rename from packages-content-model/roosterjs-content-model-types/lib/editor/StandaloneEditorOptions.ts rename to packages-content-model/roosterjs-content-model-types/lib/editor/EditorOptions.ts index d3a75d37053..3a2a3f237c8 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/editor/StandaloneEditorOptions.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorOptions.ts @@ -1,7 +1,7 @@ import type { Colors, ColorTransformFunction } from '../context/DarkColorHandler'; import type { EditorPlugin } from './EditorPlugin'; import type { ContentModelSegmentFormat } from '../format/ContentModelSegmentFormat'; -import type { StandaloneCoreApiMap } from './StandaloneEditorCore'; +import type { CoreApiMap } from './EditorCore'; import type { DomToModelOption } from '../context/DomToModelOption'; import type { ModelToDomOption } from '../context/ModelToDomOption'; import type { ContentModelDocument } from '../group/ContentModelDocument'; @@ -9,9 +9,9 @@ import type { Snapshots } from '../parameter/Snapshot'; import type { TrustedHTMLHandler } from '../parameter/TrustedHTMLHandler'; /** - * Options for Content Model editor + * Options for editor */ -export interface StandaloneEditorOptions { +export interface EditorOptions { /** * Default options used for DOM to Content Model conversion */ @@ -76,7 +76,7 @@ export interface StandaloneEditorOptions { * A function map to override default core API implementation * Default value is null */ - coreApiOverride?: Partial; + coreApiOverride?: Partial; /** * Color of the border of a selectedImage. Default color: '#DB626C' diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorPlugin.ts b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorPlugin.ts index 0da11b5b855..187003aa9dc 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorPlugin.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorPlugin.ts @@ -1,5 +1,5 @@ import type { PluginEvent } from '../event/PluginEvent'; -import type { IStandaloneEditor } from './IStandaloneEditor'; +import type { IEditor } from './IEditor'; /** * Interface of an editor plugin @@ -16,7 +16,7 @@ export interface EditorPlugin { * editor reference so that it can call to any editor method or format API later. * @param editor The editor object */ - initialize: (editor: IStandaloneEditor) => void; + initialize: (editor: IEditor) => void; /** * The last method that editor will call to a plugin before it is disposed. diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/IStandaloneEditor.ts b/packages-content-model/roosterjs-content-model-types/lib/editor/IEditor.ts similarity index 97% rename from packages-content-model/roosterjs-content-model-types/lib/editor/IStandaloneEditor.ts rename to packages-content-model/roosterjs-content-model-types/lib/editor/IEditor.ts index 87c657be187..88b194a9a35 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/editor/IStandaloneEditor.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/editor/IEditor.ts @@ -19,10 +19,9 @@ import type { TrustedHTMLHandler } from '../parameter/TrustedHTMLHandler'; import type { Rect } from '../parameter/Rect'; /** - * An interface of standalone Content Model editor. - * (This interface is still under development, and may still be changed in the future with some breaking changes) + * An interface of Editor, built on top of Content Model */ -export interface IStandaloneEditor { +export interface IEditor { /** * Create Content Model from DOM tree in this editor * @param mode What kind of Content Model we want. Currently we support the following values: diff --git a/packages-content-model/roosterjs-content-model-types/lib/index.ts b/packages-content-model/roosterjs-content-model-types/lib/index.ts index 69dceefa3dd..d7ed66a4434 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/index.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/index.ts @@ -201,8 +201,8 @@ export { } from './metadata/Definition'; export { DarkColorHandler, Colors, ColorTransformFunction } from './context/DarkColorHandler'; -export { IStandaloneEditor } from './editor/IStandaloneEditor'; -export { StandaloneEditorOptions } from './editor/StandaloneEditorOptions'; +export { IEditor } from './editor/IEditor'; +export { EditorOptions } from './editor/EditorOptions'; export { CreateContentModel, CreateEditorContext, @@ -210,8 +210,8 @@ export { SetContentModel, SetDOMSelection, FormatContentModel, - StandaloneCoreApiMap, - StandaloneEditorCore, + CoreApiMap, + EditorCore, ContentModelSettings, SwitchShadowEdit, TriggerEvent, @@ -222,8 +222,8 @@ export { RestoreUndoSnapshot, GetVisibleViewport, Paste, -} from './editor/StandaloneEditorCore'; -export { StandaloneEditorCorePlugins } from './editor/StandaloneEditorCorePlugins'; +} from './editor/EditorCore'; +export { EditorCorePlugins } from './editor/EditorCorePlugins'; export { EditorPlugin } from './editor/EditorPlugin'; export { PluginWithState } from './editor/PluginWithState'; export { ContextMenuProvider } from './editor/ContextMenuProvider'; diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/PluginState.ts b/packages-content-model/roosterjs-content-model-types/lib/pluginState/PluginState.ts index 879609b30ef..d209122478c 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/pluginState/PluginState.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/pluginState/PluginState.ts @@ -1,24 +1,24 @@ import type { PluginWithState } from '../editor/PluginWithState'; -import type { StandaloneEditorCorePlugins } from '../editor/StandaloneEditorCorePlugins'; +import type { EditorCorePlugins } from '../editor/EditorCorePlugins'; /** * Names of core plugins */ -export type PluginKey = keyof StandaloneEditorCorePlugins; +export type PluginKey = keyof EditorCorePlugins; /** * Names of the core plugins that have plugin state */ export type KeyOfStatePlugin< Key extends PluginKey -> = StandaloneEditorCorePlugins[Key] extends PluginWithState ? Key : never; +> = EditorCorePlugins[Key] extends PluginWithState ? Key : never; /** * Get type of a plugin with state */ export type TypeOfStatePlugin< Key extends PluginKey -> = StandaloneEditorCorePlugins[Key] extends PluginWithState ? U : never; +> = EditorCorePlugins[Key] extends PluginWithState ? U : never; /** * All names of plugins with plugin state diff --git a/packages-content-model/roosterjs-content-model/lib/createEditor.ts b/packages-content-model/roosterjs-content-model/lib/createEditor.ts index eed17791252..8c2558508e9 100644 --- a/packages-content-model/roosterjs-content-model/lib/createEditor.ts +++ b/packages-content-model/roosterjs-content-model/lib/createEditor.ts @@ -1,10 +1,10 @@ +import { Editor } from 'roosterjs-content-model-core'; import { EditPlugin, PastePlugin } from 'roosterjs-content-model-plugins'; -import { StandaloneEditor } from 'roosterjs-content-model-core'; import type { ContentModelDocument, EditorPlugin, - IStandaloneEditor, - StandaloneEditorOptions, + IEditor, + EditorOptions, } from 'roosterjs-content-model-types'; /** @@ -19,10 +19,10 @@ export function createEditor( contentDiv: HTMLDivElement, additionalPlugins?: EditorPlugin[], initialModel?: ContentModelDocument -): IStandaloneEditor { +): IEditor { const plugins = [new PastePlugin(), new EditPlugin(), ...(additionalPlugins ?? [])]; - const options: StandaloneEditorOptions = { + const options: EditorOptions = { plugins: plugins, initialModel, defaultSegmentFormat: { @@ -31,5 +31,5 @@ export function createEditor( textColor: '#000000', }, }; - return new StandaloneEditor(contentDiv, options); + return new Editor(contentDiv, options); } diff --git a/packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts b/packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts index a91e0fb2a5f..f3e77ea6355 100644 --- a/packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts +++ b/packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts @@ -12,11 +12,7 @@ import type { CustomData, DarkColorHandler, } from 'roosterjs-editor-types'; -import type { - ContextMenuProvider, - IStandaloneEditor, - PluginEvent, -} from 'roosterjs-content-model-types'; +import type { ContextMenuProvider, IEditor, PluginEvent } from 'roosterjs-content-model-types'; const ExclusivelyHandleEventPluginKey = '__ExclusivelyHandleEventPlugin'; @@ -93,7 +89,7 @@ export class BridgePlugin implements ContextMenuProvider { * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ - initialize(editor: IStandaloneEditor) { + initialize(editor: IEditor) { const outerEditor = this.onInitialize(this.createEditorCore(editor)); this.legacyPlugins.forEach(plugin => plugin.initialize(outerEditor)); @@ -171,7 +167,7 @@ export class BridgePlugin implements ContextMenuProvider { return allItems; } - private createEditorCore(editor: IStandaloneEditor): EditorAdapterCore { + private createEditorCore(editor: IEditor): EditorAdapterCore { return { customData: {}, experimentalFeatures: this.experimentalFeatures ?? [], @@ -187,7 +183,7 @@ export class BridgePlugin implements ContextMenuProvider { * @internal Export for test only. This function is only used for compatibility from older build */ -export function createSizeTransformer(editor: IStandaloneEditor): SizeTransformer { +export function createSizeTransformer(editor: IEditor): SizeTransformer { return size => size / editor.getDOMHelper().calculateZoomScale(); } diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts index 89d4c0edeea..e06f158a475 100644 --- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts +++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts @@ -14,7 +14,7 @@ import { isBold, redo, retrieveModelFormatState, - StandaloneEditor, + Editor, transformColor, undo, } from 'roosterjs-content-model-core'; @@ -54,7 +54,7 @@ import type { TableSelection, DOMEventHandlerObject, DarkColorHandler, - IEditor, + IEditor as ILegacyEditor, } from 'roosterjs-editor-types'; import { convertDomSelectionToRangeEx, @@ -93,8 +93,8 @@ import type { ContentModelFormatState, DOMEventRecord, ExportContentMode, - IStandaloneEditor, - StandaloneEditorOptions, + IEditor, + EditorOptions, } from 'roosterjs-content-model-types'; const GetContentModeMap: Record = { @@ -109,7 +109,7 @@ const GetContentModeMap: Record = { * Editor for Content Model. * (This class is still under development, and may still be changed in the future with some breaking changes) */ -export class EditorAdapter extends StandaloneEditor implements IEditor { +export class EditorAdapter extends Editor implements ILegacyEditor { private contentModelEditorCore: EditorAdapterCore | undefined; /** @@ -139,7 +139,7 @@ export class EditorAdapter extends StandaloneEditor implements IEditor { options.defaultSegmentFormat ) : options.initialModel; - const standaloneEditorOptions: StandaloneEditorOptions = { + const standaloneEditorOptions: EditorOptions = { ...options, plugins, initialModel, @@ -871,7 +871,7 @@ export class EditorAdapter extends StandaloneEditor implements IEditor { * @param callback The callback function to run * @returns a function to cancel this async run */ - runAsync(callback: (editor: IEditor & IStandaloneEditor) => void) { + runAsync(callback: (editor: ILegacyEditor & IEditor) => void) { const win = this.getCore().contentDiv.ownerDocument.defaultView || window; const handle = win.requestAnimationFrame(() => { if (!this.isDisposed() && callback) { diff --git a/packages/roosterjs-editor-adapter/lib/publicTypes/EditorAdapterOptions.ts b/packages/roosterjs-editor-adapter/lib/publicTypes/EditorAdapterOptions.ts index b5ed78fe6fe..69621390e57 100644 --- a/packages/roosterjs-editor-adapter/lib/publicTypes/EditorAdapterOptions.ts +++ b/packages/roosterjs-editor-adapter/lib/publicTypes/EditorAdapterOptions.ts @@ -1,10 +1,10 @@ -import type { StandaloneEditorOptions } from 'roosterjs-content-model-types'; +import type { EditorOptions } from 'roosterjs-content-model-types'; import type { EditorPlugin, ExperimentalFeatures } from 'roosterjs-editor-types'; /** * Options for editor adapter */ -export interface EditorAdapterOptions extends StandaloneEditorOptions { +export interface EditorAdapterOptions extends EditorOptions { /** * Initial HTML content * Default value is whatever already inside the editor content DIV diff --git a/packages/roosterjs-editor-adapter/test/editor/EditorAdapterTest.ts b/packages/roosterjs-editor-adapter/test/editor/EditorAdapterTest.ts index 55bb8dc7381..b958bb7a5b7 100644 --- a/packages/roosterjs-editor-adapter/test/editor/EditorAdapterTest.ts +++ b/packages/roosterjs-editor-adapter/test/editor/EditorAdapterTest.ts @@ -3,11 +3,7 @@ import * as domToContentModel from 'roosterjs-content-model-dom/lib/domToModel/d import * as findAllEntities from 'roosterjs-content-model-core/lib/corePlugin/utils/findAllEntities'; import { EditorAdapter } from '../../lib/editor/EditorAdapter'; import { EditorPlugin, PluginEventType } from 'roosterjs-editor-types'; -import { - ContentModelDocument, - EditorContext, - StandaloneEditorCore, -} from 'roosterjs-content-model-types'; +import { ContentModelDocument, EditorContext, EditorCore } from 'roosterjs-content-model-types'; const editorContext: EditorContext = { isDarkMode: false, @@ -193,7 +189,7 @@ describe('EditorAdapter', () => { it('getPendingFormat', () => { const div = document.createElement('div'); const editor = new EditorAdapter(div); - const core: StandaloneEditorCore = (editor as any).core; + const core: EditorCore = (editor as any).core; const mockedFormat = 'FORMAT' as any; expect(editor.getPendingFormat()).toBeNull(); From f63a0c946f907bb678f2f387c0bb0409a35e0eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 20 Feb 2024 13:04:56 -0300 Subject: [PATCH 07/80] add parameter --- .../lib/modelApi/list/setListType.ts | 9 ++- .../lib/publicApi/list/toggleBullet.ts | 5 +- .../lib/publicApi/list/toggleNumbering.ts | 5 +- .../test/modelApi/list/setListTypeTest.ts | 80 +++++++++++++++++++ .../test/publicApi/list/toggleBulletTest.ts | 6 +- .../publicApi/list/toggleNumberingTest.ts | 6 +- .../autoFormat/keyboardListTriggerTest.ts | 12 ++- 7 files changed, 114 insertions(+), 9 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts b/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts index a63ec869014..5f748f276fc 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts @@ -15,8 +15,13 @@ import type { * Set a list type to content model * @param model the model document * @param listType the list type OL | UL + * @param removeMargins true to remove margins, false to keep margins @default false */ -export function setListType(model: ContentModelDocument, listType: 'OL' | 'UL') { +export function setListType( + model: ContentModelDocument, + listType: 'OL' | 'UL', + removeMargins: boolean = false +) { const paragraphOrListItems = getOperationalBlocks( model, ['ListItem'], @@ -76,6 +81,8 @@ export function setListType(model: ContentModelDocument, listType: 'OL' | 'UL') : 1, direction: block.format.direction, textAlign: block.format.textAlign, + marginBottom: removeMargins ? '0px' : undefined, + marginTop: removeMargins ? '0px' : undefined, }), ], // For list bullet, we only want to carry over these formats from segments: diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts index 7729eade0c6..100eac17d9f 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts @@ -6,15 +6,16 @@ import type { IStandaloneEditor } from 'roosterjs-content-model-types'; * - When there are some blocks not in bullet list, set all blocks to the given type * - When all blocks are already in bullet list, turn off / outdent there list type * @param editor The editor to operate on + * @param removeMargins true to remove margins, false to keep margins @default false */ -export default function toggleBullet(editor: IStandaloneEditor) { +export default function toggleBullet(editor: IStandaloneEditor, removeMargins: boolean = false) { editor.focus(); editor.formatContentModel( (model, context) => { context.newPendingFormat = 'preserve'; - return setListType(model, 'UL'); + return setListType(model, 'UL', removeMargins); }, { apiName: 'toggleBullet', diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts index b98c6df7b2f..b56060f67b2 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts @@ -6,15 +6,16 @@ import type { IStandaloneEditor } from 'roosterjs-content-model-types'; * - When there are some blocks not in numbering list, set all blocks to the given type * - When all blocks are already in numbering list, turn off / outdent there list type * @param editor The editor to operate on + * @param removeMargins true to remove margins, false to keep margins @default false */ -export default function toggleNumbering(editor: IStandaloneEditor) { +export default function toggleNumbering(editor: IStandaloneEditor, removeMargins: boolean = false) { editor.focus(); editor.formatContentModel( (model, context) => { context.newPendingFormat = 'preserve'; - return setListType(model, 'OL'); + return setListType(model, 'OL', removeMargins); }, { apiName: 'toggleNumbering', diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts b/packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts index 0f603ab6287..aa2084fd99a 100644 --- a/packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts @@ -74,6 +74,8 @@ describe('indent', () => { format: { startNumberOverride: 1, direction: undefined, + marginBottom: undefined, + marginTop: undefined, textAlign: undefined, }, dataset: {}, @@ -108,6 +110,65 @@ describe('indent', () => { }); }); + it('Group with single paragraph selection remove margins', () => { + const group = createContentModelDocument(); + const para = createParagraph(); + const text = createText('test'); + + para.segments.push(text); + group.blocks.push(para); + + text.isSelected = true; + + const result = setListType(group, 'OL', true /** remove margins */); + + expect(group).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockGroupType: 'ListItem', + blockType: 'BlockGroup', + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + direction: undefined, + marginBottom: '0px', + marginTop: '0px', + textAlign: undefined, + }, + dataset: {}, + }, + ], + blocks: [para], + formatHolder: { + segmentType: 'SelectionMarker', + format: { + fontFamily: undefined, + fontSize: undefined, + textColor: undefined, + }, + isSelected: true, + }, + format: {}, + }, + ], + }); + expect(result).toBeTrue(); + expect(para).toEqual({ + blockType: 'Paragraph', + format: {}, + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + }); + }); it('Group with single list item selection in a different type', () => { const group = createContentModelDocument(); const para = createParagraph(); @@ -299,6 +360,8 @@ describe('indent', () => { format: { startNumberOverride: undefined, direction: undefined, + marginBottom: undefined, + marginTop: undefined, textAlign: undefined, }, dataset: {}, @@ -362,6 +425,8 @@ describe('indent', () => { startNumberOverride: 1, direction: 'rtl', textAlign: 'start', + marginBottom: undefined, + marginTop: undefined, }, }, ], @@ -439,6 +504,7 @@ describe('indent', () => { format: { startNumberOverride: 1, direction: undefined, + marginTop: undefined, textAlign: undefined, marginBottom: '0px', }, @@ -466,6 +532,8 @@ describe('indent', () => { dataset: {}, format: { direction: undefined, + marginBottom: undefined, + marginTop: undefined, textAlign: undefined, startNumberOverride: undefined, }, @@ -520,6 +588,8 @@ describe('indent', () => { format: { startNumberOverride: 1, direction: undefined, + marginBottom: undefined, + marginTop: undefined, textAlign: undefined, }, }, @@ -575,6 +645,8 @@ describe('indent', () => { format: { startNumberOverride: 1, direction: undefined, + marginBottom: undefined, + marginTop: undefined, textAlign: undefined, }, }, @@ -601,6 +673,8 @@ describe('indent', () => { format: { startNumberOverride: undefined, direction: undefined, + marginBottom: undefined, + marginTop: undefined, textAlign: undefined, }, }, @@ -626,6 +700,8 @@ describe('indent', () => { dataset: {}, format: { direction: undefined, + marginBottom: undefined, + marginTop: undefined, textAlign: undefined, startNumberOverride: undefined, }, @@ -699,6 +775,8 @@ describe('indent', () => { format: { startNumberOverride: 1, direction: undefined, + marginBottom: undefined, + marginTop: undefined, textAlign: undefined, }, dataset: {}, @@ -727,6 +805,8 @@ describe('indent', () => { format: { startNumberOverride: undefined, direction: undefined, + marginBottom: undefined, + marginTop: undefined, textAlign: undefined, }, dataset: {}, diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts index aff7fbd3260..90eb72ae1a3 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts @@ -45,7 +45,11 @@ describe('toggleBullet', () => { toggleBullet(editor); expect(setListType.setListType).toHaveBeenCalledTimes(1); - expect(setListType.setListType).toHaveBeenCalledWith(mockedModel, 'UL'); + expect(setListType.setListType).toHaveBeenCalledWith( + mockedModel, + 'UL', + false /** remove margins */ + ); expect(context).toEqual({ newEntities: [], deletedEntities: [], diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts index 131a7d011cc..77fc9cef988 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts @@ -44,7 +44,11 @@ describe('toggleNumbering', () => { toggleNumbering(editor); expect(setListType.setListType).toHaveBeenCalledTimes(1); - expect(setListType.setListType).toHaveBeenCalledWith(mockedModel, 'OL'); + expect(setListType.setListType).toHaveBeenCalledWith( + mockedModel, + 'OL', + false /** remove margins */ + ); expect(context).toEqual({ newEntities: [], deletedEntities: [], diff --git a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts index 93a7c9c8d1a..a9fe10ec934 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts @@ -98,6 +98,8 @@ describe('keyboardListTrigger', () => { startNumberOverride: 1, direction: undefined, textAlign: undefined, + marginBottom: undefined, + marginTop: undefined, }, dataset: { editingInfo: '{"orderedStyleType":3}', @@ -240,6 +242,8 @@ describe('keyboardListTrigger', () => { startNumberOverride: 2, direction: undefined, textAlign: undefined, + marginBottom: undefined, + marginTop: undefined, }, dataset: { editingInfo: '{"orderedStyleType":3}', @@ -366,6 +370,8 @@ describe('keyboardListTrigger', () => { startNumberOverride: 1, direction: undefined, textAlign: undefined, + marginBottom: undefined, + marginTop: undefined, }, dataset: { editingInfo: '{"unorderedStyleType":1}', @@ -706,9 +712,10 @@ describe('keyboardListTrigger', () => { listType: 'OL', format: { startNumberOverride: 3, - direction: undefined, textAlign: undefined, + marginBottom: undefined, + marginTop: undefined, }, dataset: { editingInfo: '{"orderedStyleType":3}', @@ -1017,9 +1024,10 @@ describe('keyboardListTrigger', () => { listType: 'OL', format: { startNumberOverride: 1, - direction: undefined, textAlign: undefined, + marginBottom: undefined, + marginTop: undefined, }, dataset: { editingInfo: '{"orderedStyleType":10}', From b5a925bded43592928d2813263bbe120c26335b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 20 Feb 2024 15:55:38 -0300 Subject: [PATCH 08/80] fix conflicts --- .../lib/publicApi/list/toggleBullet.ts | 2 +- .../lib/publicApi/list/toggleNumbering.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts index 759329a18cc..cbf8a36ca65 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts @@ -8,7 +8,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to operate on * @param removeMargins true to remove margins, false to keep margins @default false */ -export default function toggleBullet(editor: IStandaloneEditor, removeMargins: boolean = false) { +export default function toggleBullet(editor: IEditor, removeMargins: boolean = false) { editor.focus(); editor.formatContentModel( diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts index d4549f6e4e8..2e754b6fad6 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts @@ -8,7 +8,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to operate on * @param removeMargins true to remove margins, false to keep margins @default false */ -export default function toggleNumbering(editor: IStandaloneEditor, removeMargins: boolean = false) { +export default function toggleNumbering(editor: IEditor, removeMargins: boolean = false) { editor.focus(); editor.formatContentModel( From da1862e746ae22659e15c67fdfd61fa1e9609a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 20 Feb 2024 18:12:13 -0300 Subject: [PATCH 09/80] fix selection --- .../lib/edit/inputSteps/handleEnterOnList.ts | 25 +++++++++++++++---- .../edit/inputSteps/handleEnterOnListTest.ts | 20 +++++++-------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts index 544162dbcc3..0223b50a9de 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts @@ -7,6 +7,7 @@ import { createListItem, createListLevel, createParagraph, + createSelectionMarker, normalizeContentModel, normalizeParagraph, setParagraphNotImplicit, @@ -23,10 +24,11 @@ import type { * @internal */ export const handleEnterOnList: DeleteSelectionStep = context => { + const { deleteResult } = context; if ( - context.deleteResult == 'nothingToDelete' || - context.deleteResult == 'notDeleted' || - context.deleteResult == 'range' + deleteResult == 'nothingToDelete' || + deleteResult == 'notDeleted' || + deleteResult == 'range' ) { const { insertPoint, formatContext } = context; const { path } = insertPoint; @@ -39,7 +41,7 @@ export const handleEnterOnList: DeleteSelectionStep = context => { if (listItem && listItem.blockGroupType === 'ListItem') { const listIndex = listParent.blocks.indexOf(listItem); const nextBlock = listParent.blocks[listIndex + 1]; - if (context.deleteResult == 'range' && nextBlock) { + if (deleteResult == 'range' && nextBlock) { normalizeContentModel(listParent); const nextListItem = listParent.blocks[listIndex + 1]; if ( @@ -52,10 +54,23 @@ export const handleEnterOnList: DeleteSelectionStep = context => { ? listItem.levels[index].dataset : {}; }); + const lastParagraph = listItem.blocks[listItem.blocks.length - 1]; + const nextParagraph = nextListItem.blocks[0]; + if ( + nextParagraph.blockType === 'Paragraph' && + lastParagraph.blockType === 'Paragraph' && + lastParagraph.segments[lastParagraph.segments.length - 1].segmentType === + 'SelectionMarker' + ) { + lastParagraph.segments.pop(); + nextParagraph.segments.unshift( + createSelectionMarker(insertPoint.marker.format) + ); + } context.lastParagraph = undefined; } - } else { + } else if (deleteResult !== 'range') { if (isEmptyListItem(listItem)) { listItem.levels.pop(); } else { diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts index 4277a3c3331..a5a8234a6a1 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts @@ -1408,11 +1408,6 @@ describe('handleEnterOnList', () => { text: 'te', format: {}, }, - { - segmentType: 'SelectionMarker', - isSelected: true, - format: {}, - }, ], format: {}, }, @@ -1444,6 +1439,11 @@ describe('handleEnterOnList', () => { { blockType: 'Paragraph', segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, { segmentType: 'Text', text: 'st', @@ -1600,11 +1600,6 @@ describe('handleEnterOnList', () => { text: 'te', format: {}, }, - { - segmentType: 'SelectionMarker', - isSelected: true, - format: {}, - }, ], format: {}, }, @@ -1637,6 +1632,11 @@ describe('handleEnterOnList', () => { { blockType: 'Paragraph', segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, { segmentType: 'Text', text: 'st', From 0bade6ce2ab1fac66d9d19a528cb5c374fd9e6e7 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Tue, 20 Feb 2024 13:58:54 -0800 Subject: [PATCH 10/80] Fix insertNode: Do not reselect if updateCursor is false (#2431) --- packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts index e06f158a475..95b1a64d988 100644 --- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts +++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts @@ -219,7 +219,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { const selection = insertNode(contentDiv, this.getDOMSelection(), node, option); - if (selection) { + if (selection && option.updateCursor) { this.setDOMSelection(selection); } } From ac262d81340b06c8c5bb3a30b44cfb353481c150 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 14:41:52 -0800 Subject: [PATCH 11/80] Bump ip from 1.1.5 to 1.1.9 (#2433) Bumps [ip](https://github.com/indutny/node-ip) from 1.1.5 to 1.1.9. - [Commits](https://github.com/indutny/node-ip/compare/v1.1.5...v1.1.9) --- updated-dependencies: - dependency-name: ip dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8c977c6d941..6c4d59c9629 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3652,9 +3652,9 @@ ip-regex@^2.1.0: integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + version "1.1.9" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396" + integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ== ipaddr.js@1.9.1: version "1.9.1" From 9cec478cb643d3661fd804d27360d634dff52ea4 Mon Sep 17 00:00:00 2001 From: Bryan Valverde U Date: Tue, 20 Feb 2024 17:35:52 -0600 Subject: [PATCH 12/80] Outdent indented empty paragraph when pressing Backspace. (#2429) * outdent on backspace * Remove unneeded check * fix test build --- .../deleteSteps/deleteCollapsedSelection.ts | 43 ++- .../deleteCollapsedSelectionTest.ts | 271 ++++++++++++++++++ 2 files changed, 312 insertions(+), 2 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteCollapsedSelection.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteCollapsedSelection.ts index 5274e7a465a..08cacd3e998 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteCollapsedSelection.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteCollapsedSelection.ts @@ -1,8 +1,19 @@ -import { deleteBlock, deleteSegment } from 'roosterjs-content-model-core'; import { getLeafSiblingBlock } from '../utils/getLeafSiblingBlock'; +import { setModelIndentation } from 'roosterjs-content-model-api'; import { setParagraphNotImplicit } from 'roosterjs-content-model-dom'; +import { + deleteBlock, + deleteSegment, + getClosestAncestorBlockGroupIndex, +} from 'roosterjs-content-model-core'; import type { BlockAndPath } from '../utils/getLeafSiblingBlock'; -import type { ContentModelSegment, DeleteSelectionStep } from 'roosterjs-content-model-types'; +import type { + ContentModelBlockGroup, + ContentModelDocument, + ContentModelParagraph, + ContentModelSegment, + DeleteSelectionStep, +} from 'roosterjs-content-model-types'; function getDeleteCollapsedSelection(direction: 'forward' | 'backward'): DeleteSelectionStep { return context => { @@ -19,6 +30,7 @@ function getDeleteCollapsedSelection(direction: 'forward' | 'backward'): DeleteS const index = segments.indexOf(marker) + (isForward ? 1 : -1); const segmentToDelete = segments[index]; let blockToDelete: BlockAndPath | null; + let root: ContentModelDocument | null; if (segmentToDelete) { if (deleteSegment(paragraph, segmentToDelete, context.formatContext, direction)) { @@ -28,6 +40,12 @@ function getDeleteCollapsedSelection(direction: 'forward' | 'backward'): DeleteS // to avoid losing its format. See https://github.com/microsoft/roosterjs/issues/1953 setParagraphNotImplicit(paragraph); } + } else if ( + shouldOutdentParagraph(isForward, segments, paragraph, path) && + (root = getRoot(path)) + ) { + setModelIndentation(root, 'outdent'); + context.deleteResult = 'range'; } else if ((blockToDelete = getLeafSiblingBlock(path, paragraph, isForward))) { const { block, path, siblingSegment } = blockToDelete; @@ -82,6 +100,27 @@ function getDeleteCollapsedSelection(direction: 'forward' | 'backward'): DeleteS }; } +function getRoot(path: ContentModelBlockGroup[]): ContentModelDocument | null { + const lastInPath = path[path.length - 1]; + return lastInPath.blockGroupType == 'Document' ? lastInPath : null; +} + +function shouldOutdentParagraph( + isForward: boolean, + segments: ContentModelSegment[], + paragraph: ContentModelParagraph, + path: ContentModelBlockGroup[] +) { + return ( + !isForward && + segments.length == 1 && + segments[0].segmentType == 'SelectionMarker' && + paragraph.format.marginLeft && + parseInt(paragraph.format.marginLeft) && + getClosestAncestorBlockGroupIndex(path, ['Document', 'TableCell'], ['ListItem']) > -1 + ); +} + /** * If the last segment is BR, remove it for now. We may add it back later when normalize model. * So that if this is an empty paragraph, it will start to delete next block diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteCollapsedSelectionTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteCollapsedSelectionTest.ts index 9a8dcd7d7fd..eeadaf4b008 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteCollapsedSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteCollapsedSelectionTest.ts @@ -3232,4 +3232,275 @@ describe('deleteSelection - backward', () => { ], }); }); + + it('Outdent from empty paragraph', () => { + const model = createContentModelDocument(); + const para = createParagraph(); + const marker = createSelectionMarker(); + + para.format.marginLeft = '40px'; + + para.segments.push(marker); + model.blocks.push(para); + + const result = deleteSelection(model, [backwardDeleteCollapsedSelection]); + + expect(result.deleteResult).toBe('range'); + + expect(result.insertPoint).toEqual({ + marker: marker, + paragraph: para, + path: [model], + tableContext: undefined, + }); + + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + format: { + marginLeft: '0px', + }, + segments: [ + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + ], + }, + ], + }); + }); + + it('Dont outdent from empty paragraph nested in list', () => { + const model = createContentModelDocument(); + const para = createParagraph(); + const marker = createSelectionMarker(); + const list = createListItem([]); + + para.format.marginLeft = '40px'; + + para.segments.push(marker); + model.blocks.push(list); + list.blocks.push(para); + + const result = deleteSelection(model, [backwardDeleteCollapsedSelection]); + + expect(result.deleteResult).toBe('nothingToDelete'); + + expect(result.insertPoint).toEqual({ + marker: marker, + paragraph: para, + path: [list, model], + tableContext: undefined, + }); + + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + levels: [], + formatHolder: { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + format: {}, + blocks: [ + { + blockType: 'Paragraph', + format: { + marginLeft: '40px', + }, + segments: [ + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + ], + }, + ], + }, + ], + }); + }); + + it('Dont outdent empty para with no margins', () => { + const model = createContentModelDocument(); + const para = createParagraph(); + const marker = createSelectionMarker(); + + para.format.marginLeft = '0px'; + + para.segments.push(marker); + model.blocks.push(para); + + const result = deleteSelection(model, [backwardDeleteCollapsedSelection]); + + expect(result.deleteResult).toBe('nothingToDelete'); + + expect(result.insertPoint).toEqual({ + marker: marker, + paragraph: para, + path: [model], + tableContext: undefined, + }); + + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + format: { + marginLeft: '0px', + }, + segments: [ + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + ], + }, + ], + }); + }); + + it('Dont outdent empty para with no margins and delete', () => { + const model = createContentModelDocument(); + const para = createParagraph(); + const para0 = createParagraph(); + const marker = createSelectionMarker(); + + para.format.marginLeft = '0px'; + para.segments.push(createBr()); + para.segments.push(marker); + model.blocks.push(para0, para); + + const result = deleteSelection(model, [backwardDeleteCollapsedSelection]); + + expect(result.deleteResult).toBe('singleChar'); + + expect(result.insertPoint).toEqual({ + marker: marker, + paragraph: para, + path: [model], + tableContext: undefined, + }); + + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + format: {}, + segments: [], + }, + { + blockType: 'Paragraph', + format: { marginLeft: '0px' }, + segments: [ + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + ], + }, + ], + }); + }); + + it('Outdent paragraph inside table nested in a list', () => { + const model = createContentModelDocument(); + const list = createListItem([]); + const table = createTable(1); + const cell = createTableCell(); + const para = createParagraph(); + const marker = createSelectionMarker(); + + cell.blocks.push(para); + table.rows[0].cells.push(cell); + list.blocks.push(table); + para.format.marginLeft = '40px'; + para.segments.push(marker); + model.blocks.push(list); + + const result = deleteSelection(model, [backwardDeleteCollapsedSelection]); + + expect(result.deleteResult).toBe('range'); + + expect(result.insertPoint).toEqual({ + marker: marker, + paragraph: para, + path: [cell, list, model], + tableContext: { + table, + colIndex: 0, + rowIndex: 0, + isWholeTableSelected: false, + }, + }); + + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Table', + rows: [ + { + height: 0, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: { + marginLeft: '0px', + }, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + ], + format: {}, + widths: [], + dataset: {}, + }, + ], + levels: [], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + ], + }); + }); }); From 53441eec3942eaf8f1a88145d9da6f6e6f623a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Thu, 22 Feb 2024 13:51:07 -0300 Subject: [PATCH 13/80] handle Tab key for paragraph --- .../lib/edit/keyboardTab.ts | 42 +- .../lib/edit/tabUtils/handleTabOnList.ts | 25 + .../lib/edit/tabUtils/handleTabOnParagraph.ts | 75 +++ .../test/edit/keyboardTabTest.ts | 564 +++++++++++++++++- .../test/edit/tabUtils/handleTabOnListTest.ts | 305 ++++++++++ .../edit/tabUtils/handleTabOnParagraphTest.ts | 429 +++++++++++++ 6 files changed, 1417 insertions(+), 23 deletions(-) create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts index da530533f73..049285b3541 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts @@ -1,9 +1,11 @@ import { getOperationalBlocks, isBlockGroupOfType } from 'roosterjs-content-model-core'; -import { setModelIndentation } from 'roosterjs-content-model-api'; +import { handleTabOnList } from './tabUtils/handleTabOnList'; +import { handleTabOnParagraph } from './tabUtils/handleTabOnParagraph'; import type { ContentModelDocument, ContentModelListItem, IEditor, + RangeSelection, } from 'roosterjs-content-model-types'; /** @@ -15,32 +17,30 @@ export function keyboardTab(editor: IEditor, rawEvent: KeyboardEvent) { if (selection?.type == 'range') { editor.takeSnapshot(); - editor.formatContentModel((model, _context) => { - return handleTabOnList(model, rawEvent); - }); + editor.formatContentModel( + (model, _context) => { + return handleTab(model, rawEvent, selection); + }, + { + apiName: 'handleTabKey', + } + ); return true; } } -function isMarkerAtStartOfBlock(listItem: ContentModelListItem) { - return ( - listItem.blocks[0].blockType == 'Paragraph' && - listItem.blocks[0].segments[0].segmentType == 'SelectionMarker' - ); -} - -function handleTabOnList(model: ContentModelDocument, rawEvent: KeyboardEvent) { +function handleTab( + model: ContentModelDocument, + rawEvent: KeyboardEvent, + selection: RangeSelection +) { const blocks = getOperationalBlocks(model, ['ListItem'], ['TableCell']); - const listItem = blocks[0].block; - - if ( - isBlockGroupOfType(listItem, 'ListItem') && - isMarkerAtStartOfBlock(listItem) - ) { - setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent'); - rawEvent.preventDefault(); - return true; + const block = blocks[0].block; + if (block.blockType === 'Paragraph') { + return handleTabOnParagraph(model, block, rawEvent, selection); + } else if (isBlockGroupOfType(block, 'ListItem')) { + return handleTabOnList(model, block, rawEvent); } return false; } diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts new file mode 100644 index 00000000000..01c7abc1fd0 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts @@ -0,0 +1,25 @@ +import { ContentModelDocument, ContentModelListItem } from 'roosterjs-content-model-types'; +import { setModelIndentation } from 'roosterjs-content-model-api'; + +/** + * @internal + */ +export function handleTabOnList( + model: ContentModelDocument, + listItem: ContentModelListItem, + rawEvent: KeyboardEvent +) { + if (isMarkerAtStartOfBlock(listItem)) { + setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent'); + rawEvent.preventDefault(); + return true; + } + return false; +} + +function isMarkerAtStartOfBlock(listItem: ContentModelListItem) { + return ( + listItem.blocks[0].blockType == 'Paragraph' && + listItem.blocks[0].segments[0].segmentType == 'SelectionMarker' + ); +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts new file mode 100644 index 00000000000..e7322b724e6 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts @@ -0,0 +1,75 @@ +import { createSelectionMarker, createText } from 'roosterjs-content-model-dom/lib'; +import { setModelIndentation } from 'roosterjs-content-model-api'; +import { + ContentModelDocument, + ContentModelParagraph, + RangeSelection, +} from 'roosterjs-content-model-types/lib'; + +const tabSpaces = '    '; +const space = ' '; + +/** + * @internal + */ +export function handleTabOnParagraph( + model: ContentModelDocument, + paragraph: ContentModelParagraph, + rawEvent: KeyboardEvent, + selection: RangeSelection +) { + if (paragraph.segments[0].segmentType === 'SelectionMarker' && selection.range.collapsed) { + setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent'); + } else { + const markerIndex = paragraph.segments.findIndex( + segment => segment.segmentType === 'SelectionMarker' + ); + if (!selection.range.collapsed) { + let firstSelectedSegmentIndex: number | undefined = undefined; + let lastSelectedSegmentIndex: number | undefined = undefined; + + paragraph.segments.forEach((segment, index) => { + if (segment.isSelected) { + if (!firstSelectedSegmentIndex) { + firstSelectedSegmentIndex = index; + } + lastSelectedSegmentIndex = index; + } + }); + if (firstSelectedSegmentIndex && lastSelectedSegmentIndex) { + const firstSelectedSegment = paragraph.segments[firstSelectedSegmentIndex]; + const spaceText = createText(rawEvent.shiftKey ? tabSpaces : space); + const marker = createSelectionMarker(firstSelectedSegment.format); + paragraph.segments.splice( + firstSelectedSegmentIndex, + lastSelectedSegmentIndex - firstSelectedSegmentIndex + 1, + spaceText, + marker + ); + } else { + return false; + } + } else { + if (!rawEvent.shiftKey) { + const tabText = createText(tabSpaces); + paragraph.segments.splice(markerIndex, 0, tabText); + } else { + const tabText = paragraph.segments[markerIndex - 1]; + const tabSpacesLength = tabSpaces.length; + if (tabText.segmentType == 'Text') { + const tabSpaceTextLength = tabText.text.length - tabSpacesLength; + if (tabText.text === tabSpaces) { + paragraph.segments.splice(markerIndex - 1, 1); + } else if (tabText.text.substring(tabSpaceTextLength) === tabSpaces) { + tabText.text = tabText.text.substring(0, tabSpaceTextLength); + } else { + return false; + } + } + } + } + } + + rawEvent.preventDefault(); + return true; +} diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts index 150d9b5c673..bc218d57e84 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts @@ -1,5 +1,6 @@ import * as setModelIndentation from '../../../roosterjs-content-model-api/lib/modelApi/block/setModelIndentation'; import { ContentModelDocument } from 'roosterjs-content-model-types'; +import { editingTestCommon } from './editingTestCommon'; import { keyboardTab } from '../../lib/edit/keyboardTab'; describe('keyboardTab', () => { @@ -35,6 +36,9 @@ describe('keyboardTab', () => { getDOMSelection: () => { return { type: 'range', + range: { + collapsed: true, + }, }; }, }; @@ -56,7 +60,7 @@ describe('keyboardTab', () => { } } - it('tab on paragraph', () => { + it('tab on the end of paragraph', () => { const model: ContentModelDocument = { blockGroupType: 'Document', blocks: [ @@ -80,7 +84,34 @@ describe('keyboardTab', () => { format: {}, }; - runTest(model, undefined, false, false); + runTest(model, undefined, false, true); + }); + + it('tab on the start of paragraph', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(model, 'indent', false, true); }); it('tab on empty list', () => { @@ -868,3 +899,532 @@ describe('keyboardTab', () => { runTest(model, undefined, true, false); }); }); + +describe('keyboardTab - handleTabOnParagraph -', () => { + function runTest( + input: ContentModelDocument, + key: string, + collapsed: boolean, + shiftKey: boolean, + expectedResult: ContentModelDocument, + calledTimes: number = 1 + ) { + const preventDefault = jasmine.createSpy('preventDefault'); + const mockedEvent = ({ + key, + shiftKey: shiftKey, + preventDefault, + } as any) as KeyboardEvent; + + let editor: any; + + editingTestCommon( + 'handleTabKey', + newEditor => { + editor = newEditor; + + editor.getDOMSelection = () => ({ + type: 'range', + range: { + collapsed: collapsed, + }, + }); + + keyboardTab(editor, mockedEvent); + }, + input, + expectedResult, + calledTimes + ); + } + + it('collapsed range | tab on the end of paragraph', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'Text', + text: '    ', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, 'Tab', true, false, expectedResult); + }); + + it('collapsed range | tab on the start of paragraph', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: { + marginLeft: '40px', + }, + }, + ], + format: {}, + }; + runTest(input, 'Tab', true, false, expectedResult); + }); + + it('collapsed range | tab on the middle of paragraph', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'te', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'st', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'te', + format: {}, + }, + { + segmentType: 'Text', + text: '    ', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'st', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, 'Tab', true, false, expectedResult); + }); + + it('collapsed range | shift tab on the end of paragraph', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, 'Tab', true, true, input, 0); + }); + + it('collapsed range | shift tab on the start of paragraph indented', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: { + marginLeft: '40px', + }, + }, + ], + format: {}, + }; + + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: { + marginLeft: '0px', + }, + }, + ], + format: {}, + }; + runTest(input, 'Tab', true, true, expectedResult); + }); + + it('collapsed range | shift tab on the end of paragraph indented', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'Text', + text: '    ', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, 'Tab', true, true, expectedResult); + }); + + it('collapsed range | shift tab on the middle of paragraph indented', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'te    ', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'st', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'te', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'st', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, 'Tab', true, true, expectedResult); + }); + + it('expanded range | tab on paragraph', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: '123', + format: {}, + }, + { + segmentType: 'Text', + text: '456', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + text: '789', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: '123', + format: {}, + }, + { + segmentType: 'Text', + text: ' ', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: '789', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, 'Tab', false, false, expectedResult); + }); + + it('expanded range | shift tab on paragraph', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: '123', + format: {}, + }, + { + segmentType: 'Text', + text: '456', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + text: '789', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: '123', + format: {}, + }, + { + segmentType: 'Text', + text: '    ', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: '789', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, 'Tab', false, true, expectedResult); + }); +}); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts new file mode 100644 index 00000000000..8a14ad04cb1 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts @@ -0,0 +1,305 @@ +import { ContentModelDocument, ContentModelListItem } from 'roosterjs-content-model-types'; +import { handleTabOnList } from '../../../lib/edit/tabUtils/handleTabOnList'; + +describe('handleTabOnList', () => { + function runTest( + model: ContentModelDocument, + listItem: ContentModelListItem, + rawEvent: KeyboardEvent, + expectedReturnValue: boolean + ) { + // Act + const result = handleTabOnList(model, listItem, rawEvent); + + // Assert + expect(result).toBe(expectedReturnValue); + } + + it('should return true when the cursor is at the start of the list item', () => { + // Arrange + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + ], + format: {}, + }; + const listItem: ContentModelListItem = { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }; + const rawEvent = { + shiftKey: false, + preventDefault: () => {}, + } as KeyboardEvent; + const expectedReturnValue = true; + + // Act + runTest(model, listItem, rawEvent, expectedReturnValue); + }); + + it('Outdent - should return true when the cursor is at the start of the list item', () => { + // Arrange + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + ], + format: {}, + }; + const listItem: ContentModelListItem = { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }; + const rawEvent = { + shiftKey: false, + preventDefault: () => {}, + } as KeyboardEvent; + const expectedReturnValue = true; + + // Act + runTest(model, listItem, rawEvent, expectedReturnValue); + }); + + it('should return false when the cursor is not at the start of the list item', () => { + // Arrange + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + ], + format: {}, + }; + const listItem: ContentModelListItem = { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }; + const rawEvent = { + shiftKey: false, + preventDefault: () => {}, + } as KeyboardEvent; + const expectedReturnValue = false; + + // Act + runTest(model, listItem, rawEvent, expectedReturnValue); + }); +}); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts new file mode 100644 index 00000000000..60ee90767e3 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts @@ -0,0 +1,429 @@ +import { handleTabOnParagraph } from '../../../lib/edit/tabUtils/handleTabOnParagraph'; +import { + ContentModelDocument, + ContentModelParagraph, + RangeSelection, +} from 'roosterjs-content-model-types'; + +describe('handleTabOnParagraph', () => { + function runTest( + model: ContentModelDocument, + paragraph: ContentModelParagraph, + rawEvent: KeyboardEvent, + selection: RangeSelection, + expectedReturnValue: boolean + ) { + // Act + const result = handleTabOnParagraph(model, paragraph, rawEvent, selection); + + // Assert + expect(result).toBe(expectedReturnValue); + } + + it('Indent - collapsed range should return true when cursor is at the end', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: false, + }); + const selection = { + type: 'range', + range: { + collapsed: true, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, true); + }); + + it('Outdent - collapsed range should return false when cursor is at the end', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: true, + }); + const selection = { + type: 'range', + range: { + collapsed: true, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, false); + }); + + it('Indent - collapsed range should return true when cursor is at the start', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: false, + }); + const selection = { + type: 'range', + range: { + collapsed: true, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, true); + }); + + it('Outdent - collapsed range should return true when cursor is at the start', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: true, + }); + const selection = { + type: 'range', + range: { + collapsed: true, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, true); + }); + + it('Indent - collapsed range should return true when cursor is at the middle', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'te', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'st', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: false, + }); + const selection = { + type: 'range', + range: { + collapsed: true, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, true); + }); + + it('Outdent - collapsed range should return true when cursor is at the middle', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'te', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'st', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: true, + }); + const selection = { + type: 'range', + range: { + collapsed: true, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, false); + }); + + it('Outdent - Intended - collapsed range should return true when cursor is at the end', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'Text', + text: '    ', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: true, + }); + const selection = { + type: 'range', + range: { + collapsed: true, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, true); + }); + + it('Outdent - Intended - collapsed range should return true when cursor is at the middle', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'te', + format: {}, + }, + { + segmentType: 'Text', + text: '    ', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'st', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: true, + }); + const selection = { + type: 'range', + range: { + collapsed: true, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, true); + }); + + it('Indent - expanded range should return true', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: '123', + format: {}, + }, + { + segmentType: 'Text', + text: '456', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + text: '789', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: false, + }); + const selection = { + type: 'range', + range: { + collapsed: false, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, true); + }); + + it('outdent - expanded range should return true', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: '123', + format: {}, + }, + { + segmentType: 'Text', + text: '456', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + text: '789', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const paragraph = model.blocks[0] as ContentModelParagraph; + const rawEvent = new KeyboardEvent('keydown', { + key: 'Tab', + shiftKey: true, + }); + const selection = { + type: 'range', + range: { + collapsed: false, + }, + } as RangeSelection; + runTest(model, paragraph, rawEvent, selection, true); + }); +}); From 55bc45135274760401f54061999a93380a8cccfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Thu, 22 Feb 2024 13:54:02 -0300 Subject: [PATCH 14/80] fix build --- .../lib/edit/tabUtils/handleTabOnParagraph.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts index e7322b724e6..079a32069e2 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts @@ -1,10 +1,10 @@ import { createSelectionMarker, createText } from 'roosterjs-content-model-dom/lib'; import { setModelIndentation } from 'roosterjs-content-model-api'; -import { +import type { ContentModelDocument, ContentModelParagraph, RangeSelection, -} from 'roosterjs-content-model-types/lib'; +} from 'roosterjs-content-model-types'; const tabSpaces = '    '; const space = ' '; From 6b1b9a8087ce1b8e9d2eb6b7ef324d0ffca28c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Thu, 22 Feb 2024 13:54:53 -0300 Subject: [PATCH 15/80] fix build --- .../lib/edit/tabUtils/handleTabOnList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts index 01c7abc1fd0..e46740c7f53 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts @@ -1,5 +1,5 @@ -import { ContentModelDocument, ContentModelListItem } from 'roosterjs-content-model-types'; import { setModelIndentation } from 'roosterjs-content-model-api'; +import type { ContentModelDocument, ContentModelListItem } from 'roosterjs-content-model-types'; /** * @internal From 34c735a0a05793b1c77d8d6ace7ef45cf27b4be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Thu, 22 Feb 2024 13:57:28 -0300 Subject: [PATCH 16/80] remove /lib --- .../lib/edit/tabUtils/handleTabOnParagraph.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts index 079a32069e2..05dce57db71 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts @@ -1,4 +1,4 @@ -import { createSelectionMarker, createText } from 'roosterjs-content-model-dom/lib'; +import { createSelectionMarker, createText } from 'roosterjs-content-model-dom'; import { setModelIndentation } from 'roosterjs-content-model-api'; import type { ContentModelDocument, From 77f0a7cf60a9e00e7e3f551718c44bb0d905678a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Mon, 26 Feb 2024 15:44:01 -0300 Subject: [PATCH 17/80] disable list features --- .../ContentModelEditorOptionsPlugin.ts | 18 +++++++++++++++++- .../getDefaultContentEditFeatureSettings.ts | 17 ----------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts b/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts index dfb958d69c3..0f85a463869 100644 --- a/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts +++ b/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts @@ -4,6 +4,22 @@ import getDefaultContentEditFeatureSettings from './getDefaultContentEditFeature import SidePanePluginImpl from '../SidePanePluginImpl'; import { SidePaneElementProps } from '../SidePaneElement'; +const listFeatures = { + autoBullet: false, + indentWhenTab: false, + outdentWhenShiftTab: false, + outdentWhenBackspaceOnEmptyFirstLine: false, + outdentWhenEnterOnEmptyLine: false, + mergeInNewLineWhenBackspaceOnFirstChar: false, + maintainListChain: false, + maintainListChainWhenDelete: false, + autoNumberingList: false, + autoBulletList: false, + mergeListOnBackspaceAfterList: false, + outdentWhenAltShiftLeft: false, + indentWhenAltShiftRight: false, +}; + const initialState: BuildInPluginState = { pluginList: { contentEdit: true, @@ -22,7 +38,7 @@ const initialState: BuildInPluginState = { autoFormat: true, announce: true, }, - contentEditFeatures: getDefaultContentEditFeatureSettings(), + contentEditFeatures: { ...getDefaultContentEditFeatureSettings(), ...listFeatures }, defaultFormat: {}, linkTitle: 'Ctrl+Click to follow the link:' + UrlPlaceholder, watermarkText: 'Type content here ...', diff --git a/demo/scripts/controls/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts b/demo/scripts/controls/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts index 0118762dd9e..fe8280631e1 100644 --- a/demo/scripts/controls/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts +++ b/demo/scripts/controls/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts @@ -10,22 +10,5 @@ export default function getDefaultContentEditFeatureSettings(): ContentEditFeatu settings[key] = !allFeatures[key].defaultDisabled; return settings; }, {}), - ...listFeatures, }; } - -const listFeatures = { - autoBullet: false, - indentWhenTab: false, - outdentWhenShiftTab: false, - outdentWhenBackspaceOnEmptyFirstLine: false, - outdentWhenEnterOnEmptyLine: false, - mergeInNewLineWhenBackspaceOnFirstChar: false, - maintainListChain: false, - maintainListChainWhenDelete: false, - autoNumberingList: false, - autoBulletList: false, - mergeListOnBackspaceAfterList: false, - outdentWhenAltShiftLeft: false, - indentWhenAltShiftRight: false, -}; From abfae448b139f3c783ffd51024357eeaa4e7dec5 Mon Sep 17 00:00:00 2001 From: Julia Roldi Date: Mon, 26 Feb 2024 17:22:37 -0300 Subject: [PATCH 18/80] save last focus --- .../roosterjs-content-model-core/lib/coreApi/paste.ts | 2 ++ .../roosterjs-content-model-core/test/coreApi/pasteTest.ts | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts index 44258f6ff84..b1b57ffe5e5 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts @@ -1,3 +1,4 @@ +import { addUndoSnapshot } from './addUndoSnapshot'; import { cloneModel } from '../publicApi/model/cloneModel'; import { convertInlineCss } from '../utils/convertInlineCss'; import { createPasteFragment } from '../utils/paste/createPasteFragment'; @@ -30,6 +31,7 @@ export const paste: Paste = ( pasteType: PasteType = 'normal' ) => { core.api.focus(core); + addUndoSnapshot(core, false); if (clipboardData.modelBeforePaste) { core.api.setContentModel(core, cloneModel(clipboardData.modelBeforePaste, CloneOption)); diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts index fcb8f1e13d4..31286822ad0 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts @@ -1,4 +1,5 @@ import * as addParserF from 'roosterjs-content-model-plugins/lib/paste/utils/addParser'; +import * as addUndoSnapshot from '../../lib/coreApi/addUndoSnapshot'; import * as cloneModel from '../../lib/publicApi/model/cloneModel'; import * as domToContentModel from 'roosterjs-content-model-dom/lib/domToModel/domToContentModel'; import * as ExcelF from 'roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel'; @@ -31,6 +32,7 @@ describe('Paste ', () => { let mockedModel: ContentModelDocument; let mockedMergeModel: ContentModelDocument; let getVisibleViewport: jasmine.Spy; + let addUndoSnapshotSpy: jasmine.Spy; const mockedCloneModel = 'CloneModel' as any; @@ -39,6 +41,7 @@ describe('Paste ', () => { beforeEach(() => { spyOn(domToContentModel, 'domToContentModel').and.callThrough(); spyOn(cloneModel, 'cloneModel').and.returnValue(mockedCloneModel); + addUndoSnapshotSpy = spyOn(addUndoSnapshot, 'addUndoSnapshot'); clipboardData = { types: ['image/png', 'text/html'], text: '', @@ -95,12 +98,14 @@ describe('Paste ', () => { editor.pasteFromClipboard(clipboardData); expect(mockedModel).toEqual(mockedMergeModel); + expect(addUndoSnapshotSpy).toHaveBeenCalledTimes(1); }); it('Execute | As plain text', () => { editor.pasteFromClipboard(clipboardData, 'asPlainText'); expect(mockedModel).toEqual(mockedMergeModel); + expect(addUndoSnapshotSpy).toHaveBeenCalledTimes(1); }); }); From 68643be88063ebe03f7bbb866a3ab067d61c1451 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Tue, 27 Feb 2024 08:45:36 -0800 Subject: [PATCH 19/80] Support adding options for exportContent (#2443) --- .../lib/publicApi/model/exportContent.ts | 16 +++++-- .../test/publicApi/model/exportContentTest.ts | 46 ++++++++++++++++++- .../lib/editor/EditorAdapter.ts | 6 ++- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts index e7a021d55ef..fc3096adbde 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts @@ -3,7 +3,7 @@ import { contentModelToText, createModelToDomContext, } from 'roosterjs-content-model-dom'; -import type { ExportContentMode, IEditor } from 'roosterjs-content-model-types'; +import type { ExportContentMode, IEditor, ModelToDomOption } from 'roosterjs-content-model-types'; /** * Export string content of editor @@ -12,8 +12,13 @@ import type { ExportContentMode, IEditor } from 'roosterjs-content-model-types'; * - HTML: Export HTML content. If there are entities, this will cause EntityOperation event with option = 'replaceTemporaryContent' to get a dehydrated entity * - PlainText: Export plain text content * - PlainTextFast: Export plain text using editor's textContent property directly + * @param options @optional Options for Model to DOM conversion */ -export function exportContent(editor: IEditor, mode: ExportContentMode = 'HTML'): string { +export function exportContent( + editor: IEditor, + mode: ExportContentMode = 'HTML', + options?: ModelToDomOption +): string { if (mode == 'PlainTextFast') { return editor.getDOMHelper().getTextContent(); } else { @@ -25,7 +30,12 @@ export function exportContent(editor: IEditor, mode: ExportContentMode = 'HTML') const doc = editor.getDocument(); const div = doc.createElement('div'); - contentModelToDom(doc, div, model, createModelToDomContext()); + contentModelToDom( + doc, + div, + model, + createModelToDomContext(undefined /*editorContext*/, options) + ); editor.triggerEvent('extractContentWithDom', { clonedRoot: div }, true /*broadcast*/); diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts index d525421cf41..494f2c12033 100644 --- a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts @@ -72,7 +72,51 @@ describe('exportContent', () => { expect(html).toBe(mockedHTML); expect(getContentModelCopySpy).toHaveBeenCalledWith('disconnected'); - expect(createModelToDomContextSpy).toHaveBeenCalledWith(); + expect(createModelToDomContextSpy).toHaveBeenCalledWith(undefined, undefined); + expect(contentModelToDomSpy).toHaveBeenCalledWith( + mockedDoc, + mockedDiv, + mockedModel, + mockedContext + ); + expect(triggerEventSpy).toHaveBeenCalledWith( + 'extractContentWithDom', + { clonedRoot: mockedDiv }, + true + ); + }); + + it('HTML with options', () => { + const mockedModel = 'MODEL' as any; + const getContentModelCopySpy = jasmine + .createSpy('getContentModelCopy') + .and.returnValue(mockedModel); + const mockedHTML = 'HTML'; + const mockedDiv = { + innerHTML: mockedHTML, + } as any; + const mockedDoc = { + createElement: () => mockedDiv, + } as any; + const triggerEventSpy = jasmine.createSpy('triggerEvent'); + const editor: IEditor = { + getContentModelCopy: getContentModelCopySpy, + getDocument: () => mockedDoc, + triggerEvent: triggerEventSpy, + } as any; + const contentModelToDomSpy = spyOn(contentModelToDom, 'contentModelToDom'); + const mockedContext = 'CONTEXT' as any; + const createModelToDomContextSpy = spyOn( + createModelToDomContext, + 'createModelToDomContext' + ).and.returnValue(mockedContext); + const mockedOptions = 'OPTIONS' as any; + + const html = exportContent(editor, 'HTML', mockedOptions); + + expect(html).toBe(mockedHTML); + expect(getContentModelCopySpy).toHaveBeenCalledWith('disconnected'); + expect(createModelToDomContextSpy).toHaveBeenCalledWith(undefined, mockedOptions); expect(contentModelToDomSpy).toHaveBeenCalledWith( mockedDoc, mockedDiv, diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts index 95b1a64d988..162a03cae74 100644 --- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts +++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts @@ -343,7 +343,11 @@ export class EditorAdapter extends Editor implements ILegacyEditor { * @returns HTML string representing current editor content */ getContent(mode: GetContentMode | CompatibleGetContentMode = GetContentMode.CleanHTML): string { - return exportContent(this, GetContentModeMap[mode]); + return exportContent( + this, + GetContentModeMap[mode], + this.getCore().modelToDomSettings.customized + ); } /** From 1f788023d415ab93f7f37566c43a45bf75d08e30 Mon Sep 17 00:00:00 2001 From: Bryan Valverde U Date: Tue, 27 Feb 2024 11:17:51 -0600 Subject: [PATCH 20/80] Set RootFontSize to SanitizingContext (#2445) * init * fix Build * Check once if it is Rem Unit --- .../lib/coreApi/createEditorContext.ts | 15 +++------------ .../publicApi/model/createModelFromHtml.ts | 2 +- .../createDomToModelContextForSanitizing.ts | 3 +++ .../utils/getRootComputedStyleForContext.ts | 14 ++++++++++++++ .../lib/utils/paste/mergePasteContent.ts | 1 + .../model/createModelFromHtmlTest.ts | 6 +++++- ...reateDomToModelContextForSanitizingTest.ts | 5 ++++- .../test/utils/paste/mergePasteContentTest.ts | 4 ++++ .../segment/fontSizeFormatHandler.ts | 13 ++++++------- .../segment/fontSizeFormatHandlerTest.ts | 19 +++++++++++++++++++ 10 files changed, 60 insertions(+), 22 deletions(-) create mode 100644 packages-content-model/roosterjs-content-model-core/lib/utils/getRootComputedStyleForContext.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts index bf0a54b7e33..0895824658a 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts @@ -1,7 +1,5 @@ -import { parseValueWithUnit } from 'roosterjs-content-model-dom'; -import type { EditorContext, CreateEditorContext, EditorCore } from 'roosterjs-content-model-types'; - -const DefaultRootFontSize = 16; +import { getRootComputedStyleForContext } from '../utils/getRootComputedStyleForContext'; +import type { EditorContext, CreateEditorContext } from 'roosterjs-content-model-types'; /** * @internal @@ -19,8 +17,7 @@ export const createEditorContext: CreateEditorContext = (core, saveIndex) => { allowCacheElement: true, domIndexer: saveIndex ? cache.domIndexer : undefined, zoomScale: domHelper.calculateZoomScale(), - rootFontSize: - parseValueWithUnit(getRootComputedStyle(core)?.fontSize) || DefaultRootFontSize, + ...getRootComputedStyleForContext(contentDiv.ownerDocument), }; checkRootRtl(contentDiv, context); @@ -35,9 +32,3 @@ function checkRootRtl(element: HTMLElement, context: EditorContext) { context.isRootRtl = true; } } - -function getRootComputedStyle(core: EditorCore) { - const document = core.contentDiv.ownerDocument; - const rootComputedStyle = document.defaultView?.getComputedStyle(document.documentElement); - return rootComputedStyle; -} diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/createModelFromHtml.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/createModelFromHtml.ts index d9b8bb2f76d..e7796d492bf 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/createModelFromHtml.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/createModelFromHtml.ts @@ -26,7 +26,7 @@ export function createModelFromHtml( : null; if (doc?.body) { - const context = createDomToModelContextForSanitizing(defaultSegmentFormat, options); + const context = createDomToModelContextForSanitizing(doc, defaultSegmentFormat, options); const cssRules = doc ? retrieveCssRules(doc) : []; convertInlineCss(doc, cssRules); diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/createDomToModelContextForSanitizing.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/createDomToModelContextForSanitizing.ts index ec0471f3098..fa101508aab 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/createDomToModelContextForSanitizing.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/createDomToModelContextForSanitizing.ts @@ -2,6 +2,7 @@ import { containerSizeFormatParser } from '../override/containerSizeFormatParser import { createDomToModelContext } from 'roosterjs-content-model-dom'; import { createPasteEntityProcessor } from '../override/pasteEntityProcessor'; import { createPasteGeneralProcessor } from '../override/pasteGeneralProcessor'; +import { getRootComputedStyleForContext } from './getRootComputedStyleForContext'; import { pasteBlockEntityParser } from '../override/pasteCopyBlockEntityParser'; import { pasteDisplayFormatParser } from '../override/pasteDisplayFormatParser'; import { pasteTextProcessor } from '../override/pasteTextProcessor'; @@ -26,6 +27,7 @@ const DefaultSanitizingOption: DomToModelOptionForSanitizing = { * @internal */ export function createDomToModelContextForSanitizing( + document: Document, defaultFormat?: ContentModelSegmentFormat, defaultOption?: DomToModelOption, additionalSanitizingOption?: DomToModelOptionForSanitizing @@ -38,6 +40,7 @@ export function createDomToModelContextForSanitizing( return createDomToModelContext( { defaultFormat, + ...getRootComputedStyleForContext(document), }, defaultOption, { diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/getRootComputedStyleForContext.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/getRootComputedStyleForContext.ts new file mode 100644 index 00000000000..aee5926837c --- /dev/null +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/getRootComputedStyleForContext.ts @@ -0,0 +1,14 @@ +import { parseValueWithUnit } from 'roosterjs-content-model-dom'; +import type { EditorContext } from 'roosterjs-content-model-types'; + +const DefaultRootFontSize = 16; + +/** + * @internal + */ +export function getRootComputedStyleForContext( + document: Document +): Pick { + const rootComputedStyle = document.defaultView?.getComputedStyle(document.documentElement); + return { rootFontSize: parseValueWithUnit(rootComputedStyle?.fontSize) || DefaultRootFontSize }; +} diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts index 5ba9d88bed4..f77fcfdd7e2 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts @@ -43,6 +43,7 @@ export function mergePasteContent( (model, context) => { const selectedSegment = getSelectedSegments(model, true /*includeFormatHolder*/)[0]; const domToModelContext = createDomToModelContextForSanitizing( + core.contentDiv.ownerDocument, undefined /*defaultFormat*/, core.domToModelSettings.customized, domToModelOption diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts index b700d4de8e4..6b5fa650314 100644 --- a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts @@ -131,7 +131,11 @@ describe('createModelFromHtml', () => { mockedContext ); expect(createContextSpy).toHaveBeenCalledTimes(1); - expect(createContextSpy).toHaveBeenCalledWith(mockedDefaultSegmentFormat, mockedOptions); + expect(createContextSpy).toHaveBeenCalledWith( + mockedDoc, + mockedDefaultSegmentFormat, + mockedOptions + ); expect(domToContentModelSpy).toHaveBeenCalledWith('BODY' as any, mockedContext); expect(retrieveCssRulesSpy).toHaveBeenCalledWith(mockedDoc); expect(convertInlineCssSpy).toHaveBeenCalledWith(mockedDoc, mockedRules); diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/createDomToModelContextForSanitizingTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/createDomToModelContextForSanitizingTest.ts index f99a0bc17e3..3b434723cb5 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/createDomToModelContextForSanitizingTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/createDomToModelContextForSanitizingTest.ts @@ -43,12 +43,13 @@ describe('createDomToModelContextForSanitizing', () => { }); it('no options', () => { - const context = createDomToModelContextForSanitizing(); + const context = createDomToModelContextForSanitizing(document); expect(context).toBe(mockedResult); expect(createDomToModelContextSpy).toHaveBeenCalledWith( { defaultFormat: undefined, + rootFontSize: 16, }, undefined, { @@ -77,6 +78,7 @@ describe('createDomToModelContextForSanitizing', () => { const mockedAdditionalOption = { a: 'b' } as any; const context = createDomToModelContextForSanitizing( + document, mockedDefaultFormat, mockedOption, mockedAdditionalOption @@ -91,6 +93,7 @@ describe('createDomToModelContextForSanitizing', () => { expect(createDomToModelContextSpy).toHaveBeenCalledWith( { defaultFormat: mockedDefaultFormat, + rootFontSize: 16, }, mockedOption, { diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts index 0bf16378888..3d7a63df650 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts @@ -51,6 +51,9 @@ describe('mergePasteContent', () => { formatContentModel, }, domToModelSettings: {}, + contentDiv: { + ownerDocument: document, + }, } as any; }); @@ -399,6 +402,7 @@ describe('mergePasteContent', () => { mergeTable: false, }); expect(createDomToModelContextSpy).toHaveBeenCalledWith( + document, undefined, mockedDomToModelOptions, mockedDefaultDomToModelOptions diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/fontSizeFormatHandler.ts b/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/fontSizeFormatHandler.ts index 61434860976..dbd33cd2223 100644 --- a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/fontSizeFormatHandler.ts +++ b/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/fontSizeFormatHandler.ts @@ -50,6 +50,7 @@ function normalizeFontSize( context: EditorContext ): string | undefined { const knownFontSize = KnownFontSizes[fontSize]; + const isRemUnit = fontSize.endsWith('rem'); if (knownFontSize) { return knownFontSize; @@ -58,16 +59,14 @@ function normalizeFontSize( fontSize == 'larger' || fontSize.endsWith('em') || fontSize.endsWith('%') || - fontSize.endsWith('rem') + isRemUnit ) { - if (!contextFont) { + if (!contextFont && !isRemUnit) { return undefined; } else { - const existingFontSize = parseValueWithUnit( - contextFont, - fontSize.endsWith('rem') ? context.rootFontSize : undefined /*element*/, - 'px' - ); + const existingFontSize = isRemUnit + ? context.rootFontSize + : parseValueWithUnit(contextFont); if (existingFontSize) { switch (fontSize) { diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/fontSizeFormatHandlerTest.ts b/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/fontSizeFormatHandlerTest.ts index 3c2b55781bd..8d263d52b09 100644 --- a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/fontSizeFormatHandlerTest.ts +++ b/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/fontSizeFormatHandlerTest.ts @@ -167,6 +167,25 @@ describe('fontSizeFormatHandler.parse', () => { expect(format.fontSize).toBe('8px'); }); + + it('rem handling', () => { + div.style.fontSize = '0.75rem'; + context.rootFontSize = 16; + context.segmentFormat.fontSize = '12pt'; + + fontSizeFormatHandler.parse(format, div, context, {}); + + expect(format.fontSize).toBe('12px'); + }); + + it('rem handling without segment format', () => { + div.style.fontSize = '0.75rem'; + context.rootFontSize = 16; + + fontSizeFormatHandler.parse(format, div, context, {}); + + expect(format.fontSize).toBe('12px'); + }); }); describe('fontSizeFormatHandler.apply', () => { From 44f6c07bde7d1f0c4b2ebd9d9dd4b2742ec5d597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 27 Feb 2024 14:42:01 -0300 Subject: [PATCH 21/80] refactor --- .../lib/edit/keyboardTab.ts | 28 +- .../lib/edit/tabUtils/handleTabOnList.ts | 20 +- .../lib/edit/tabUtils/handleTabOnParagraph.ts | 43 +- .../test/edit/keyboardTabTest.ts | 437 +++++++++++++++++- .../test/edit/tabUtils/handleTabOnListTest.ts | 5 +- 5 files changed, 499 insertions(+), 34 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts index 049285b3541..04f4e25e0dc 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts @@ -1,11 +1,11 @@ import { getOperationalBlocks, isBlockGroupOfType } from 'roosterjs-content-model-core'; import { handleTabOnList } from './tabUtils/handleTabOnList'; import { handleTabOnParagraph } from './tabUtils/handleTabOnParagraph'; +import { setModelIndentation } from 'roosterjs-content-model-api'; import type { ContentModelDocument, ContentModelListItem, IEditor, - RangeSelection, } from 'roosterjs-content-model-types'; /** @@ -15,11 +15,9 @@ export function keyboardTab(editor: IEditor, rawEvent: KeyboardEvent) { const selection = editor.getDOMSelection(); if (selection?.type == 'range') { - editor.takeSnapshot(); - editor.formatContentModel( - (model, _context) => { - return handleTab(model, rawEvent, selection); + model => { + return handleTab(model, rawEvent); }, { apiName: 'handleTabKey', @@ -30,15 +28,19 @@ export function keyboardTab(editor: IEditor, rawEvent: KeyboardEvent) { } } -function handleTab( - model: ContentModelDocument, - rawEvent: KeyboardEvent, - selection: RangeSelection -) { +/** + * If multiple blocks are selected, indent or outdent the selected blocks with setModelIndentation. + * If only one block is selected, call handleTabOnParagraph or handleTabOnList to handle the tab key. + */ +function handleTab(model: ContentModelDocument, rawEvent: KeyboardEvent) { const blocks = getOperationalBlocks(model, ['ListItem'], ['TableCell']); - const block = blocks[0].block; - if (block.blockType === 'Paragraph') { - return handleTabOnParagraph(model, block, rawEvent, selection); + const block = blocks.length > 0 ? blocks[0].block : undefined; + if (blocks.length > 1) { + setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent'); + rawEvent.preventDefault(); + return true; + } else if (block?.blockType === 'Paragraph') { + return handleTabOnParagraph(model, block, rawEvent); } else if (isBlockGroupOfType(block, 'ListItem')) { return handleTabOnList(model, block, rawEvent); } diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts index e46740c7f53..1f2cbf130ff 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts @@ -1,7 +1,10 @@ +import { handleTabOnParagraph } from './handleTabOnParagraph'; import { setModelIndentation } from 'roosterjs-content-model-api'; import type { ContentModelDocument, ContentModelListItem } from 'roosterjs-content-model-types'; /** + * 1. When the selection is collapsed and the cursor is at start of a list item, call setModelIndentation. + * 2. Otherwise call handleTabOnParagraph. * @internal */ export function handleTabOnList( @@ -9,12 +12,18 @@ export function handleTabOnList( listItem: ContentModelListItem, rawEvent: KeyboardEvent ) { - if (isMarkerAtStartOfBlock(listItem)) { + const selectedParagraph = findSelectedParagraph(listItem); + if ( + !isMarkerAtStartOfBlock(listItem) && + selectedParagraph.length == 1 && + selectedParagraph[0].blockType === 'Paragraph' + ) { + return handleTabOnParagraph(model, selectedParagraph[0], rawEvent); + } else { setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent'); rawEvent.preventDefault(); return true; } - return false; } function isMarkerAtStartOfBlock(listItem: ContentModelListItem) { @@ -23,3 +32,10 @@ function isMarkerAtStartOfBlock(listItem: ContentModelListItem) { listItem.blocks[0].segments[0].segmentType == 'SelectionMarker' ); } + +function findSelectedParagraph(listItem: ContentModelListItem) { + return listItem.blocks.filter( + block => + block.blockType == 'Paragraph' && block.segments.some(segment => segment.isSelected) + ); +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts index 05dce57db71..684ef48e70e 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts @@ -1,30 +1,38 @@ import { createSelectionMarker, createText } from 'roosterjs-content-model-dom'; import { setModelIndentation } from 'roosterjs-content-model-api'; -import type { - ContentModelDocument, - ContentModelParagraph, - RangeSelection, -} from 'roosterjs-content-model-types'; +import type { ContentModelDocument, ContentModelParagraph } from 'roosterjs-content-model-types'; const tabSpaces = '    '; const space = ' '; /** * @internal + The handleTabOnParagraph function will handle the tab key in following scenarios: + * 1. When the selection is collapsed and the cursor is at the end of a paragraph, add 4 spaces. + * 2. When the selection is collapsed and the cursor is at the start of a paragraph, call setModelIndention function to indent the whole paragraph + * 3. When the selection is collapsed and the cursor is at the middle of a paragraph, add 4 spaces. + * 4. When the selection is not collapsed, replace the selected range with a single space. + * 5. When the selection is not collapsed, but all segments are selected, call setModelIndention function to indent the whole paragraph + The handleTabOnParagraph function will handle the shift + tab key in a indented paragraph in following scenarios: + * 1. When the selection is collapsed and the cursor is at the end of a paragraph, remove 4 spaces. + * 2. When the selection is collapsed and the cursor is at the start of a paragraph, call setModelIndention function to outdent the whole paragraph + * 3. When the selection is collapsed and the cursor is at the middle of a paragraph, remove 4 spaces. + * 4. When the selection is not collapsed, replace the selected range with a 4 space. + * 5. When the selection is not collapsed, but all segments are selected, call setModelIndention function to outdent the whole paragraph */ export function handleTabOnParagraph( model: ContentModelDocument, paragraph: ContentModelParagraph, - rawEvent: KeyboardEvent, - selection: RangeSelection + rawEvent: KeyboardEvent ) { - if (paragraph.segments[0].segmentType === 'SelectionMarker' && selection.range.collapsed) { + const selectedSegments = paragraph.segments.filter(segment => segment.isSelected); + const isCollapsed = + selectedSegments.length === 1 && selectedSegments[0].segmentType === 'SelectionMarker'; + const isAllSelected = paragraph.segments.every(segment => segment.isSelected); + if ((paragraph.segments[0].segmentType === 'SelectionMarker' && isCollapsed) || isAllSelected) { setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent'); } else { - const markerIndex = paragraph.segments.findIndex( - segment => segment.segmentType === 'SelectionMarker' - ); - if (!selection.range.collapsed) { + if (!isCollapsed) { let firstSelectedSegmentIndex: number | undefined = undefined; let lastSelectedSegmentIndex: number | undefined = undefined; @@ -38,7 +46,10 @@ export function handleTabOnParagraph( }); if (firstSelectedSegmentIndex && lastSelectedSegmentIndex) { const firstSelectedSegment = paragraph.segments[firstSelectedSegmentIndex]; - const spaceText = createText(rawEvent.shiftKey ? tabSpaces : space); + const spaceText = createText( + rawEvent.shiftKey ? tabSpaces : space, + firstSelectedSegment.format + ); const marker = createSelectionMarker(firstSelectedSegment.format); paragraph.segments.splice( firstSelectedSegmentIndex, @@ -50,8 +61,12 @@ export function handleTabOnParagraph( return false; } } else { + const markerIndex = paragraph.segments.findIndex( + segment => segment.segmentType === 'SelectionMarker' + ); if (!rawEvent.shiftKey) { - const tabText = createText(tabSpaces); + const markerFormat = paragraph.segments[markerIndex].format; + const tabText = createText(tabSpaces, markerFormat); paragraph.segments.splice(markerIndex, 0, tabText); } else { const tabText = paragraph.segments[markerIndex - 1]; diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts index bc218d57e84..bfc2cf68bd4 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts @@ -413,7 +413,7 @@ describe('keyboardTab', () => { ], format: {}, }; - runTest(model, undefined, false, false); + runTest(model, undefined, false, true); }); it('tab on the start second item on the list', () => { @@ -665,7 +665,7 @@ describe('keyboardTab', () => { ], format: {}, }; - runTest(model, undefined, false, false); + runTest(model, undefined, false, true); }); it('shift tab on empty list item', () => { @@ -1427,4 +1427,437 @@ describe('keyboardTab - handleTabOnParagraph -', () => { }; runTest(input, 'Tab', false, true, expectedResult); }); + + it('expanded range | multiple paragraphs', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: {}, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: {}, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: { + marginLeft: '40px', + }, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: { + marginLeft: '40px', + }, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: { + marginLeft: '40px', + }, + }, + ], + format: {}, + }; + + runTest(input, 'Tab', false, false, expectedResult); + }); + + it('expanded range | outdent paragraphs', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: { + marginLeft: '40px', + }, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: { + marginLeft: '40px', + }, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: { + marginLeft: '40px', + }, + }, + ], + format: {}, + }; + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: { + marginLeft: '0px', + }, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: { + marginLeft: '0px', + }, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + isSelected: true, + }, + ], + format: { + marginLeft: '0px', + }, + }, + ], + format: {}, + }; + runTest(input, 'Tab', false, true, expectedResult); + }); + + it('collapsed range | middle list', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'te', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'st', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + }, + dataset: {}, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: { + marginLeft: '0px', + }, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: {}, + dataset: {}, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: { + marginLeft: '0px', + }, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: {}, + dataset: {}, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: { + marginLeft: '0px', + }, + }, + ], + format: {}, + }; + const expectedResult: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'te', + format: {}, + }, + { + segmentType: 'Text', + text: '    ', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'st', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + }, + dataset: {}, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: { + marginLeft: '0px', + }, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: {}, + dataset: {}, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: { + marginLeft: '0px', + }, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: {}, + dataset: {}, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: { + marginLeft: '0px', + }, + }, + ], + format: {}, + }; + runTest(input, 'Tab', true, false, expectedResult); + }); }); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts index 8a14ad04cb1..6f457fb0ae6 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts @@ -207,7 +207,7 @@ describe('handleTabOnList', () => { runTest(model, listItem, rawEvent, expectedReturnValue); }); - it('should return false when the cursor is not at the start of the list item', () => { + it('should return true when the cursor is not at the start of the list item', () => { // Arrange const model: ContentModelDocument = { blockGroupType: 'Document', @@ -297,9 +297,8 @@ describe('handleTabOnList', () => { shiftKey: false, preventDefault: () => {}, } as KeyboardEvent; - const expectedReturnValue = false; // Act - runTest(model, listItem, rawEvent, expectedReturnValue); + runTest(model, listItem, rawEvent, true); }); }); From a692c399a1e3469ad7b21d6781b1e40bce528bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 27 Feb 2024 14:56:29 -0300 Subject: [PATCH 22/80] fix build --- .../test/edit/tabUtils/handleTabOnParagraphTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts index 60ee90767e3..9f79f57b793 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts @@ -14,7 +14,7 @@ describe('handleTabOnParagraph', () => { expectedReturnValue: boolean ) { // Act - const result = handleTabOnParagraph(model, paragraph, rawEvent, selection); + const result = handleTabOnParagraph(model, paragraph, rawEvent); // Assert expect(result).toBe(expectedReturnValue); From 2c2e833ff57e4adc53f0dfa854388c83a03709ec Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Tue, 27 Feb 2024 10:17:49 -0800 Subject: [PATCH 23/80] Fix #2435 (#2442) --- .../lib/utils/sanitizeElement.ts | 8 ++++++- .../test/utils/sanitizeElementTest.ts | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/sanitizeElement.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/sanitizeElement.ts index 20f75114fa3..80402409eae 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/sanitizeElement.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/sanitizeElement.ts @@ -288,7 +288,13 @@ export function sanitizeElement( if (sanitizedElement) { for (let child = element.firstChild; child; child = child.nextSibling) { const newChild = isNodeOfType(child, 'ELEMENT_NODE') - ? sanitizeElement(child, allowedTags, disallowedTags, styleSanitizers) + ? sanitizeElement( + child, + allowedTags, + disallowedTags, + styleSanitizers, + attributeSanitizers + ) : isNodeOfType(child, 'TEXT_NODE') ? child.cloneNode() : null; diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/sanitizeElementTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/sanitizeElementTest.ts index c24c35e6749..526f03f84c6 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/sanitizeElementTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/sanitizeElementTest.ts @@ -143,6 +143,7 @@ describe('sanitizeElement', () => { }); expect(result!.outerHTML).toBe('
'); + expect(sanitizerSpy).toHaveBeenCalledTimes(1); expect(sanitizerSpy).toHaveBeenCalledWith('a', 'div'); }); @@ -157,6 +158,27 @@ describe('sanitizeElement', () => { expect(result!.outerHTML).toBe('
'); }); + + it('attributeCallbacks with child element', () => { + const element = document.createElement('div'); + const child = document.createElement('span'); + const sanitizerSpy = jasmine + .createSpy('sanitizer') + .and.callFake((value: string) => value + value); + + element.id = 'a'; + child.id = 'b'; + element.appendChild(child); + + const result = sanitizeElement(element, AllowedTags, DisallowedTags, undefined, { + id: sanitizerSpy, + }); + + expect(result!.outerHTML).toBe('
'); + expect(sanitizerSpy).toHaveBeenCalledTimes(2); + expect(sanitizerSpy).toHaveBeenCalledWith('a', 'div'); + expect(sanitizerSpy).toHaveBeenCalledWith('b', 'span'); + }); }); describe('sanitizeHtml', () => { From a04f9f0eefdaef48826716f55a26441b78070978 Mon Sep 17 00:00:00 2001 From: Julia Roldi Date: Tue, 27 Feb 2024 19:52:14 -0300 Subject: [PATCH 24/80] format with content model --- .../lib/coreApi/formatContentModel.ts | 4 +--- .../lib/coreApi/paste.ts | 2 -- .../lib/editor/SnapshotsManagerImpl.ts | 20 ++++++++++++------- .../test/coreApi/formatContentModelTest.ts | 10 +++++----- .../test/coreApi/pasteTest.ts | 6 +----- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts index d7d1d2c5f6e..3e01ee49079 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts @@ -42,9 +42,7 @@ export const formatContentModel: FormatContentModel = (core, formatter, options) if (shouldAddSnapshot) { core.undo.isNested = true; - if (core.undo.snapshotsManager.hasNewContent || entityStates) { - core.api.addUndoSnapshot(core, !!canUndoByBackspace); - } + core.api.addUndoSnapshot(core, !!canUndoByBackspace, entityStates); } try { diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts index b1b57ffe5e5..44258f6ff84 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts @@ -1,4 +1,3 @@ -import { addUndoSnapshot } from './addUndoSnapshot'; import { cloneModel } from '../publicApi/model/cloneModel'; import { convertInlineCss } from '../utils/convertInlineCss'; import { createPasteFragment } from '../utils/paste/createPasteFragment'; @@ -31,7 +30,6 @@ export const paste: Paste = ( pasteType: PasteType = 'normal' ) => { core.api.focus(core); - addUndoSnapshot(core, false); if (clipboardData.modelBeforePaste) { core.api.setContentModel(core, cloneModel(clipboardData.modelBeforePaste, CloneOption)); diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts b/packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts index cfe4d25f59e..cb00a28ddb1 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts @@ -47,13 +47,9 @@ class SnapshotsManagerImpl implements SnapshotsManager { addSnapshot(snapshot: Snapshot, isAutoCompleteSnapshot: boolean): void { const currentSnapshot = this.snapshots.snapshots[this.snapshots.currentIndex]; - const isSameSnapshot = - currentSnapshot && - currentSnapshot.html == snapshot.html && - !currentSnapshot.entityStates && - !snapshot.entityStates; + const addSnapshot = !currentSnapshot || shouldAddSnapshot(currentSnapshot, snapshot); - if (this.snapshots.currentIndex < 0 || !currentSnapshot || !isSameSnapshot) { + if (this.snapshots.currentIndex < 0 || addSnapshot) { this.clearRedo(); this.snapshots.snapshots.push(snapshot); this.snapshots.currentIndex++; @@ -82,7 +78,7 @@ class SnapshotsManagerImpl implements SnapshotsManager { if (isAutoCompleteSnapshot) { this.snapshots.autoCompleteIndex = this.snapshots.currentIndex; } - } else if (isSameSnapshot) { + } else if (!addSnapshot) { // replace the currentSnapshot's metadata so the selection is updated this.snapshots.snapshots.splice(this.snapshots.currentIndex, 1, snapshot); } @@ -129,3 +125,13 @@ class SnapshotsManagerImpl implements SnapshotsManager { export function createSnapshotsManager(snapshots?: Snapshots): SnapshotsManager { return new SnapshotsManagerImpl(snapshots); } + +function shouldAddSnapshot(currentSnapshot: Snapshot, snapshot: Snapshot) { + return ( + currentSnapshot.html !== snapshot.html || + (currentSnapshot.entityStates && + snapshot.entityStates && + currentSnapshot.entityStates !== snapshot.entityStates) || + (!currentSnapshot.entityStates && snapshot.entityStates) + ); +} diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts index 40a382043e4..884709a8055 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts @@ -93,7 +93,7 @@ describe('formatContentModel', () => { newImages: [], }); expect(createContentModel).toHaveBeenCalledTimes(1); - expect(addUndoSnapshot).toHaveBeenCalledTimes(1); + expect(addUndoSnapshot).toHaveBeenCalledTimes(2); expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, undefined); expect(setContentModel).toHaveBeenCalledTimes(1); expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined); @@ -725,7 +725,7 @@ describe('formatContentModel', () => { expect(callback).toHaveBeenCalledTimes(1); expect(addUndoSnapshot).toHaveBeenCalledTimes(2); - expect(addUndoSnapshot).toHaveBeenCalledWith(core, false); + expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, undefined); expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, undefined); expect(setContentModel).toHaveBeenCalledTimes(1); expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined); @@ -750,7 +750,7 @@ describe('formatContentModel', () => { expect(callback).toHaveBeenCalledTimes(1); expect(addUndoSnapshot).toHaveBeenCalledTimes(2); - expect(addUndoSnapshot).toHaveBeenCalledWith(core, false); + expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, mockedEntityState); expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, mockedEntityState); expect(setContentModel).toHaveBeenCalledTimes(1); expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined); @@ -771,7 +771,7 @@ describe('formatContentModel', () => { formatContentModel(core, callback); expect(callback).toHaveBeenCalledTimes(1); - expect(addUndoSnapshot).toHaveBeenCalledTimes(1); + expect(addUndoSnapshot).toHaveBeenCalledTimes(2); expect(addUndoSnapshot).toHaveBeenCalledWith(core, true, undefined); expect(setContentModel).toHaveBeenCalledTimes(1); expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined); @@ -800,7 +800,7 @@ describe('formatContentModel', () => { formatContentModel(core, callback); expect(callback).toHaveBeenCalledTimes(1); - expect(addUndoSnapshot).toHaveBeenCalledTimes(1); + expect(addUndoSnapshot).toHaveBeenCalledTimes(2); expect(addUndoSnapshot).toHaveBeenCalledWith(core, true, undefined); expect(setContentModel).toHaveBeenCalledTimes(1); expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined); diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts index 31286822ad0..fcf2e11f8e4 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts @@ -1,5 +1,4 @@ import * as addParserF from 'roosterjs-content-model-plugins/lib/paste/utils/addParser'; -import * as addUndoSnapshot from '../../lib/coreApi/addUndoSnapshot'; import * as cloneModel from '../../lib/publicApi/model/cloneModel'; import * as domToContentModel from 'roosterjs-content-model-dom/lib/domToModel/domToContentModel'; import * as ExcelF from 'roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel'; @@ -32,7 +31,6 @@ describe('Paste ', () => { let mockedModel: ContentModelDocument; let mockedMergeModel: ContentModelDocument; let getVisibleViewport: jasmine.Spy; - let addUndoSnapshotSpy: jasmine.Spy; const mockedCloneModel = 'CloneModel' as any; @@ -41,7 +39,7 @@ describe('Paste ', () => { beforeEach(() => { spyOn(domToContentModel, 'domToContentModel').and.callThrough(); spyOn(cloneModel, 'cloneModel').and.returnValue(mockedCloneModel); - addUndoSnapshotSpy = spyOn(addUndoSnapshot, 'addUndoSnapshot'); + clipboardData = { types: ['image/png', 'text/html'], text: '', @@ -98,14 +96,12 @@ describe('Paste ', () => { editor.pasteFromClipboard(clipboardData); expect(mockedModel).toEqual(mockedMergeModel); - expect(addUndoSnapshotSpy).toHaveBeenCalledTimes(1); }); it('Execute | As plain text', () => { editor.pasteFromClipboard(clipboardData, 'asPlainText'); expect(mockedModel).toEqual(mockedMergeModel); - expect(addUndoSnapshotSpy).toHaveBeenCalledTimes(1); }); }); From ac25fcf4fb1b381e6c20edf2137c6f1d54369a0c Mon Sep 17 00:00:00 2001 From: Julia Roldi Date: Tue, 27 Feb 2024 19:54:34 -0300 Subject: [PATCH 25/80] remove empty line --- .../roosterjs-content-model-core/test/coreApi/pasteTest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts index fcf2e11f8e4..fcb8f1e13d4 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts @@ -39,7 +39,6 @@ describe('Paste ', () => { beforeEach(() => { spyOn(domToContentModel, 'domToContentModel').and.callThrough(); spyOn(cloneModel, 'cloneModel').and.returnValue(mockedCloneModel); - clipboardData = { types: ['image/png', 'text/html'], text: '', From b8fd5f361f73db3334d68493ef88705dd00bdf07 Mon Sep 17 00:00:00 2001 From: florian-msft <87671048+florian-msft@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:18:31 -0800 Subject: [PATCH 26/80] Split contentDiv into physicalRoot and logicalRoot (#2441) * Split contentDiv into physicalRoot and logicalRoot * Fix failing test * Fixed failing test harnesses * Updated in response to PR comments * Fix tests --- .../lib/coreApi/addUndoSnapshot.ts | 4 +- .../lib/coreApi/attachDomEvent.ts | 4 +- .../lib/coreApi/createContentModel.ts | 2 +- .../lib/coreApi/createEditorContext.ts | 6 +- .../lib/coreApi/focus.ts | 2 +- .../lib/coreApi/getDOMSelection.ts | 4 +- .../lib/coreApi/getVisibleViewport.ts | 4 +- .../lib/coreApi/hasFocus.ts | 4 +- .../lib/coreApi/paste.ts | 2 +- .../lib/coreApi/setContentModel.ts | 4 +- .../lib/coreApi/setDOMSelection.ts | 4 +- .../lib/coreApi/switchShadowEdit.ts | 4 +- .../lib/editor/Editor.ts | 4 +- .../lib/editor/createEditorCore.ts | 3 +- .../lib/utils/createSnapshotSelection.ts | 12 ++-- .../lib/utils/paste/mergePasteContent.ts | 2 +- .../lib/utils/restoreSnapshotColors.ts | 2 +- .../lib/utils/restoreSnapshotHTML.ts | 10 ++-- .../lib/utils/restoreSnapshotSelection.ts | 12 ++-- .../test/coreApi/addUndoSnapshotTest.ts | 3 +- .../test/coreApi/attachDomEventTest.ts | 3 +- .../test/coreApi/createContentModelTest.ts | 6 +- .../test/coreApi/createEditorContextTest.ts | 15 +++-- .../test/coreApi/focusTest.ts | 3 +- .../test/coreApi/getDOMSelectionTest.ts | 19 +++--- .../test/coreApi/getVisibleViewportTest.ts | 6 +- .../test/coreApi/hasFocusTest.ts | 16 +++-- .../test/coreApi/setContentModelTest.ts | 3 +- .../test/coreApi/setDOMSelectionTest.ts | 3 +- .../test/coreApi/switchShadowEditTest.ts | 5 +- .../test/coreApi/triggerEventTest.ts | 3 +- .../test/editor/EditorTest.ts | 3 +- .../test/editor/createEditorCoreTest.ts | 6 +- .../test/utils/createSnapshotSelectionTest.ts | 9 ++- .../test/utils/paste/mergePasteContentTest.ts | 2 +- .../test/utils/restoreSnapshotColorsTest.ts | 3 +- .../test/utils/restoreSnapshotHTMLTest.ts | 3 +- .../utils/restoreSnapshotSelectionTest.ts | 3 +- .../lib/editor/EditorCore.ts | 11 +++- .../lib/editor/EditorAdapter.ts | 60 ++++++++++--------- 40 files changed, 162 insertions(+), 112 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts index 1e5f6e33724..a4d44d45983 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts @@ -11,13 +11,13 @@ import type { AddUndoSnapshot, Snapshot } from 'roosterjs-content-model-types'; * when undo/redo to this snapshot */ export const addUndoSnapshot: AddUndoSnapshot = (core, canUndoByBackspace, entityStates) => { - const { lifecycle, contentDiv, undo } = core; + const { lifecycle, physicalRoot, undo } = core; let snapshot: Snapshot | null = null; if (!lifecycle.shadowEditFragment) { // Need to create snapshot selection before retrieve innerHTML since HTML can be changed during creating selection when normalize table const selection = createSnapshotSelection(core); - const html = contentDiv.innerHTML; + const html = physicalRoot.innerHTML; snapshot = { html, diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts index b93b02f5168..b32a19877e8 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts @@ -30,10 +30,10 @@ export const attachDomEvent: AttachDomEvent = (core, eventMap) => { } }; - core.contentDiv.addEventListener(eventName, onEvent); + core.logicalRoot.addEventListener(eventName, onEvent); return () => { - core.contentDiv.removeEventListener(eventName, onEvent); + core.logicalRoot.removeEventListener(eventName, onEvent); }; }); diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createContentModel.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/createContentModel.ts index 42f09475ec6..64640a328c4 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createContentModel.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/createContentModel.ts @@ -39,7 +39,7 @@ export const createContentModel: CreateContentModel = (core, option, selectionOv ) : createDomToModelContextWithConfig(core.domToModelSettings.calculated, editorContext); - const model = domToContentModel(core.contentDiv, domToModelContext, selection); + const model = domToContentModel(core.logicalRoot, domToModelContext, selection); if (saveIndex) { core.cache.cachedModel = model; diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts index 0895824658a..7e96ba42119 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts @@ -6,7 +6,7 @@ import type { EditorContext, CreateEditorContext } from 'roosterjs-content-model * Create a EditorContext object used by ContentModel API */ export const createEditorContext: CreateEditorContext = (core, saveIndex) => { - const { lifecycle, format, darkColorHandler, contentDiv, cache, domHelper } = core; + const { lifecycle, format, darkColorHandler, logicalRoot, cache, domHelper } = core; const context: EditorContext = { isDarkMode: lifecycle.isDarkMode, @@ -17,10 +17,10 @@ export const createEditorContext: CreateEditorContext = (core, saveIndex) => { allowCacheElement: true, domIndexer: saveIndex ? cache.domIndexer : undefined, zoomScale: domHelper.calculateZoomScale(), - ...getRootComputedStyleForContext(contentDiv.ownerDocument), + ...getRootComputedStyleForContext(logicalRoot.ownerDocument), }; - checkRootRtl(contentDiv, context); + checkRootRtl(logicalRoot, context); return context; }; diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts index 8e6104f7bda..6d51b4a71f7 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts @@ -15,7 +15,7 @@ export const focus: Focus = core => { // fallback, in case editor still have no focus if (!core.api.hasFocus(core)) { - core.contentDiv.focus(); + core.logicalRoot.focus(); } } }; diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts index fddc26c9d93..11f64f26b23 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts @@ -16,10 +16,10 @@ export const getDOMSelection: GetDOMSelection = core => { }; function getNewSelection(core: EditorCore): DOMSelection | null { - const selection = core.contentDiv.ownerDocument.defaultView?.getSelection(); + const selection = core.logicalRoot.ownerDocument.defaultView?.getSelection(); const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; - return range && core.contentDiv.contains(range.commonAncestorContainer) + return range && core.logicalRoot.contains(range.commonAncestorContainer) ? { type: 'range', range, diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts index bd3f8b00fc8..74609933caa 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts @@ -9,7 +9,9 @@ export const getVisibleViewport: GetVisibleViewport = core => { const scrollContainer = core.domEvent.scrollContainer; return getIntersectedRect( - scrollContainer == core.contentDiv ? [scrollContainer] : [scrollContainer, core.contentDiv] + scrollContainer == core.physicalRoot + ? [scrollContainer] + : [scrollContainer, core.physicalRoot] ); }; diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts index 9f4cc0cc4b9..ba41f871a56 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts @@ -7,6 +7,6 @@ import type { HasFocus } from 'roosterjs-content-model-types'; * @returns True if the editor has focus, otherwise false */ export const hasFocus: HasFocus = core => { - const activeElement = core.contentDiv.ownerDocument.activeElement; - return !!(activeElement && core.contentDiv.contains(activeElement)); + const activeElement = core.logicalRoot.ownerDocument.activeElement; + return !!(activeElement && core.logicalRoot.contains(activeElement)); }; diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts index 44258f6ff84..ea1654ecdc0 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts @@ -45,7 +45,7 @@ export const paste: Paste = ( // 3. Create target fragment const sourceFragment = createPasteFragment( - core.contentDiv.ownerDocument, + core.physicalRoot.ownerDocument, clipboardData, pasteType, (clipboardData.rawHtml == clipboardData.html diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/setContentModel.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/setContentModel.ts index bf776ab6b70..39409e3e7df 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/setContentModel.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/setContentModel.ts @@ -26,8 +26,8 @@ export const setContentModel: SetContentModel = (core, model, option, onNodeCrea modelToDomContext.onNodeCreated = onNodeCreated; const selection = contentModelToDom( - core.contentDiv.ownerDocument, - core.contentDiv, + core.logicalRoot.ownerDocument, + core.logicalRoot, model, modelToDomContext ); diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts index 9f40ab68127..5c9b1fb63b6 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts @@ -23,14 +23,14 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC // Set skipReselectOnFocus to skip this behavior const skipReselectOnFocus = core.selection.skipReselectOnFocus; - const doc = core.contentDiv.ownerDocument; + const doc = core.physicalRoot.ownerDocument; const sheet = core.selection.selectionStyleNode?.sheet; core.selection.skipReselectOnFocus = true; try { let selectionRules: string[] | undefined; - const rootSelector = '#' + addUniqueId(core.contentDiv, CONTENT_DIV_ID); + const rootSelector = '#' + addUniqueId(core.physicalRoot, CONTENT_DIV_ID); switch (selection?.type) { case 'image': diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts b/packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts index 818b35e71a7..c703a641ee0 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts @@ -14,8 +14,8 @@ export const switchShadowEdit: SwitchShadowEdit = (editorCore, isOn): void => { if (isOn != !!core.lifecycle.shadowEditFragment) { if (isOn) { const model = !core.cache.cachedModel ? core.api.createContentModel(core) : null; - const fragment = core.contentDiv.ownerDocument.createDocumentFragment(); - const clonedRoot = core.contentDiv.cloneNode(true /*deep*/); + const fragment = core.logicalRoot.ownerDocument.createDocumentFragment(); + const clonedRoot = core.logicalRoot.cloneNode(true /*deep*/); moveChildNodes(fragment, clonedRoot); diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts b/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts index acd1c29e800..03485506e05 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts @@ -196,7 +196,7 @@ export class Editor implements IEditor { * @returns The HTML document which contains this editor */ getDocument(): Document { - return this.getCore().contentDiv.ownerDocument; + return this.getCore().physicalRoot.ownerDocument; } /** @@ -275,7 +275,7 @@ export class Editor implements IEditor { if (!!isDarkMode != core.lifecycle.isDarkMode) { transformColor( - core.contentDiv, + core.physicalRoot, false /*includeSelf*/, isDarkMode ? 'lightToDark' : 'darkToLight', core.darkColorHandler diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorCore.ts b/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorCore.ts index c222dacebce..9fefb9a34d2 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorCore.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorCore.ts @@ -20,7 +20,8 @@ export function createEditorCore(contentDiv: HTMLDivElement, options: EditorOpti const corePlugins = createEditorCorePlugins(options, contentDiv); return { - contentDiv, + physicalRoot: contentDiv, + logicalRoot: contentDiv, api: { ...coreApiMap, ...options.coreApiOverride }, originalApi: { ...coreApiMap }, plugins: [ diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts index fd32fa44096..e18b8de138e 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts @@ -5,20 +5,20 @@ import type { SnapshotSelection, EditorCore } from 'roosterjs-content-model-type * @internal */ export function createSnapshotSelection(core: EditorCore): SnapshotSelection { - const { contentDiv, api } = core; + const { physicalRoot, api } = core; const selection = api.getDOMSelection(core); // Normalize tables to ensure they have TBODY element between TABLE and TR so that the selection path will include correct values if (selection?.type == 'range') { const { startContainer, startOffset, endContainer, endOffset } = selection.range; - let isDOMChanged = normalizeTableTree(startContainer, contentDiv); + let isDOMChanged = normalizeTableTree(startContainer, physicalRoot); if (endContainer != startContainer) { - isDOMChanged = normalizeTableTree(endContainer, contentDiv) || isDOMChanged; + isDOMChanged = normalizeTableTree(endContainer, physicalRoot) || isDOMChanged; } if (isDOMChanged) { - const newRange = contentDiv.ownerDocument.createRange(); + const newRange = physicalRoot.ownerDocument.createRange(); newRange.setStart(startContainer, startOffset); newRange.setEnd(endContainer, endOffset); @@ -56,8 +56,8 @@ export function createSnapshotSelection(core: EditorCore): SnapshotSelection { return { type: 'range', - start: getPath(range.startContainer, range.startOffset, contentDiv), - end: getPath(range.endContainer, range.endOffset, contentDiv), + start: getPath(range.startContainer, range.startOffset, physicalRoot), + end: getPath(range.endContainer, range.endOffset, physicalRoot), isReverted: !!selection.isReverted, }; diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts index f77fcfdd7e2..87b8bf9af71 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts @@ -43,7 +43,7 @@ export function mergePasteContent( (model, context) => { const selectedSegment = getSelectedSegments(model, true /*includeFormatHolder*/)[0]; const domToModelContext = createDomToModelContextForSanitizing( - core.contentDiv.ownerDocument, + core.physicalRoot.ownerDocument, undefined /*defaultFormat*/, core.domToModelSettings.customized, domToModelOption diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts index 768d2a1797a..733b7601757 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts @@ -11,7 +11,7 @@ export function restoreSnapshotColors(core: EditorCore, snapshot: Snapshot) { if (!!snapshot.isDarkMode != !!isDarkMode) { transformColor( - core.contentDiv, + core.physicalRoot, false /*includeSelf*/, isDarkMode ? 'lightToDark' : 'darkToLight', core.darkColorHandler diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts index b70474d9446..1e8a67da11c 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts @@ -19,10 +19,10 @@ const BlockEntityContainer = '_E_EBlockEntityContainer'; */ export function restoreSnapshotHTML(core: EditorCore, snapshot: Snapshot) { const { - contentDiv, + physicalRoot, entity: { entityMap }, } = core; - let refNode: Node | null = contentDiv.firstChild; + let refNode: Node | null = physicalRoot.firstChild; const body = new DOMParser().parseFromString( core.trustedHTMLHandler?.(snapshot.html) ?? snapshot.html, @@ -34,9 +34,9 @@ export function restoreSnapshotHTML(core: EditorCore, snapshot: Snapshot) { const originalEntityElement = tryGetEntityElement(entityMap, currentNode); if (originalEntityElement) { - refNode = reuseCachedElement(contentDiv, originalEntityElement, refNode); + refNode = reuseCachedElement(physicalRoot, originalEntityElement, refNode); } else { - contentDiv.insertBefore(currentNode, refNode); + physicalRoot.insertBefore(currentNode, refNode); if (isNodeOfType(currentNode, 'ELEMENT_NODE')) { const childEntities = getAllEntityWrappers(currentNode); @@ -51,7 +51,7 @@ export function restoreSnapshotHTML(core: EditorCore, snapshot: Snapshot) { // Then after replaceChild(), the original refNode will be moved away const markerNode = wrapper.cloneNode(); - contentDiv.insertBefore(markerNode, refNode); + physicalRoot.insertBefore(markerNode, refNode); refNode = markerNode; } diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts index 9c835d72b6f..505d056a5ca 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts @@ -6,15 +6,15 @@ import type { DOMSelection, EditorCore, Snapshot } from 'roosterjs-content-model */ export function restoreSnapshotSelection(core: EditorCore, snapshot: Snapshot) { const snapshotSelection = snapshot.selection; - const { contentDiv } = core; + const { physicalRoot } = core; let domSelection: DOMSelection | null = null; if (snapshotSelection) { switch (snapshotSelection.type) { case 'range': - const startPos = getPositionFromPath(contentDiv, snapshotSelection.start); - const endPos = getPositionFromPath(contentDiv, snapshotSelection.end); - const range = contentDiv.ownerDocument.createRange(); + const startPos = getPositionFromPath(physicalRoot, snapshotSelection.start); + const endPos = getPositionFromPath(physicalRoot, snapshotSelection.end); + const range = physicalRoot.ownerDocument.createRange(); range.setStart(startPos.node, startPos.offset); range.setEnd(endPos.node, endPos.offset); @@ -26,7 +26,7 @@ export function restoreSnapshotSelection(core: EditorCore, snapshot: Snapshot) { }; break; case 'table': - const table = contentDiv.querySelector( + const table = physicalRoot.querySelector( '#' + snapshotSelection.tableId ) as HTMLTableElement; @@ -42,7 +42,7 @@ export function restoreSnapshotSelection(core: EditorCore, snapshot: Snapshot) { } break; case 'image': - const image = contentDiv.querySelector( + const image = physicalRoot.querySelector( '#' + snapshotSelection.imageId ) as HTMLImageElement; diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts index 718b1b1fff5..04133f4d1c0 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts @@ -21,7 +21,8 @@ describe('addUndoSnapshot', () => { } as any; core = { - contentDiv, + physicalRoot: contentDiv, + logicalRoot: contentDiv, darkColorHandler: { getKnownColorsCopy: getKnownColorsCopySpy, }, diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts index 15e48826b3a..0f8126e28ab 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts @@ -9,7 +9,8 @@ describe('attachDomEvent', () => { div = document.createElement('div'); document.body.appendChild(div); core = { - contentDiv: div, + physicalRoot: div, + logicalRoot: div, api: {}, } as any; }); diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts index 3353d9c6a99..5d738afd2b2 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts @@ -34,7 +34,8 @@ describe('createContentModel', () => { ); core = ({ - contentDiv: mockedDiv, + physicalRoot: mockedDiv, + logicalRoot: mockedDiv, api: { createEditorContext, getDOMSelection, @@ -99,7 +100,8 @@ describe('createContentModel with selection', () => { ); core = { - contentDiv: MockedDiv, + physicalRoot: MockedDiv, + logicalRoot: MockedDiv, api: { getDOMSelection: getDOMSelectionSpy, createEditorContext: createEditorContextSpy, diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts index 0768c3ad517..3d9104ce3ce 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts @@ -19,7 +19,8 @@ describe('createEditorContext', () => { }; const core = ({ - contentDiv: div, + physicalRoot: div, + logicalRoot: div, lifecycle: { isDarkMode, }, @@ -67,7 +68,8 @@ describe('createEditorContext', () => { }; const core = ({ - contentDiv: div, + physicalRoot: div, + logicalRoot: div, lifecycle: { isDarkMode, }, @@ -115,7 +117,8 @@ describe('createEditorContext', () => { }; const core = ({ - contentDiv: div, + physicalRoot: div, + logicalRoot: div, lifecycle: { isDarkMode, }, @@ -167,7 +170,8 @@ describe('createEditorContext - checkZoomScale', () => { }, }; core = ({ - contentDiv: div, + physicalRoot: div, + logicalRoot: div, lifecycle: { isDarkMode, }, @@ -221,7 +225,8 @@ describe('createEditorContext - checkRootDir', () => { }, }; core = ({ - contentDiv: div, + physicalRoot: div, + logicalRoot: div, lifecycle: { isDarkMode, }, diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts index 52f2ef37489..ac792875dcd 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts @@ -24,13 +24,14 @@ describe('focus', () => { } as any; core = { + physicalRoot: div, + logicalRoot: div, lifecycle: {}, api: { hasFocus: hasFocusSpy, setDOMSelection: setDOMSelectionSpy, }, selection: {}, - contentDiv: div, } as any; }); diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts index 3feabab9692..db3fccce6c2 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts @@ -12,17 +12,20 @@ describe('getDOMSelection', () => { containsSpy = jasmine.createSpy('contains'); hasFocusSpy = jasmine.createSpy('hasFocus'); + const contentDiv = { + ownerDocument: { + defaultView: { + getSelection: getSelectionSpy, + }, + }, + contains: containsSpy, + }; + core = { + physicalRoot: contentDiv, + logicalRoot: contentDiv, lifecycle: {}, selection: {}, - contentDiv: { - ownerDocument: { - defaultView: { - getSelection: getSelectionSpy, - }, - }, - contains: containsSpy, - }, api: { hasFocus: hasFocusSpy, }, diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/getVisibleViewportTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/getVisibleViewportTest.ts index 785895374bb..bc4ebd2644b 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/getVisibleViewportTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/getVisibleViewportTest.ts @@ -6,7 +6,8 @@ describe('getVisibleViewport', () => { getBoundingClientRect: () => ({ left: 100, right: 200, top: 300, bottom: 400 }), }; const core = { - contentDiv: div, + physicalRoot: div, + logicalRoot: div, domEvent: { scrollContainer: div, }, @@ -25,7 +26,8 @@ describe('getVisibleViewport', () => { getBoundingClientRect: () => ({ left: 150, right: 250, top: 350, bottom: 450 }), }; const core = { - contentDiv: div1, + physicalRoot: div1, + logicalRoot: div1, domEvent: { scrollContainer: div2, }, diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts index 62ceda15096..a1e5c7d9a35 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts @@ -8,11 +8,15 @@ describe('hasFocus', () => { beforeEach(() => { containsSpy = jasmine.createSpy('contains'); + + const mockedRoot = { + ownerDocument: {}, + contains: containsSpy, + }; + core = { - contentDiv: { - ownerDocument: {}, - contains: containsSpy, - }, + physicalRoot: mockedRoot, + logicalRoot: mockedRoot, } as any; }); @@ -21,7 +25,7 @@ describe('hasFocus', () => { }); it('Has active element inside editor', () => { - (core.contentDiv.ownerDocument as any).activeElement = mockedElement; + (core.physicalRoot.ownerDocument as any).activeElement = mockedElement; containsSpy.and.returnValue(true); let result = hasFocus(core); @@ -30,7 +34,7 @@ describe('hasFocus', () => { }); it('Has active element outside editor', () => { - (core.contentDiv.ownerDocument as any).activeElement = mockedElement; + (core.physicalRoot.ownerDocument as any).activeElement = mockedElement; containsSpy.and.returnValue(false); let result = hasFocus(core); diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts index 62fbe1044da..cb3a22b2456 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts @@ -36,7 +36,8 @@ describe('setContentModel', () => { getDOMSelectionSpy = jasmine.createSpy('getDOMSelection'); core = ({ - contentDiv: mockedDiv, + physicalRoot: mockedDiv, + logicalRoot: mockedDiv, api: { createEditorContext, setDOMSelection: setDOMSelectionSpy, diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts index 3e631e7fb49..65a4ba02f49 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts @@ -46,10 +46,11 @@ describe('setDOMSelection', () => { } as any; core = { + physicalRoot: contentDiv, + logicalRoot: contentDiv, selection: { selectionStyleNode: mockedStyleNode, }, - contentDiv, api: { hasFocus: hasFocusSpy, triggerEvent: triggerEventSpy, diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts index 3f707622c82..ea5469f628b 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts @@ -18,7 +18,11 @@ describe('switchShadowEdit', () => { getSelectionRange = jasmine.createSpy('getSelectionRange'); triggerEvent = jasmine.createSpy('triggerEvent'); + const contentDiv = document.createElement('div'); + core = ({ + physicalRoot: contentDiv, + logicalRoot: contentDiv, api: { createContentModel, setContentModel, @@ -26,7 +30,6 @@ describe('switchShadowEdit', () => { triggerEvent, }, lifecycle: {}, - contentDiv: document.createElement('div'), cache: {}, } as any) as EditorCore; }); diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts b/packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts index bfe18c4001f..053410fbd1d 100644 --- a/packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts @@ -10,7 +10,8 @@ describe('triggerEvent', () => { document.body.appendChild(div); core = { - contentDiv: div, + physicalRoot: div, + logicalRoot: div, api: {}, plugins: [], lifecycle: {}, diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts b/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts index a16b0025f2f..0effbb59304 100644 --- a/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts @@ -790,7 +790,8 @@ describe('Editor', () => { const mockedCore = { plugins: [], darkColorHandler: mockedColorHandler, - contentDiv: div, + physicalRoot: div, + logicalRoot: div, lifecycle: { isDarkMode: false, }, diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts b/packages-content-model/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts index a0332204118..b14ba8ffc01 100644 --- a/packages-content-model/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts @@ -64,7 +64,8 @@ describe('createEditorCore', () => { const core = createEditorCore(contentDiv, options); expect(core).toEqual({ - contentDiv: contentDiv, + physicalRoot: contentDiv, + logicalRoot: contentDiv, api: coreApiMap, originalApi: coreApiMap, plugins: [ @@ -155,7 +156,8 @@ describe('createEditorCore', () => { } as any; runTest(mockedDiv, mockedOptions, { - contentDiv: mockedDiv, + physicalRoot: mockedDiv, + logicalRoot: mockedDiv, api: { ...coreApiMap, a: 'b' } as any, plugins: [ mockedCachePlugin, diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts index c2dbb03a726..866d65f06f2 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts @@ -10,7 +10,8 @@ describe('createSnapshotSelection', () => { div = document.createElement('div'); getDOMSelectionSpy = jasmine.createSpy('getDOMSelection'); core = { - contentDiv: div, + physicalRoot: div, + logicalRoot: div, api: { getDOMSelection: getDOMSelectionSpy, }, @@ -73,7 +74,8 @@ describe('createSnapshotSelection - Range selection', () => { div = document.createElement('div'); getDOMSelectionSpy = jasmine.createSpy('getDOMSelection'); core = { - contentDiv: div, + physicalRoot: div, + logicalRoot: div, api: { getDOMSelection: getDOMSelectionSpy, }, @@ -236,7 +238,8 @@ describe('createSnapshotSelection - Normalize Table', () => { getDOMSelectionSpy = jasmine.createSpy('getDOMSelection'); setDOMSelectionSpy = jasmine.createSpy('setDOMSelection'); core = { - contentDiv: div, + physicalRoot: div, + logicalRoot: div, api: { getDOMSelection: getDOMSelectionSpy, setDOMSelection: setDOMSelectionSpy, diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts index 3d7a63df650..7a28b5aea9b 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts @@ -51,7 +51,7 @@ describe('mergePasteContent', () => { formatContentModel, }, domToModelSettings: {}, - contentDiv: { + physicalRoot: { ownerDocument: document, }, } as any; diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts index 1de6d608eb9..bec0dcb1ff6 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts @@ -17,10 +17,11 @@ describe('restoreSnapshotColors', () => { } as any; core = { + physicalRoot: mockedDiv, + logicalRoot: mockedDiv, lifecycle: { isDarkMode: false, }, - contentDiv: mockedDiv, darkColorHandler, } as any; }); diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts index fdaadb8b488..37404206593 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts @@ -10,7 +10,8 @@ describe('restoreSnapshotHTML', () => { div = document.createElement('div'); core = { - contentDiv: div, + physicalRoot: div, + logicalRoot: div, entity: { entityMap: {}, }, diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts index c0425cc4770..aaad9729ce7 100644 --- a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts @@ -12,7 +12,8 @@ describe('restoreSnapshotSelection', () => { setDOMSelectionSpy = jasmine.createSpy('setDOMSelection'); core = { - contentDiv: div, + physicalRoot: div, + logicalRoot: div, api: { setDOMSelection: setDOMSelectionSpy, }, diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCore.ts b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCore.ts index f0145320be1..dfacba10408 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCore.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCore.ts @@ -286,9 +286,16 @@ export interface CoreApiMap { */ export interface EditorCore extends PluginState { /** - * The content DIV element of this editor + * The root DIV element of this editor (formerly contentDiv) */ - readonly contentDiv: HTMLDivElement; + readonly physicalRoot: HTMLDivElement; + + /** + * The content DIV element that operations should be applied to + * By default, the logical root is the same as the physical root, + * but if nested editors are used, the logical root changes to that of the inner editor + */ + logicalRoot: HTMLDivElement; /** * Core API map of this editor diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts index 162a03cae74..f986577a5fd 100644 --- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts +++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts @@ -199,14 +199,14 @@ export class EditorAdapter extends Editor implements ILegacyEditor { insertToRegionRoot: false, }; - const { contentDiv } = this.getCore(); + const { physicalRoot } = this.getCore(); if (option.updateCursor) { this.focus(); } if (option.position == ContentPosition.Outside) { - contentDiv.parentNode?.insertBefore(node, contentDiv.nextSibling); + physicalRoot.parentNode?.insertBefore(node, physicalRoot.nextSibling); } else { if (this.isDarkMode()) { transformColor( @@ -217,7 +217,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { ); } - const selection = insertNode(contentDiv, this.getDOMSelection(), node, option); + const selection = insertNode(physicalRoot, this.getDOMSelection(), node, option); if (selection && option.updateCursor) { this.setDOMSelection(selection); @@ -274,14 +274,14 @@ export class EditorAdapter extends Editor implements ILegacyEditor { * @returns The BlockElement result */ getBlockElementAtNode(node: Node): BlockElement | null { - return getBlockElementAtNode(this.getCore().contentDiv, node); + return getBlockElementAtNode(this.getCore().logicalRoot, node); } contains(arg: Node | Range | null): boolean { if (!arg) { return false; } - return contains(this.getCore().contentDiv, arg); + return contains(this.getCore().logicalRoot, arg); } queryElements( @@ -300,10 +300,16 @@ export class EditorAdapter extends Editor implements ILegacyEditor { const selectionEx = scope == QueryScope.Body ? null : this.getSelectionRangeEx(); if (selectionEx) { selectionEx.ranges.forEach(range => { - result.push(...queryElements(core.contentDiv, selector, callback, scope, range)); + result.push(...queryElements(core.logicalRoot, selector, callback, scope, range)); }); } else { - return queryElements(core.contentDiv, selector, callback, scope, undefined /* range */); + return queryElements( + core.logicalRoot, + selector, + callback, + scope, + undefined /* range */ + ); } return result; @@ -321,7 +327,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { * otherwise just return start and end */ collapseNodes(start: Node, end: Node, canSplitParent: boolean): Node[] { - return collapseNodes(this.getCore().contentDiv, start, end, canSplitParent); + return collapseNodes(this.getCore().physicalRoot, start, end, canSplitParent); } //#endregion @@ -334,7 +340,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { * @returns True if there's no visible content, otherwise false */ isEmpty(trim?: boolean): boolean { - return isNodeEmpty(this.getCore().contentDiv, trim); + return isNodeEmpty(this.getCore().physicalRoot, trim); } /** @@ -357,7 +363,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { */ setContent(content: string, triggerContentChangedEvent: boolean = true) { const core = this.getCore(); - const { contentDiv, api, trustedHTMLHandler, lifecycle, darkColorHandler } = core; + const { physicalRoot, api, trustedHTMLHandler, lifecycle, darkColorHandler } = core; api.triggerEvent( core, @@ -387,7 +393,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { false /*broadcast*/ ); } else if (lifecycle.isDarkMode) { - transformColor(contentDiv, false /*includeSelf*/, 'lightToDark', darkColorHandler); + transformColor(physicalRoot, false /*includeSelf*/, 'lightToDark', darkColorHandler); } } @@ -429,7 +435,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { deleteSelectedContent(): NodePosition | null { const range = this.getSelectionRange(); if (range && !range.collapsed) { - return deleteSelectedContent(this.getCore().contentDiv, range); + return deleteSelectedContent(this.getCore().physicalRoot, range); } return null; } @@ -497,7 +503,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { */ getSelectionPath(): SelectionPath | null { const range = this.getSelectionRange(); - return range && getSelectionPath(this.getCore().contentDiv, range); + return range && getSelectionPath(this.getCore().physicalRoot, range); } select( @@ -507,7 +513,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { arg4?: number | PositionType ): boolean { const core = this.getCore(); - const rangeEx = buildRangeEx(core.contentDiv, arg1, arg2, arg3, arg4); + const rangeEx = buildRangeEx(core.physicalRoot, arg1, arg2, arg3, arg4); const selection = convertRangeExToDomSelection(rangeEx); this.setDOMSelection(selection); @@ -558,7 +564,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { } return ( startFrom && - findClosestElementAncestor(startFrom, this.getCore().contentDiv, selector) + findClosestElementAncestor(startFrom, this.getCore().physicalRoot, selector) ); }) ?? null ); @@ -571,7 +577,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { * @returns True if position is at beginning of the editor, otherwise false */ isPositionAtBeginning(position: NodePosition): boolean { - return isPositionAtBeginningOf(position, this.getCore().contentDiv); + return isPositionAtBeginningOf(position, this.getCore().logicalRoot); } /** @@ -580,9 +586,9 @@ export class EditorAdapter extends Editor implements ILegacyEditor { getSelectedRegions(type: RegionType | CompatibleRegionType = RegionType.Table): Region[] { const selection = this.getSelectionRangeEx(); const result: Region[] = []; - const contentDiv = this.getCore().contentDiv; + const logicalRoot = this.getCore().logicalRoot; selection.ranges.forEach(range => { - result.push(...(range ? getRegionsFromRange(contentDiv, range, type) : [])); + result.push(...(range ? getRegionsFromRange(logicalRoot, range, type) : [])); }); return result.filter((value, index, self) => { return self.indexOf(value) === index; @@ -826,7 +832,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { * @param startNode The node to start from. If not passed, it will start from the beginning of the body */ getBodyTraverser(startNode?: Node): IContentTraverser { - return ContentTraverser.createBodyTraverser(this.getCore().contentDiv, startNode); + return ContentTraverser.createBodyTraverser(this.getCore().logicalRoot, startNode); } /** @@ -836,7 +842,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { getSelectionTraverser(range?: Range): IContentTraverser | null { range = range ?? this.getSelectionRange() ?? undefined; return range - ? ContentTraverser.createSelectionTraverser(this.getCore().contentDiv, range) + ? ContentTraverser.createSelectionTraverser(this.getCore().logicalRoot, range) : null; } @@ -850,7 +856,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { ): IContentTraverser | null { const range = this.getSelectionRange(); return range - ? ContentTraverser.createBlockTraverser(this.getCore().contentDiv, range, startFrom) + ? ContentTraverser.createBlockTraverser(this.getCore().logicalRoot, range, startFrom) : null; } @@ -865,7 +871,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { const range = this.getSelectionRange(); return ( range && - new PositionContentSearcher(this.getCore().contentDiv, Position.getStart(range)) + new PositionContentSearcher(this.getCore().logicalRoot, Position.getStart(range)) ); }); } @@ -876,7 +882,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { * @returns a function to cancel this async run */ runAsync(callback: (editor: ILegacyEditor & IEditor) => void) { - const win = this.getCore().contentDiv.ownerDocument.defaultView || window; + const win = this.getCore().physicalRoot.ownerDocument.defaultView || window; const handle = win.requestAnimationFrame(() => { if (!this.isDisposed() && callback) { callback(this); @@ -916,8 +922,8 @@ export class EditorAdapter extends Editor implements ILegacyEditor { */ getRelativeDistanceToEditor(element: HTMLElement, addScroll?: boolean): number[] | null { if (this.contains(element)) { - const contentDiv = this.getCore().contentDiv; - const editorRect = contentDiv.getBoundingClientRect(); + const physicalRoot = this.getCore().physicalRoot; + const editorRect = physicalRoot.getBoundingClientRect(); const elementRect = element.getBoundingClientRect(); if (editorRect && elementRect) { @@ -925,8 +931,8 @@ export class EditorAdapter extends Editor implements ILegacyEditor { let y = elementRect.top - editorRect?.top; if (addScroll) { - x += contentDiv.scrollLeft; - y += contentDiv.scrollTop; + x += physicalRoot.scrollLeft; + y += physicalRoot.scrollTop; } return [x, y]; From c0ff665bf84db61f4f3f704abf7ecb3c9eb73d89 Mon Sep 17 00:00:00 2001 From: "Julia Roldi (from Dev Box)" Date: Wed, 28 Feb 2024 11:45:27 -0300 Subject: [PATCH 27/80] add unit test --- .../lib/editor/SnapshotsManagerImpl.ts | 7 +- .../test/editor/SnapshotsManagerImplTest.ts | 165 ++++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts b/packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts index cb00a28ddb1..338d076295a 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts @@ -47,6 +47,11 @@ class SnapshotsManagerImpl implements SnapshotsManager { addSnapshot(snapshot: Snapshot, isAutoCompleteSnapshot: boolean): void { const currentSnapshot = this.snapshots.snapshots[this.snapshots.currentIndex]; + const isSameSnapshot = + currentSnapshot && + currentSnapshot.html == snapshot.html && + !currentSnapshot.entityStates && + !snapshot.entityStates; const addSnapshot = !currentSnapshot || shouldAddSnapshot(currentSnapshot, snapshot); if (this.snapshots.currentIndex < 0 || addSnapshot) { @@ -78,7 +83,7 @@ class SnapshotsManagerImpl implements SnapshotsManager { if (isAutoCompleteSnapshot) { this.snapshots.autoCompleteIndex = this.snapshots.currentIndex; } - } else if (!addSnapshot) { + } else if (isSameSnapshot) { // replace the currentSnapshot's metadata so the selection is updated this.snapshots.snapshots.splice(this.snapshots.currentIndex, 1, snapshot); } diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/SnapshotsManagerImplTest.ts b/packages-content-model/roosterjs-content-model-core/test/editor/SnapshotsManagerImplTest.ts index 70d0d6ec426..636f850d0b4 100644 --- a/packages-content-model/roosterjs-content-model-core/test/editor/SnapshotsManagerImplTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/editor/SnapshotsManagerImplTest.ts @@ -300,6 +300,171 @@ describe('SnapshotsManagerImpl.addSnapshot', () => { ]); }); + it('Add snapshot with entity state with equal entity states', () => { + const mockedEntityStates = 'ENTITYSTATES' as any; + + service.addSnapshot( + { + html: 'test', + isDarkMode: false, + }, + false + ); + + expect(snapshots.snapshots).toEqual([ + { + html: 'test', + isDarkMode: false, + }, + ]); + + service.addSnapshot( + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + false + ); + + expect(snapshots.snapshots).toEqual([ + { + html: 'test', + isDarkMode: false, + }, + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + ]); + + service.addSnapshot( + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + false + ); + + expect(snapshots.snapshots).toEqual([ + { + html: 'test', + isDarkMode: false, + }, + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + ]); + }); + + it('Add snapshot with entity state with different entity states', () => { + const mockedEntityStates = 'ENTITYSTATES' as any; + const mockedEntityStates2 = 'ENTITYSTATES2' as any; + + service.addSnapshot( + { + html: 'test', + isDarkMode: false, + }, + false + ); + + expect(snapshots.snapshots).toEqual([ + { + html: 'test', + isDarkMode: false, + }, + ]); + + service.addSnapshot( + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + false + ); + + expect(snapshots.snapshots).toEqual([ + { + html: 'test', + isDarkMode: false, + }, + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + ]); + + service.addSnapshot( + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates2, + }, + false + ); + + expect(snapshots.snapshots).toEqual([ + { + html: 'test', + isDarkMode: false, + }, + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates2, + }, + ]); + }); + + it('Add snapshot without entity state after a snapshot with empty state', () => { + const mockedEntityStates = 'ENTITYSTATES' as any; + + service.addSnapshot( + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + false + ); + + expect(snapshots.snapshots).toEqual([ + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + ]); + + service.addSnapshot( + { + html: 'test', + isDarkMode: false, + }, + false + ); + + expect(snapshots.snapshots).toEqual([ + { + html: 'test', + isDarkMode: false, + entityStates: mockedEntityStates, + }, + ]); + }); + it('Has onChanged', () => { const onChanged = jasmine.createSpy('onChanged'); snapshots.onChanged = onChanged; From eae992c40f0a4769ceedfdc21a44f1727f5746a0 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Wed, 28 Feb 2024 10:57:54 -0800 Subject: [PATCH 28/80] Fix dispose plugin issus in EditorAdapter (#2452) --- packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts index f986577a5fd..84a85f712fc 100644 --- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts +++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts @@ -152,6 +152,8 @@ export class EditorAdapter extends Editor implements ILegacyEditor { * Dispose this editor, dispose all plugins and custom data */ dispose(): void { + super.dispose(); + const core = this.contentModelEditorCore; if (core) { @@ -167,8 +169,6 @@ export class EditorAdapter extends Editor implements ILegacyEditor { this.contentModelEditorCore = undefined; } - - super.dispose(); } /** From 743de926a963ea393786828540840410efdd277d Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Thu, 29 Feb 2024 09:51:32 -0800 Subject: [PATCH 29/80] Fix #2202 again (#2450) --- .../lib/publicApi/selection/deleteBlock.ts | 3 -- .../lib/publicApi/selection/deleteSegment.ts | 3 -- .../lib/domUtils/reuseCachedElement.ts | 4 ++- .../test/domUtils/reuseCachedElementTest.ts | 29 +++++++++++++++++++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteBlock.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteBlock.ts index 0b48a50f51e..6b58dc828e9 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteBlock.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteBlock.ts @@ -38,9 +38,6 @@ export function deleteBlock( : undefined; if (operation !== undefined) { - const wrapper = blockToDelete.wrapper; - - wrapper.parentNode?.removeChild(wrapper); replacement ? blocks.splice(index, 1, replacement) : blocks.splice(index, 1); context?.deletedEntities.push({ entity: blockToDelete, diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteSegment.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteSegment.ts index 64bccd128ff..1f35bb9bdcd 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteSegment.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteSegment.ts @@ -48,9 +48,6 @@ export function deleteSegment( ? 'removeFromEnd' : undefined; if (operation !== undefined) { - const wrapper = segmentToDelete.wrapper; - - wrapper.parentNode?.removeChild(wrapper); segments.splice(index, 1); context?.deletedEntities.push({ entity: segmentToDelete, diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/reuseCachedElement.ts b/packages-content-model/roosterjs-content-model-dom/lib/domUtils/reuseCachedElement.ts index 282bad51333..471c94ece4a 100644 --- a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/reuseCachedElement.ts +++ b/packages-content-model/roosterjs-content-model-dom/lib/domUtils/reuseCachedElement.ts @@ -11,10 +11,12 @@ import { isEntityElement } from './entityUtils'; */ export function reuseCachedElement(parent: Node, element: Node, refNode: Node | null): Node | null { if (element.parentNode == parent) { + const isEntity = isEntityElement(element); + // Remove nodes before the one we are hitting since they don't appear in Content Model at this position. // But we don't want to touch entity since it would better to keep entity at its place unless it is removed // In that case we will remove it after we have handled all other nodes - while (refNode && refNode != element && !isEntityElement(refNode)) { + while (refNode && refNode != element && (isEntity || !isEntityElement(refNode))) { const next = refNode.nextSibling; refNode.parentNode?.removeChild(refNode); diff --git a/packages-content-model/roosterjs-content-model-dom/test/domUtils/reuseCachedElementTest.ts b/packages-content-model/roosterjs-content-model-dom/test/domUtils/reuseCachedElementTest.ts index 7a834034f9f..1f612a8c6cb 100644 --- a/packages-content-model/roosterjs-content-model-dom/test/domUtils/reuseCachedElementTest.ts +++ b/packages-content-model/roosterjs-content-model-dom/test/domUtils/reuseCachedElementTest.ts @@ -66,6 +66,7 @@ describe('reuseCachedElement', () => { const refNode = document.createElement('div'); const element = document.createElement('span'); const nextNode = document.createElement('br'); + const removeChildSpy = spyOn(Node.prototype, 'removeChild').and.callThrough(); parent.appendChild(refNode); parent.appendChild(element); @@ -75,6 +76,7 @@ describe('reuseCachedElement', () => { const result = reuseCachedElement(parent, element, refNode); + expect(removeChildSpy).not.toHaveBeenCalled(); expect(parent.outerHTML).toBe( '

' ); @@ -82,4 +84,31 @@ describe('reuseCachedElement', () => { expect(parent.firstChild?.nextSibling).toBe(refNode); expect(result).toBe(refNode); }); + + it('RefNode is entity, current element is entity', () => { + const parent = document.createElement('div'); + const refNode = document.createElement('div'); + const element = document.createElement('span'); + const nextNode = document.createElement('br'); + const removeChildSpy = spyOn(Node.prototype, 'removeChild').and.callThrough(); + + parent.appendChild(refNode); + parent.appendChild(element); + parent.appendChild(nextNode); + + setEntityElementClasses(refNode, 'TestEntity', true); + setEntityElementClasses(element, 'TestEntity2', true); + + const result = reuseCachedElement(parent, element, refNode); + + expect(removeChildSpy).toHaveBeenCalledTimes(1); + expect(removeChildSpy).toHaveBeenCalledWith(refNode); + + expect(parent.outerHTML).toBe( + '

' + ); + expect(parent.firstChild).toBe(element); + expect(parent.firstChild?.nextSibling).toBe(nextNode); + expect(result).toBe(nextNode); + }); }); From 78b498a72fd4cab886a849ce663200000847c42e Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Thu, 29 Feb 2024 09:59:19 -0800 Subject: [PATCH 30/80] Improve Entity State related API (#2444) * Improve Entity State related API * fix build and test * add test --------- Co-authored-by: Bryan Valverde U --- .../lib/publicApi/entity/insertEntity.ts | 36 +++++++- .../test/publicApi/entity/insertEntityTest.ts | 85 +++++++++++++++++++ .../lib/corePlugin/EntityPlugin.ts | 27 +++--- .../lib/editor/Editor.ts | 10 ++- .../lib/utils/restoreSnapshotHTML.ts | 32 +++---- .../test/corePlugin/EntityPluginTest.ts | 6 +- .../test/editor/EditorTest.ts | 34 +++++++- .../lib/domUtils/entityUtils.ts | 23 ++++- .../entity/entityFormatHandler.ts | 13 +-- .../roosterjs-content-model-dom/lib/index.ts | 2 +- .../test/domUtils/entityUtilTest.ts | 70 +++++++-------- .../lib/editor/IEditor.ts | 4 +- .../lib/parameter/InsertEntityOptions.ts | 5 ++ 13 files changed, 252 insertions(+), 95 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts b/packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts index 4fa758d60ee..c6615a0089c 100644 --- a/packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts +++ b/packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts @@ -1,12 +1,17 @@ import { ChangeSource } from 'roosterjs-content-model-core'; -import { createEntity, normalizeContentModel } from 'roosterjs-content-model-dom'; import { insertEntityModel } from '../../modelApi/entity/insertEntityModel'; +import { + createEntity, + normalizeContentModel, + parseEntityFormat, +} from 'roosterjs-content-model-dom'; import type { ContentModelEntity, DOMSelection, InsertEntityPosition, InsertEntityOptions, IEditor, + EntityState, } from 'roosterjs-content-model-types'; const BlockEntityTag = 'div'; @@ -57,7 +62,8 @@ export default function insertEntity( position?: InsertEntityPosition | DOMSelection, options?: InsertEntityOptions ): ContentModelEntity | null { - const { contentNode, focusAfterEntity, wrapperDisplay, skipUndoSnapshot } = options || {}; + const { contentNode, focusAfterEntity, wrapperDisplay, skipUndoSnapshot, initialEntityState } = + options || {}; const document = editor.getDocument(); const wrapper = document.createElement(isBlock ? BlockEntityTag : InlineEntityTag); const display = wrapperDisplay ?? (isBlock ? undefined : 'inline-block'); @@ -75,6 +81,10 @@ export default function insertEntity( const entityModel = createEntity(wrapper, true /* isReadonly */, undefined /*format*/, type); + if (!skipUndoSnapshot) { + editor.takeSnapshot(); + } + editor.formatContentModel( (model, context) => { insertEntityModel( @@ -88,7 +98,7 @@ export default function insertEntity( normalizeContentModel(model); - context.skipUndoSnapshot = skipUndoSnapshot; + context.skipUndoSnapshot = true; context.newEntities.push(entityModel); return true; @@ -106,5 +116,25 @@ export default function insertEntity( } ); + if (!skipUndoSnapshot) { + let entityState: EntityState | undefined; + + if (initialEntityState) { + const format = parseEntityFormat(wrapper); + const { id, entityType } = format; + + entityState = + id && entityType + ? { + id: id, + type: entityType, + state: initialEntityState, + } + : undefined; + } + + editor.takeSnapshot(entityState); + } + return entityModel; } diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts index f8a8f3619e2..475163b52e5 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts @@ -24,6 +24,7 @@ describe('insertEntity', () => { let insertEntityModelSpy: jasmine.Spy; let isDarkModeSpy: jasmine.Spy; let normalizeContentModelSpy: jasmine.Spy; + let takeSnapshotSpy: jasmine.Spy; const type = 'Entity'; const apiName = 'insertEntity'; @@ -39,6 +40,7 @@ describe('insertEntity', () => { appendChildSpy = jasmine.createSpy('appendChildSpy'); insertEntityModelSpy = spyOn(insertEntityModel, 'insertEntityModel'); isDarkModeSpy = jasmine.createSpy('isDarkMode'); + takeSnapshotSpy = jasmine.createSpy('takeSnapshot'); wrapper = { style: { @@ -68,6 +70,7 @@ describe('insertEntity', () => { getDocument: getDocumentSpy, isDarkMode: isDarkModeSpy, formatContentModel: formatWithContentModelSpy, + takeSnapshot: takeSnapshotSpy, } as any; spyOn(entityUtils, 'addDelimiters').and.returnValue([]); @@ -76,6 +79,9 @@ describe('insertEntity', () => { it('insert inline entity to top', () => { const entity = insertEntity(editor, type, false, 'begin'); + expect(takeSnapshotSpy).toHaveBeenCalledTimes(2); + expect(takeSnapshotSpy).toHaveBeenCalledWith(); + expect(takeSnapshotSpy).toHaveBeenCalledWith(undefined); expect(createElementSpy).toHaveBeenCalledWith('span'); expect(setPropertySpy).toHaveBeenCalledWith('display', 'inline-block'); expect(appendChildSpy).not.toHaveBeenCalled(); @@ -120,6 +126,9 @@ describe('insertEntity', () => { it('block inline entity to root', () => { const entity = insertEntity(editor, type, true, 'root'); + expect(takeSnapshotSpy).toHaveBeenCalledTimes(2); + expect(takeSnapshotSpy).toHaveBeenCalledWith(); + expect(takeSnapshotSpy).toHaveBeenCalledWith(undefined); expect(createElementSpy).toHaveBeenCalledWith('div'); expect(setPropertySpy).toHaveBeenCalledWith('display', 'inline-block'); expect(setPropertySpy).toHaveBeenCalledWith('width', '100%'); @@ -172,6 +181,7 @@ describe('insertEntity', () => { wrapperDisplay: 'none', }); + expect(takeSnapshotSpy).toHaveBeenCalledTimes(0); expect(createElementSpy).toHaveBeenCalledWith('div'); expect(setPropertySpy).toHaveBeenCalledWith('display', 'none'); expect(setPropertySpy).not.toHaveBeenCalledWith('display', 'inline-block'); @@ -221,6 +231,81 @@ describe('insertEntity', () => { const entity = insertEntity(editor, type, false, 'begin'); + expect(takeSnapshotSpy).toHaveBeenCalledTimes(2); + expect(takeSnapshotSpy).toHaveBeenCalledWith(); + expect(takeSnapshotSpy).toHaveBeenCalledWith(undefined); + expect(createElementSpy).toHaveBeenCalledWith('span'); + expect(setPropertySpy).toHaveBeenCalledWith('display', 'inline-block'); + expect(appendChildSpy).not.toHaveBeenCalled(); + expect(formatWithContentModelSpy.calls.argsFor(0)[1].apiName).toBe(apiName); + expect(formatWithContentModelSpy.calls.argsFor(0)[1].changeSource).toBe( + ChangeSource.InsertEntity + ); + expect(insertEntityModelSpy).toHaveBeenCalledWith( + model, + { + segmentType: 'Entity', + blockType: 'Entity', + format: {}, + entityFormat: { + id: undefined, + entityType: type, + isReadonly: true, + }, + wrapper: wrapper, + }, + 'begin', + false, + undefined, + context + ); + expect(triggerContentChangedEventSpy).not.toHaveBeenCalled(); + expect(normalizeContentModelSpy).toHaveBeenCalled(); + + expect(context.newEntities).toEqual([ + { + segmentType: 'Entity', + blockType: 'Entity', + format: {}, + entityFormat: { + id: undefined, + entityType: 'Entity', + isReadonly: true, + }, + wrapper, + }, + ]); + + expect(entity).toEqual({ + segmentType: 'Entity', + blockType: 'Entity', + format: {}, + entityFormat: { + id: undefined, + entityType: type, + isReadonly: true, + }, + wrapper: wrapper, + }); + }); + + it('Insert with initial state', () => { + spyOn(entityUtils, 'parseEntityFormat').and.returnValue({ + id: 'A', + entityType: 'B', + }); + + const entity = insertEntity(editor, type, false, 'begin', { + initialEntityState: 'test', + }); + + expect(takeSnapshotSpy).toHaveBeenCalledTimes(2); + expect(takeSnapshotSpy).toHaveBeenCalledWith(); + expect(takeSnapshotSpy).toHaveBeenCalledWith({ + id: 'A', + type: 'B', + state: 'test', + }); expect(createElementSpy).toHaveBeenCalledWith('span'); expect(setPropertySpy).toHaveBeenCalledWith('display', 'inline-block'); expect(appendChildSpy).not.toHaveBeenCalled(); diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts index ec0cf75cb08..460ef490da6 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts @@ -11,12 +11,11 @@ import { getAllEntityWrappers, getObjectKeys, isEntityElement, - parseEntityClassName, + parseEntityFormat, } from 'roosterjs-content-model-dom'; import type { ChangedEntity, ContentChangedEvent, - ContentModelEntityFormat, EntityOperation, EntityPluginState, IEditor, @@ -209,16 +208,19 @@ class EntityPlugin implements PluginWithState { result.splice(index, 1); } else { // Entity is not in editor, which means it is deleted, use a temporary entity here to represent this entity - const tempEntity = createEntity(entry.element); - let isEntity = false; - - entry.element.classList.forEach(name => { - isEntity = parseEntityClassName(name, tempEntity.entityFormat) || isEntity; - }); + const format = parseEntityFormat(entry.element); + + if (!format.isFakeEntity) { + const entity = createEntity( + entry.element, + format.isReadonly, + {}, + format.entityType, + format.id + ); - if (isEntity) { result.push({ - entity: tempEntity, + entity: entity, operation: 'overwrite', }); } @@ -244,10 +246,7 @@ class EntityPlugin implements PluginWithState { rawEvent?: Event, state?: string ) { - const format: ContentModelEntityFormat = {}; - wrapper.classList.forEach(name => { - parseEntityClassName(name, format); - }); + const format = parseEntityFormat(wrapper); return format.id && format.entityType && !format.isFakeEntity ? editor.triggerEvent('entityOperation', { diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts b/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts index 03485506e05..5477ab1dcdf 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts @@ -27,6 +27,7 @@ import type { EditorOptions, TrustedHTMLHandler, Rect, + EntityState, } from 'roosterjs-content-model-types'; /** @@ -174,11 +175,16 @@ export class Editor implements IEditor { /** * Add a single undo snapshot to undo stack + * @param entityState @optional State for entity if we want to add entity state for this snapshot */ - takeSnapshot(): Snapshot | null { + takeSnapshot(entityState?: EntityState): Snapshot | null { const core = this.getCore(); - return core.api.addUndoSnapshot(core, false /*canUndoByBackspace*/); + return core.api.addUndoSnapshot( + core, + false /*canUndoByBackspace*/, + entityState ? [entityState] : undefined + ); } /** diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts index 1e8a67da11c..ad4a2e1a5f7 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts @@ -2,15 +2,10 @@ import { getAllEntityWrappers, isEntityElement, isNodeOfType, - parseEntityClassName, + parseEntityFormat, reuseCachedElement, } from 'roosterjs-content-model-dom'; -import type { - Snapshot, - EditorCore, - KnownEntityItem, - ContentModelEntityFormat, -} from 'roosterjs-content-model-types'; +import type { Snapshot, EditorCore, KnownEntityItem } from 'roosterjs-content-model-types'; const BlockEntityContainer = '_E_EBlockEntityContainer'; @@ -79,11 +74,7 @@ function tryGetEntityElement( if (isNodeOfType(node, 'ELEMENT_NODE')) { if (isEntityElement(node)) { - const format: ContentModelEntityFormat = {}; - - node.classList.forEach(name => { - parseEntityClassName(name, format); - }); + const format = parseEntityFormat(node); result = (format.id && entityMap[format.id]?.element) || null; } else if (isBlockEntityContainer(node)) { @@ -93,6 +84,7 @@ function tryGetEntityElement( return result; } + function isBlockEntityContainer(node: HTMLElement) { return node.classList.contains(BlockEntityContainer); } @@ -101,14 +93,16 @@ function tryGetEntityFromContainer( element: HTMLElement, entityMap: Record ): HTMLElement | null { - const format: ContentModelEntityFormat = {}; - element.childNodes.forEach(node => { + for (let node = element.firstChild; node; node = node.nextSibling) { if (isEntityElement(node) && isNodeOfType(node, 'ELEMENT_NODE')) { - node.classList.forEach(name => parseEntityClassName(name, format)); - } - }); + const format = parseEntityFormat(node); + const parent = format.id ? entityMap[format.id]?.element.parentElement : null; - const parent = format.id ? entityMap[format.id]?.element.parentElement : null; + return isNodeOfType(parent, 'ELEMENT_NODE') && isBlockEntityContainer(parent) + ? parent + : null; + } + } - return isNodeOfType(parent, 'ELEMENT_NODE') && isBlockEntityContainer(parent) ? parent : null; + return null; } diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts index 010938c9869..0f9bf77b654 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts @@ -599,7 +599,7 @@ describe('EntityPlugin', () => { it('Click on entity', () => { const mockedNode = { parentNode: null as any, - classList: ['_ENtity', '_EType_A', '_EId_A'], + classList: ['_Entity', '_EType_A', '_EId_A'], } as any; const mockedEvent = { target: mockedNode, @@ -631,7 +631,7 @@ describe('EntityPlugin', () => { it('Click on child of entity', () => { const mockedNode1 = { parentNode: null as any, - classList: ['_ENtity', '_EType_A', '_EId_A'], + classList: ['_Entity', '_EType_A', '_EId_A'], } as any; const mockedNode2 = { @@ -667,7 +667,7 @@ describe('EntityPlugin', () => { it('Not clicking', () => { const mockedNode = { parentNode: null as any, - classList: ['_ENtity', '_EType_A', '_EId_A'], + classList: ['_Entity', '_EType_A', '_EId_A'], } as any; const mockedEvent = { target: mockedNode, diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts b/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts index 0effbb59304..591c3df519b 100644 --- a/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts @@ -431,7 +431,7 @@ describe('Editor', () => { const snapshot = editor.takeSnapshot(); - expect(addUndoSnapshotSpy).toHaveBeenCalledWith(mockedCore, false); + expect(addUndoSnapshotSpy).toHaveBeenCalledWith(mockedCore, false, undefined); expect(snapshot).toBe(mockedSnapshot); editor.dispose(); @@ -465,6 +465,38 @@ describe('Editor', () => { expect(() => editor.takeSnapshot()).toThrow(); }); + it('takeSnapshot', () => { + const div = document.createElement('div'); + const mockedSnapshot = 'SNAPSHOT' as any; + const resetSpy = jasmine.createSpy('reset'); + const addUndoSnapshotSpy = jasmine + .createSpy('addUndoSnapshot') + .and.returnValue(mockedSnapshot); + const mockedCore = { + plugins: [], + darkColorHandler: { + updateKnownColor: updateKnownColorSpy, + reset: resetSpy, + }, + api: { addUndoSnapshot: addUndoSnapshotSpy, setContentModel: setContentModelSpy }, + } as any; + + createEditorCoreSpy.and.returnValue(mockedCore); + + const editor = new Editor(div); + const snapshot = editor.takeSnapshot(); + + expect(snapshot).toEqual(mockedSnapshot); + expect(addUndoSnapshotSpy).toHaveBeenCalledTimes(1); + expect(addUndoSnapshotSpy).toHaveBeenCalledWith(mockedCore, false, undefined); + + const mockedState = 'STATE' as any; + + editor.takeSnapshot(mockedState); + expect(addUndoSnapshotSpy).toHaveBeenCalledTimes(2); + expect(addUndoSnapshotSpy).toHaveBeenCalledWith(mockedCore, false, [mockedState]); + }); + it('restoreSnapshot', () => { const div = document.createElement('div'); const mockedSnapshot = 'SNAPSHOT' as any; diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts b/packages-content-model/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts index a95979139dd..2579f769c37 100644 --- a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts +++ b/packages-content-model/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts @@ -32,12 +32,33 @@ export function getAllEntityWrappers(root: HTMLElement): HTMLElement[] { return toArray(root.querySelectorAll('.' + ENTITY_INFO_NAME)) as HTMLElement[]; } +/** + * Parse entity format from entity wrapper element + * @param wrapper The wrapper element to parse entity format from + * @returns Entity format + */ +export function parseEntityFormat(wrapper: HTMLElement): ContentModelEntityFormat { + let isEntity = false; + const format: ContentModelEntityFormat = {}; + + wrapper.classList.forEach(name => { + isEntity = parseEntityClassName(name, format) || isEntity; + }); + + if (!isEntity) { + format.isFakeEntity = true; + format.isReadonly = !wrapper.isContentEditable; + } + + return format; +} + /** * Parse entity class names from entity wrapper element * @param className Class names of entity * @param format The output entity format object */ -export function parseEntityClassName( +function parseEntityClassName( className: string, format: ContentModelEntityFormat ): boolean | undefined { diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/entity/entityFormatHandler.ts b/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/entity/entityFormatHandler.ts index 2c360c3f243..d1192fda95b 100644 --- a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/entity/entityFormatHandler.ts +++ b/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/entity/entityFormatHandler.ts @@ -1,4 +1,4 @@ -import { generateEntityClassNames, parseEntityClassName } from '../../domUtils/entityUtils'; +import { generateEntityClassNames, parseEntityFormat } from '../../domUtils/entityUtils'; import type { EntityInfoFormat, IdFormat } from 'roosterjs-content-model-types'; import type { FormatHandler } from '../FormatHandler'; @@ -7,16 +7,7 @@ import type { FormatHandler } from '../FormatHandler'; */ export const entityFormatHandler: FormatHandler = { parse: (format, element) => { - let isEntity = false; - - element.classList.forEach(name => { - isEntity = parseEntityClassName(name, format) || isEntity; - }); - - if (!isEntity) { - format.isFakeEntity = true; - format.isReadonly = !element.isContentEditable; - } + Object.assign(format, parseEntityFormat(element)); }, apply: (format, element) => { diff --git a/packages-content-model/roosterjs-content-model-dom/lib/index.ts b/packages-content-model/roosterjs-content-model-dom/lib/index.ts index 0ffe238f6e4..b8f909736da 100644 --- a/packages-content-model/roosterjs-content-model-dom/lib/index.ts +++ b/packages-content-model/roosterjs-content-model-dom/lib/index.ts @@ -24,7 +24,7 @@ export { wrap } from './domUtils/wrap'; export { isEntityElement, getAllEntityWrappers, - parseEntityClassName, + parseEntityFormat, generateEntityClassNames, addDelimiters, isEntityDelimiter, diff --git a/packages-content-model/roosterjs-content-model-dom/test/domUtils/entityUtilTest.ts b/packages-content-model/roosterjs-content-model-dom/test/domUtils/entityUtilTest.ts index 74d896e4da0..686a73ed507 100644 --- a/packages-content-model/roosterjs-content-model-dom/test/domUtils/entityUtilTest.ts +++ b/packages-content-model/roosterjs-content-model-dom/test/domUtils/entityUtilTest.ts @@ -5,7 +5,7 @@ import { getAllEntityWrappers, isEntityDelimiter, isEntityElement, - parseEntityClassName, + parseEntityFormat, } from '../../lib/domUtils/entityUtils'; export function setEntityElementClasses( @@ -43,69 +43,61 @@ describe('isEntityElement', () => { }); }); -describe('parseEntityClassName', () => { +describe('parseEntityFormat', () => { it('No entity class', () => { - const format: ContentModelEntityFormat = {}; + const div = document.createElement('div'); + + div.className = 'test'; - const result = parseEntityClassName('test', format); + const format = parseEntityFormat(div); - expect(result).toBeFalsy(); - expect(format).toEqual({}); + expect(format).toEqual({ + isFakeEntity: true, + isReadonly: true, + }); }); it('Entity class', () => { - const format: ContentModelEntityFormat = {}; - - const result = parseEntityClassName('_Entity', format); - - expect(result).toBeTrue(); - expect(format).toEqual({}); - }); + const div = document.createElement('div'); - it('EntityId class', () => { - const format: ContentModelEntityFormat = {}; + div.className = '_Entity _EId_A _EType_B _EReadonly_1'; - const result = parseEntityClassName('_EId_A', format); + const format = parseEntityFormat(div); - expect(result).toBeFalsy(); expect(format).toEqual({ id: 'A', + entityType: 'B', + isReadonly: true, }); }); - it('EntityType class', () => { - const format: ContentModelEntityFormat = {}; + it('Fake entity', () => { + const div = document.createElement('div'); - const result = parseEntityClassName('_EType_B', format); + div.contentEditable = 'true'; - expect(result).toBeFalsy(); - expect(format).toEqual({ - entityType: 'B', - }); - }); - - it('Entity readonly class', () => { - const format: ContentModelEntityFormat = {}; + div.className = '_EId_A _EType_B _EReadonly_1'; - const result = parseEntityClassName('_EReadonly_1', format); + const format = parseEntityFormat(div); - expect(result).toBeFalsy(); expect(format).toEqual({ - isReadonly: true, + isFakeEntity: true, + isReadonly: false, + id: 'A', + entityType: 'B', }); }); - it('Parse class on existing format', () => { - const format: ContentModelEntityFormat = { - id: 'A', - }; + it('Fake entity, readonly', () => { + const div = document.createElement('div'); + + div.contentEditable = 'false'; - const result = parseEntityClassName('_EType_B', format); + const format = parseEntityFormat(div); - expect(result).toBeFalsy(); expect(format).toEqual({ - id: 'A', - entityType: 'B', + isFakeEntity: true, + isReadonly: true, }); }); }); diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/IEditor.ts b/packages-content-model/roosterjs-content-model-types/lib/editor/IEditor.ts index 88b194a9a35..301a2367ace 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/editor/IEditor.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/editor/IEditor.ts @@ -17,6 +17,7 @@ import type { import type { DarkColorHandler } from '../context/DarkColorHandler'; import type { TrustedHTMLHandler } from '../parameter/TrustedHTMLHandler'; import type { Rect } from '../parameter/Rect'; +import type { EntityState } from '../parameter/FormatContentModelContext'; /** * An interface of Editor, built on top of Content Model @@ -125,8 +126,9 @@ export interface IEditor { /** * Add a single undo snapshot to undo stack + * @param entityState @optional State for entity if we want to add entity state for this snapshot */ - takeSnapshot(): Snapshot | null; + takeSnapshot(entityState?: EntityState): Snapshot | null; /** * Restore an undo snapshot into editor diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/InsertEntityOptions.ts b/packages-content-model/roosterjs-content-model-types/lib/parameter/InsertEntityOptions.ts index e936fd88255..8844a0c1e9c 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/parameter/InsertEntityOptions.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/parameter/InsertEntityOptions.ts @@ -21,4 +21,9 @@ export interface InsertEntityOptions { * Whether skip adding an undo snapshot around */ skipUndoSnapshot?: boolean; + + /** + * Initial entity state, this is used when restore an undo snapshot to right after entity is inserted, this state will be used for set initial state of entity + */ + initialEntityState?: string; } From c950e5ba6b2aa3a51f8fce696af7623999d68bbb Mon Sep 17 00:00:00 2001 From: Andres-CT98 <107568016+Andres-CT98@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:43:02 -0600 Subject: [PATCH 31/80] Port Table edit plugin (#2358) * Standalone Editor step 2 * Standalone Editor step 3 * improve * Standalone Editor step 4 * Standalone Editor: Remove compatible enums from standalone editor * improve * Standalone Editor: Create new event types * Port to new event system * Revert "Port to new event system" This reverts commit 60cf041b3c3334df8a1781e22b2e81adc0775662. * Port to new event system * Improve * fix build * fix demo * Fix buttons * fix build * fix build * fix build * plugin added to cm demo site * pluginUtils imported * IStandaloneEditor changes * editTable can skip undo snapshot * TableEditPlugin port start * move D&D to folder * add plugin to Standalone demo and remove old plugin * demo fix * rename D&D and fix import * demo site changes * rename selector to mover * new plugin utils, organisation * isMobileOrTablet * cleanup * normalise width too * remove onShowHelperElement, fix containment, instancing, others * implement cell resizer * fix type import * fix selection * fix imports * fix instanceof, use formatTableWithContentModel, getDOMHelper * fix merge * small fix * fix table resize functionality * fix merge * fix import * fix dependency * fix exports * merge fix * export MIN_WIDTH * fix resizers * add check * fix color * fix instanceof * change IStandaloneEditor to IEditor * fix export const * rename const * fix cell resizer, add ids * add tests * fix build issues --------- Co-authored-by: Jiuqing Song --- demo/scripts/controls/BuildInPluginState.ts | 1 - .../controls/ContentModelEditorMainPane.tsx | 10 +- .../controls/StandaloneEditorMainPane.tsx | 5 +- demo/scripts/controls/getToggleablePlugins.ts | 4 - .../ContentModelEditorOptionsPlugin.ts | 1 - .../editorOptions/ContentModelPlugins.tsx | 1 - .../editorOptions/EditorOptionsPlugin.ts | 1 - .../sidePane/editorOptions/Plugins.tsx | 1 - .../editorOptions/codes/PluginsCode.ts | 2 - .../roosterjs-content-model-core/lib/index.ts | 2 +- .../lib/publicApi/table/normalizeTable.ts | 6 + .../lib/index.ts | 1 + .../lib/tableEdit/TableEditPlugin.ts | 175 ++++ .../lib/tableEdit/editors/TableEditor.ts | 385 ++++++++ .../tableEdit/editors/features/CellResizer.ts | 244 +++++ .../editors/features/TableEditorFeature.ts | 22 + .../editors/features/TableInserter.ts | 173 ++++ .../tableEdit/editors/features/TableMover.ts | 136 +++ .../editors/features/TableResizer.ts | 249 +++++ .../test/TestHelper.ts | 27 + .../test/tableEdit/TableEditTestHelper.ts | 253 +++++ .../test/tableEdit/cellResizerTest.ts | 157 ++++ .../test/tableEdit/tableData.ts | 862 ++++++++++++++++++ .../test/tableEdit/tableEditPluginTest.ts | 229 +++++ .../test/tableEdit/tableInserterTest.ts | 115 +++ .../test/tableEdit/tableMoverTest.ts | 230 +++++ .../test/tableEdit/teableResizerTest.ts | 197 ++++ 27 files changed, 3475 insertions(+), 14 deletions(-) create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/TestHelper.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/tableEdit/cellResizerTest.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableData.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableInserterTest.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts diff --git a/demo/scripts/controls/BuildInPluginState.ts b/demo/scripts/controls/BuildInPluginState.ts index fb61f825b38..919c41da93b 100644 --- a/demo/scripts/controls/BuildInPluginState.ts +++ b/demo/scripts/controls/BuildInPluginState.ts @@ -15,7 +15,6 @@ export interface BuildInPluginList { imageEdit: boolean; cutPasteListChain: boolean; tableCellSelection: boolean; - tableResize: boolean; customReplace: boolean; listEditMenu: boolean; imageEditMenu: boolean; diff --git a/demo/scripts/controls/ContentModelEditorMainPane.tsx b/demo/scripts/controls/ContentModelEditorMainPane.tsx index f8999ff58f5..b09021fd353 100644 --- a/demo/scripts/controls/ContentModelEditorMainPane.tsx +++ b/demo/scripts/controls/ContentModelEditorMainPane.tsx @@ -20,7 +20,6 @@ import { alignJustifyButton } from './ribbonButtons/contentModel/alignJustifyBut import { alignLeftButton } from './ribbonButtons/contentModel/alignLeftButton'; import { alignRightButton } from './ribbonButtons/contentModel/alignRightButton'; import { arrayPush } from 'roosterjs-editor-dom'; -import { AutoFormatPlugin, EditPlugin, PastePlugin } from 'roosterjs-content-model-plugins'; import { backgroundColorButton } from './ribbonButtons/contentModel/backgroundColorButton'; import { blockQuoteButton } from './ribbonButtons/contentModel/blockQuoteButton'; import { boldButton } from './ribbonButtons/contentModel/boldButton'; @@ -81,6 +80,12 @@ import { underlineButton } from './ribbonButtons/contentModel/underlineButton'; import { undoButton } from './ribbonButtons/contentModel/undoButton'; import { zoom } from './ribbonButtons/contentModel/zoom'; import { ContentModelSegmentFormat, IEditor, Snapshots } from 'roosterjs-content-model-types'; +import { + AutoFormatPlugin, + EditPlugin, + PastePlugin, + TableEditPlugin, +} from 'roosterjs-content-model-plugins'; import { spaceAfterButton, spaceBeforeButton, @@ -170,6 +175,7 @@ class ContentModelEditorMainPane extends MainPaneBase private formatPainterPlugin: ContentModelFormatPainterPlugin; private pastePlugin: PastePlugin; private sampleEntityPlugin: SampleEntityPlugin; + private tableEditPlugin: TableEditPlugin; private snapshots: Snapshots; private buttons: ContentModelRibbonButton[] = [ formatPainterButton, @@ -259,6 +265,7 @@ class ContentModelEditorMainPane extends MainPaneBase this.pasteOptionPlugin = createPasteOptionPlugin(); this.emojiPlugin = createEmojiPlugin(); this.formatPainterPlugin = new ContentModelFormatPainterPlugin(); + this.tableEditPlugin = new TableEditPlugin(); this.pastePlugin = new PastePlugin(); this.sampleEntityPlugin = new SampleEntityPlugin(); this.state = { @@ -378,6 +385,7 @@ class ContentModelEditorMainPane extends MainPaneBase this.contentModelRibbonPlugin, this.formatPainterPlugin, this.pastePlugin, + this.tableEditPlugin, this.contentModelAutoFormatPlugin, this.contentModelEditPlugin, this.contentModelPanePlugin.getInnerRibbonPlugin(), diff --git a/demo/scripts/controls/StandaloneEditorMainPane.tsx b/demo/scripts/controls/StandaloneEditorMainPane.tsx index a55e13cbe73..6aeb849214e 100644 --- a/demo/scripts/controls/StandaloneEditorMainPane.tsx +++ b/demo/scripts/controls/StandaloneEditorMainPane.tsx @@ -17,7 +17,7 @@ import { alignCenterButton } from './ribbonButtons/contentModel/alignCenterButto import { alignJustifyButton } from './ribbonButtons/contentModel/alignJustifyButton'; import { alignLeftButton } from './ribbonButtons/contentModel/alignLeftButton'; import { alignRightButton } from './ribbonButtons/contentModel/alignRightButton'; -import { AutoFormatPlugin, EditPlugin } from 'roosterjs-content-model-plugins'; +import { AutoFormatPlugin, EditPlugin, TableEditPlugin } from 'roosterjs-content-model-plugins'; import { backgroundColorButton } from './ribbonButtons/contentModel/backgroundColorButton'; import { blockQuoteButton } from './ribbonButtons/contentModel/blockQuoteButton'; import { boldButton } from './ribbonButtons/contentModel/boldButton'; @@ -166,6 +166,7 @@ class ContentModelEditorMainPane extends MainPaneBase private contentAutoFormatPlugin: AutoFormatPlugin; private snapshotPlugin: ContentModelSnapshotPlugin; private formatPainterPlugin: ContentModelFormatPainterPlugin; + private tableEditPlugin: TableEditPlugin; private snapshots: Snapshots; private buttons: ContentModelRibbonButton[] = [ formatPainterButton, @@ -253,6 +254,7 @@ class ContentModelEditorMainPane extends MainPaneBase this.contentAutoFormatPlugin = new AutoFormatPlugin(); this.contentModelRibbonPlugin = new ContentModelRibbonPlugin(); this.formatPainterPlugin = new ContentModelFormatPainterPlugin(); + this.tableEditPlugin = new TableEditPlugin(); this.state = { showSidePane: window.location.hash != '', popoutWindow: null, @@ -345,6 +347,7 @@ class ContentModelEditorMainPane extends MainPaneBase plugins={[ this.contentModelRibbonPlugin, this.formatPainterPlugin, + this.tableEditPlugin, this.contentModelEditPlugin, this.contentAutoFormatPlugin, ]} diff --git a/demo/scripts/controls/getToggleablePlugins.ts b/demo/scripts/controls/getToggleablePlugins.ts index 9a3512a312c..27ad93ed877 100644 --- a/demo/scripts/controls/getToggleablePlugins.ts +++ b/demo/scripts/controls/getToggleablePlugins.ts @@ -9,7 +9,6 @@ import { HyperLink } from 'roosterjs-editor-plugins/lib/HyperLink'; import { ImageEdit } from 'roosterjs-editor-plugins/lib/ImageEdit'; import { Paste } from 'roosterjs-editor-plugins/lib/Paste'; import { TableCellSelection } from 'roosterjs-editor-plugins/lib/TableCellSelection'; -import { TableResize } from 'roosterjs-editor-plugins/lib/TableResize'; import { Watermark } from 'roosterjs-editor-plugins/lib/Watermark'; import { createContextMenuPlugin, @@ -43,9 +42,6 @@ export default function getToggleablePlugins(initState: BuildInPluginState) { imageEdit, cutPasteListChain: pluginList.cutPasteListChain ? new CutPasteListChain() : null, tableCellSelection: pluginList.tableCellSelection ? new TableCellSelection() : null, - tableResize: pluginList.tableResize - ? new TableResize(undefined, initState.tableFeaturesContainerSelector) - : null, customReplace: pluginList.customReplace ? new CustomReplacePlugin() : null, autoFormat: pluginList.autoFormat ? new AutoFormat() : null, listEditMenu: diff --git a/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts b/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts index 0f85a463869..7b4befdfa18 100644 --- a/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts +++ b/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts @@ -29,7 +29,6 @@ const initialState: BuildInPluginState = { imageEdit: true, cutPasteListChain: false, tableCellSelection: true, - tableResize: true, customReplace: true, listEditMenu: true, imageEditMenu: true, diff --git a/demo/scripts/controls/sidePane/editorOptions/ContentModelPlugins.tsx b/demo/scripts/controls/sidePane/editorOptions/ContentModelPlugins.tsx index a1944307c38..8ec200746bf 100644 --- a/demo/scripts/controls/sidePane/editorOptions/ContentModelPlugins.tsx +++ b/demo/scripts/controls/sidePane/editorOptions/ContentModelPlugins.tsx @@ -63,7 +63,6 @@ export default class ContentModelPlugins extends React.Component (state.applyChangesOnMouseUp = value) ) )} - {this.renderPluginItem('tableResize', 'Table Resize Plugin')} {this.renderPluginItem('customReplace', 'Custom Replace Plugin (autocomplete)')} {this.renderPluginItem( 'contextMenu', diff --git a/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts b/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts index 01c06bc44d0..47528ae642d 100644 --- a/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts +++ b/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts @@ -13,7 +13,6 @@ const initialState: BuildInPluginState = { imageEdit: true, cutPasteListChain: true, tableCellSelection: true, - tableResize: true, customReplace: true, listEditMenu: true, imageEditMenu: true, diff --git a/demo/scripts/controls/sidePane/editorOptions/Plugins.tsx b/demo/scripts/controls/sidePane/editorOptions/Plugins.tsx index 952f890ce85..8135a4c0638 100644 --- a/demo/scripts/controls/sidePane/editorOptions/Plugins.tsx +++ b/demo/scripts/controls/sidePane/editorOptions/Plugins.tsx @@ -54,7 +54,6 @@ export default class Plugins extends React.Component { ) )} {this.renderPluginItem('cutPasteListChain', 'CutPasteListChainPlugin')} - {this.renderPluginItem('tableResize', 'Table Resize Plugin')} {this.renderPluginItem('customReplace', 'Custom Replace Plugin (autocomplete)')} {this.renderPluginItem( 'contextMenu', diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/PluginsCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/PluginsCode.ts index 0148327ffff..10fa248b3b0 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/PluginsCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/PluginsCode.ts @@ -9,7 +9,6 @@ import { CutPasteListChainCode, ImageEditCode, ContentModelPasteCode, - TableResizeCode, } from './SimplePluginCode'; export default class PluginsCode extends CodeElement { @@ -26,7 +25,6 @@ export default class PluginsCode extends CodeElement { pluginList.watermark && new WatermarkCode(this.state.watermarkText), pluginList.imageEdit && new ImageEditCode(), pluginList.cutPasteListChain && new CutPasteListChainCode(), - pluginList.tableResize && new TableResizeCode(), pluginList.customReplace && new CustomReplaceCode(), pluginList.tableCellSelection && new TableCellSelectionCode(), ].filter(plugin => !!plugin); diff --git a/packages-content-model/roosterjs-content-model-core/lib/index.ts b/packages-content-model/roosterjs-content-model-core/lib/index.ts index 9a2034e3450..739257e9367 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/index.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/index.ts @@ -33,7 +33,7 @@ export { export { setSelection } from './publicApi/selection/setSelection'; export { applyTableFormat } from './publicApi/table/applyTableFormat'; -export { normalizeTable } from './publicApi/table/normalizeTable'; +export { normalizeTable, MIN_ALLOWED_TABLE_CELL_WIDTH } from './publicApi/table/normalizeTable'; export { setTableCellBackgroundColor } from './publicApi/table/setTableCellBackgroundColor'; export { getSelectedCells } from './publicApi/table/getSelectedCells'; diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/table/normalizeTable.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/table/normalizeTable.ts index 3a43078f9cd..d4052e6c64c 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/publicApi/table/normalizeTable.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/table/normalizeTable.ts @@ -6,6 +6,10 @@ import type { ContentModelTableCell, } from 'roosterjs-content-model-types'; +/** + * Minimum width for a table cell + */ +export const MIN_ALLOWED_TABLE_CELL_WIDTH: number = 30; const MIN_HEIGHT = 22; /** @@ -75,6 +79,8 @@ export function normalizeTable( for (let i = 0; i < columns; i++) { if (table.widths[i] === undefined) { table.widths[i] = getTableCellWidth(columns); + } else if (table.widths[i] < MIN_ALLOWED_TABLE_CELL_WIDTH) { + table.widths[i] = MIN_ALLOWED_TABLE_CELL_WIDTH; } } diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/index.ts b/packages-content-model/roosterjs-content-model-plugins/lib/index.ts index fe5a21770a7..1c3da4abc9d 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/index.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/index.ts @@ -1,3 +1,4 @@ +export { TableEditPlugin } from './tableEdit/TableEditPlugin'; export { PastePlugin } from './paste/PastePlugin'; export { EditPlugin } from './edit/EditPlugin'; export { AutoFormatPlugin, AutoFormatOptions } from './autoFormat/AutoFormatPlugin'; diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts new file mode 100644 index 00000000000..f2b0c1b9649 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts @@ -0,0 +1,175 @@ +import normalizeRect from '../pluginUtils/Rect/normalizeRect'; +import TableEditor from './editors/TableEditor'; +import { isNodeOfType } from 'roosterjs-content-model-dom'; +import type { EditorPlugin, IEditor, PluginEvent, Rect } from 'roosterjs-content-model-types'; + +const TABLE_RESIZER_LENGTH = 12; + +/** + * TableEdit plugin, provides the ability to resize a table by drag-and-drop + */ +export class TableEditPlugin implements EditorPlugin { + private editor: IEditor | null = null; + private onMouseMoveDisposer: (() => void) | null = null; + private tableRectMap: { table: HTMLTableElement; rect: Rect }[] | null = null; + private tableEditor: TableEditor | null = null; + + /** + * Construct a new instance of TableResize plugin + * @param anchorContainerSelector An optional selector string to specify the container to host the plugin. + * The container must not be affected by transform: scale(), otherwise the position calculation will be wrong. + * If not specified, the plugin will be inserted in document.body + */ + constructor(private anchorContainerSelector?: string) {} + + /** + * Get a friendly name of this plugin + */ + getName() { + return 'TableEdit'; + } + + /** + * Initialize this plugin. This should only be called from Editor + * @param editor Editor instance + */ + initialize(editor: IEditor) { + this.editor = editor; + this.onMouseMoveDisposer = this.editor.attachDomEvent({ + mousemove: { beforeDispatch: this.onMouseMove }, + }); + const scrollContainer = this.editor.getScrollContainer(); + scrollContainer.addEventListener('mouseout', this.onMouseOut); + } + + private onMouseOut = ({ relatedTarget, currentTarget }: MouseEvent) => { + const relatedTargetNode = relatedTarget as Node; + const currentTargetNode = currentTarget as Node; + if ( + isNodeOfType(relatedTargetNode, 'ELEMENT_NODE') && + isNodeOfType(currentTargetNode, 'ELEMENT_NODE') && + this.tableEditor && + !this.tableEditor.isOwnedElement(relatedTargetNode) && + !currentTargetNode.contains(relatedTargetNode) + ) { + this.setTableEditor(null); + } + }; + + /** + * Dispose this plugin + */ + dispose() { + const scrollContainer = this.editor?.getScrollContainer(); + scrollContainer?.removeEventListener('mouseout', this.onMouseOut); + this.onMouseMoveDisposer?.(); + this.invalidateTableRects(); + this.disposeTableEditor(); + this.editor = null; + this.onMouseMoveDisposer = null; + } + + /** + * Handle events triggered from editor + * @param event PluginEvent object + */ + onPluginEvent(e: PluginEvent) { + switch (e.eventType) { + case 'input': + case 'contentChanged': + case 'scroll': + case 'zoomChanged': + this.setTableEditor(null); + this.invalidateTableRects(); + break; + } + } + + private onMouseMove = (event: Event) => { + const e = event as MouseEvent; + + if (e.buttons > 0 || !this.editor) { + return; + } + + this.ensureTableRects(); + + const editorWindow = this.editor.getDocument().defaultView || window; + const x = e.pageX - editorWindow.scrollX; + const y = e.pageY - editorWindow.scrollY; + let currentTable: HTMLTableElement | null = null; + + //Find table in range of mouse + if (this.tableRectMap) { + for (let i = this.tableRectMap.length - 1; i >= 0; i--) { + const { table, rect } = this.tableRectMap[i]; + + if ( + x >= rect.left - TABLE_RESIZER_LENGTH && + x <= rect.right + TABLE_RESIZER_LENGTH && + y >= rect.top - TABLE_RESIZER_LENGTH && + y <= rect.bottom + TABLE_RESIZER_LENGTH + ) { + currentTable = table; + break; + } + } + } + + this.setTableEditor(currentTable, e); + this.tableEditor?.onMouseMove(x, y); + }; + + /** + * @internal Public only for unit test + * @param table Table to use when setting the Editors + * @param event (Optional) Mouse event + */ + public setTableEditor(table: HTMLTableElement | null, event?: MouseEvent) { + if (this.tableEditor && !this.tableEditor.isEditing() && table != this.tableEditor.table) { + this.disposeTableEditor(); + } + + if (!this.tableEditor && table && this.editor && table.rows.length > 0) { + const container = this.anchorContainerSelector + ? this.editor.getDOMHelper().queryElements(this.anchorContainerSelector)[0] + : undefined; + + this.tableEditor = new TableEditor( + this.editor, + table, + this.invalidateTableRects, + isNodeOfType(container as Node, 'ELEMENT_NODE') ? container : undefined, + event?.currentTarget + ); + } + } + + private invalidateTableRects = () => { + this.tableRectMap = null; + }; + + private disposeTableEditor() { + this.tableEditor?.dispose(); + this.tableEditor = null; + } + + private ensureTableRects() { + if (!this.tableRectMap && this.editor) { + this.tableRectMap = []; + + const tables = this.editor.getDOMHelper().queryElements('table'); + tables.forEach(table => { + if (table.isContentEditable) { + const rect = normalizeRect(table.getBoundingClientRect()); + if (rect && this.tableRectMap) { + this.tableRectMap.push({ + table, + rect, + }); + } + } + }); + } + } +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts new file mode 100644 index 00000000000..96cedfe6d22 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts @@ -0,0 +1,385 @@ +import createCellResizer from './features/CellResizer'; +import createTableInserter from './features/TableInserter'; +import createTableMover from './features/TableMover'; +import createTableResizer from './features/TableResizer'; +import normalizeRect from '../../pluginUtils/Rect/normalizeRect'; +import { disposeTableEditFeature } from './features/TableEditorFeature'; +import { isNodeOfType } from 'roosterjs-content-model-dom'; +import type TableEditFeature from './features/TableEditorFeature'; +import type { IEditor, TableSelection } from 'roosterjs-content-model-types'; + +const INSERTER_HOVER_OFFSET = 6; +const enum TOP_OR_SIDE { + top = 0, + side = 1, +} +/** + * @internal + * + * A table has 6 hot areas to be resized/edited (take LTR example): + * + * [6] [ ] + * +[ 1 ]+--------------------+ + * |[ ]| | + * [ ] [ ] | + * [ ] [ ] | + * [2] [3] | + * [ ] [ ] | + * [ ][ 4 ]| | + * +------------------+--------------------+ + * | | | + * | | | + * | | | + * +------------------+--------------------+ + * [5] + * + * 1 - Hover area to show insert column button + * 2 - Hover area to show insert row button + * 3 - Hover area to show vertical resizing bar + * 4 - Hover area to show horizontal resizing bar + * 5 - Hover area to show whole table resize handle + * 6 - Hover area to show whole table mover handle + * + * When set a different current table or change current TD, we need to update these areas + */ +export default class TableEditor { + // 1, 2 - Insert a column or a row + private horizontalInserter: TableEditFeature | null = null; + private verticalInserter: TableEditFeature | null = null; + + // 3, 4 - Resize a column or a row from a cell + private horizontalResizer: TableEditFeature | null = null; + private verticalResizer: TableEditFeature | null = null; + + // 5 - Resize whole table + private tableResizer: TableEditFeature | null = null; + + // 6 - Move as well as select whole table + private tableMover: TableEditFeature | null = null; + + private isRTL: boolean; + private range: Range | null = null; + private isCurrentlyEditing: boolean; + + constructor( + private editor: IEditor, + public readonly table: HTMLTableElement, + private onChanged: () => void, + private anchorContainer?: HTMLElement, + private contentDiv?: EventTarget | null + ) { + this.isRTL = editor.getDocument().defaultView?.getComputedStyle(table).direction == 'rtl'; + this.setEditorFeatures(); + this.isCurrentlyEditing = false; + } + + dispose() { + this.disposeTableResizer(); + this.disposeCellResizers(); + this.disposeTableInserter(); + this.disposeTableMover(); + } + + isEditing(): boolean { + return this.isCurrentlyEditing; + } + + isOwnedElement(node: Node) { + return [ + this.tableResizer, + this.tableMover, + this.horizontalInserter, + this.verticalInserter, + this.horizontalResizer, + this.verticalResizer, + ] + .filter(feature => !!feature?.div) + .some(feature => feature?.div == node); + } + + onMouseMove(x: number, y: number) { + // Get whole table rect + const tableRect = normalizeRect(this.table.getBoundingClientRect()); + + //console.log('>>>tableRect', tableRect); + if (!tableRect) { + return; + } + + // Determine if cursor is on top or side + const topOrSide = + y <= tableRect.top + INSERTER_HOVER_OFFSET + ? TOP_OR_SIDE.top + : this.isRTL + ? x >= tableRect.right - INSERTER_HOVER_OFFSET + ? TOP_OR_SIDE.side + : undefined + : x <= tableRect.left + INSERTER_HOVER_OFFSET + ? TOP_OR_SIDE.side + : undefined; + const topOrSideBinary = topOrSide ? 1 : 0; + + // i is row index, j is column index + for (let i = 0; i < this.table.rows.length; i++) { + const tr = this.table.rows[i]; + let j = 0; + for (; j < tr.cells.length; j++) { + const td = tr.cells[j]; + const tdRect = normalizeRect(td.getBoundingClientRect()); + + if (!tdRect || !tableRect) { + continue; + } + + // Determine the cell the cursor is in range of + // Offset is only used for first row and column + const lessThanBottom = y <= tdRect.bottom; + const lessThanRight = this.isRTL + ? x <= tdRect.right + INSERTER_HOVER_OFFSET * topOrSideBinary + : x <= tdRect.right; + const moreThanLeft = this.isRTL + ? x >= tdRect.left + : x >= tdRect.left - INSERTER_HOVER_OFFSET * topOrSideBinary; + + if (lessThanBottom && lessThanRight && moreThanLeft) { + if (i === 0 && topOrSide == TOP_OR_SIDE.top) { + const center = (tdRect.left + tdRect.right) / 2; + const isOnRightHalf = this.isRTL ? x < center : x > center; + this.setInserterTd( + isOnRightHalf ? td : tr.cells[j - 1], + false /*isHorizontal*/ + ); + } else if (j === 0 && topOrSide == TOP_OR_SIDE.side) { + const tdAbove = this.table.rows[i - 1]?.cells[0]; + const tdAboveRect = tdAbove + ? normalizeRect(tdAbove.getBoundingClientRect()) + : null; + + const isTdNotAboveMerged = !tdAboveRect + ? null + : this.isRTL + ? tdAboveRect.right === tdRect.right + : tdAboveRect.left === tdRect.left; + + this.setInserterTd( + y < (tdRect.top + tdRect.bottom) / 2 && isTdNotAboveMerged + ? tdAbove + : td, + true /*isHorizontal*/ + ); + } else { + this.setInserterTd(null); + } + + this.setResizingTd(td); + + //Cell found + break; + } + } + + if (j < tr.cells.length) { + break; + } + } + + // Create Mover and Resizer + this.setEditorFeatures(); + } + + private setEditorFeatures() { + if (!this.tableMover) { + this.tableMover = createTableMover( + this.table, + this.editor, + this.isRTL, + this.onSelect, + this.getOnMouseOut, + this.contentDiv, + this.anchorContainer + ); + } + + if (!this.tableResizer) { + this.tableResizer = createTableResizer( + this.table, + this.editor, + this.isRTL, + this.onStartTableResize, + this.onFinishEditing, + this.contentDiv, + this.anchorContainer + ); + } + } + + private setResizingTd(td: HTMLTableCellElement) { + if (this.horizontalResizer && this.horizontalResizer.node != td) { + this.disposeCellResizers(); + } + + if (!this.horizontalResizer && td) { + this.horizontalResizer = createCellResizer( + this.editor, + td, + this.table, + this.isRTL, + true /*isHorizontal*/, + this.onStartCellResize, + this.onFinishEditing, + this.anchorContainer + ); + this.verticalResizer = createCellResizer( + this.editor, + td, + this.table, + this.isRTL, + false /*isHorizontal*/, + this.onStartCellResize, + this.onFinishEditing, + this.anchorContainer + ); + } + } + + /** + * create or remove TableInserter + * @param td td to attach to, set this to null to remove inserters (both horizontal and vertical) + */ + private setInserterTd(td: HTMLTableCellElement | null, isHorizontal?: boolean) { + const inserter = isHorizontal ? this.horizontalInserter : this.verticalInserter; + if (td === null || (inserter && inserter.node != td)) { + this.disposeTableInserter(); + } + + if (!this.horizontalInserter && !this.verticalInserter && td) { + const newInserter = createTableInserter( + this.editor, + td, + this.table, + this.isRTL, + !!isHorizontal, + this.onInserted, + this.getOnMouseOut, + this.anchorContainer + ); + if (isHorizontal) { + this.horizontalInserter = newInserter; + } else { + this.verticalInserter = newInserter; + } + } + } + + private disposeTableResizer() { + if (this.tableResizer) { + disposeTableEditFeature(this.tableResizer); + this.tableResizer = null; + } + } + + private disposeTableInserter() { + if (this.horizontalInserter) { + disposeTableEditFeature(this.horizontalInserter); + this.horizontalInserter = null; + } + if (this.verticalInserter) { + disposeTableEditFeature(this.verticalInserter); + this.verticalInserter = null; + } + } + + private disposeCellResizers() { + if (this.horizontalResizer) { + disposeTableEditFeature(this.horizontalResizer); + this.horizontalResizer = null; + } + if (this.verticalResizer) { + disposeTableEditFeature(this.verticalResizer); + this.verticalResizer = null; + } + } + + private disposeTableMover() { + if (this.tableMover) { + disposeTableEditFeature(this.tableMover); + this.tableMover = null; + } + } + + private onFinishEditing = (): false => { + this.editor.focus(); + + if (this.range) { + this.editor.setDOMSelection({ type: 'range', range: this.range, isReverted: false }); + } + + this.editor.takeSnapshot(); // Pass in an empty callback to make sure ContentChangedEvent is triggered + this.onChanged(); + this.isCurrentlyEditing = false; + + return false; + }; + + private onStartTableResize = () => { + this.isCurrentlyEditing = true; + this.onStartResize(); + }; + + private onStartCellResize = () => { + this.isCurrentlyEditing = true; + this.disposeTableResizer(); + this.onStartResize(); + }; + + private onStartResize() { + this.isCurrentlyEditing = true; + const range = this.editor.getDOMSelection(); + + if (range && range.type == 'range') { + this.range = range.range; + } + + this.editor.takeSnapshot(); + } + + private onInserted = () => { + this.disposeTableResizer(); + this.onFinishEditing(); + }; + + /** + * Public only for testing purposes + * @param table the table to select + */ + public onSelect = (table: HTMLTableElement) => { + this.editor.focus(); + + if (table) { + const selection: TableSelection = { + table: table, + firstRow: 0, + firstColumn: 0, + lastRow: table.rows.length - 1, + lastColumn: table.rows[table.rows.length - 1].cells.length - 1, + type: 'table', + }; + + this.editor.setDOMSelection(selection); + } + }; + + private getOnMouseOut = (feature: HTMLElement) => { + return (ev: MouseEvent) => { + if ( + feature && + ev.relatedTarget != feature && + isNodeOfType(this.contentDiv as Node, 'ELEMENT_NODE') && + isNodeOfType(ev.relatedTarget as Node, 'ELEMENT_NODE') && + !(this.contentDiv == ev.relatedTarget) + ) { + this.dispose(); + } + }; + }; +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts new file mode 100644 index 00000000000..799879858be --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts @@ -0,0 +1,244 @@ +import createElement from '../../../pluginUtils/CreateElement/createElement'; +import DragAndDropHelper from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; +import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; +import { isElementOfType } from 'roosterjs-content-model-dom'; +import { + getFirstSelectedTable, + MIN_ALLOWED_TABLE_CELL_WIDTH, + normalizeTable, +} from 'roosterjs-content-model-core'; +import type DragAndDropHandler from '../../../pluginUtils/DragAndDrop/DragAndDropHandler'; +import type { ContentModelTable, IEditor } from 'roosterjs-content-model-types'; +import type TableEditFeature from './TableEditorFeature'; + +const CELL_RESIZER_WIDTH = 4; + +/** + * @internal + */ +export default function createCellResizer( + editor: IEditor, + td: HTMLTableCellElement, + table: HTMLTableElement, + isRTL: boolean, + isHorizontal: boolean, + onStart: () => void, + onEnd: () => false, + anchorContainer?: HTMLElement +): TableEditFeature | null { + const document = td.ownerDocument; + const createElementData = { + tag: 'div', + style: `position: fixed; cursor: ${isHorizontal ? 'row' : 'col'}-resize; user-select: none`, + }; + const zoomScale = editor.getDOMHelper().calculateZoomScale(); + + const div = createElement(createElementData, document) as HTMLDivElement; + + (anchorContainer || document.body).appendChild(div); + + const context: DragAndDropContext = { editor, td, table, isRTL, zoomScale, onStart }; + const setPosition = isHorizontal ? setHorizontalPosition : setVerticalPosition; + setPosition(context, div); + + const handler: DragAndDropHandler = { + onDragStart, + // Horizontal modifies row height, vertical modifies column width + onDragging: isHorizontal ? onDraggingHorizontal : onDraggingVertical, + onDragEnd: onEnd, + }; + + const featureHandler = new DragAndDropHelper( + div, + context, + setPosition, + handler, + zoomScale, + editor.getEnvironment().isMobileOrTablet + ); + + return { node: td, div, featureHandler }; +} + +interface DragAndDropContext { + editor: IEditor; + td: HTMLTableCellElement; + table: HTMLTableElement; + isRTL: boolean; + zoomScale: number; + onStart: () => void; +} + +interface DragAndDropInitValue { + cmTable: ContentModelTable | undefined; + anchorColumn: number | undefined; + anchorRow: number | undefined; + anchorRowHeight: number; + allWidths: number[]; +} + +function onDragStart(context: DragAndDropContext, event: MouseEvent): DragAndDropInitValue { + const { td, onStart } = context; + const rect = normalizeRect(td.getBoundingClientRect()); + + // Get cell coordinates + const columnIndex = td.cellIndex; + const row = + td.parentElement && isElementOfType(td.parentElement, 'tr') ? td.parentElement : undefined; + const rowIndex = row?.rowIndex; + + if (rowIndex == undefined) { + return { + cmTable: undefined, + anchorColumn: undefined, + anchorRow: undefined, + anchorRowHeight: -1, + allWidths: [], + }; // Just a fallback + } + + const { editor, table } = context; + + // Get current selection + const selection = editor.getDOMSelection(); + + // Select first cell of the table + editor.setDOMSelection({ + type: 'table', + firstColumn: 0, + firstRow: 0, + lastColumn: 0, + lastRow: 0, + table: table, + }); + + // Get the table content model + const cmTable = getFirstSelectedTable(editor.getContentModelCopy('disconnected'))[0]; + + // Restore selection + editor.setDOMSelection(selection); + + if (rect && cmTable) { + onStart(); + + return { + cmTable, + anchorColumn: columnIndex, + anchorRow: rowIndex, + anchorRowHeight: cmTable.rows[rowIndex].height, + allWidths: [...cmTable.widths], + }; + } else { + return { + cmTable, + anchorColumn: undefined, + anchorRow: undefined, + anchorRowHeight: -1, + allWidths: [], + }; // Just a fallback + } +} + +function onDraggingHorizontal( + context: DragAndDropContext, + event: MouseEvent, + initValue: DragAndDropInitValue, + deltaX: number, + deltaY: number +) { + const { table } = context; + const { cmTable, anchorRow, anchorRowHeight } = initValue; + + // Assign new widths and heights to the CM table + if (cmTable && anchorRow != undefined) { + // Modify the CM Table size + cmTable.rows[anchorRow].height = (anchorRowHeight ?? 0) + deltaY; + + // Normalize the table + normalizeTable(cmTable); + + // Writeback CM Table size changes to DOM Table + const tableRow = table.rows[anchorRow]; + for (let col = 0; col < tableRow.cells.length; col++) { + const td = tableRow.cells[col]; + td.style.height = cmTable.rows[anchorRow].height + 'px'; + } + + return true; + } else { + return false; + } +} + +function onDraggingVertical( + context: DragAndDropContext, + event: MouseEvent, + initValue: DragAndDropInitValue, + deltaX: number +) { + const { table, isRTL } = context; + const { cmTable, anchorColumn, allWidths } = initValue; + + // Assign new widths and heights to the CM table + if (cmTable && anchorColumn != undefined) { + // Modify the CM Table size + const lastColumn = anchorColumn == cmTable.widths.length - 1; + const change = deltaX * (isRTL ? -1 : 1); + // This is the last column + if (lastColumn) { + // Only the last column changes + cmTable.widths[anchorColumn] = allWidths[anchorColumn] + change; + } else { + // Any other two columns + const anchorChange = allWidths[anchorColumn] + change; + const nextAnchorChange = allWidths[anchorColumn + 1] - change; + if ( + anchorChange < MIN_ALLOWED_TABLE_CELL_WIDTH || + nextAnchorChange < MIN_ALLOWED_TABLE_CELL_WIDTH + ) { + return false; + } + cmTable.widths[anchorColumn] = anchorChange; + cmTable.widths[anchorColumn + 1] = nextAnchorChange; + } + + // Normalize the table + normalizeTable(cmTable); + + // Writeback CM Table size changes to DOM Table + for (let row = 0; row < table.rows.length; row++) { + const tableRow = table.rows[row]; + for (let col = 0; col < tableRow.cells.length; col++) { + tableRow.cells[col].style.width = cmTable.widths[col] + 'px'; + } + } + + return true; + } else { + return false; + } +} + +function setHorizontalPosition(context: DragAndDropContext, trigger: HTMLElement) { + const { td } = context; + const rect = normalizeRect(td.getBoundingClientRect()); + if (rect) { + trigger.id = 'horizontalResizer'; + trigger.style.top = rect.bottom - CELL_RESIZER_WIDTH + 'px'; + trigger.style.left = rect.left + 'px'; + trigger.style.width = rect.right - rect.left + 'px'; + trigger.style.height = CELL_RESIZER_WIDTH + 'px'; + } +} + +function setVerticalPosition(context: DragAndDropContext, trigger: HTMLElement) { + const { td, isRTL } = context; + const rect = normalizeRect(td.getBoundingClientRect()); + if (rect) { + trigger.id = 'verticalResizer'; + trigger.style.top = rect.top + 'px'; + trigger.style.left = (isRTL ? rect.left : rect.right) - CELL_RESIZER_WIDTH + 1 + 'px'; + trigger.style.width = CELL_RESIZER_WIDTH + 'px'; + trigger.style.height = rect.bottom - rect.top + 'px'; + } +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts new file mode 100644 index 00000000000..f244e2bd39d --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts @@ -0,0 +1,22 @@ +import type Disposable from '../../../pluginUtils/Disposable'; + +/** + * @internal + */ +export default interface TableEditFeature { + node: Node; + div: HTMLDivElement | null; + featureHandler: Disposable | null; +} + +/** + * @internal + */ +export function disposeTableEditFeature(resizer: TableEditFeature | null) { + if (resizer) { + resizer.div?.parentNode?.removeChild(resizer.div); + resizer.div = null; + resizer.featureHandler?.dispose(); + resizer.featureHandler = null; + } +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts new file mode 100644 index 00000000000..9a6d45eec5d --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts @@ -0,0 +1,173 @@ +import createElement from '../../../pluginUtils/CreateElement/createElement'; +import getIntersectedRect from '../../../pluginUtils/Rect/getIntersectedRect'; +import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; +import { isElementOfType } from 'roosterjs-content-model-dom'; +import { + formatTableWithContentModel, + insertTableColumn, + insertTableRow, +} from 'roosterjs-content-model-api'; +import type CreateElementData from '../../../pluginUtils/CreateElement/CreateElementData'; +import type Disposable from '../../../pluginUtils/Disposable'; +import type TableEditFeature from './TableEditorFeature'; +import type { IEditor } from 'roosterjs-content-model-types'; + +const INSERTER_COLOR = '#4A4A4A'; +const INSERTER_COLOR_DARK_MODE = 'white'; +const INSERTER_SIDE_LENGTH = 12; +const INSERTER_BORDER_SIZE = 1; + +/** + * @internal + */ +export default function createTableInserter( + editor: IEditor, + td: HTMLTableCellElement, + table: HTMLTableElement, + isRTL: boolean, + isHorizontal: boolean, + onInsert: () => void, + getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void, + anchorContainer?: HTMLElement +): TableEditFeature | null { + const tdRect = normalizeRect(td.getBoundingClientRect()); + const viewPort = editor.getVisibleViewport(); + const tableRect = table && viewPort ? getIntersectedRect([table], [viewPort]) : null; + + // set inserter position + if (tdRect && tableRect) { + const document = td.ownerDocument; + const createElementData = getInsertElementData( + isHorizontal, + editor.isDarkMode(), + isRTL, + editor.getDOMHelper().getDomStyle('backgroundColor') || 'white' + ); + + const div = createElement(createElementData, document) as HTMLDivElement; + + if (isHorizontal) { + // tableRect.left/right is used because the Inserter is always intended to be on the side + div.id = 'horizontalInserter'; + div.style.left = `${ + isRTL + ? tableRect.right + : tableRect.left - (INSERTER_SIDE_LENGTH - 1 + 2 * INSERTER_BORDER_SIZE) + }px`; + div.style.top = `${tdRect.bottom - 8}px`; + (div.firstChild as HTMLElement).style.width = `${tableRect.right - tableRect.left}px`; + } else { + div.id = 'verticalInserter'; + div.style.left = `${isRTL ? tdRect.left - 8 : tdRect.right - 8}px`; + // tableRect.top is used because the Inserter is always intended to be on top + div.style.top = `${ + tableRect.top - (INSERTER_SIDE_LENGTH - 1 + 2 * INSERTER_BORDER_SIZE) + }px`; + (div.firstChild as HTMLElement).style.height = `${tableRect.bottom - tableRect.top}px`; + } + + (anchorContainer || document.body).appendChild(div); + + const handler = new TableInsertHandler( + div, + td, + table, + isHorizontal, + editor, + onInsert, + getOnMouseOut + ); + + return { div, featureHandler: handler, node: td }; + } + + return null; +} + +class TableInsertHandler implements Disposable { + private onMouseOutEvent: null | ((ev: MouseEvent) => void); + constructor( + private div: HTMLDivElement, + private td: HTMLTableCellElement, + private table: HTMLTableElement, + private isHorizontal: boolean, + private editor: IEditor, + private onInsert: () => void, + getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void + ) { + this.div.addEventListener('click', this.insertTd); + this.onMouseOutEvent = getOnMouseOut(div); + this.div.addEventListener('mouseout', this.onMouseOutEvent); + } + + dispose() { + this.div.removeEventListener('click', this.insertTd); + + if (this.onMouseOutEvent) { + this.div.removeEventListener('mouseout', this.onMouseOutEvent); + } + + this.onMouseOutEvent = null; + } + + private insertTd = () => { + // Get cell coordinates + const columnIndex = this.td.cellIndex; + const row = + this.td.parentElement && isElementOfType(this.td.parentElement, 'tr') + ? this.td.parentElement + : undefined; + const rowIndex = row && row.rowIndex; + + if (row?.cells == undefined || rowIndex == undefined) { + return; + } + + // Insert row or column + formatTableWithContentModel( + this.editor, + 'editTablePlugin', + tableModel => { + this.isHorizontal + ? insertTableRow(tableModel, 'insertBelow') + : insertTableColumn(tableModel, 'insertRight'); + }, // Select cell to make insertion + { + type: 'table', + firstColumn: columnIndex, + firstRow: rowIndex, + lastColumn: columnIndex, + lastRow: rowIndex, + table: this.table, + } + ); + + this.onInsert(); + }; +} + +function getInsertElementData( + isHorizontal: boolean, + isDark: boolean, + isRTL: boolean, + backgroundColor: string +): CreateElementData { + const inserterColor = isDark ? INSERTER_COLOR_DARK_MODE : INSERTER_COLOR; + const outerDivStyle = `position: fixed; width: ${INSERTER_SIDE_LENGTH}px; height: ${INSERTER_SIDE_LENGTH}px; font-size: 16px; color: black; line-height: 8px; vertical-align: middle; text-align: center; cursor: pointer; border: solid ${INSERTER_BORDER_SIZE}px ${inserterColor}; border-radius: 50%; background-color: ${backgroundColor}`; + const leftOrRight = isRTL ? 'right' : 'left'; + const childBaseStyles = `position: absolute; box-sizing: border-box; background-color: ${backgroundColor};`; + const childInfo: CreateElementData = { + tag: 'div', + style: + childBaseStyles + + (isHorizontal + ? `${leftOrRight}: 12px; top: 5px; height: 3px; border-top: 1px solid ${inserterColor}; border-bottom: 1px solid ${inserterColor}; border-right: 1px solid ${inserterColor}; border-left: 0px;` + : `left: 5px; top: 12px; width: 3px; border-left: 1px solid ${inserterColor}; border-right: 1px solid ${inserterColor}; border-bottom: 1px solid ${inserterColor}; border-top: 0px;`), + }; + + return { + tag: 'div', + style: outerDivStyle, + children: [childInfo, '+'], + }; +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts new file mode 100644 index 00000000000..d4dd46a94c0 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts @@ -0,0 +1,136 @@ +import createElement from '../../../pluginUtils/CreateElement/createElement'; +import DragAndDropHelper from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; +import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; +import { isNodeOfType } from 'roosterjs-content-model-dom'; +import type DragAndDropHandler from '../../../pluginUtils/DragAndDrop/DragAndDropHandler'; +import type { IEditor, Rect } from 'roosterjs-content-model-types'; +import type TableEditorFeature from './TableEditorFeature'; + +const TABLE_MOVER_LENGTH = 12; +const TABLE_MOVER_ID = '_Table_Mover'; + +/** + * @internal + * Contains the function to select whole table + * Moving behavior not implemented yet + */ +export default function createTableMover( + table: HTMLTableElement, + editor: IEditor, + isRTL: boolean, + onFinishDragging: (table: HTMLTableElement) => void, + getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void, + contentDiv?: EventTarget | null, + anchorContainer?: HTMLElement +): TableEditorFeature | null { + const rect = normalizeRect(table.getBoundingClientRect()); + + if (!isTableTopVisible(editor, rect, contentDiv as Node)) { + return null; + } + + const zoomScale = editor.getDOMHelper().calculateZoomScale(); + const document = table.ownerDocument; + const createElementData = { + tag: 'div', + style: 'position: fixed; cursor: all-scroll; user-select: none; border: 1px solid #808080', + }; + + const div = createElement(createElementData, document) as HTMLDivElement; + + div.id = TABLE_MOVER_ID; + div.style.width = `${TABLE_MOVER_LENGTH}px`; + div.style.height = `${TABLE_MOVER_LENGTH}px`; + + (anchorContainer || document.body).appendChild(div); + + const context: TableMoverContext = { + table, + zoomScale, + rect, + isRTL, + }; + + setDivPosition(context, div); + + const onDragEnd = (context: TableMoverContext, event: MouseEvent): false => { + if (event.target == div) { + onFinishDragging(context.table); + } + return false; + }; + + const featureHandler = new TableMoverFeature( + div, + context, + setDivPosition, + { + onDragEnd, + }, + context.zoomScale, + getOnMouseOut + ); + + return { div, featureHandler, node: table }; +} + +interface TableMoverContext { + table: HTMLTableElement; + zoomScale: number; + rect: Rect | null; + isRTL: boolean; +} + +interface TableMoverInitValue { + event: MouseEvent; +} + +class TableMoverFeature extends DragAndDropHelper { + private onMouseOut: ((ev: MouseEvent) => void) | null; + + constructor( + private div: HTMLElement, + context: TableMoverContext, + onSubmit: ( + context: TableMoverContext, + trigger: HTMLElement, + container?: HTMLElement + ) => void, + handler: DragAndDropHandler, + zoomScale: number, + getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void, + forceMobile?: boolean | undefined, + container?: HTMLElement + ) { + super(div, context, onSubmit, handler, zoomScale, forceMobile); + this.onMouseOut = getOnMouseOut(div); + div.addEventListener('mouseout', this.onMouseOut); + } + + dispose(): void { + super.dispose(); + if (this.onMouseOut) { + this.div.removeEventListener('mouseout', this.onMouseOut); + } + this.onMouseOut = null; + } +} + +function setDivPosition(context: TableMoverContext, trigger: HTMLElement) { + const { rect } = context; + if (rect) { + trigger.style.top = `${rect.top - TABLE_MOVER_LENGTH}px`; + trigger.style.left = `${rect.left - TABLE_MOVER_LENGTH - 2}px`; + } +} + +function isTableTopVisible(editor: IEditor, rect: Rect | null, contentDiv?: Node | null): boolean { + const visibleViewport = editor.getVisibleViewport(); + if (isNodeOfType(contentDiv, 'ELEMENT_NODE') && visibleViewport && rect) { + const containerRect = normalizeRect(contentDiv.getBoundingClientRect()); + + return !!containerRect && containerRect.top <= rect.top && visibleViewport.top <= rect.top; + } + + return true; +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts new file mode 100644 index 00000000000..f455c9f83a3 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts @@ -0,0 +1,249 @@ +import createElement from '../../../pluginUtils/CreateElement/createElement'; +import DragAndDropHelper from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; +import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; +import { getFirstSelectedTable, normalizeTable } from 'roosterjs-content-model-core'; +import { isNodeOfType } from 'roosterjs-content-model-dom'; +import type { ContentModelTable, IEditor, Rect } from 'roosterjs-content-model-types'; +import type TableEditFeature from './TableEditorFeature'; + +const TABLE_RESIZER_LENGTH = 12; +const TABLE_RESIZER_ID = '_Table_Resizer'; + +/** + * @internal + */ +export default function createTableResizer( + table: HTMLTableElement, + editor: IEditor, + isRTL: boolean, + onStart: () => void, + onEnd: () => false, + contentDiv?: EventTarget | null, + anchorContainer?: HTMLElement +): TableEditFeature | null { + const rect = normalizeRect(table.getBoundingClientRect()); + + if (!isTableBottomVisible(editor, rect, contentDiv as Node)) { + return null; + } + + const document = table.ownerDocument; + const zoomScale = editor.getDOMHelper().calculateZoomScale(); + const createElementData = { + tag: 'div', + style: `position: fixed; cursor: ${ + isRTL ? 'ne' : 'nw' + }-resize; user-select: none; border: 1px solid #808080`, + }; + + const div = createElement(createElementData, document) as HTMLDivElement; + + div.id = TABLE_RESIZER_ID; + div.style.width = `${TABLE_RESIZER_LENGTH}px`; + div.style.height = `${TABLE_RESIZER_LENGTH}px`; + + (anchorContainer || document.body).appendChild(div); + + const context: DragAndDropContext = { + isRTL, + table, + zoomScale, + onStart, + onEnd, + div, + editor, + contentDiv, + }; + + setDivPosition(context, div); + + const featureHandler = new DragAndDropHelper( + div, + context, + hideResizer, // Resizer is hidden while dragging only + { + onDragStart, + onDragging, + onDragEnd, + }, + zoomScale, + editor.getEnvironment().isMobileOrTablet + ); + + return { node: table, div, featureHandler }; +} + +interface DragAndDropContext { + table: HTMLTableElement; + isRTL: boolean; + zoomScale: number; + onStart: () => void; + onEnd: () => false; + div: HTMLDivElement; + editor: IEditor; + contentDiv?: EventTarget | null; +} + +interface DragAndDropInitValue { + originalRect: DOMRect; + originalHeights: number[]; + originalWidths: number[]; + cmTable: ContentModelTable | undefined; +} + +function onDragStart(context: DragAndDropContext, event: MouseEvent) { + context.onStart(); + + const { editor, table } = context; + + // Get current selection + const selection = editor.getDOMSelection(); + + // Select first cell of the table + editor.setDOMSelection({ + type: 'table', + firstColumn: 0, + firstRow: 0, + lastColumn: 0, + lastRow: 0, + table: table, + }); + + // Get the table content model + const cmTable = getFirstSelectedTable(editor.getContentModelCopy('disconnected'))[0]; + + // Restore selection + editor.setDOMSelection(selection); + + // Save original widths and heights + const heights: number[] = []; + cmTable?.rows.forEach(row => { + heights.push(row.height); + }); + const widths: number[] = []; + cmTable?.widths.forEach(width => { + widths.push(width); + }); + + return { + originalRect: table.getBoundingClientRect(), + cmTable, + originalHeights: heights ?? [], + originalWidths: widths ?? [], + }; +} + +function onDragging( + context: DragAndDropContext, + event: MouseEvent, + initValue: DragAndDropInitValue, + deltaX: number, + deltaY: number +) { + const { isRTL, zoomScale, table } = context; + const { originalRect, originalHeights, originalWidths, cmTable } = initValue; + + const ratioX = 1.0 + (deltaX / originalRect.width) * zoomScale * (isRTL ? -1 : 1); + const ratioY = 1.0 + (deltaY / originalRect.height) * zoomScale; + const shouldResizeX = Math.abs(ratioX - 1.0) > 1e-3; + const shouldResizeY = Math.abs(ratioY - 1.0) > 1e-3; + + // If the width of some external table is fixed, we need to make it resizable + table.style.setProperty('width', null); + // If the height of some external table is fixed, we need to make it resizable + table.style.setProperty('height', null); + + // Assign new widths and heights to the CM table + if (cmTable && cmTable.rows && (shouldResizeX || shouldResizeY)) { + // Modify the CM Table size + for (let i = 0; i < cmTable.rows.length; i++) { + for (let j = 0; j < cmTable.rows[i].cells.length; j++) { + const cell = cmTable.rows[i].cells[j]; + if (cell) { + if (shouldResizeX && i == 0) { + cmTable.widths[j] = (originalWidths[j] ?? 0) * ratioX; + } + if (shouldResizeY && j == 0) { + cmTable.rows[i].height = (originalHeights[i] ?? 0) * ratioY; + } + } + } + } + + // Normalize the table + normalizeTable(cmTable); + + // Writeback CM Table size changes to DOM Table + for (let row = 0; row < table.rows.length; row++) { + const tableRow = table.rows[row]; + + if (tableRow.cells.length == 0) { + // Skip empty row + continue; + } + + for (let col = 0; col < tableRow.cells.length; col++) { + const td = tableRow.cells[col]; + td.style.width = cmTable.widths[col] + 'px'; + td.style.height = cmTable.rows[row].height + 'px'; + } + } + return true; + } else { + return false; + } +} + +function onDragEnd( + context: DragAndDropContext, + event: MouseEvent, + initValue: DragAndDropInitValue | undefined +) { + if ( + isTableBottomVisible( + context.editor, + normalizeRect(context.table.getBoundingClientRect()), + context.contentDiv as Node + ) + ) { + context.div.style.visibility = 'visible'; + setDivPosition(context, context.div); + } + context.onEnd(); + return false; +} + +function setDivPosition(context: DragAndDropContext, trigger: HTMLElement) { + const { table, isRTL } = context; + const rect = normalizeRect(table.getBoundingClientRect()); + + if (rect) { + trigger.style.top = `${rect.bottom}px`; + trigger.style.left = isRTL + ? `${rect.left - TABLE_RESIZER_LENGTH - 2}px` + : `${rect.right}px`; + } +} + +function hideResizer(context: DragAndDropContext, trigger: HTMLElement) { + trigger.style.visibility = 'hidden'; +} + +function isTableBottomVisible( + editor: IEditor, + rect: Rect | null, + contentDiv?: Node | null +): boolean { + const visibleViewport = editor.getVisibleViewport(); + if (isNodeOfType(contentDiv, 'ELEMENT_NODE') && visibleViewport && rect) { + const containerRect = normalizeRect(contentDiv.getBoundingClientRect()); + + return ( + !!containerRect && + containerRect.bottom >= rect.bottom && + visibleViewport.bottom >= rect.bottom + ); + } + + return true; +} diff --git a/packages-content-model/roosterjs-content-model-plugins/test/TestHelper.ts b/packages-content-model/roosterjs-content-model-plugins/test/TestHelper.ts new file mode 100644 index 00000000000..b9260f1468c --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/TestHelper.ts @@ -0,0 +1,27 @@ +import { ContentModelDocument, CoreApiMap, EditorPlugin } from 'roosterjs-content-model-types'; +import { Editor } from 'roosterjs-content-model-core'; + +export function initEditor( + id: string, + plugins?: EditorPlugin[], + initialModel?: ContentModelDocument, + coreApiOverride?: Partial +) { + let node = document.createElement('div'); + node.id = id; + document.body.insertBefore(node, document.body.childNodes[0]); + + return new Editor(node, { + plugins, + initialModel, + coreApiOverride, + }); +} + +// Remove the element with id from the DOM +export function removeElement(id: string) { + let node = document.getElementById(id); + if (node && node.parentNode) { + node.parentNode.removeChild(node); + } +} diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts new file mode 100644 index 00000000000..4450662d622 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts @@ -0,0 +1,253 @@ +import * as TestHelper from '../TestHelper'; +import { DOMEventHandlerFunction } from 'roosterjs-editor-types'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import { normalizeTable } from 'roosterjs-content-model-core'; +import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; +import { + ContentModelTable, + DOMEventRecord, + EditorCore, + IEditor, +} from 'roosterjs-content-model-types'; + +/** + * Function to be called before each Table Edit test + * @param TEST_ID The id of the editor div + * @returns The editor, plugin, and handler to be used in the test + */ +export function beforeTableTest(TEST_ID: string) { + const plugin = new TableEditPlugin(); + + let handler: Record = {}; + const attachDomEvent = jasmine + .createSpy('attachDomEvent') + .and.callFake((core: EditorCore, eventMap: Record>) => { + getObjectKeys(eventMap || {}).forEach(key => { + const eventname = key as keyof HTMLElementEventMap; + const { beforeDispatch } = eventMap[key]; + const onEvent = (event: HTMLElementEventMap[typeof eventname]) => { + beforeDispatch && beforeDispatch(event); + }; + handler[eventname] = onEvent; + }); + return () => { + handler = {}; + }; + }); + + const coreApiOverride = { + attachDomEvent, + }; + const editor = TestHelper.initEditor(TEST_ID, [plugin], undefined, coreApiOverride); + + plugin.initialize(editor); + + return { editor, plugin, handler }; +} + +/** + * Function to be called after each Table Edit test + * @param editor The editor to be disposed + * @param plugin The plugin to be disposed + * @param TEST_ID The id of the editor div + */ +export function afterTableTest(editor: IEditor, plugin: TableEditPlugin, TEST_ID: string) { + editor.dispose(); + plugin.dispose(); + TestHelper.removeElement(TEST_ID); + document.body = document.createElement('body'); +} + +/** + * Function to get the current table in the editor + * @param editor The editor to get the table from + * @returns The current table in the editor + */ +export function getCurrentTable(editor: IEditor): HTMLTableElement { + const table = editor.getDOMHelper().queryElements('table')[0] as HTMLTableElement; + return table; +} + +/** + * Function to get the number of rows in the table + * @param table The table to get the number of rows from + * @returns The number of rows in the table + */ +export function getTableRows(table: HTMLTableElement): number { + return table.rows.length; +} + +/** + * Function to get the number of columns in the table + * @param table The table to get the number of columns from + * @returns The number of columns in the table + */ +export function getTableColumns(table: HTMLTableElement): number { + return table.rows[0].cells.length; +} + +/** + * Function to get the rect of a cell in the table + * @param editor The editor to get the table from + * @param i The row index of the cell + * @param j The column index of the cell + * @returns The rect of the cell + */ +export function getCellRect(editor: IEditor, i: number, j: number): DOMRect | undefined { + const tables = editor.getDOMHelper().queryElements('table'); + if (!tables || tables.length < 1) { + return undefined; + } + + const table = tables[0]; + if (i >= table.rows.length || j >= table.rows[i].cells.length) { + return undefined; + } + + const cell = table.rows[i].cells[j]; + return cell.getBoundingClientRect(); +} + +/** + * Insert the content model table on the edito + * @param editor The editor to insert the table into + * @param table The table to insert + * @param isRtl Whether the table is RTL + * @returns The rect of the table + */ +export function initialize( + editor: IEditor, + table: ContentModelTable, + isRtl: boolean = false +): DOMRect { + if (isRtl) { + editor.getDocument().body.style.direction = 'rtl'; + } + editor.formatContentModel((model, context) => { + normalizeTable(table); + model.blocks = [table]; + return true; + }); + const DOMTable = editor.getDOMHelper().queryElements('table')[0]; + return DOMTable.getBoundingClientRect(); +} + +/* Used to specify mouse coordinates */ +export type Position = { + x: number; + y: number; +}; +/* Used to specify the direction of the resize */ +export type resizeDirection = 'horizontal' | 'vertical' | 'both'; + +/* IDs for the resizers */ +const VERTICAL_RESIZER_ID = 'verticalResizer'; +const HORIZONTAL_RESIZER_ID = 'horizontalResizer'; +const TABLE_RESIZER_ID = '_Table_Resizer'; + +/** + * Function to move and resize the table + * @param mouseStart The starting position of the mouse + * @param mouseEnd The ending position of the mouse + * @param resizeState The direction of the resize + * @param editor The editor to resize the table in + * @param handler The handler to handle the mouse events + * @param TEST_ID The id of the editor div + */ +export function moveAndResize( + mouseStart: Position, + mouseEnd: Position, + resizeState: resizeDirection, + editor: IEditor, + handler: Record, + TEST_ID: string +) { + const editorDiv = editor.getDocument().getElementById(TEST_ID); + let resizerId: string; + switch (resizeState) { + case 'both': + resizerId = TABLE_RESIZER_ID; + break; + case 'horizontal': + resizerId = HORIZONTAL_RESIZER_ID; + break; + case 'vertical': + resizerId = VERTICAL_RESIZER_ID; + break; + default: + resizerId = ''; + } + + // Move mouse to show resizer + const mouseMoveEvent = new MouseEvent('mousemove', { + clientX: mouseStart.x, + clientY: mouseStart.y, + }); + handler.mousemove(mouseMoveEvent); + + let resizer = editor.getDocument().getElementById(resizerId); + if (!!resizer && editorDiv) { + const tableBeforeClick = getTableRectSet(getCurrentTable(editor)); + // Click on the resizer to start resizing + const mouseClickEvent = new MouseEvent('mousedown', { + clientX: mouseStart.x, + clientY: mouseStart.y, + }); + resizer.dispatchEvent(mouseClickEvent); + const tableAfterClick = getTableRectSet(getCurrentTable(editor)); + + // Validate the table doesn't shift after clicking on the resizer + runTableShapeTest(tableBeforeClick, tableAfterClick); + + // Move mouse and resize + const mouseMoveResize = new MouseEvent('mousemove', { + clientX: mouseEnd.x, + clientY: mouseEnd.y, + }); + + editorDiv.dispatchEvent(mouseMoveResize); + handler.mousemove(mouseMoveResize); + + // Release mouse and stop resizing + const mouseMoveEndEvent = new MouseEvent('mouseup'); + editorDiv.dispatchEvent(mouseMoveEndEvent); + } +} + +/** + * Function to ckeck if the table rects are the same + * @param tableRectSet1 The first set of table rects + * @param tableRectSet2 The second set of table rects + */ +function runTableShapeTest(tableRectSet1: DOMRect[], tableRectSet2: DOMRect[]) { + expect(tableRectSet1.length).toBe(tableRectSet2.length); + const isSameRect = (rect1: DOMRect, rect2: DOMRect): boolean => { + return ( + rect1.left == rect2.left && + rect1.right == rect2.right && + rect1.top == rect2.top && + rect1.bottom == rect2.bottom + ); + }; + tableRectSet1.forEach((rect, i) => { + expect(isSameRect(rect, tableRectSet2[i])).toBe(true); + }); +} + +/** + * Get all rects from a table + * @param table The table to get the rects from + * @returns The set of rects for the table, first the whole table rect and then the cell rects + */ +export function getTableRectSet(table: HTMLTableElement): DOMRect[] { + const rectSet: DOMRect[] = []; + if (!!table) { + rectSet.push(table.getBoundingClientRect()); + } + for (let i = 0; i < table.rows.length; i++) { + for (let j = 0; j < table.rows[i].cells.length; j++) { + rectSet.push(table.rows[i].cells[j].getBoundingClientRect()); + } + } + return rectSet; +} diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/cellResizerTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/cellResizerTest.ts new file mode 100644 index 00000000000..d1a47fbc6b5 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/cellResizerTest.ts @@ -0,0 +1,157 @@ +import { ContentModelTable, DOMEventHandlerFunction, IEditor } from 'roosterjs-content-model-types'; +import { getModelTable } from './tableData'; +import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; +import { + afterTableTest, + beforeTableTest, + getCellRect, + getCurrentTable, + initialize, + moveAndResize, +} from './TableEditTestHelper'; + +describe('Cell Resizer tests', () => { + let editor: IEditor; + let plugin: TableEditPlugin; + const TEST_ID = 'cellResizerTest'; + let handler: Record; + + beforeEach(() => { + const setup = beforeTableTest(TEST_ID); + editor = setup.editor; + plugin = setup.plugin; + handler = setup.handler; + }); + + afterEach(() => { + afterTableTest(editor, plugin, TEST_ID); + }); + + /************************ Resizing row related tests ************************/ + + function resizeRowTest( + table: ContentModelTable, + growth: number, + cellRow: number, + cellColumn: number + ) { + initialize(editor, table); + const delta = 50 * growth; + const cellRect = getCellRect(editor, cellRow, cellColumn); + const targetPos: number = cellRect.bottom + delta; + + const beforeHeight = getCurrentTable(editor).rows[cellRow].getBoundingClientRect().height; + moveAndResize( + { x: cellRect.left + cellRect.width / 2, y: cellRect.bottom }, + { x: cellRect.left + cellRect.width / 2, y: targetPos }, + 'horizontal', + editor, + handler, + TEST_ID + ); + const afterHeight = getCurrentTable(editor).rows[cellRow].getBoundingClientRect().height; + + growth > 0 + ? expect(afterHeight).toBeGreaterThan(beforeHeight) + : expect(afterHeight).toBeLessThan(beforeHeight); + } + + it('increases the height of the first row', () => { + resizeRowTest(getModelTable(), 1, 0, 0); + }); + + it('increases the height of the last row', () => { + const MODEL_TABLE = getModelTable(); + resizeRowTest(MODEL_TABLE, 1, MODEL_TABLE.rows.length - 1, MODEL_TABLE.widths.length - 1); + }); + + it('decreases the height of the first row', () => { + resizeRowTest(getModelTable(), -1, 0, 0); + }); + + it('decreases the height of the last row', () => { + const MODEL_TABLE = getModelTable(); + resizeRowTest(MODEL_TABLE, -1, MODEL_TABLE.rows.length - 1, MODEL_TABLE.widths.length - 1); + }); + + /************************ Resizing column related tests ************************/ + + function resizeColumnTest( + table: ContentModelTable, + direction: number, + cellRow: number, + cellColumn: number + ) { + initialize(editor, table); + const delta = 20 * direction; + const cellRect = getCellRect(editor, cellRow, cellColumn); + const targetPos: number = cellRect.right + delta; + + const beforeWidth = getCurrentTable(editor).rows[cellRow].cells[ + cellColumn + ].getBoundingClientRect().width; + const beforeNextWidth = + cellColumn < table.widths.length - 1 + ? getCurrentTable(editor).rows[cellRow].cells[ + cellColumn + 1 + ].getBoundingClientRect().width + : undefined; + + moveAndResize( + { x: cellRect.right, y: cellRect.top + cellRect.height / 2 }, + { x: targetPos, y: cellRect.top + cellRect.height / 2 }, + 'vertical', + editor, + handler, + TEST_ID + ); + + const afterWidth = getCurrentTable(editor).rows[cellRow].cells[ + cellColumn + ].getBoundingClientRect().width; + const afterNextWidth = + cellColumn < table.widths.length - 1 + ? getCurrentTable(editor).rows[cellRow].cells[ + cellColumn + 1 + ].getBoundingClientRect().width + : undefined; + + direction > 0 + ? expect(afterWidth).toBeGreaterThan(beforeWidth) + : expect(afterWidth).toBeLessThan(beforeWidth); + + if (beforeNextWidth && afterNextWidth) { + direction > 0 + ? expect(afterNextWidth).toBeLessThan(beforeNextWidth) + : expect(afterNextWidth).toBeGreaterThan(beforeNextWidth); + } + } + + it('increases the width of the first column', () => { + resizeColumnTest(getModelTable(), 1, 0, 0); + }); + + it('increases the width of the last column', () => { + const MODEL_TABLE = getModelTable(); + resizeColumnTest( + MODEL_TABLE, + 1, + MODEL_TABLE.rows.length - 1, + MODEL_TABLE.widths.length - 1 + ); + }); + + it('decreases the width of the first column', () => { + resizeColumnTest(getModelTable(), -1, 0, 0); + }); + + it('decreases the width of the last column', () => { + const MODEL_TABLE = getModelTable(); + resizeColumnTest( + MODEL_TABLE, + -1, + MODEL_TABLE.rows.length - 1, + MODEL_TABLE.widths.length - 1 + ); + }); +}); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableData.ts b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableData.ts new file mode 100644 index 00000000000..802f58c1713 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableData.ts @@ -0,0 +1,862 @@ +import { ContentModelTable } from 'roosterjs-content-model-types'; + +export const WORD_TABLE = + '
dsfsdf2342323423
Sdf323234234234234
23232343242erfwfwf
'; +export const EXCEL_TABLE = + '
111222333
222333444
777666555
'; +export const DEFAULT_TABLE = + '









'; +export const DEFAULT_TABLE_MERGED = + '
















'; + +/** + * Regular 3 x 3 Table + */ +export function getModelTable(): ContentModelTable { + /* + * —————————————— + * | a1 | b1 | c1 | + * —————————————— + * | a2 | b2 | c2 | + * —————————————— + * | a3 | b3 | c3 | + * —————————————— + */ + return { + blockType: 'Table', + rows: [ + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'b1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'c1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a2', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'b2', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'c2', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a3', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'b3', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'c3', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + ], + format: {}, + widths: [50, 50, 50], + dataset: {}, + }; +} + +/** + * 3 x 3 Table with merged central column + */ +export function getMergedCenterColumnTable(): ContentModelTable { + /* + * —————————————— + * | a1 | | c1 | + * ———— ———— + * | a2 | b1 | c2 | + * ———— ———— + * | a3 | | c3 | + * —————————————— + */ + return { + blockType: 'Table', + rows: [ + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'b1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'c1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a2', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [], + format: {}, + spanLeft: false, + spanAbove: true, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'c2', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a3', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [], + format: {}, + spanLeft: false, + spanAbove: true, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'c3', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + ], + format: {}, + widths: [50, 50, 50], + dataset: {}, + }; +} + +/** + * 3 x 3 Table with merged central row + */ +export function getMergedCenterRowTable(): ContentModelTable { + /* + * —————————————— + * | a1 | b1 | c1 | + * —————————————— + * | a2 | + * —————————————— + * | a3 | b3 | c3 | + * —————————————— + */ + return { + blockType: 'Table', + rows: [ + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'b1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'c1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a2', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [], + format: {}, + spanLeft: true, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [], + format: {}, + spanLeft: true, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a3', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'b3', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'c3', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + ], + format: {}, + widths: [50, 50, 50], + dataset: {}, + }; +} + +/** + * 2 x 2 Table with merged top row + */ +export function getMergedTopRowTable(): ContentModelTable { + /* + * ————————— + * | a1 | + * ————————— + * | a2 | b2 | + * ————————— + */ + return { + blockType: 'Table', + rows: [ + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [], + format: {}, + spanLeft: true, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a2', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'b2', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + ], + format: {}, + widths: [50, 50], + dataset: {}, + }; +} + +/** + * 2 x 2 Table with merged first column + */ +export function getMergedFirstColumnTable(): ContentModelTable { + /* + * ————————— + * | a1 | b1 | + * ———— + * | | b2 | + * ————————— + */ + return { + blockType: 'Table', + rows: [ + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'b1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + { + height: 50, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [], + format: {}, + spanLeft: false, + spanAbove: true, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'b2', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + ], + format: {}, + widths: [50, 50], + dataset: {}, + }; +} diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts new file mode 100644 index 00000000000..664ea407aed --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts @@ -0,0 +1,229 @@ +import * as TestHelper from '../TestHelper'; +import createElement from '../../lib/pluginUtils/CreateElement/createElement'; +import { getModelTable } from './tableData'; +import { IEditor } from 'roosterjs-content-model-types'; +import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; + +describe('TableEditPlugin', () => { + let editor: IEditor; + let plugin: TableEditPlugin; + const TEST_ID = 'inserterTest'; + + let mouseOutListener: undefined | ((this: HTMLElement, ev: MouseEvent) => any); + + beforeEach(() => { + editor = TestHelper.initEditor(TEST_ID); + plugin = new TableEditPlugin(); + + spyOn(editor, 'getScrollContainer').and.returnValue(({ + addEventListener: ( + type: K, + listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, + options?: boolean | AddEventListenerOptions + ) => { + if (type == 'mouseout') { + mouseOutListener = listener as (this: HTMLElement, ev: MouseEvent) => any; + } + }, + removeEventListener: ( + type: K, + listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, + options?: boolean | EventListenerOptions + ) => { + if (type == 'mouseout') { + mouseOutListener = undefined; + } + }, + })); + plugin.initialize(editor); + }); + + afterEach(() => { + plugin.dispose(); + editor.dispose(); + TestHelper.removeElement(TEST_ID); + document.body = document.createElement('body'); + }); + + it('setTableEditor - Dismiss table editor on mouse out', () => { + const ele = createElement( + { + tag: 'div', + children: [ + { + tag: 'div', + children: ['test'], + }, + ], + }, + editor.getDocument() + ); + editor.formatContentModel(model => { + model.blocks = [getModelTable()]; + return true; + }); + + const table = editor.getDOMHelper().queryElements('table')[0]; + + spyOn(plugin, 'setTableEditor').and.callThrough(); + + plugin.setTableEditor(table); + + if (mouseOutListener) { + const boundedListener = mouseOutListener.bind(ele); + ele && spyOn(ele, 'contains').and.returnValue(false); + boundedListener(({ + currentTarget: ele, + relatedTarget: ele, + })); + + expect(plugin.setTableEditor).toHaveBeenCalledWith(null); + } + }); + + it('setTableEditor - Do not dismiss table editor on mouse out, related target is contained in scroll container', () => { + const ele = createElement( + { + tag: 'div', + children: [ + { + tag: 'div', + children: ['test'], + }, + ], + }, + editor.getDocument() + ); + editor.formatContentModel(model => { + model.blocks = [getModelTable()]; + return true; + }); + + const table = editor.getDOMHelper().queryElements('table')[0]; + + spyOn(plugin, 'setTableEditor').and.callThrough(); + + plugin.setTableEditor(table); + + if (mouseOutListener) { + const boundedListener = mouseOutListener.bind(ele); + ele && spyOn(ele, 'contains').and.returnValue(true); + boundedListener(({ + currentTarget: ele, + relatedTarget: ele, + })); + + expect(plugin.setTableEditor).not.toHaveBeenCalledWith(null); + } + }); + + it('setTableEditor - Do not dismiss table editor on mouse out, table editor not', () => { + const ele = createElement( + { + tag: 'div', + children: [ + { + tag: 'div', + children: ['test'], + }, + ], + }, + editor.getDocument() + ); + editor.formatContentModel(model => { + model.blocks = [getModelTable()]; + return true; + }); + + spyOn(plugin, 'setTableEditor').and.callThrough(); + + if (mouseOutListener) { + const boundedListener = mouseOutListener.bind(ele); + ele && spyOn(ele, 'contains').and.returnValue(false); + boundedListener(({ + currentTarget: ele, + relatedTarget: ele, + })); + + expect(plugin.setTableEditor).not.toHaveBeenCalledWith(null); + } + }); + + it('setTableEditor - Do not dismiss table editor on mouse out, related target null', () => { + const ele = createElement( + { + tag: 'div', + children: [ + { + tag: 'div', + children: ['test'], + }, + ], + }, + editor.getDocument() + ); + editor.formatContentModel(model => { + model.blocks = [getModelTable()]; + return true; + }); + + const table = editor.getDOMHelper().queryElements('table')[0]; + + spyOn(plugin, 'setTableEditor').and.callThrough(); + + plugin.setTableEditor(table); + + if (mouseOutListener) { + const boundedListener = mouseOutListener.bind(ele); + ele && spyOn(ele, 'contains').and.returnValue(false); + boundedListener(({ + currentTarget: ele, + relatedTarget: null, + })); + + expect(plugin.setTableEditor).not.toHaveBeenCalledWith(null); + } + }); + + it('setTableEditor - Do not dismiss table editor on mouse out, currentTarget null', () => { + const ele = createElement( + { + tag: 'div', + children: [ + { + tag: 'div', + children: ['test'], + }, + ], + }, + editor.getDocument() + ); + editor.formatContentModel(model => { + model.blocks = [getModelTable()]; + return true; + }); + + const table = editor.getDOMHelper().queryElements('table')[0]; + + spyOn(plugin, 'setTableEditor').and.callThrough(); + + plugin.setTableEditor(table); + + if (mouseOutListener) { + const boundedListener = mouseOutListener.bind(ele); + ele && spyOn(ele, 'contains').and.returnValue(false); + boundedListener(({ + currentTarget: null, + relatedTarget: ele, + })); + + expect(plugin.setTableEditor).not.toHaveBeenCalledWith(null); + } + }); + + it('returns the actual plugin name', () => { + const expectedName = 'TableEdit'; + const pluginName = plugin.getName(); + expect(pluginName).toBe(expectedName); + }); +}); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableInserterTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableInserterTest.ts new file mode 100644 index 00000000000..17901bace20 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableInserterTest.ts @@ -0,0 +1,115 @@ +import { DOMEventHandlerFunction, IEditor } from 'roosterjs-content-model-types'; +import { getMergedFirstColumnTable, getMergedTopRowTable, getModelTable } from './tableData'; +import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; +import { + Position, + afterTableTest, + beforeTableTest, + getCurrentTable, + getTableColumns, + getTableRows, + initialize, +} from './TableEditTestHelper'; + +const VERTICAL_INSERTER_ID = 'verticalInserter'; +const HORIZONTAL_INSERTER_ID = 'horizontalInserter'; + +describe('Table Inserter tests', () => { + let editor: IEditor; + let plugin: TableEditPlugin; + const insideTheOffset = 5; + const TEST_ID = 'inserterTest'; + let handler: Record; + + beforeEach(() => { + const setup = beforeTableTest(TEST_ID); + editor = setup.editor; + plugin = setup.plugin; + handler = setup.handler; + }); + + afterEach(() => { + afterTableTest(editor, plugin, TEST_ID); + }); + + function isClickInsideInserter(click: Position, rect: DOMRect) { + return ( + click.x >= rect.left && + click.x <= rect.right && + click.y >= rect.top && + click.y <= rect.bottom + ); + } + + function runInserterTest(inserterType: string, mouseEnd: Position) { + handler.mousemove( + new MouseEvent('mousemove', { + clientX: mouseEnd.x, + clientY: mouseEnd.y, + }) + ); + + const inserter = editor.getDocument().getElementById(inserterType); + if (!!inserter) { + const inserterRect = inserter.getBoundingClientRect(); + if (!isClickInsideInserter(mouseEnd, inserterRect)) { + // Inserter is visible, but pointer is not over it + return 'not clickable'; + } + const table = getCurrentTable(editor); + const rows = getTableRows(table); + const cols = getTableColumns(table); + inserter.dispatchEvent(new MouseEvent('click')); + const newRows = getTableRows(table); + const newCols = getTableColumns(table); + expect(newRows).toBe(inserterType == VERTICAL_INSERTER_ID ? rows : rows + 1); + expect(newCols).toBe(inserterType == HORIZONTAL_INSERTER_ID ? cols : cols + 1); + } + return !!inserter ? 'found' : 'not found'; + } + + it('adds a new column if the vertical inserter is detected and clicked', () => { + const rect = initialize(editor, getModelTable()); + const inserterFound = runInserterTest(VERTICAL_INSERTER_ID, { + x: rect.right, + y: rect.top - insideTheOffset, + }); + expect(inserterFound).toBe('found'); + }); + + it('adds a new row if the horizontal inserter is detected and clicked', () => { + const rect = initialize(editor, getModelTable()); + const inserterFound = runInserterTest(HORIZONTAL_INSERTER_ID, { + x: rect.left - insideTheOffset, + y: rect.bottom, + }); + expect(inserterFound).toBe('found'); + }); + + it('does not add inserter if top left corner hovered', () => { + const rect = initialize(editor, getModelTable()); + const inserterFound = runInserterTest(VERTICAL_INSERTER_ID, { + x: rect.left - insideTheOffset, + y: rect.top - insideTheOffset, + }); + expect(inserterFound).toBe('not found'); + }); + + it('does not add new column if top middle clicked on merged top row', () => { + const rect = initialize(editor, getMergedTopRowTable()); + const inserterFound = runInserterTest(VERTICAL_INSERTER_ID, { + x: (rect.right - rect.left) / 2 + 10, + y: rect.top - insideTheOffset, + }); + expect(inserterFound).toBe('not clickable'); + }); + + it('does not add new row if left middle clicked on merged first column', () => { + const rect = initialize(editor, getMergedFirstColumnTable()); + const inserterFound = runInserterTest(HORIZONTAL_INSERTER_ID, { + x: rect.left - insideTheOffset, + y: (rect.bottom - rect.top) / 2, + }); + expect(inserterFound).toBe('not clickable'); + }); +}); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts new file mode 100644 index 00000000000..2a9a49448a6 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts @@ -0,0 +1,230 @@ +import createTableMover from '../../lib/tableEdit/editors/features/TableMover'; +import TableEditor from '../../lib/tableEdit/editors/TableEditor'; +import { Editor } from 'roosterjs-content-model-core'; +import { EditorOptions, IEditor } from 'roosterjs-content-model-types'; +import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; + +describe('Table Mover Tests', () => { + let editor: IEditor; + let id = 'tableSelectionContainerId'; + let targetId = 'tableSelectionTestId'; + let tableEdit: TableEditPlugin; + let node: HTMLDivElement; + + beforeEach(() => { + document.body.innerHTML = ''; + node = document.createElement('div'); + node.id = id; + document.body.insertBefore(node, document.body.childNodes[0]); + tableEdit = new TableEditPlugin(); + + let options: EditorOptions = { + plugins: [tableEdit], + initialModel: { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Table', + rows: [ + { + height: 20, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a1', + format: {}, + }, + ], + format: {}, + isImplicit: true, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'z1', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + { + height: 20, + format: {}, + cells: [ + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'a2', + format: {}, + }, + ], + format: {}, + isImplicit: true, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + { + blockGroupType: 'TableCell', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'z2', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + spanLeft: false, + spanAbove: false, + isHeader: false, + dataset: {}, + }, + ], + }, + ], + format: { + id: `${targetId}`, + }, + widths: [10, 10], + dataset: {}, + }, + ], + format: {}, + }, + }; + + editor = new Editor(node, options); + }); + + afterEach(() => { + editor.dispose(); + const div = document.getElementById(id); + div?.parentNode?.removeChild(div); + node.parentElement?.removeChild(node); + }); + + it('Display component on mouse move inside table', () => { + runTest(0, true); + }); + + it('Do not display component, top of table is no visible in the container.', () => { + //Arrange + runTest(15, false); + }); + + it('Do not display component, Top of table is no visible in the scroll container.', () => { + //Arrange + const scrollContainer = document.createElement('div'); + document.body.insertBefore(scrollContainer, document.body.childNodes[0]); + scrollContainer.append(node); + spyOn(editor, 'getScrollContainer').and.returnValue(scrollContainer); + + runTest(15, false); + }); + + it('Display component, Top of table is visible in the scroll container scrolled down.', () => { + //Arrange + const scrollContainer = document.createElement('div'); + scrollContainer.innerHTML = '
'; + document.body.insertBefore(scrollContainer, document.body.childNodes[0]); + scrollContainer.append(node); + spyOn(editor, 'getScrollContainer').and.returnValue(scrollContainer); + + runTest(0, true); + }); + + it('On click event', () => { + const table = document.getElementById(targetId) as HTMLTableElement; + + const tableEditor = new TableEditor(editor, table, () => true); + + tableEditor.onSelect(table); + + const selection = editor.getDOMSelection(); + expect(selection?.type).toBe('table'); + if (selection?.type == 'table') { + expect(selection).toEqual({ + table, + firstRow: 0, + firstColumn: 0, + lastRow: 1, + lastColumn: 1, + type: 'table', + }); + } + }); + + function runTest(scrollTop: number, isNotNull: boolean | null) { + //Arrange + node.style.height = '10px'; + node.style.overflowX = 'auto'; + node.scrollTop = scrollTop; + const target = document.getElementById(targetId); + editor.focus(); + + //Act + const result = createTableMover( + target as HTMLTableElement, + editor, + false, + () => {}, + () => () => {}, + node + ); + + //Assert + if (!isNotNull) { + expect(result).toBeNull(); + } else { + expect(result).toBeDefined(); + } + } +}); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts new file mode 100644 index 00000000000..d4bba0575ab --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts @@ -0,0 +1,197 @@ +import { getModelTable } from './tableData'; +import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; +import { + ContentModelTable, + DOMEventHandlerFunction, + IEditor, + PluginEvent, +} from 'roosterjs-content-model-types'; +import { + Position, + afterTableTest, + beforeTableTest, + getCellRect, + getCurrentTable, + getTableRectSet, + initialize, + moveAndResize, + resizeDirection, +} from './TableEditTestHelper'; + +const TABLE_RESIZER_ID = '_Table_Resizer'; + +describe('Table Resizer tests', () => { + let editor: IEditor; + let plugin: TableEditPlugin; + const TEST_ID = 'resizerTest'; + let handler: Record; + + beforeEach(() => { + const setup = beforeTableTest(TEST_ID); + editor = setup.editor; + plugin = setup.plugin; + handler = setup.handler; + }); + + afterEach(() => { + afterTableTest(editor, plugin, TEST_ID); + }); + + /************************** Resizier removing tests **************************/ + + function removeResizerTest(pluginEvent: PluginEvent) { + let resizer: HTMLElement | null = null; + plugin.initialize(editor); + initialize(editor, getModelTable()); + const cellRect = getCellRect(editor, 0, 0); + handler.mousemove( + new MouseEvent('mousemove', { clientX: cellRect?.right, clientY: cellRect?.bottom }) + ); + resizer = editor.getDocument().getElementById(TABLE_RESIZER_ID); + expect(!!resizer).toBe(true); + plugin.onPluginEvent(pluginEvent); + resizer = editor.getDocument().getElementById(TABLE_RESIZER_ID); + expect(!!resizer).toBe(false); + } + + it('removes table resizer on input', () => { + const pluginEvent: PluginEvent = { + eventType: 'input', + rawEvent: null, + }; + removeResizerTest(pluginEvent); + }); + + it('removes table resizer on content change', () => { + const pluginEvent: PluginEvent = { + eventType: 'contentChanged', + source: null, + }; + removeResizerTest(pluginEvent); + }); + + it('removes table resizer on scrolling', () => { + const pluginEvent: PluginEvent = { + eventType: 'scroll', + scrollContainer: editor.getDocument().body as HTMLElement, + rawEvent: null, + }; + removeResizerTest(pluginEvent); + }); + + /************************ Resizing table related tests ************************/ + + function resizeWholeTableTest( + table: ContentModelTable, + growth: number, + direction: resizeDirection + ) { + const delta = 20 * growth; + const tableRect = initialize(editor, table); + const mouseStart = { x: tableRect.right + 3, y: tableRect.bottom + 3 }; + let mouseEnd: Position = { x: 0, y: 0 }; + switch (direction) { + case 'horizontal': + mouseEnd = { x: tableRect.right + 3 + delta, y: tableRect.bottom + 3 }; + break; + case 'vertical': + mouseEnd = { x: tableRect.right + 3, y: tableRect.bottom + 3 + delta }; + break; + case 'both': + mouseEnd = { x: tableRect.right + 3 + delta, y: tableRect.bottom + 3 + delta }; + break; + } + const beforeSize = getTableRectSet(getCurrentTable(editor)); + moveAndResize(mouseStart, mouseEnd, 'both', editor, handler, TEST_ID); + const afterSize = getTableRectSet(getCurrentTable(editor)); + compareTableRects(beforeSize, afterSize, growth, direction); + } + + function verifyTableRectChange( + rect1: DOMRect, + rect2: DOMRect, + growth: number, + direction: resizeDirection + ): boolean { + switch (direction) { + case 'horizontal': + return growth > 0 ? rect1.width < rect2.width : rect1.width > rect2.width; + case 'vertical': + return growth > 0 ? rect1.height < rect2.height : rect1.height > rect2.height; + case 'both': + return growth > 0 + ? rect1.width < rect2.width && rect1.height < rect2.height + : rect1.width > rect2.width && rect1.height > rect2.height; + } + } + + function verifyCellRectChange( + rect1: DOMRect, + rect2: DOMRect, + growth: number, + direction: resizeDirection + ): boolean { + switch (direction) { + case 'horizontal': + return rect1.top == rect2.top && rect1.bottom == rect2.bottom && growth > 0 + ? rect1.left <= rect2.left && rect1.right <= rect2.right + : rect1.left >= rect2.left && rect1.right >= rect2.right; + case 'vertical': + return rect1.left == rect2.left && rect1.right == rect2.right && growth > 0 + ? rect1.top <= rect2.top && rect1.bottom <= rect2.bottom + : rect1.top >= rect2.top && rect1.bottom >= rect2.bottom; + case 'both': + return growth > 0 + ? rect1.left <= rect2.left && + rect1.right <= rect2.right && + rect1.top <= rect2.top && + rect1.bottom <= rect2.bottom + : rect1.left >= rect2.left && + rect1.right >= rect2.right && + rect1.top >= rect2.top && + rect1.bottom >= rect2.bottom; + } + } + + function compareTableRects( + beforeTableRectSet1: DOMRect[], + afterTableRectSet2: DOMRect[], + growth: number, + direction: resizeDirection + ) { + expect(beforeTableRectSet1.length).toBe(afterTableRectSet2.length); + beforeTableRectSet1.forEach((rect, i) => { + i == 0 + ? expect( + verifyTableRectChange(rect, afterTableRectSet2[i], growth, direction) + ).toBe(true) // Verify a change to whole table size + : expect(verifyCellRectChange(rect, afterTableRectSet2[i], growth, direction)).toBe( + true // Verify a change to each cell size + ); + }); + } + + it('increases the width of the table', () => { + resizeWholeTableTest(getModelTable(), 1, 'horizontal'); + }); + + it('increases the height of the table', () => { + resizeWholeTableTest(getModelTable(), 1, 'vertical'); + }); + + it('increases the width and height of the table', () => { + resizeWholeTableTest(getModelTable(), 1, 'both'); + }); + + it('decreases the width of the table', () => { + resizeWholeTableTest(getModelTable(), -1, 'horizontal'); + }); + + it('decreases the height of the table', () => { + resizeWholeTableTest(getModelTable(), -1, 'vertical'); + }); + + it('decreases the width and height of the table', () => { + resizeWholeTableTest(getModelTable(), -1, 'both'); + }); +}); From 0a9e2e87fa875d1c35194d28f06631f446fc3947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Thu, 29 Feb 2024 15:47:15 -0300 Subject: [PATCH 32/80] WIP --- .../ContentModelEditorOptionsPlugin.ts | 11 +++++++- .../edit/inputSteps/handleEnterOnQuotes.ts | 28 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnQuotes.ts diff --git a/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts b/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts index 0f85a463869..3a130e73c80 100644 --- a/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts +++ b/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts @@ -20,6 +20,11 @@ const listFeatures = { indentWhenAltShiftRight: false, }; +const quoteFeatures = { + unquoteWhenBackspaceOnEmptyFirstLine: false, + unquoteWhenEnterOnEmptyLine: false, +}; + const initialState: BuildInPluginState = { pluginList: { contentEdit: true, @@ -38,7 +43,11 @@ const initialState: BuildInPluginState = { autoFormat: true, announce: true, }, - contentEditFeatures: { ...getDefaultContentEditFeatureSettings(), ...listFeatures }, + contentEditFeatures: { + ...getDefaultContentEditFeatureSettings(), + ...listFeatures, + ...quoteFeatures, + }, defaultFormat: {}, linkTitle: 'Ctrl+Click to follow the link:' + UrlPlaceholder, watermarkText: 'Type content here ...', diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnQuotes.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnQuotes.ts new file mode 100644 index 00000000000..56c40e3f169 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnQuotes.ts @@ -0,0 +1,28 @@ +import { getClosestAncestorBlockGroupIndex } from 'roosterjs-content-model-core'; +import {} from 'roosterjs-content-model-dom'; +import type { DeleteSelectionStep } from 'roosterjs-content-model-types'; + +/** + * @internal + */ +export const handleEnterOnList: DeleteSelectionStep = context => { + const { deleteResult } = context; + if ( + deleteResult == 'nothingToDelete' || + deleteResult == 'notDeleted' || + deleteResult == 'range' + ) { + const { insertPoint, formatContext } = context; + const { path } = insertPoint; + const rawEvent = formatContext?.rawEvent; + const index = getClosestAncestorBlockGroupIndex( + path, + ['FormatContainer'], + ['TableCell', 'ListItem'] + ); + const quote = path[index]; + if (quote.blockGroupType === 'FormatContainer' && quote.tagName == 'blockquote') { + rawEvent?.preventDefault(); + } + } +}; From e9ae3a331ef9ffc25bcd506603995013c7f6d5e4 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Thu, 29 Feb 2024 13:26:34 -0800 Subject: [PATCH 33/80] Port ShortcutPlugin (#2427) * Port ShortcutPlugin * fix build * add test * add test * add test * fix build * fix build * improve * fix comment * fix test --- .../controls/StandaloneEditorMainPane.tsx | 10 +- .../roosterjs-content-model-core/lib/index.ts | 1 + .../publicApi/domUtils/cacheGetEventData.ts | 23 + .../domUtils/cacheGetEventDataTest.ts | 31 + .../lib/index.ts | 17 + .../lib/shortcut/ShortcutCommand.ts | 50 ++ .../lib/shortcut/ShortcutPlugin.ts | 150 +++++ .../lib/shortcut/shortcuts.ts | 195 ++++++ .../test/shortcut/ShortcutPluginTest.ts | 589 ++++++++++++++++++ .../lib/corePlugins/BridgePlugin.ts | 50 +- .../test/corePlugins/BridgePluginTest.ts | 14 +- 11 files changed, 1104 insertions(+), 26 deletions(-) create mode 100644 packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/cacheGetEventData.ts create mode 100644 packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/cacheGetEventDataTest.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts diff --git a/demo/scripts/controls/StandaloneEditorMainPane.tsx b/demo/scripts/controls/StandaloneEditorMainPane.tsx index 6aeb849214e..73c420f20f8 100644 --- a/demo/scripts/controls/StandaloneEditorMainPane.tsx +++ b/demo/scripts/controls/StandaloneEditorMainPane.tsx @@ -17,7 +17,6 @@ import { alignCenterButton } from './ribbonButtons/contentModel/alignCenterButto import { alignJustifyButton } from './ribbonButtons/contentModel/alignJustifyButton'; import { alignLeftButton } from './ribbonButtons/contentModel/alignLeftButton'; import { alignRightButton } from './ribbonButtons/contentModel/alignRightButton'; -import { AutoFormatPlugin, EditPlugin, TableEditPlugin } from 'roosterjs-content-model-plugins'; import { backgroundColorButton } from './ribbonButtons/contentModel/backgroundColorButton'; import { blockQuoteButton } from './ribbonButtons/contentModel/blockQuoteButton'; import { boldButton } from './ribbonButtons/contentModel/boldButton'; @@ -76,6 +75,12 @@ import { trustedHTMLHandler } from '../utils/trustedHTMLHandler'; import { underlineButton } from './ribbonButtons/contentModel/underlineButton'; import { undoButton } from './ribbonButtons/contentModel/undoButton'; import { zoom } from './ribbonButtons/contentModel/zoom'; +import { + AutoFormatPlugin, + EditPlugin, + ShortcutPlugin, + TableEditPlugin, +} from 'roosterjs-content-model-plugins'; import { ContentModelSegmentFormat, IEditor, @@ -165,6 +170,7 @@ class ContentModelEditorMainPane extends MainPaneBase private contentModelRibbonPlugin: RibbonPlugin; private contentAutoFormatPlugin: AutoFormatPlugin; private snapshotPlugin: ContentModelSnapshotPlugin; + private shortcutPlugin: ShortcutPlugin; private formatPainterPlugin: ContentModelFormatPainterPlugin; private tableEditPlugin: TableEditPlugin; private snapshots: Snapshots; @@ -252,6 +258,7 @@ class ContentModelEditorMainPane extends MainPaneBase this.contentModelPanePlugin = new ContentModelPanePlugin(); this.contentModelEditPlugin = new EditPlugin(); this.contentAutoFormatPlugin = new AutoFormatPlugin(); + this.shortcutPlugin = new ShortcutPlugin(); this.contentModelRibbonPlugin = new ContentModelRibbonPlugin(); this.formatPainterPlugin = new ContentModelFormatPainterPlugin(); this.tableEditPlugin = new TableEditPlugin(); @@ -350,6 +357,7 @@ class ContentModelEditorMainPane extends MainPaneBase this.tableEditPlugin, this.contentModelEditPlugin, this.contentAutoFormatPlugin, + this.shortcutPlugin, ]} defaultSegmentFormat={defaultFormat} inDarkMode={this.state.isDarkMode} diff --git a/packages-content-model/roosterjs-content-model-core/lib/index.ts b/packages-content-model/roosterjs-content-model-core/lib/index.ts index 739257e9367..6407e084e5f 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/index.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/index.ts @@ -43,6 +43,7 @@ export { isPunctuation, isSpace, normalizeText } from './publicApi/domUtils/stri export { parseTableCells, createTableRanges } from './publicApi/domUtils/tableCellUtils'; export { getSegmentTextFormat } from './publicApi/domUtils/getSegmentTextFormat'; export { readFile } from './publicApi/domUtils/readFile'; +export { cacheGetEventData } from './publicApi/domUtils/cacheGetEventData'; export { undo } from './publicApi/undo/undo'; export { redo } from './publicApi/undo/redo'; diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/cacheGetEventData.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/cacheGetEventData.ts new file mode 100644 index 00000000000..2e99ef02add --- /dev/null +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/cacheGetEventData.ts @@ -0,0 +1,23 @@ +import type { PluginEvent } from 'roosterjs-content-model-types'; + +/** + * Gets the cached event data by cache key from event object if there is already one. + * Otherwise, call getter function to create one, and cache it. + * @param event The event object + * @param key Cache key string, need to be unique + * @param getter Getter function to get the object when it is not in cache yet + */ +export function cacheGetEventData( + event: E, + key: string, + getter: (event: E) => T +): T { + const result = + event.eventDataCache && event.eventDataCache.hasOwnProperty(key) + ? event.eventDataCache[key] + : getter(event); + event.eventDataCache = event.eventDataCache || {}; + event.eventDataCache[key] = result; + + return result; +} diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/cacheGetEventDataTest.ts b/packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/cacheGetEventDataTest.ts new file mode 100644 index 00000000000..aa9c28f8732 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/cacheGetEventDataTest.ts @@ -0,0 +1,31 @@ +import { cacheGetEventData } from '../../../lib/publicApi/domUtils/cacheGetEventData'; +import { EditorReadyEvent } from 'roosterjs-content-model-types'; + +describe('cacheGetEventData', () => { + const cacheKey = '__Key'; + it('get cached data', () => { + const event: EditorReadyEvent = { + eventType: 'editorReady', + }; + + const mockedData = 'DATA'; + const mockedGetter = jasmine.createSpy('getter').and.returnValue(mockedData); + + const data = cacheGetEventData(event, cacheKey, mockedGetter); + + expect(data).toBe(mockedData); + expect(mockedGetter).toHaveBeenCalledTimes(1); + expect(mockedGetter).toHaveBeenCalledWith(event); + expect(event).toEqual({ + eventType: 'editorReady', + eventDataCache: { + [cacheKey]: mockedData, + }, + }); + + const data2 = cacheGetEventData(event, cacheKey, mockedGetter); + + expect(data2).toBe(mockedData); + expect(mockedGetter).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/index.ts b/packages-content-model/roosterjs-content-model-plugins/lib/index.ts index 1c3da4abc9d..0f75ee9b442 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/index.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/index.ts @@ -2,3 +2,20 @@ export { TableEditPlugin } from './tableEdit/TableEditPlugin'; export { PastePlugin } from './paste/PastePlugin'; export { EditPlugin } from './edit/EditPlugin'; export { AutoFormatPlugin, AutoFormatOptions } from './autoFormat/AutoFormatPlugin'; + +export { + ShortcutBold, + ShortcutItalic, + ShortcutUnderline, + ShortcutClearFormat, + ShortcutUndo, + ShortcutUndo2, + ShortcutRedo, + ShortcutRedoMacOS, + ShortcutBullet, + ShortcutNumbering, + ShortcutIncreaseFont, + ShortcutDecreaseFont, +} from './shortcut/shortcuts'; +export { ShortcutPlugin } from './shortcut/ShortcutPlugin'; +export { ShortcutKeyDefinition, ShortcutCommand } from './shortcut/ShortcutCommand'; diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts b/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts new file mode 100644 index 00000000000..2c2e4416712 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts @@ -0,0 +1,50 @@ +import type { IEditor } from 'roosterjs-content-model-types'; + +/** + * Definition of the shortcut key + */ +export interface ShortcutKeyDefinition { + /** + * Modifier key for this shortcut, allowed values are: + * ctrl: Ctrl key (or Meta key on MacOS) + * alt: Alt key + */ + modifierKey: 'ctrl' | 'alt'; + + /** + * Whether ALT key is required for this shortcut + */ + shiftKey: boolean; + + /** + * Key code for this shortcut. The value should be the value of KeyboardEvent.which + * We are still using key code here rather than key name (event.key) although event.which is deprecated because of globalization. + * For example, on US keyboard, Shift+Comma="<" but on Spanish keyboard it is ":" + * And we still want the shortcut key to be registered on the same key, in that case key name is different but key code keeps the same. + */ + which: number; +} + +/** + * Represents a command for shortcut + */ +export interface ShortcutCommand { + /** + * Definition of the shortcut key + */ + shortcutKey: ShortcutKeyDefinition; + + /** + * @optional Required environment for this command + * all: (Default) This feature is available for all environments + * mac: This feature is available on MacOS only + * nonMac: This feature is available on OS other than MacOS + */ + environment?: 'all' | 'mac' | 'nonMac'; + + /** + * The callback function to invoke when this shortcut is triggered + * @param editor The editor object + */ + onClick: (editor: IEditor) => void; +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts b/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts new file mode 100644 index 00000000000..9b7fe7a3f60 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts @@ -0,0 +1,150 @@ +import { cacheGetEventData } from 'roosterjs-content-model-core'; +import type { ShortcutCommand, ShortcutKeyDefinition } from './ShortcutCommand'; +import { + ShortcutBold, + ShortcutBullet, + ShortcutClearFormat, + ShortcutDecreaseFont, + ShortcutIncreaseFont, + ShortcutItalic, + ShortcutNumbering, + ShortcutRedo, + ShortcutRedoMacOS, + ShortcutUnderline, + ShortcutUndo, + ShortcutUndo2, +} from './shortcuts'; +import type { + EditorPlugin, + IEditor, + KeyDownEvent, + PluginEvent, +} from 'roosterjs-content-model-types'; + +const defaultShortcuts: ShortcutCommand[] = [ + ShortcutBold, + ShortcutItalic, + ShortcutUnderline, + ShortcutClearFormat, + ShortcutUndo, + ShortcutUndo2, + ShortcutRedo, + ShortcutRedoMacOS, + ShortcutBullet, + ShortcutNumbering, + ShortcutIncreaseFont, + ShortcutDecreaseFont, +]; +const CommandCacheKey = '__ShortcutCommandCache'; + +/** + * Shortcut plugin hook on the specified shortcut keys and trigger related format API + */ +export class ShortcutPlugin implements EditorPlugin { + private editor: IEditor | null = null; + private isMac = false; + + /** + * Create a new instance of ShortcutPlugin + * @param [shortcuts=defaultShortcuts] Allowed commands + */ + constructor(private shortcuts: ShortcutCommand[] = defaultShortcuts) {} + + /** + * Get name of this plugin + */ + getName() { + return 'Shortcut'; + } + + /** + * The first method that editor will call to a plugin when editor is initializing. + * It will pass in the editor instance, plugin should take this chance to save the + * editor reference so that it can call to any editor method or format API later. + * @param editor The editor object + */ + initialize(editor: IEditor) { + this.editor = editor; + this.isMac = !!this.editor.getEnvironment().isMac; + } + + /** + * The last method that editor will call to a plugin before it is disposed. + * Plugin can take this chance to clear the reference to editor. After this method is + * called, plugin should not call to any editor method since it will result in error. + */ + dispose() { + this.editor = null; + } + + /** + * Check if the plugin should handle the given event exclusively. + * Handle an event exclusively means other plugin will not receive this event in + * onPluginEvent method. + * If two plugins will return true in willHandleEventExclusively() for the same event, + * the final result depends on the order of the plugins are added into editor + * @param event The event to check: + */ + willHandleEventExclusively(event: PluginEvent) { + return ( + event.eventType == 'keyDown' && + (event.rawEvent.ctrlKey || event.rawEvent.altKey || event.rawEvent.metaKey) && + !!this.cacheGetCommand(event) + ); + } + + /** + * Core method for a plugin. Once an event happens in editor, editor will call this + * method of each plugin to handle the event as long as the event is not handled + * exclusively by another plugin. + * @param event The event to handle: + */ + onPluginEvent(event: PluginEvent) { + if (this.editor && event.eventType == 'keyDown') { + const command = this.cacheGetCommand(event); + + if (command) { + command.onClick(this.editor); + event.rawEvent.preventDefault(); + } + } + } + + private cacheGetCommand(event: KeyDownEvent) { + return cacheGetEventData(event, CommandCacheKey, event => { + const editor = this.editor; + + return ( + editor && + this.shortcuts.filter( + command => + this.matchOS(command.environment) && + this.matchShortcut(command.shortcutKey, event.rawEvent) + )[0] + ); + }); + } + + private matchOS(environment?: 'all' | 'mac' | 'nonMac') { + switch (environment) { + case 'mac': + return this.isMac; + + case 'nonMac': + return !this.isMac; + + default: + return true; + } + } + + private matchShortcut(shortcutKey: ShortcutKeyDefinition, event: KeyboardEvent) { + const { ctrlKey, altKey, shiftKey, which, metaKey } = event; + const ctrlOrMeta = this.isMac ? metaKey : ctrlKey; + const matchModifier = + (shortcutKey.modifierKey == 'ctrl' && ctrlOrMeta && !altKey) || + (shortcutKey.modifierKey == 'alt' && altKey && !ctrlOrMeta); + + return matchModifier && shiftKey == shortcutKey.shiftKey && shortcutKey.which == which; + } +} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts b/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts new file mode 100644 index 00000000000..8c2520485c4 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts @@ -0,0 +1,195 @@ +import { redo, undo } from 'roosterjs-content-model-core'; +import { + changeFontSize, + clearFormat, + toggleBold, + toggleBullet, + toggleItalic, + toggleNumbering, + toggleUnderline, +} from 'roosterjs-content-model-api'; +import type { ShortcutCommand } from './ShortcutCommand'; + +const enum Keys { + BACKSPACE = 8, + SPACE = 32, + B = 66, + I = 73, + U = 85, + Y = 89, + Z = 90, + COMMA = 188, + PERIOD = 190, + FORWARD_SLASH = 191, +} + +/** + * Shortcut command for Bold + * Windows: Ctrl + B + * MacOS: Meta + B + */ +export const ShortcutBold: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: false, + which: Keys.B, + }, + onClick: editor => toggleBold(editor), +}; + +/** + * Shortcut command for Italic + * Windows: Ctrl + I + * MacOS: Meta + I + */ +export const ShortcutItalic: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: false, + which: Keys.I, + }, + onClick: editor => toggleItalic(editor), +}; + +/** + * Shortcut command for Underline + * Windows: Ctrl + U + * MacOS: Meta + U + */ +export const ShortcutUnderline: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: false, + which: Keys.U, + }, + onClick: editor => toggleUnderline(editor), +}; + +/** + * Shortcut command for Clear Format + * Windows: Ctrl + Space + * MacOS: Meta + Space + */ +export const ShortcutClearFormat: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: false, + which: Keys.SPACE, + }, + onClick: editor => clearFormat(editor), +}; + +/** + * Shortcut command for Undo 1 + * Windows: Ctrl + Z + * MacOS: Meta + Z + */ +export const ShortcutUndo: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: false, + which: Keys.Z, + }, + onClick: editor => undo(editor), +}; + +/** + * Shortcut command for Undo 2 + * Windows: Alt + Backspace + * MacOS: N/A + */ +export const ShortcutUndo2: ShortcutCommand = { + shortcutKey: { + modifierKey: 'alt', + shiftKey: false, + which: Keys.BACKSPACE, + }, + onClick: editor => undo(editor), + environment: 'nonMac', +}; + +/** + * Shortcut command for Redo 1 + * Windows: Ctrl + Y + * MacOS: N/A + */ +export const ShortcutRedo: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: false, + which: Keys.Y, + }, + onClick: editor => redo(editor), + environment: 'nonMac', +}; + +/** + * Shortcut command for Redo 2 + * Windows: N/A + * MacOS: Meta + Shift + Z + */ +export const ShortcutRedoMacOS: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: true, + which: Keys.Z, + }, + onClick: editor => redo(editor), + environment: 'mac', +}; + +/** + * Shortcut command for Bullet List + * Windows: Ctrl + . (Period) + * MacOS: Meta + . (Period) + */ +export const ShortcutBullet: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: false, + which: Keys.PERIOD, + }, + onClick: editor => toggleBullet(editor), +}; + +/** + * Shortcut command for Numbering List + * Windows: Ctrl + / (Forward slash) + * MacOS: Meta + / (Forward slash) + */ +export const ShortcutNumbering: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: false, + which: Keys.FORWARD_SLASH, + }, + onClick: editor => toggleNumbering(editor), +}; + +/** + * Shortcut command for Increase Font + * Windows: Ctrl + Shift + . (Period) + * MacOS: Meta + Shift + . (Period) + */ +export const ShortcutIncreaseFont: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: true, + which: Keys.PERIOD, + }, + onClick: editor => changeFontSize(editor, 'increase'), +}; + +/** + * Shortcut command for Decrease Font + * Windows: Ctrl + Shift + , (Comma) + * MacOS: Meta + Shift + , (Comma) + */ +export const ShortcutDecreaseFont: ShortcutCommand = { + shortcutKey: { + modifierKey: 'ctrl', + shiftKey: true, + which: Keys.COMMA, + }, + onClick: editor => changeFontSize(editor, 'decrease'), +}; diff --git a/packages-content-model/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts new file mode 100644 index 00000000000..0c4a69a47d0 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts @@ -0,0 +1,589 @@ +import * as changeFontSize from 'roosterjs-content-model-api/lib/publicApi/segment/changeFontSize'; +import * as clearFormat from 'roosterjs-content-model-api/lib/publicApi/format/clearFormat'; +import * as redo from 'roosterjs-content-model-core/lib/publicApi/undo/redo'; +import * as toggleBold from 'roosterjs-content-model-api/lib/publicApi/segment/toggleBold'; +import * as toggleBullet from 'roosterjs-content-model-api/lib/publicApi/list/toggleBullet'; +import * as toggleItalic from 'roosterjs-content-model-api/lib/publicApi/segment/toggleItalic'; +import * as toggleNumbering from 'roosterjs-content-model-api/lib/publicApi/list/toggleNumbering'; +import * as toggleUnderline from 'roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline'; +import * as undo from 'roosterjs-content-model-core/lib/publicApi/undo/undo'; +import { EditorEnvironment, IEditor, PluginEvent } from 'roosterjs-content-model-types'; +import { ShortcutPlugin } from '../../lib/shortcut/ShortcutPlugin'; + +const enum Keys { + BACKSPACE = 8, + SPACE = 32, + A = 65, + B = 66, + I = 73, + U = 85, + Y = 89, + Z = 90, + COMMA = 188, + PERIOD = 190, + FORWARD_SLASH = 191, +} + +describe('ShortcutPlugin', () => { + let preventDefaultSpy: jasmine.Spy; + let mockedEditor: IEditor; + let mockedEnvironment: EditorEnvironment; + + beforeEach(() => { + preventDefaultSpy = jasmine.createSpy('preventDefault'); + mockedEnvironment = {}; + mockedEditor = { + getEnvironment: () => mockedEnvironment, + } as any; + }); + + function createMockedEvent( + which: number, + ctrlKey: boolean, + altKey: boolean, + shiftKey: boolean, + metaKey: boolean + ): KeyboardEvent { + return { + which, + ctrlKey, + shiftKey, + altKey, + metaKey, + preventDefault: preventDefaultSpy, + } as any; + } + + describe('Windows', () => { + it('not a shortcut', () => { + const apiSpy = spyOn(toggleBold, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.A, true, false, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeFalse(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeUndefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).not.toHaveBeenCalled(); + }); + + it('bold', () => { + const apiSpy = spyOn(toggleBold, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.B, true, false, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('italic', () => { + const apiSpy = spyOn(toggleItalic, 'default'); + const plugin = new ShortcutPlugin(); + + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.I, true, false, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('underline', () => { + const apiSpy = spyOn(toggleUnderline, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.U, true, false, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('clear format', () => { + const apiSpy = spyOn(clearFormat, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.SPACE, true, false, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('undo 1', () => { + const apiSpy = spyOn(undo, 'undo'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.Z, true, false, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('undo 2', () => { + const apiSpy = spyOn(undo, 'undo'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.BACKSPACE, false, true, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('redo 1', () => { + const apiSpy = spyOn(redo, 'redo'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.Y, true, false, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('redo 2', () => { + const apiSpy = spyOn(redo, 'redo'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.Z, true, false, true, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeFalse(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeUndefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).not.toHaveBeenCalled(); + }); + + it('bullet list', () => { + const apiSpy = spyOn(toggleBullet, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.PERIOD, true, false, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('numbering list', () => { + const apiSpy = spyOn(toggleNumbering, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.FORWARD_SLASH, true, false, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('increase font', () => { + const apiSpy = spyOn(changeFontSize, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.PERIOD, true, false, true, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'increase'); + }); + + it('decrease font', () => { + const apiSpy = spyOn(changeFontSize, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.COMMA, true, false, true, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'decrease'); + }); + }); + + describe('Mac', () => { + beforeEach(() => { + mockedEnvironment.isMac = true; + }); + + it('not a shortcut', () => { + const apiSpy = spyOn(toggleBold, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.A, false, false, false, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeFalse(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeUndefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).not.toHaveBeenCalled(); + }); + + it('bold', () => { + const apiSpy = spyOn(toggleBold, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.B, false, false, false, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('italic', () => { + const apiSpy = spyOn(toggleItalic, 'default'); + const plugin = new ShortcutPlugin(); + + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.I, false, false, false, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('underline', () => { + const apiSpy = spyOn(toggleUnderline, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.U, false, false, false, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('clear format', () => { + const apiSpy = spyOn(clearFormat, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.SPACE, false, false, false, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('undo 1', () => { + const apiSpy = spyOn(undo, 'undo'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.Z, false, false, false, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('undo 2', () => { + const apiSpy = spyOn(undo, 'undo'); + + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.BACKSPACE, false, true, false, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeFalse(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeUndefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).not.toHaveBeenCalled(); + }); + + it('redo 1', () => { + const apiSpy = spyOn(redo, 'redo'); + + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.Y, false, false, false, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeFalse(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeUndefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).not.toHaveBeenCalled(); + }); + + it('redo 2', () => { + const apiSpy = spyOn(redo, 'redo'); + + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.Z, false, false, true, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('bullet list', () => { + const apiSpy = spyOn(toggleBullet, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.PERIOD, false, false, false, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('numbering list', () => { + const apiSpy = spyOn(toggleNumbering, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.FORWARD_SLASH, false, false, false, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor); + }); + + it('increase font', () => { + const apiSpy = spyOn(changeFontSize, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.PERIOD, false, false, true, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'increase'); + }); + + it('decrease font', () => { + const apiSpy = spyOn(changeFontSize, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.COMMA, false, false, true, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'decrease'); + }); + }); +}); diff --git a/packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts b/packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts index f3e77ea6355..7d1a76bac2a 100644 --- a/packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts +++ b/packages/roosterjs-editor-adapter/lib/corePlugins/BridgePlugin.ts @@ -1,9 +1,9 @@ +import { cacheGetEventData } from 'roosterjs-content-model-core'; import { createDarkColorHandler } from '../editor/DarkColorHandlerImpl'; import { createEditPlugin } from './EditPlugin'; import { newEventToOldEvent, oldEventToNewEvent } from '../editor/utils/eventConverter'; import type { EditorPlugin as LegacyEditorPlugin, - PluginEvent as LegacyPluginEvent, ContextMenuProvider as LegacyContextMenuProvider, IEditor as ILegacyEditor, ExperimentalFeatures, @@ -15,6 +15,7 @@ import type { import type { ContextMenuProvider, IEditor, PluginEvent } from 'roosterjs-content-model-types'; const ExclusivelyHandleEventPluginKey = '__ExclusivelyHandleEventPlugin'; +const OldEventKey = '__OldEventFromNewEvent'; /** * @internal @@ -107,33 +108,14 @@ export class BridgePlugin implements ContextMenuProvider { } willHandleEventExclusively(event: PluginEvent) { - let oldEvent: LegacyPluginEvent | undefined; - - if (this.checkExclusivelyHandling && (oldEvent = newEventToOldEvent(event))) { - for (let i = 0; i < this.legacyPlugins.length; i++) { - const plugin = this.legacyPlugins[i]; - - if (plugin.willHandleEventExclusively?.(oldEvent)) { - if (!event.eventDataCache) { - event.eventDataCache = {}; - } - - event.eventDataCache[ExclusivelyHandleEventPluginKey] = plugin; - return true; - } - } - } - - return false; + return this.checkExclusivelyHandling && !!this.cacheGetExclusivelyHandlePlugin(event); } onPluginEvent(event: PluginEvent) { - const oldEvent = newEventToOldEvent(event); + const oldEvent = this.cacheGetOldEvent(event); if (oldEvent) { - const exclusivelyHandleEventPlugin = event.eventDataCache?.[ - ExclusivelyHandleEventPluginKey - ] as LegacyEditorPlugin | undefined; + const exclusivelyHandleEventPlugin = this.cacheGetExclusivelyHandlePlugin(event); if (exclusivelyHandleEventPlugin) { exclusivelyHandleEventPlugin.onPluginEvent?.(oldEvent); @@ -167,6 +149,28 @@ export class BridgePlugin implements ContextMenuProvider { return allItems; } + private cacheGetExclusivelyHandlePlugin(event: PluginEvent) { + return cacheGetEventData(event, ExclusivelyHandleEventPluginKey, event => { + const oldEvent = this.cacheGetOldEvent(event); + + if (oldEvent) { + for (let i = 0; i < this.legacyPlugins.length; i++) { + const plugin = this.legacyPlugins[i]; + + if (plugin.willHandleEventExclusively?.(oldEvent)) { + return plugin; + } + } + } + + return null; + }); + } + + private cacheGetOldEvent(event: PluginEvent) { + return cacheGetEventData(event, OldEventKey, newEventToOldEvent); + } + private createEditorCore(editor: IEditor): EditorAdapterCore { return { customData: {}, diff --git a/packages/roosterjs-editor-adapter/test/corePlugins/BridgePluginTest.ts b/packages/roosterjs-editor-adapter/test/corePlugins/BridgePluginTest.ts index ec3a93d133e..a766c9f950f 100644 --- a/packages/roosterjs-editor-adapter/test/corePlugins/BridgePluginTest.ts +++ b/packages/roosterjs-editor-adapter/test/corePlugins/BridgePluginTest.ts @@ -223,6 +223,7 @@ describe('BridgePlugin', () => { expect(mockedEvent).toEqual({ eventDataCache: { __ExclusivelyHandleEventPlugin: mockedPlugin2, + __OldEventFromNewEvent: 'NEW_[object Object]', }, }); expect(eventConverter.newEventToOldEvent).toHaveBeenCalledTimes(1); @@ -298,12 +299,20 @@ describe('BridgePlugin', () => { { eventType: 'new_old_newEvent' as any, data: 'plugin2', + eventDataCache: { + __ExclusivelyHandleEventPlugin: null, + __OldEventFromNewEvent: { eventType: 'old_newEvent', data: 'plugin2' }, + }, } ); expect(mockedEvent).toEqual({ eventType: 'new_old_newEvent', data: 'plugin2', + eventDataCache: { + __ExclusivelyHandleEventPlugin: null, + __OldEventFromNewEvent: { eventType: 'old_newEvent', data: 'plugin2' }, + }, }); plugin.dispose(); @@ -343,7 +352,7 @@ describe('BridgePlugin', () => { const mockedEvent = { eventType: 'newEvent', eventDataCache: { - ['__ExclusivelyHandleEventPlugin']: mockedPlugin2, + __ExclusivelyHandleEventPlugin: mockedPlugin2, }, } as any; @@ -354,7 +363,8 @@ describe('BridgePlugin', () => { expect(onPluginEventSpy2).toHaveBeenCalledWith({ eventType: 'old_newEvent', eventDataCache: { - ['__ExclusivelyHandleEventPlugin']: mockedPlugin2, + __ExclusivelyHandleEventPlugin: mockedPlugin2, + __OldEventFromNewEvent: jasmine.anything(), }, }); expect(eventConverter.newEventToOldEvent).toHaveBeenCalledTimes(1); From 2397f7bed723062f88b0d73dd828e26e938209fe Mon Sep 17 00:00:00 2001 From: Bryan Valverde U Date: Thu, 29 Feb 2024 16:18:08 -0600 Subject: [PATCH 34/80] Add option to configure the default paste type (#2457) * init * Add tests * Fix build --- .../lib/corePlugin/CopyPastePlugin.ts | 3 +- .../test/corePlugin/CopyPastePluginTest.ts | 132 +++++++++++++++++- .../lib/editor/EditorOptions.ts | 6 + .../lib/pluginState/CopyPastePluginState.ts | 7 + 4 files changed, 143 insertions(+), 5 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts index 6946d36daee..ef98518470a 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts @@ -48,6 +48,7 @@ class CopyPastePlugin implements PluginWithState { this.state = { allowedCustomPasteType: option.allowedCustomPasteType || [], tempDiv: null, + defaultPasteType: option.defaultPasteType, }; } @@ -199,7 +200,7 @@ class CopyPastePlugin implements PluginWithState { this.state.allowedCustomPasteType ).then((clipboardData: ClipboardData) => { if (!editor.isDisposed()) { - editor.pasteFromClipboard(clipboardData); + editor.pasteFromClipboard(clipboardData, this.state.defaultPasteType); } }); } diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts b/packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts index 2337b8dbe5f..66eb559940a 100644 --- a/packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts @@ -19,6 +19,7 @@ import { CopyPastePluginState, PluginWithState, DarkColorHandler, + PasteType, } from 'roosterjs-content-model-types'; import { adjustSelectionForCopyCut, @@ -42,18 +43,21 @@ describe('CopyPastePlugin.Ctor', () => { expect(state).toEqual({ allowedCustomPasteType: [], tempDiv: null, + defaultPasteType: undefined, }); }); it('Ctor with options', () => { const plugin = createCopyPastePlugin({ allowedCustomPasteType, + defaultPasteType: 'mergeFormat', }); const state = plugin.getState(); expect(state).toEqual({ allowedCustomPasteType: allowedCustomPasteType, tempDiv: null, + defaultPasteType: 'mergeFormat', }); }); }); @@ -145,8 +149,8 @@ describe('CopyPastePlugin |', () => { isDarkMode: () => { return false; }, - pasteFromClipboard: (ar1: any) => { - pasteSpy(ar1); + pasteFromClipboard: (ar1: any, pasteType?: PasteType) => { + pasteSpy(ar1, pasteType); }, getColorManager: () => mockedDarkColorHandler, isDisposed, @@ -552,7 +556,7 @@ describe('CopyPastePlugin |', () => { domEvents.paste.beforeDispatch?.(clipboardEvent); - expect(pasteSpy).toHaveBeenCalledWith(clipboardData); + expect(pasteSpy).toHaveBeenCalledWith(clipboardData, undefined); expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( Array.from(clipboardEvent.clipboardData!.items), allowedCustomPasteType @@ -581,7 +585,127 @@ describe('CopyPastePlugin |', () => { domEvents.paste.beforeDispatch?.(clipboardEvent); - expect(pasteSpy).toHaveBeenCalledWith(clipboardData); + expect(pasteSpy).toHaveBeenCalledWith(clipboardData, undefined); + expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( + Array.from(clipboardEvent.clipboardData!.items), + allowedCustomPasteType + ); + expect(preventDefaultSpy).toHaveBeenCalledTimes(1); + }); + + it('Handle with defaultPasteType mergePaste', () => { + const preventDefaultSpy = jasmine.createSpy('preventDefaultPaste'); + plugin.getState().defaultPasteType = 'mergeFormat'; + let clipboardEvent = { + clipboardData: ({ + items: [{}], + }), + preventDefault() { + preventDefaultSpy(); + }, + }; + spyOn(extractClipboardItemsFile, 'extractClipboardItems').and.returnValue(< + Promise + >{ + then: (cb: (value: ClipboardData) => void | PromiseLike) => { + cb(clipboardData); + }, + }); + isDisposed.and.returnValue(false); + + domEvents.paste.beforeDispatch?.(clipboardEvent); + + expect(pasteSpy).toHaveBeenCalledWith(clipboardData, 'mergeFormat'); + expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( + Array.from(clipboardEvent.clipboardData!.items), + allowedCustomPasteType + ); + expect(preventDefaultSpy).toHaveBeenCalledTimes(1); + }); + + it('Handle with defaultPasteType asImage', () => { + const preventDefaultSpy = jasmine.createSpy('preventDefaultPaste'); + plugin.getState().defaultPasteType = 'asImage'; + let clipboardEvent = { + clipboardData: ({ + items: [{}], + }), + preventDefault() { + preventDefaultSpy(); + }, + }; + spyOn(extractClipboardItemsFile, 'extractClipboardItems').and.returnValue(< + Promise + >{ + then: (cb: (value: ClipboardData) => void | PromiseLike) => { + cb(clipboardData); + }, + }); + isDisposed.and.returnValue(false); + + domEvents.paste.beforeDispatch?.(clipboardEvent); + + expect(pasteSpy).toHaveBeenCalledWith(clipboardData, 'asImage'); + expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( + Array.from(clipboardEvent.clipboardData!.items), + allowedCustomPasteType + ); + expect(preventDefaultSpy).toHaveBeenCalledTimes(1); + }); + + it('Handle with defaultPasteType asPlainText', () => { + const preventDefaultSpy = jasmine.createSpy('preventDefaultPaste'); + plugin.getState().defaultPasteType = 'asPlainText'; + let clipboardEvent = { + clipboardData: ({ + items: [{}], + }), + preventDefault() { + preventDefaultSpy(); + }, + }; + spyOn(extractClipboardItemsFile, 'extractClipboardItems').and.returnValue(< + Promise + >{ + then: (cb: (value: ClipboardData) => void | PromiseLike) => { + cb(clipboardData); + }, + }); + isDisposed.and.returnValue(false); + + domEvents.paste.beforeDispatch?.(clipboardEvent); + + expect(pasteSpy).toHaveBeenCalledWith(clipboardData, 'asPlainText'); + expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( + Array.from(clipboardEvent.clipboardData!.items), + allowedCustomPasteType + ); + expect(preventDefaultSpy).toHaveBeenCalledTimes(1); + }); + + it('Handle with defaultPasteType asPlainText', () => { + const preventDefaultSpy = jasmine.createSpy('preventDefaultPaste'); + plugin.getState().defaultPasteType = 'normal'; + let clipboardEvent = { + clipboardData: ({ + items: [{}], + }), + preventDefault() { + preventDefaultSpy(); + }, + }; + spyOn(extractClipboardItemsFile, 'extractClipboardItems').and.returnValue(< + Promise + >{ + then: (cb: (value: ClipboardData) => void | PromiseLike) => { + cb(clipboardData); + }, + }); + isDisposed.and.returnValue(false); + + domEvents.paste.beforeDispatch?.(clipboardEvent); + + expect(pasteSpy).toHaveBeenCalledWith(clipboardData, 'normal'); expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( Array.from(clipboardEvent.clipboardData!.items), allowedCustomPasteType diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorOptions.ts b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorOptions.ts index 3a2a3f237c8..91847753530 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorOptions.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/editor/EditorOptions.ts @@ -1,3 +1,4 @@ +import type { PasteType } from '../enum/PasteType'; import type { Colors, ColorTransformFunction } from '../context/DarkColorHandler'; import type { EditorPlugin } from './EditorPlugin'; import type { ContentModelSegmentFormat } from '../format/ContentModelSegmentFormat'; @@ -109,4 +110,9 @@ export interface EditorOptions { * @param error The error object we got */ disposeErrorHandler?: (plugin: EditorPlugin, error: Error) => void; + + /** + * Default paste type. By default will use the normal (as-is) paste type. + */ + defaultPasteType?: PasteType; } diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/CopyPastePluginState.ts b/packages-content-model/roosterjs-content-model-types/lib/pluginState/CopyPastePluginState.ts index 02cd0442006..04ba00a1968 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/pluginState/CopyPastePluginState.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/pluginState/CopyPastePluginState.ts @@ -1,3 +1,5 @@ +import type { PasteType } from '../enum/PasteType'; + /** * The state object for CopyPastePlugin */ @@ -12,4 +14,9 @@ export interface CopyPastePluginState { * A temporary DIV element used for cut/copy content */ tempDiv: HTMLDivElement | null; + + /** + * Default paste type. By default will use the normal (as-is) paste type. + */ + defaultPasteType?: PasteType; } From f3db5ad04ab530bccf52c53fb5af58e475c0bb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Fri, 1 Mar 2024 15:13:52 -0300 Subject: [PATCH 35/80] add delete empty quote with backspace and enter --- .../lib/edit/deleteSteps/deleteEmptyQuote.ts | 60 ++++++++ .../lib/edit/deleteSteps/deleteList.ts | 1 + .../lib/edit/inputSteps/handleEnterOnList.ts | 2 +- .../edit/inputSteps/handleEnterOnQuotes.ts | 28 ---- .../lib/edit/keyboardDelete.ts | 10 +- .../lib/edit/keyboardInput.ts | 3 +- .../edit/deleteSteps/deleteEmptyQuoteTest.ts | 131 ++++++++++++++++++ .../edit/inputSteps/handleEnterOnListTest.ts | 2 +- .../test/edit/keyboardDeleteTest.ts | 37 +++-- .../test/edit/keyboardInputTest.ts | 3 +- 10 files changed, 232 insertions(+), 45 deletions(-) create mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteEmptyQuote.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnQuotes.ts create mode 100644 packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteEmptyQuote.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteEmptyQuote.ts new file mode 100644 index 00000000000..c1650806a7d --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteEmptyQuote.ts @@ -0,0 +1,60 @@ +import { unwrapBlock } from 'roosterjs-content-model-dom'; +import { + getClosestAncestorBlockGroupIndex, + isBlockGroupOfType, +} from 'roosterjs-content-model-core'; +import type { + ContentModelFormatContainer, + DeleteSelectionStep, +} from 'roosterjs-content-model-types'; + +/** + * @internal + */ +export const deleteEmptyQuote: DeleteSelectionStep = context => { + const { deleteResult } = context; + if ( + deleteResult == 'nothingToDelete' || + deleteResult == 'notDeleted' || + deleteResult == 'range' + ) { + const { insertPoint, formatContext } = context; + const { path } = insertPoint; + const rawEvent = formatContext?.rawEvent; + const index = getClosestAncestorBlockGroupIndex( + path, + ['FormatContainer', 'ListItem'], + ['TableCell'] + ); + const quote = path[index]; + + if ( + quote && + quote.blockGroupType === 'FormatContainer' && + quote.tagName == 'blockquote' && + isEmptyQuote(quote) + ) { + const parent = path[index + 1]; + const quoteBlockIndex = parent.blocks.indexOf(quote); + const blockQuote = parent.blocks[quoteBlockIndex]; + if ( + isBlockGroupOfType(blockQuote, 'FormatContainer') && + blockQuote.tagName === 'blockquote' + ) { + unwrapBlock(parent, blockQuote); + rawEvent?.preventDefault(); + context.deleteResult = 'range'; + } + } + } +}; + +const isEmptyQuote = (quote: ContentModelFormatContainer) => { + return ( + quote.blocks.length === 1 && + quote.blocks[0].blockType === 'Paragraph' && + quote.blocks[0].segments.every( + s => s.segmentType === 'SelectionMarker' || s.segmentType === 'Br' + ) + ); +}; diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts index 5b3d2643bf2..713951ce970 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts @@ -11,6 +11,7 @@ export const deleteList: DeleteSelectionStep = context => { const index = getClosestAncestorBlockGroupIndex(path, ['ListItem', 'TableCell']); const item = path[index]; if ( + item && index >= 0 && paragraph.segments[0] == marker && item.blockGroupType == 'ListItem' && diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts index 0223b50a9de..cfdb00de36b 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts @@ -38,7 +38,7 @@ export const handleEnterOnList: DeleteSelectionStep = context => { const listItem = path[index]; const listParent = path[index + 1]; - if (listItem && listItem.blockGroupType === 'ListItem') { + if (listItem && listItem.blockGroupType === 'ListItem' && listParent) { const listIndex = listParent.blocks.indexOf(listItem); const nextBlock = listParent.blocks[listIndex + 1]; if (deleteResult == 'range' && nextBlock) { diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnQuotes.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnQuotes.ts deleted file mode 100644 index 56c40e3f169..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnQuotes.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { getClosestAncestorBlockGroupIndex } from 'roosterjs-content-model-core'; -import {} from 'roosterjs-content-model-dom'; -import type { DeleteSelectionStep } from 'roosterjs-content-model-types'; - -/** - * @internal - */ -export const handleEnterOnList: DeleteSelectionStep = context => { - const { deleteResult } = context; - if ( - deleteResult == 'nothingToDelete' || - deleteResult == 'notDeleted' || - deleteResult == 'range' - ) { - const { insertPoint, formatContext } = context; - const { path } = insertPoint; - const rawEvent = formatContext?.rawEvent; - const index = getClosestAncestorBlockGroupIndex( - path, - ['FormatContainer'], - ['TableCell', 'ListItem'] - ); - const quote = path[index]; - if (quote.blockGroupType === 'FormatContainer' && quote.tagName == 'blockquote') { - rawEvent?.preventDefault(); - } - } -}; diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts index 0fd7522eba3..05cd9cbc30d 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts @@ -1,5 +1,6 @@ import { ChangeSource, deleteSelection, isModifierKey } from 'roosterjs-content-model-core'; import { deleteAllSegmentBefore } from './deleteSteps/deleteAllSegmentBefore'; +import { deleteEmptyQuote } from './deleteSteps/deleteEmptyQuote'; import { deleteList } from './deleteSteps/deleteList'; import { isNodeOfType } from 'roosterjs-content-model-dom'; import { @@ -64,7 +65,14 @@ function getDeleteSteps(rawEvent: KeyboardEvent, isMac: boolean): (DeleteSelecti const deleteCollapsedSelection = isForward ? forwardDeleteCollapsedSelection : backwardDeleteCollapsedSelection; - return [deleteAllSegmentBeforeStep, deleteWordSelection, deleteCollapsedSelection, deleteList]; + const deleteQuote = !isForward ? deleteEmptyQuote : null; + return [ + deleteAllSegmentBeforeStep, + deleteWordSelection, + deleteCollapsedSelection, + deleteList, + deleteQuote, + ]; } function shouldDeleteWithContentModel(selection: DOMSelection | null, rawEvent: KeyboardEvent) { diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts index f8c3219c12e..ee264c12aaa 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts @@ -1,3 +1,4 @@ +import { deleteEmptyQuote } from './deleteSteps/deleteEmptyQuote'; import { deleteSelection, isModifierKey } from 'roosterjs-content-model-core'; import { handleEnterOnList } from './inputSteps/handleEnterOnList'; import { normalizeContentModel } from 'roosterjs-content-model-dom'; @@ -45,7 +46,7 @@ export function keyboardInput(editor: IEditor, rawEvent: KeyboardEvent) { } function getInputSteps(selection: DOMSelection | null, rawEvent: KeyboardEvent) { - return shouldHandleEnterKey(selection, rawEvent) ? [handleEnterOnList] : []; + return shouldHandleEnterKey(selection, rawEvent) ? [handleEnterOnList, deleteEmptyQuote] : []; } function shouldInputWithContentModel(selection: DOMSelection | null, rawEvent: KeyboardEvent) { diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts new file mode 100644 index 00000000000..d8d9848ddf9 --- /dev/null +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts @@ -0,0 +1,131 @@ +import { ContentModelDocument } from 'roosterjs-content-model-types'; +import { deleteEmptyQuote } from '../../../lib/edit/deleteSteps/deleteEmptyQuote'; +import { deleteSelection } from 'roosterjs-content-model-core'; +import { normalizeContentModel } from 'roosterjs-content-model-dom'; + +describe('deleteEmptyQuoteTest', () => { + function runTest( + model: ContentModelDocument, + expectedModel: ContentModelDocument, + deleteResult: string + ) { + const result = deleteSelection(model, [deleteEmptyQuote]); + normalizeContentModel(model); + expect(result.deleteResult).toEqual(deleteResult); + expect(model).toEqual(expectedModel); + } + + it('should delete empty quote', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'FormatContainer', + tagName: 'blockquote', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + { + segmentType: 'Br', + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: {}, + }, + ], + format: { + marginTop: '1em', + marginRight: '40px', + marginBottom: '1em', + marginLeft: '40px', + paddingLeft: '10px', + borderLeft: '3px solid rgb(200, 200, 200)', + }, + }, + ], + format: {}, + }; + const expectedTestModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + { + segmentType: 'Br', + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(model, expectedTestModel, 'range'); + }); + + it('should not delete non-empty quote', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'FormatContainer', + tagName: 'blockquote', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: {}, + }, + ], + format: { + marginTop: '1em', + marginRight: '40px', + marginBottom: '1em', + marginLeft: '40px', + paddingLeft: '10px', + borderLeft: '3px solid rgb(200, 200, 200)', + }, + }, + ], + format: {}, + }; + runTest(model, model, 'notDeleted'); + }); +}); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts index a5a8234a6a1..0f8eb45ea4c 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts @@ -1674,7 +1674,7 @@ describe('handleEnterOnList', () => { }); }); -describe('keyboardInput - handleEnterOnList', () => { +describe(' handleEnterOnList - keyboardInput', () => { function runTest( input: ContentModelDocument, isShiftKey: boolean, diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts index 6da4cb1964d..d0c774ca223 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts @@ -1,12 +1,13 @@ import * as deleteSelection from 'roosterjs-content-model-core/lib/publicApi/selection/deleteSelection'; import * as handleKeyboardEventResult from '../../lib/edit/handleKeyboardEventCommon'; import { ChangeSource } from 'roosterjs-content-model-core'; +import { ContentModelDocument, DOMSelection, IEditor } from 'roosterjs-content-model-types'; import { deleteAllSegmentBefore } from '../../lib/edit/deleteSteps/deleteAllSegmentBefore'; +import { deleteEmptyQuote } from '../../lib/edit/deleteSteps/deleteEmptyQuote'; import { deleteList } from '../../lib/edit/deleteSteps/deleteList'; import { DeleteResult, DeleteSelectionStep } from 'roosterjs-content-model-types'; import { editingTestCommon } from './editingTestCommon'; import { keyboardDelete } from '../../lib/edit/keyboardDelete'; -import { ContentModelDocument, DOMSelection, IEditor } from 'roosterjs-content-model-types'; import { backwardDeleteWordSelection, forwardDeleteWordSelection, @@ -89,7 +90,7 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [null!, null!, forwardDeleteCollapsedSelection, deleteList], + [null!, null!, forwardDeleteCollapsedSelection, deleteList, null!], 'notDeleted', true, 0 @@ -107,7 +108,7 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [null!, null!, backwardDeleteCollapsedSelection, deleteList], + [null!, null!, backwardDeleteCollapsedSelection, deleteList, deleteEmptyQuote], 'notDeleted', true, 0 @@ -127,7 +128,7 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [null!, forwardDeleteWordSelection, forwardDeleteCollapsedSelection, deleteList], + [null!, forwardDeleteWordSelection, forwardDeleteCollapsedSelection, deleteList, null!], 'notDeleted', true, 0 @@ -147,7 +148,13 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [null!, backwardDeleteWordSelection, backwardDeleteCollapsedSelection, deleteList], + [ + null!, + backwardDeleteWordSelection, + backwardDeleteCollapsedSelection, + deleteList, + deleteEmptyQuote, + ], 'notDeleted', true, 0 @@ -167,7 +174,7 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [null!, null!, forwardDeleteCollapsedSelection, deleteList], + [null!, null!, forwardDeleteCollapsedSelection, deleteList, null!], 'notDeleted', true, 0 @@ -187,7 +194,13 @@ describe('keyboardDelete', () => { blockGroupType: 'Document', blocks: [], }, - [deleteAllSegmentBefore, null!, backwardDeleteCollapsedSelection, deleteList], + [ + deleteAllSegmentBefore, + null!, + backwardDeleteCollapsedSelection, + deleteList, + deleteEmptyQuote, + ], 'notDeleted', true, 0 @@ -229,7 +242,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, forwardDeleteCollapsedSelection, deleteList], + [null!, null!, forwardDeleteCollapsedSelection, deleteList, null!], 'notDeleted', true, 0 @@ -271,7 +284,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, backwardDeleteCollapsedSelection, deleteList], + [null!, null!, backwardDeleteCollapsedSelection, deleteList, deleteEmptyQuote], 'notDeleted', true, 0 @@ -323,7 +336,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, forwardDeleteCollapsedSelection, deleteList], + [null!, null!, forwardDeleteCollapsedSelection, deleteList, null!], 'singleChar', false, 1 @@ -375,7 +388,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, backwardDeleteCollapsedSelection, deleteList], + [null!, null!, backwardDeleteCollapsedSelection, deleteList, deleteEmptyQuote], 'singleChar', false, 1 @@ -469,7 +482,7 @@ describe('keyboardDelete', () => { }, ], }, - [null!, null!, backwardDeleteCollapsedSelection, deleteList], + [null!, null!, backwardDeleteCollapsedSelection, deleteList, deleteEmptyQuote], 'singleChar', false, 1 diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts index ae2a67ce817..c28572759f8 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts @@ -1,5 +1,6 @@ import * as deleteSelection from 'roosterjs-content-model-core/lib/publicApi/selection/deleteSelection'; import * as normalizeContentModel from 'roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel'; +import { deleteEmptyQuote } from '../../lib/edit/deleteSteps/deleteEmptyQuote'; import { handleEnterOnList } from '../../lib/edit/inputSteps/handleEnterOnList'; import { keyboardInput } from '../../lib/edit/keyboardInput'; import { @@ -415,7 +416,7 @@ describe('keyboardInput', () => { expect(formatContentModelSpy).toHaveBeenCalled(); expect(deleteSelectionSpy).toHaveBeenCalledWith( mockedModel, - [handleEnterOnList], + [handleEnterOnList, deleteEmptyQuote], mockedContext ); expect(formatResult).toBeTrue(); From 9b1c0f8f927e46eadcb67db0590f1667685cec56 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Fri, 1 Mar 2024 10:18:23 -0800 Subject: [PATCH 36/80] Fix 258837 and 257626 (#2459) --- .../test/publicApi/image/insertImageTest.ts | 1 + .../model/createModelFromHtmlTest.ts | 2 +- .../test/publicApi/model/mergeModelTest.ts | 2 + .../domToModel/utils/addSelectionMarker.ts | 23 ++- .../lib/modelApi/common/normalizeParagraph.ts | 46 +++++- .../utils/addSelectionMarkerTest.ts | 84 ++++++++++ .../test/endToEndTest.ts | 39 ++++- .../modelApi/common/normalizeParagraphTest.ts | 154 ++++++++++++++++++ .../paste/e2e/cmPasteFromExcelOnlineTest.ts | 120 ++++++++++++++ ...processPastedContentFromWordDesktopTest.ts | 93 +++++++++++ 10 files changed, 559 insertions(+), 5 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts b/packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts index f381c98ac82..ab9cbd3212b 100644 --- a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts +++ b/packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts @@ -198,6 +198,7 @@ describe('insertImage', () => { isSelected: true, }, ], + segmentFormat: { fontFamily: 'Test', fontSize: '20px' }, }, ], format: { diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts index 6b5fa650314..68efec22da1 100644 --- a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts @@ -72,7 +72,7 @@ describe('createModelFromHtml', () => { format: { fontSize: '20px', textColor: 'red', fontFamily: 'Arial' }, }, ], - segmentFormat: { fontSize: '20px', fontFamily: 'Arial' }, + segmentFormat: { fontSize: '20px', fontFamily: 'Arial', textColor: 'red' }, format: {}, }, ], diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts index 83e0c4112d6..0015f4caaf3 100644 --- a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts @@ -2333,6 +2333,7 @@ describe('mergeModel', () => { resultMarker, ], format: {}, + segmentFormat: { fontFamily: 'sourceFontFamily' }, }; expect(majorModel).toEqual({ @@ -2510,6 +2511,7 @@ describe('mergeModel', () => { marker, ], format: {}, + segmentFormat: { fontFamily: 'Calibri', fontSize: '11pt', textColor: 'black' }, }; expect(majorModel).toEqual({ diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/addSelectionMarker.ts b/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/addSelectionMarker.ts index 68956b4eb53..9c23e18e7ca 100644 --- a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/addSelectionMarker.ts +++ b/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/addSelectionMarker.ts @@ -1,7 +1,11 @@ import { addDecorators } from '../../modelApi/common/addDecorators'; import { addSegment } from '../../modelApi/common/addSegment'; import { createSelectionMarker } from '../../modelApi/creators/createSelectionMarker'; -import type { ContentModelBlockGroup, DomToModelContext } from 'roosterjs-content-model-types'; +import type { + ContentModelBlockGroup, + ContentModelSegmentFormat, + DomToModelContext, +} from 'roosterjs-content-model-types'; /** * @internal @@ -12,6 +16,22 @@ export function addSelectionMarker( container?: Node, offset?: number ) { + const lastPara = group.blocks[group.blocks.length - 1]; + const formatFromParagraph: ContentModelSegmentFormat = + !lastPara || lastPara.blockType != 'Paragraph' + ? {} + : lastPara.decorator + ? { + fontFamily: lastPara.decorator.format.fontFamily, + fontSize: lastPara.decorator.format.fontSize, + } + : lastPara.segmentFormat + ? { + fontFamily: lastPara.segmentFormat.fontFamily, + fontSize: lastPara.segmentFormat.fontSize, + } + : {}; + const pendingFormat = context.pendingFormat && context.pendingFormat.posContainer === container && @@ -20,6 +40,7 @@ export function addSelectionMarker( : undefined; const segmentFormat = { ...context.defaultFormat, + ...formatFromParagraph, ...context.segmentFormat, ...pendingFormat, }; diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts b/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts index 8f9a9d41849..14616124d47 100644 --- a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts +++ b/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts @@ -3,7 +3,11 @@ import { createBr } from '../creators/createBr'; import { isSegmentEmpty } from './isEmpty'; import { isWhiteSpacePreserved } from '../../domUtils/isWhiteSpacePreserved'; import { normalizeAllSegments } from './normalizeSegment'; -import type { ContentModelParagraph } from 'roosterjs-content-model-types'; +import type { + ContentModelParagraph, + ContentModelSegment, + ContentModelSegmentFormat, +} from 'roosterjs-content-model-types'; /** * @param paragraph The paragraph to normalize @@ -42,6 +46,8 @@ export function normalizeParagraph(paragraph: ContentModelParagraph) { removeEmptyLinks(paragraph); removeEmptySegments(paragraph); + + moveUpSegmentFormat(paragraph); } function removeEmptySegments(block: ContentModelParagraph) { @@ -74,3 +80,41 @@ function removeEmptyLinks(paragraph: ContentModelParagraph) { } } } + +type FormatsToMoveUp = 'fontFamily' | 'fontSize' | 'textColor'; +const formatsToMoveUp: FormatsToMoveUp[] = ['fontFamily', 'fontSize', 'textColor']; + +// When all segments are sharing the same segment format (font name, size and color), we can move its format to paragraph +function moveUpSegmentFormat(paragraph: ContentModelParagraph) { + if (!paragraph.decorator) { + const segments = paragraph.segments.filter(x => x.segmentType != 'SelectionMarker'); + const target = paragraph.segmentFormat || {}; + let changed = false; + + formatsToMoveUp.forEach(key => { + changed = internalMoveUpSegmentFormat(segments, target, key) || changed; + }); + + if (changed) { + paragraph.segmentFormat = target; + } + } +} + +function internalMoveUpSegmentFormat( + segments: ContentModelSegment[], + target: ContentModelSegmentFormat, + formatKey: FormatsToMoveUp +): boolean { + const firstFormat = segments[0]?.format; + + if ( + firstFormat?.[formatKey] && + segments.every(segment => segment.format[formatKey] == firstFormat[formatKey]) + ) { + target[formatKey] = firstFormat[formatKey]; + return true; + } else { + return false; + } +} diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/addSelectionMarkerTest.ts b/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/addSelectionMarkerTest.ts index 162e4fd0bc9..9b83269e9ff 100644 --- a/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/addSelectionMarkerTest.ts +++ b/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/addSelectionMarkerTest.ts @@ -88,6 +88,90 @@ describe('addSelectionMarker', () => { }); }); + it('add marker with block format from existing block', () => { + const doc = createContentModelDocument(); + const context = createDomToModelContext(); + + doc.blocks.push({ + blockType: 'Paragraph', + segments: [], + format: {}, + segmentFormat: { + fontFamily: 'Arial', + }, + }); + + addSelectionMarker(doc, context); + + expect(doc).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + format: {}, + segmentFormat: { + fontFamily: 'Arial', + }, + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { fontFamily: 'Arial', fontSize: undefined }, + }, + ], + }, + ], + }); + }); + + it('add marker with block format from existing block decorator', () => { + const doc = createContentModelDocument(); + const context = createDomToModelContext(); + + doc.blocks.push({ + blockType: 'Paragraph', + segments: [], + format: {}, + segmentFormat: { + fontFamily: 'Arial', + }, + decorator: { + tagName: 'h1', + format: { + fontFamily: 'Tahoma', + }, + }, + }); + + addSelectionMarker(doc, context); + + expect(doc).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + format: {}, + segmentFormat: { + fontFamily: 'Arial', + }, + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { fontFamily: 'Tahoma', fontSize: undefined }, + }, + ], + decorator: { + tagName: 'h1', + format: { + fontFamily: 'Tahoma', + }, + }, + }, + ], + }); + }); + it('add marker with link format', () => { const doc = createContentModelDocument(); const context = createDomToModelContext(); diff --git a/packages-content-model/roosterjs-content-model-dom/test/endToEndTest.ts b/packages-content-model/roosterjs-content-model-dom/test/endToEndTest.ts index 37c555d2953..f3fd9d32d89 100644 --- a/packages-content-model/roosterjs-content-model-dom/test/endToEndTest.ts +++ b/packages-content-model/roosterjs-content-model-dom/test/endToEndTest.ts @@ -57,6 +57,11 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, ], isImplicit: true, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], levels: [ @@ -99,6 +104,11 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, ], isImplicit: true, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], levels: [ @@ -262,6 +272,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { ], format: { whiteSpace: 'pre' }, isImplicit: true, + segmentFormat: { fontFamily: 'monospace' }, }, ], format: { @@ -329,6 +340,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, ], isImplicit: true, + segmentFormat: { fontFamily: 'monospace' }, }, ], }, @@ -353,6 +365,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, ], isImplicit: true, + segmentFormat: { fontFamily: 'monospace' }, }, ], }, @@ -905,6 +918,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { whiteSpace: 'pre', }, isImplicit: true, + segmentFormat: { fontFamily: 'monospace' }, }, ], format: { @@ -934,6 +948,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { whiteSpace: 'pre', }, isImplicit: true, + segmentFormat: { fontFamily: 'monospace', fontSize: '20px' }, }, ], format: { @@ -945,7 +960,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { ], }, 'aaa\nbbb\r\naaa\nbb', - '
aaa\nbbb
aaa\nbb
' + '
aaa\nbbb
aaa\nbb
' ); }); @@ -1034,6 +1049,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, ], format: {}, + segmentFormat: { textColor: 'red' }, }, { blockType: 'BlockGroup', @@ -1054,6 +1070,11 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, ], format: {}, + segmentFormat: { + fontFamily: 'Calibri, Arial, Helvetica, sans-serif', + fontSize: '12pt', + textColor: 'rgb(102, 102, 102)', + }, }, ], format: { @@ -1077,6 +1098,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, ], format: {}, + segmentFormat: { textColor: 'red' }, }, { blockType: 'Paragraph', @@ -1090,6 +1112,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, ], format: {}, + segmentFormat: { textColor: 'red' }, }, { blockType: 'Paragraph', @@ -1108,6 +1131,11 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { marginRight: '40px', marginLeft: '40px', }, + segmentFormat: { + fontFamily: 'Calibri, Arial, Helvetica, sans-serif', + fontSize: '12pt', + textColor: 'rgb(102, 102, 102)', + }, }, { blockType: 'Paragraph', @@ -1121,11 +1149,14 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, ], format: {}, + segmentFormat: { + textColor: 'red', + }, }, ], }, 'aaaa\r\nbbbbbb\r\ncccc\r\naaaa\r\nbbbbbb\r\ncccc', - '
aaaa
bbbbbb
cccc
aaaa
bbbbbb
cccc
' + '
aaaa
bbbbbb
cccc
aaaa
bbbbbb
cccc
' ); }); @@ -1167,6 +1198,9 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { format: { whiteSpace: 'pre', }, + segmentFormat: { + fontFamily: 'monospace', + }, }, ], format: { @@ -1570,6 +1604,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { }, }, ], + segmentFormat: { textColor: 'red' }, }, ], }, diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts b/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts index a74793a4d16..95f6f6175f5 100644 --- a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts +++ b/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts @@ -5,6 +5,7 @@ import { createParagraph } from '../../../lib/modelApi/creators/createParagraph' import { createSelectionMarker } from '../../../lib/modelApi/creators/createSelectionMarker'; import { createText } from '../../../lib/modelApi/creators/createText'; import { normalizeContentModel } from '../../../lib/modelApi/common/normalizeContentModel'; +import { normalizeParagraph } from '../../../lib/modelApi/common/normalizeParagraph'; describe('Normalize text that contains space', () => { function runTest(texts: string[], expected: string[], whiteSpace?: string) { @@ -354,3 +355,156 @@ describe('Normalize text that contains space', () => { }); }); }); + +describe('Normalize paragraph with segmentFormat', () => { + it('Empty paragraph', () => { + const paragraph = createParagraph(); + + normalizeParagraph(paragraph); + + expect(paragraph).toEqual({ + blockType: 'Paragraph', + segments: [], + format: {}, + }); + }); + + it('Single text segment', () => { + const paragraph = createParagraph(); + const text = createText('test', { + fontFamily: 'Arial', + }); + + paragraph.segments.push(text); + + normalizeParagraph(paragraph); + + expect(paragraph).toEqual({ + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + format: { fontFamily: 'Arial' }, + text: 'test', + }, + ], + format: {}, + segmentFormat: { fontFamily: 'Arial' }, + }); + }); + + it('text + selection marker + text', () => { + const paragraph = createParagraph(); + const text1 = createText('test1', { + fontFamily: 'Arial', + }); + const text2 = createText('test2', { + fontFamily: 'Arial', + }); + const marker = createSelectionMarker(); + + paragraph.segments.push(text1, marker, text2); + + normalizeParagraph(paragraph); + + expect(paragraph).toEqual({ + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + format: { fontFamily: 'Arial' }, + text: 'test1', + }, + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + format: { fontFamily: 'Arial' }, + text: 'test2', + }, + ], + format: {}, + segmentFormat: { fontFamily: 'Arial' }, + }); + }); + + it('text + selection marker + text, formats are different', () => { + const paragraph = createParagraph(); + const text1 = createText('test1', { + fontFamily: 'Arial', + }); + const text2 = createText('test2', { + fontFamily: 'Tahoma', + }); + const marker = createSelectionMarker(); + + paragraph.segments.push(text1, marker, text2); + + normalizeParagraph(paragraph); + + expect(paragraph).toEqual({ + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + format: { fontFamily: 'Arial' }, + text: 'test1', + }, + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + format: { fontFamily: 'Tahoma' }, + text: 'test2', + }, + ], + format: {}, + }); + }); + + it('text + selection marker + text, formats are partially different', () => { + const paragraph = createParagraph(); + const text1 = createText('test1', { + fontFamily: 'Arial', + fontSize: '10px', + }); + const text2 = createText('test2', { + fontFamily: 'Tahoma', + fontSize: '10px', + }); + const marker = createSelectionMarker(); + + paragraph.segments.push(text1, marker, text2); + + normalizeParagraph(paragraph); + + expect(paragraph).toEqual({ + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + format: { fontFamily: 'Arial', fontSize: '10px' }, + text: 'test1', + }, + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + format: { fontFamily: 'Tahoma', fontSize: '10px' }, + text: 'test2', + }, + ], + format: {}, + segmentFormat: { fontSize: '10px' }, + }); + }); +}); diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts index d7fd8e61f1f..1e955d6f64a 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts @@ -88,6 +88,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -131,6 +136,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -172,6 +182,11 @@ describe(ID, () => { format: { textAlign: 'center', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -218,6 +233,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -268,6 +288,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(5, 99, 193)', + }, }, ], format: { @@ -309,6 +334,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(219, 219, 219)', + }, }, ], format: { @@ -356,6 +386,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -406,6 +441,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(5, 99, 193)', + }, }, ], format: { @@ -447,6 +487,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(219, 219, 219)', + }, }, ], format: { @@ -494,6 +539,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -544,6 +594,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(5, 99, 193)', + }, }, ], format: { @@ -585,6 +640,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(219, 219, 219)', + }, }, ], format: { @@ -632,6 +692,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -682,6 +747,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(5, 99, 193)', + }, }, ], format: { @@ -723,6 +793,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(219, 219, 219)', + }, }, ], format: { @@ -770,6 +845,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -820,6 +900,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(5, 99, 193)', + }, }, ], format: { @@ -861,6 +946,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(219, 219, 219)', + }, }, ], format: { @@ -908,6 +998,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -958,6 +1053,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(5, 99, 193)', + }, }, ], format: { @@ -999,6 +1099,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(219, 219, 219)', + }, }, ], format: { @@ -1046,6 +1151,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'black', + }, }, ], format: { @@ -1096,6 +1206,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(5, 99, 193)', + }, }, ], format: { @@ -1137,6 +1252,11 @@ describe(ID, () => { textAlign: 'center', whiteSpace: 'nowrap', }, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '11pt', + textColor: 'rgb(219, 219, 219)', + }, }, ], format: { diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts index e818e12b1ae..2fa1514ea94 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts @@ -350,6 +350,7 @@ describe('processPastedContentFromWordDesktopTest', () => { ], format: {}, isImplicit: true, + segmentFormat: { fontSize: '2em' }, }, ], levels: [ @@ -441,6 +442,7 @@ describe('processPastedContentFromWordDesktopTest', () => { ], format: {}, isImplicit: true, + segmentFormat: { fontSize: '2em' }, }, ], levels: [ @@ -541,6 +543,7 @@ describe('processPastedContentFromWordDesktopTest', () => { ], format: {}, isImplicit: true, + segmentFormat: { fontSize: '2em' }, }, ], levels: [ @@ -648,6 +651,7 @@ describe('processPastedContentFromWordDesktopTest', () => { ], format: {}, isImplicit: true, + segmentFormat: { fontSize: '2em' }, }, ], levels: [ @@ -1302,6 +1306,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], format: {}, isImplicit: true, + segmentFormat: { + fontFamily: 'Calibri, sans-serif', + fontSize: '12pt', + }, }, ], levels: [ @@ -1372,6 +1380,7 @@ describe('processPastedContentFromWordDesktopTest', () => { ], format: {}, isImplicit: true, + segmentFormat: { fontFamily: 'Arial, sans-serif' }, }, ], levels: [ @@ -1407,6 +1416,7 @@ describe('processPastedContentFromWordDesktopTest', () => { ], format: {}, isImplicit: true, + segmentFormat: { fontFamily: 'Arial, sans-serif' }, }, ], levels: [ @@ -1512,6 +1522,9 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + textColor: 'rgb(70, 120, 134)', + }, }, ], }, @@ -1554,6 +1567,9 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + textColor: 'rgb(70, 120, 134)', + }, }, ], }, @@ -1596,6 +1612,9 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + textColor: 'rgb(70, 120, 134)', + }, }, ], }, @@ -1660,6 +1679,9 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + textColor: 'rgb(70, 120, 134)', + }, }, ], }, @@ -1702,6 +1724,9 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + textColor: 'rgb(70, 120, 134)', + }, }, ], }, @@ -2516,6 +2541,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -2579,6 +2608,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -2654,6 +2687,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -2741,6 +2778,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -2840,6 +2881,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -2951,6 +2996,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -3074,6 +3123,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -3209,6 +3262,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -3356,6 +3413,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -3490,6 +3551,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -3612,6 +3677,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -3722,6 +3791,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -3820,6 +3893,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -3906,6 +3983,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -3980,6 +4061,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -4042,6 +4127,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, @@ -4092,6 +4181,10 @@ describe('processPastedContentFromWordDesktopTest', () => { ], blockType: 'Paragraph', format: {}, + segmentFormat: { + fontFamily: 'Aptos, sans-serif', + fontSize: '12pt', + }, }, ], }, From 8693be879bd161be4f705be749e87e1ad70e234d Mon Sep 17 00:00:00 2001 From: Bryan Valverde U Date: Fri, 1 Mar 2024 12:30:31 -0600 Subject: [PATCH 37/80] Remove Bg Color from block on Merge Paste (#2458) * init * Remove unneeded code * address comment * remove empty line --- .../lib/publicApi/model/mergeModel.ts | 7 ++ .../test/publicApi/model/mergeModelTest.ts | 74 +++++++++++++++++++ .../lib/format/ContentModelListItemFormat.ts | 4 +- 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts b/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts index 2a6621fa30f..bb499451ad1 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts +++ b/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts @@ -343,6 +343,7 @@ function applyDefaultFormat( applyDefaultFormatOption: 'mergeAll' | 'keepSourceEmphasisFormat' ) { group.blocks.forEach(block => { + mergeBlockFormat(applyDefaultFormatOption, block); switch (block.blockType) { case 'BlockGroup': if (block.blockGroupType == 'ListItem') { @@ -384,6 +385,12 @@ function applyDefaultFormat( }); } +function mergeBlockFormat(applyDefaultFormatOption: string, block: ContentModelBlock) { + if (applyDefaultFormatOption == 'keepSourceEmphasisFormat' && block.format.backgroundColor) { + delete block.format.backgroundColor; + } +} + function mergeSegmentFormat( applyDefaultFormatOption: 'mergeAll' | 'keepSourceEmphasisFormat', targetformat: ContentModelSegmentFormat, diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts index 0015f4caaf3..4cde086cbb4 100644 --- a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts +++ b/packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts @@ -1784,6 +1784,80 @@ describe('mergeModel', () => { }); }); + it('Merge with keepSourceEmphasisFormat and remove background color of model', () => { + const MockedFormat = { + formatName: 'mocked', + } as any; + const majorModel = createContentModelDocument(MockedFormat); + const sourceModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: { + formatName: 'ToBeRemoved', + fontWeight: 'sourceFontWeight', + italic: true, + underline: true, + } as any, + }, + ], + format: { + backgroundColor: 'ToBeRemoved', + }, + }, + ], + }; + const para1 = createParagraph(); + const marker = createSelectionMarker(); + + para1.segments.push(marker); + majorModel.blocks.push(para1); + + const result = mergeModel( + majorModel, + sourceModel, + { newEntities: [], deletedEntities: [], newImages: [] }, + { + mergeFormat: 'keepSourceEmphasisFormat', + } + ); + + const paragraph: ContentModelParagraph = { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: { + formatName: 'mocked', + fontWeight: 'sourceFontWeight', + italic: true, + underline: true, + } as any, + }, + marker, + ], + format: {}, + }; + + expect(majorModel).toEqual({ + blockGroupType: 'Document', + blocks: [paragraph], + format: MockedFormat, + }); + expect(result).toEqual({ + marker, + paragraph, + path: [majorModel], + tableContext: undefined, + }); + }); + it('Merge model with List Item with default format, keep the source bold, italic and underline', () => { const MockedFormat = { formatName: 'mocked', diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelListItemFormat.ts b/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelListItemFormat.ts index 8fdcd75c06d..f2d7b550b62 100644 --- a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelListItemFormat.ts +++ b/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelListItemFormat.ts @@ -1,3 +1,4 @@ +import type { BackgroundColorFormat } from './formatParts/BackgroundColorFormat'; import type { DirectionFormat } from './formatParts/DirectionFormat'; import type { LineHeightFormat } from './formatParts/LineHeightFormat'; import type { ListStyleFormat } from './formatParts/ListStyleFormat'; @@ -15,4 +16,5 @@ export type ContentModelListItemFormat = DirectionFormat & PaddingFormat & TextAlignFormat & ListStyleFormat & - TextIndentFormat; + TextIndentFormat & + BackgroundColorFormat; From 489ff95f07d270758f64e2787cc207a2411c55b8 Mon Sep 17 00:00:00 2001 From: "Julia Roldi (from Dev Box)" Date: Fri, 1 Mar 2024 16:17:48 -0300 Subject: [PATCH 38/80] fix test --- .../test/edit/deleteSteps/deleteEmptyQuoteTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts index d8d9848ddf9..4eb894006bc 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts @@ -76,6 +76,7 @@ describe('deleteEmptyQuoteTest', () => { }, }, ], + segmentFormat: { textColor: 'rgb(102, 102, 102)' }, format: {}, }, ], From a67d54c30026bb9bc8fd2b13fcbfd6819cd5eb29 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Mon, 4 Mar 2024 09:12:31 -0800 Subject: [PATCH 39/80] Reorganize source code (#2464) * Reorganize source code * fix test --- demo/index.html | 1 + demo/scripts/tsconfig.json | 40 +++----- karma.conf.js | 35 +------ package.json | 4 - packages-content-model/tsconfig.json | 24 ----- packages-content-model/tsconfig.test.json | 24 ----- packages-ui/tsconfig.json | 23 ----- packages-ui/tsconfig.test.json | 23 ----- .../roosterjs-content-model-api/lib/index.ts | 0 .../lib/modelApi/block/setModelAlignment.ts | 0 .../lib/modelApi/block/setModelDirection.ts | 0 .../lib/modelApi/block/setModelIndentation.ts | 0 .../modelApi/block/toggleModelBlockQuote.ts | 0 .../lib/modelApi/common/clearModelFormat.ts | 0 .../lib/modelApi/common/wrapBlock.ts | 0 .../lib/modelApi/entity/insertEntityModel.ts | 0 .../modelApi/image/applyImageBorderFormat.ts | 0 .../lib/modelApi/link/matchLink.ts | 0 .../list/findListItemsInSameThread.ts | 0 .../lib/modelApi/list/setListType.ts | 0 .../selection/adjustSegmentSelection.ts | 0 .../selection/adjustTrailingSpaceSelection.ts | 0 .../modelApi/selection/adjustWordSelection.ts | 0 .../selection/collapseTableSelection.ts | 0 .../lib/modelApi/table/alignTable.ts | 0 .../lib/modelApi/table/alignTableCell.ts | 0 .../lib/modelApi/table/canMergeCells.ts | 0 .../lib/modelApi/table/clearSelectedCells.ts | 0 .../modelApi/table/createTableStructure.ts | 0 .../lib/modelApi/table/deleteTable.ts | 0 .../lib/modelApi/table/deleteTableColumn.ts | 0 .../lib/modelApi/table/deleteTableRow.ts | 0 .../table/ensureFocusableParagraphForTable.ts | 0 .../lib/modelApi/table/insertTableColumn.ts | 0 .../lib/modelApi/table/insertTableRow.ts | 0 .../lib/modelApi/table/mergeTableCells.ts | 0 .../lib/modelApi/table/mergeTableColumn.ts | 0 .../lib/modelApi/table/mergeTableRow.ts | 0 .../table/splitTableCellHorizontally.ts | 0 .../table/splitTableCellVertically.ts | 0 .../lib/publicApi/block/setAlignment.ts | 0 .../lib/publicApi/block/setDirection.ts | 0 .../lib/publicApi/block/setHeadingLevel.ts | 0 .../lib/publicApi/block/setIndentation.ts | 0 .../lib/publicApi/block/setParagraphMargin.ts | 0 .../lib/publicApi/block/setSpacing.ts | 0 .../lib/publicApi/block/toggleBlockQuote.ts | 0 .../lib/publicApi/entity/insertEntity.ts | 0 .../lib/publicApi/format/clearFormat.ts | 0 .../lib/publicApi/format/getFormatState.ts | 0 .../publicApi/image/adjustImageSelection.ts | 0 .../lib/publicApi/image/changeImage.ts | 0 .../lib/publicApi/image/insertImage.ts | 0 .../lib/publicApi/image/setImageAltText.ts | 0 .../lib/publicApi/image/setImageBorder.ts | 0 .../lib/publicApi/image/setImageBoxShadow.ts | 0 .../lib/publicApi/link/adjustLinkSelection.ts | 0 .../lib/publicApi/link/insertLink.ts | 0 .../lib/publicApi/link/removeLink.ts | 0 .../lib/publicApi/list/setListStartNumber.ts | 0 .../lib/publicApi/list/setListStyle.ts | 0 .../lib/publicApi/list/toggleBullet.ts | 0 .../lib/publicApi/list/toggleNumbering.ts | 0 .../publicApi/segment/applySegmentFormat.ts | 0 .../publicApi/segment/changeCapitalization.ts | 0 .../lib/publicApi/segment/changeFontSize.ts | 0 .../publicApi/segment/setBackgroundColor.ts | 0 .../lib/publicApi/segment/setFontName.ts | 0 .../lib/publicApi/segment/setFontSize.ts | 0 .../lib/publicApi/segment/setTextColor.ts | 0 .../lib/publicApi/segment/toggleBold.ts | 0 .../lib/publicApi/segment/toggleCode.ts | 0 .../lib/publicApi/segment/toggleItalic.ts | 0 .../publicApi/segment/toggleStrikethrough.ts | 0 .../lib/publicApi/segment/toggleSubscript.ts | 0 .../publicApi/segment/toggleSuperscript.ts | 0 .../lib/publicApi/segment/toggleUnderline.ts | 0 .../publicApi/table/applyTableBorderFormat.ts | 0 .../lib/publicApi/table/editTable.ts | 0 .../lib/publicApi/table/formatTable.ts | 0 .../lib/publicApi/table/insertTable.ts | 0 .../lib/publicApi/table/setTableCellShade.ts | 0 .../utils/formatImageWithContentModel.ts | 0 .../utils/formatParagraphWithContentModel.ts | 0 .../utils/formatSegmentWithContentModel.ts | 0 .../utils/formatTableWithContentModel.ts | 0 .../roosterjs-content-model-api/package.json | 0 .../modelApi/block/setModelAlignmentTest.ts | 0 .../modelApi/block/setModelDirectionTest.ts | 0 .../modelApi/block/setModelIndentationTest.ts | 0 .../block/toggleModelBlockQuoteTest.ts | 0 .../modelApi/common/clearModelFormatTest.ts | 0 .../test/modelApi/common/wrapBlockTest.ts | 0 .../modelApi/entity/insertEntityModelTest.ts | 0 .../image/applyImageBorderFormatTest.ts | 0 .../test/modelApi/link/matchLinkTest.ts | 0 .../list/findListItemsInSameThreadTest.ts | 0 .../test/modelApi/list/setListTypeTest.ts | 0 .../selection/adjustSegmentSelectionTest.ts | 0 .../adjustTrailingSpaceSelectionTest.ts | 0 .../selection/adjustWordSelectionTest.ts | 0 .../selection/collapseTableSelectionTest.ts | 0 .../test/modelApi/table/alignTableCellTest.ts | 0 .../test/modelApi/table/alignTableTest.ts | 0 .../test/modelApi/table/canMergeCellsTest.ts | 0 .../modelApi/table/clearSelectedCellsTest.ts | 0 .../table/createTableStructureTest.ts | 0 .../modelApi/table/deleteTableColumnTest.ts | 0 .../test/modelApi/table/deleteTableRowTest.ts | 0 .../test/modelApi/table/deleteTableTest.ts | 0 .../ensureFocusableParagraphForTableTest.ts | 0 .../modelApi/table/insertTableColumnTest.ts | 0 .../test/modelApi/table/insertTableRowTest.ts | 0 .../modelApi/table/mergeTableCellsTest.ts | 0 .../modelApi/table/mergeTableColumnTest.ts | 0 .../test/modelApi/table/mergeTableRowTest.ts | 0 .../table/splitTableCellHorizontallyTest.ts | 0 .../table/splitTableCellVerticallyTest.ts | 0 .../publicApi/block/paragraphTestCommon.ts | 0 .../test/publicApi/block/setAlignmentTest.ts | 0 .../test/publicApi/block/setDirectionTest.ts | 0 .../publicApi/block/setHeadingLevelTest.ts | 0 .../publicApi/block/setIndentationTest.ts | 0 .../publicApi/block/setParagraphMarginTest.ts | 0 .../test/publicApi/block/setSpacingTest.ts | 0 .../publicApi/block/toggleBlockQuoteTest.ts | 0 .../test/publicApi/entity/insertEntityTest.ts | 0 .../test/publicApi/format/clearFormatTest.ts | 0 .../publicApi/format/getFormatStateTest.ts | 0 .../image/adjustImageSelectionTest.ts | 0 .../test/publicApi/image/changeImageTest.ts | 0 .../test/publicApi/image/insertImageTest.ts | 0 .../publicApi/image/setImageAltTextTest.ts | 0 .../publicApi/image/setImageBorderTest.ts | 0 .../publicApi/image/setImageBoxShadowTest.ts | 0 .../publicApi/link/adjustLinkSelectionTest.ts | 0 .../test/publicApi/link/insertLinkTest.ts | 0 .../test/publicApi/link/removeLinkTest.ts | 0 .../publicApi/list/setListStartNumberTest.ts | 0 .../test/publicApi/list/setListStyleTest.ts | 0 .../test/publicApi/list/toggleBulletTest.ts | 0 .../publicApi/list/toggleNumberingTest.ts | 0 .../segment/applySegmentFormatTest.ts | 0 .../segment/changeCapitalizationTest.ts | 0 .../publicApi/segment/changeFontSizeTest.ts | 0 .../publicApi/segment/segmentTestCommon.ts | 0 .../segment/setBackgroundColorTest.ts | 0 .../test/publicApi/segment/setFontNameTest.ts | 0 .../test/publicApi/segment/setFontSizeTest.ts | 0 .../publicApi/segment/setTextColorTest.ts | 0 .../test/publicApi/segment/toggleBoldTest.ts | 0 .../test/publicApi/segment/toggleCodeTest.ts | 0 .../publicApi/segment/toggleItalicTest.ts | 0 .../segment/toggleStrikethroughTest.ts | 0 .../publicApi/segment/toggleSubscriptTest.ts | 0 .../segment/toggleSuperscriptTest.ts | 0 .../publicApi/segment/toggleUnderlineTest.ts | 0 .../table/applyTableBorderFormatTest.ts | 0 .../test/publicApi/table/editTableTest.ts | 0 .../publicApi/table/setTableCellShadeTest.ts | 0 .../utils/formatImageWithContentModelTest.ts | 0 .../formatParagraphWithContentModelTest.ts | 0 .../formatSegmentWithContentModelTest.ts | 0 .../utils/formatTableWithContentModelTest.ts | 0 .../lib/constants/BulletListType.ts | 0 .../lib/constants/ChangeSource.ts | 0 .../lib/constants/NumberingListType.ts | 0 .../lib/constants/TableBorderFormat.ts | 0 .../lib/coreApi/addUndoSnapshot.ts | 0 .../lib/coreApi/attachDomEvent.ts | 0 .../lib/coreApi/createContentModel.ts | 0 .../lib/coreApi/createEditorContext.ts | 0 .../lib/coreApi/focus.ts | 0 .../lib/coreApi/formatContentModel.ts | 0 .../lib/coreApi/getDOMSelection.ts | 0 .../lib/coreApi/getVisibleViewport.ts | 0 .../lib/coreApi/hasFocus.ts | 0 .../lib/coreApi/paste.ts | 0 .../lib/coreApi/restoreUndoSnapshot.ts | 0 .../lib/coreApi/setContentModel.ts | 0 .../lib/coreApi/setDOMSelection.ts | 0 .../lib/coreApi/switchShadowEdit.ts | 0 .../lib/coreApi/triggerEvent.ts | 0 .../lib/corePlugin/CachePlugin.ts | 0 .../lib/corePlugin/ContextMenuPlugin.ts | 0 .../lib/corePlugin/CopyPastePlugin.ts | 0 .../lib/corePlugin/DOMEventPlugin.ts | 0 .../lib/corePlugin/EntityPlugin.ts | 0 .../lib/corePlugin/FormatPlugin.ts | 0 .../lib/corePlugin/LifecyclePlugin.ts | 0 .../lib/corePlugin/SelectionPlugin.ts | 0 .../lib/corePlugin/UndoPlugin.ts | 0 .../lib/corePlugin/createEditorCorePlugins.ts | 0 .../corePlugin/utils/addRangeToSelection.ts | 0 .../corePlugin/utils/applyDefaultFormat.ts | 0 .../corePlugin/utils/applyPendingFormat.ts | 0 .../lib/corePlugin/utils/areSameSelection.ts | 0 .../lib/corePlugin/utils/deleteEmptyList.ts | 0 .../lib/corePlugin/utils/domIndexerImpl.ts | 0 .../corePlugin/utils/entityDelimiterUtils.ts | 0 .../lib/corePlugin/utils/findAllEntities.ts | 0 .../corePlugin/utils/textMutationObserver.ts | 0 .../lib/editor/DOMHelperImpl.ts | 0 .../lib/editor/DarkColorHandlerImpl.ts | 0 .../lib/editor/Editor.ts | 0 .../lib/editor/SnapshotsManagerImpl.ts | 0 .../lib/editor/coreApiMap.ts | 0 .../lib/editor/createEditorCore.ts | 0 .../lib/editor/createEditorDefaultSettings.ts | 0 .../roosterjs-content-model-core/lib/index.ts | 0 .../lib/metadata/definitionCreators.ts | 0 .../lib/metadata/updateImageMetadata.ts | 0 .../lib/metadata/updateListMetadata.ts | 0 .../lib/metadata/updateTableCellMetadata.ts | 0 .../lib/metadata/updateTableMetadata.ts | 0 .../modelApi/edit/deleteExpandedSelection.ts | 0 .../lib/modelApi/edit/deleteSingleChar.ts | 0 .../lib/override/containerSizeFormatParser.ts | 0 .../override/pasteCopyBlockEntityParser.ts | 86 ++++++++-------- .../lib/override/pasteDisplayFormatParser.ts | 0 .../lib/override/pasteEntityProcessor.ts | 0 .../lib/override/pasteGeneralProcessor.ts | 0 .../lib/override/pasteTextProcessor.ts | 0 .../override/reducedModelChildProcessor.ts | 0 .../lib/override/tablePreProcessor.ts | 0 .../lib/publicApi/color/transformColor.ts | 0 .../lib/publicApi/domUtils/borderValues.ts | 0 .../publicApi/domUtils/cacheGetEventData.ts | 0 .../lib/publicApi/domUtils/eventUtils.ts | 0 .../domUtils/getSegmentTextFormat.ts | 0 .../lib/publicApi/domUtils/readFile.ts | 0 .../lib/publicApi/domUtils/stringUtil.ts | 0 .../lib/publicApi/domUtils/tableCellUtils.ts | 0 .../format/retrieveModelFormatState.ts | 0 .../lib/publicApi/model/cloneModel.ts | 0 .../publicApi/model/createModelFromHtml.ts | 0 .../lib/publicApi/model/exportContent.ts | 0 .../getClosestAncestorBlockGroupIndex.ts | 0 .../lib/publicApi/model/isBlockGroupOfType.ts | 0 .../lib/publicApi/model/isBold.ts | 0 .../lib/publicApi/model/mergeModel.ts | 0 .../publicApi/selection/collectSelections.ts | 0 .../lib/publicApi/selection/deleteBlock.ts | 0 .../lib/publicApi/selection/deleteSegment.ts | 0 .../publicApi/selection/deleteSelection.ts | 0 .../selection/getSelectionRootNode.ts | 0 .../selection/hasSelectionInBlock.ts | 0 .../selection/hasSelectionInBlockGroup.ts | 0 .../selection/hasSelectionInSegment.ts | 0 .../publicApi/selection/iterateSelections.ts | 0 .../lib/publicApi/selection/setSelection.ts | 0 .../lib/publicApi/table/applyTableFormat.ts | 0 .../lib/publicApi/table/getSelectedCells.ts | 0 .../lib/publicApi/table/normalizeTable.ts | 0 .../table/setTableCellBackgroundColor.ts | 0 .../lib/publicApi/undo/redo.ts | 0 .../lib/publicApi/undo/undo.ts | 0 .../lib/utils/convertInlineCss.ts | 0 .../createDomToModelContextForSanitizing.ts | 0 .../lib/utils/createSnapshotSelection.ts | 0 .../lib/utils/extractClipboardItems.ts | 0 .../utils/getRootComputedStyleForContext.ts | 0 .../lib/utils/paste/createPasteFragment.ts | 0 .../paste/generatePasteOptionFromPlugins.ts | 0 .../lib/utils/paste/mergePasteContent.ts | 0 .../lib/utils/paste/retrieveHtmlInfo.ts | 0 .../lib/utils/restoreSnapshotColors.ts | 0 .../lib/utils/restoreSnapshotHTML.ts | 0 .../lib/utils/restoreSnapshotSelection.ts | 0 .../lib/utils/sanitizeElement.ts | 0 .../roosterjs-content-model-core/package.json | 0 .../test/coreApi/addUndoSnapshotTest.ts | 0 .../test/coreApi/attachDomEventTest.ts | 0 .../test/coreApi/createContentModelTest.ts | 0 .../test/coreApi/createEditorContextTest.ts | 0 .../test/coreApi/focusTest.ts | 0 .../test/coreApi/formatContentModelTest.ts | 0 .../test/coreApi/getDOMSelectionTest.ts | 0 .../test/coreApi/getVisibleViewportTest.ts | 0 .../test/coreApi/hasFocusTest.ts | 0 .../test/coreApi/pasteTest.ts | 0 .../test/coreApi/restoreUndoSnapshotTest.ts | 0 .../test/coreApi/setContentModelTest.ts | 0 .../test/coreApi/setDOMSelectionTest.ts | 0 .../test/coreApi/switchShadowEditTest.ts | 0 .../test/coreApi/triggerEventTest.ts | 0 .../test/corePlugin/CachePluginTest.ts | 0 .../test/corePlugin/ContextMenuPluginTest.ts | 0 .../test/corePlugin/CopyPastePluginTest.ts | 0 .../test/corePlugin/DomEventPluginTest.ts | 0 .../test/corePlugin/EntityPluginTest.ts | 0 .../test/corePlugin/FormatPluginTest.ts | 0 .../test/corePlugin/LifecyclePluginTest.ts | 0 .../test/corePlugin/SelectionPluginTest.ts | 0 .../test/corePlugin/UndoPluginTest.ts | 0 .../utils/applyDefaultFormatTest.ts | 0 .../utils/applyPendingFormatTest.ts | 0 .../corePlugin/utils/areSameRangeExTest.ts | 0 .../corePlugin/utils/delimiterUtilsTest.ts | 0 .../test/corePlugin/utils/domIndexerTest.ts | 0 .../corePlugin/utils/findAllEntitiesTest.ts | 0 .../utils/textMutationObserverTest.ts | 0 .../test/editor/DOMHelperImplTest.ts | 0 .../test/editor/DarkColorHandlerImplTest.ts | 0 .../test/editor/EditorTest.ts | 0 .../test/editor/SnapshotsManagerImplTest.ts | 0 .../test/editor/createEditorCoreTest.ts | 0 .../editor/createEditorDefaultSettingsTest.ts | 0 .../handleListItemWithMetadataTest.ts | 0 .../metadata/handleListWithMetadataTest.ts | 0 .../test/metadata/updateImageMetadataTest.ts | 0 .../test/metadata/updateListMetadataTest.ts | 0 .../metadata/updateTableCellMetadataTest.ts | 0 .../test/metadata/updateTableMetadataTest.ts | 0 .../modelApi/edit/deleteSingleCharTest.ts | 0 .../containerSizeFormatParserTest.ts | 0 .../pasteCopyBlockEntityParserTest.ts | 0 .../overrides/pasteDisplayFormatParserTest.ts | 0 .../overrides/pasteEntityProcessorTest.ts | 0 .../overrides/pasteGeneralProcessorTest.ts | 0 .../test/overrides/pasteTextProcessorTest.ts | 0 .../reducedModelChildProcessorTest.ts | 0 .../test/overrides/tablePreProcessorTest.ts | 0 .../publicApi/color/transformColorTest.ts | 0 .../publicApi/domUtils/borderValuesTest.ts | 0 .../domUtils/cacheGetEventDataTest.ts | 0 .../domUtils/getSegmentTextFormatTest.ts | 0 .../publicApi/domUtils/tableCellUtilsTest.ts | 0 .../format/retrieveModelFormatStateTest.ts | 0 .../test/publicApi/model/cloneModelTest.ts | 0 .../model/createModelFromHtmlTest.ts | 0 .../test/publicApi/model/exportContentTest.ts | 0 .../getClosestAncestorBlockGroupIndexTest.ts | 0 .../publicApi/model/isBlockGroupOfTypeTest.ts | 0 .../test/publicApi/model/mergeModelTest.ts | 0 .../selection/collectSelectionsTest.ts | 0 .../selection/deleteSelectionTest.ts | 0 .../selection/getSelectedSegmentsTest.ts | 0 .../selection/getSelectionRootNodeTest.ts | 0 .../selection/hasSelectionInBlockTest.ts | 0 .../selection/hasSelectionInSegmentTest.ts | 0 .../selection/iterateSelectionsTest.ts | 0 .../publicApi/selection/setSelectionTest.ts | 0 .../publicApi/table/applyTableFormatTest.ts | 0 .../publicApi/table/getSelectedCellsTest.ts | 0 .../publicApi/table/normalizeTableTest.ts | 0 .../table/setTableCellBackgroundColorTest.ts | 0 .../test/publicApi/undo/redoTest.ts | 0 .../test/publicApi/undo/undoTest.ts | 0 .../test/utils/convertInlineCssTest.ts | 0 ...reateDomToModelContextForSanitizingTest.ts | 0 .../test/utils/createSnapshotSelectionTest.ts | 0 .../test/utils/extractClipboardItemsTest.ts | 0 .../utils/paste/createPasteFragmentTest.ts | 0 .../generatePasteOptionFromPluginsTest.ts | 0 .../test/utils/paste/mergePasteContentTest.ts | 0 .../test/utils/paste/retrieveHtmlInfoTest.ts | 0 .../test/utils/restoreSnapshotColorsTest.ts | 0 .../test/utils/restoreSnapshotHTMLTest.ts | 0 .../utils/restoreSnapshotSelectionTest.ts | 0 .../test/utils/sanitizeElementTest.ts | 0 .../config/defaultContentModelFormatMap.ts | 0 .../lib/config/defaultHTMLStyleMap.ts | 0 .../context/createDomToModelContext.ts | 0 .../domToModel/context/defaultProcessors.ts | 0 .../lib/domToModel/domToContentModel.ts | 0 .../domToModel/processors/blockProcessor.ts | 0 .../lib/domToModel/processors/brProcessor.ts | 0 .../domToModel/processors/childProcessor.ts | 0 .../domToModel/processors/codeProcessor.ts | 0 .../processors/delimiterProcessor.ts | 0 .../domToModel/processors/elementProcessor.ts | 0 .../domToModel/processors/entityProcessor.ts | 0 .../domToModel/processors/fontProcessor.ts | 0 .../processors/formatContainerProcessor.ts | 0 .../domToModel/processors/generalProcessor.ts | 0 .../domToModel/processors/headingProcessor.ts | 0 .../lib/domToModel/processors/hrProcessor.ts | 0 .../domToModel/processors/imageProcessor.ts | 0 .../processors/knownElementProcessor.ts | 0 .../domToModel/processors/linkProcessor.ts | 0 .../processors/listItemProcessor.ts | 0 .../domToModel/processors/listProcessor.ts | 0 .../lib/domToModel/processors/pProcessor.ts | 0 .../domToModel/processors/tableProcessor.ts | 0 .../domToModel/processors/textProcessor.ts | 0 .../domToModel/utils/addSelectionMarker.ts | 0 .../lib/domToModel/utils/areSameFormats.ts | 0 .../domToModel/utils/getBoundingClientRect.ts | 0 .../lib/domToModel/utils/getDefaultStyle.ts | 0 .../utils/getRegularSelectionOffsets.ts | 0 .../lib/domToModel/utils/isBlockElement.ts | 0 .../lib/domToModel/utils/parseFormat.ts | 0 .../lib/domToModel/utils/stackFormat.ts | 0 .../lib/domUtils/entityUtils.ts | 0 .../lib/domUtils/getObjectKeys.ts | 0 .../lib/domUtils/isElementOfType.ts | 0 .../lib/domUtils/isNodeOfType.ts | 0 .../lib/domUtils/isWhiteSpacePreserved.ts | 0 .../lib/domUtils/metadata/updateMetadata.ts | 0 .../lib/domUtils/metadata/validate.ts | 0 .../lib/domUtils/moveChildNodes.ts | 0 .../lib/domUtils/reuseCachedElement.ts | 0 .../lib/domUtils/toArray.ts | 0 .../lib/domUtils/unwrap.ts | 0 .../lib/domUtils/wrap.ts | 0 .../lib/formatHandlers/FormatHandler.ts | 0 .../block/directionFormatHandler.ts | 0 .../block/displayFormatHandler.ts | 0 .../block/htmlAlignFormatHandler.ts | 0 .../block/lineHeightFormatHandler.ts | 0 .../block/marginFormatHandler.ts | 0 .../block/paddingFormatHandler.ts | 0 .../block/textAlignFormatHandler.ts | 0 .../block/textIndentFormatHandler.ts | 0 .../block/whiteSpaceFormatHandler.ts | 0 .../common/backgroundColorFormatHandler.ts | 0 .../common/borderBoxFormatHandler.ts | 0 .../common/borderFormatHandler.ts | 0 .../common/boxShadowFormatHandler.ts | 0 .../common/datasetFormatHandler.ts | 0 .../common/floatFormatHandler.ts | 0 .../formatHandlers/common/idFormatHandler.ts | 0 .../common/sizeFormatHandler.ts | 0 .../common/verticalAlignFormatHandler.ts | 0 .../common/wordBreakFormatHandler.ts | 0 .../formatHandlers/defaultFormatHandlers.ts | 0 .../entity/entityFormatHandler.ts | 0 .../list/listItemThreadFormatHandler.ts | 0 .../list/listLevelThreadFormatHandler.ts | 0 .../list/listStyleFormatHandler.ts | 0 .../segment/boldFormatHandler.ts | 0 .../segment/fontFamilyFormatHandler.ts | 0 .../segment/fontSizeFormatHandler.ts | 0 .../segment/italicFormatHandler.ts | 0 .../segment/letterSpacingFormatHandler.ts | 0 .../segment/linkFormatHandler.ts | 0 .../segment/strikeFormatHandler.ts | 0 .../segment/superOrSubScriptFormatHandler.ts | 0 .../segment/textColorFormatHandler.ts | 0 .../segment/underlineFormatHandler.ts | 0 .../table/tableLayoutFormatHandler.ts | 0 .../table/tableSpacingFormatHandler.ts | 0 .../textColorOnTableCellFormatHandler.ts | 0 .../lib/formatHandlers/utils/color.ts | 0 .../lib/formatHandlers/utils/dir.ts | 0 .../utils/parseValueWithUnit.ts | 0 .../formatHandlers/utils/shouldSetValue.ts | 0 .../roosterjs-content-model-dom/lib/index.ts | 0 .../modelApi/block/setParagraphNotImplicit.ts | 0 .../lib/modelApi/common/addBlock.ts | 0 .../lib/modelApi/common/addDecorators.ts | 0 .../lib/modelApi/common/addSegment.ts | 0 .../lib/modelApi/common/ensureParagraph.ts | 0 .../lib/modelApi/common/hasSpacesOnly.ts | 0 .../lib/modelApi/common/hasSpacesOnlyTest.ts | 0 .../lib/modelApi/common/isEmpty.ts | 0 .../lib/modelApi/common/isGeneralSegment.ts | 0 .../modelApi/common/normalizeContentModel.ts | 0 .../lib/modelApi/common/normalizeParagraph.ts | 0 .../lib/modelApi/common/normalizeSegment.ts | 0 .../lib/modelApi/common/unwrapBlock.ts | 0 .../lib/modelApi/creators/createBr.ts | 0 .../creators/createContentModelDocument.ts | 0 .../lib/modelApi/creators/createDivider.ts | 0 .../lib/modelApi/creators/createEmptyModel.ts | 0 .../lib/modelApi/creators/createEntity.ts | 0 .../creators/createFormatContainer.ts | 0 .../modelApi/creators/createGeneralBlock.ts | 0 .../modelApi/creators/createGeneralSegment.ts | 0 .../lib/modelApi/creators/createImage.ts | 0 .../lib/modelApi/creators/createListItem.ts | 0 .../lib/modelApi/creators/createListLevel.ts | 0 .../lib/modelApi/creators/createParagraph.ts | 0 .../creators/createParagraphDecorator.ts | 0 .../creators/createSelectionMarker.ts | 0 .../lib/modelApi/creators/createTable.ts | 0 .../lib/modelApi/creators/createTableCell.ts | 0 .../lib/modelApi/creators/createText.ts | 0 .../lib/modelToDom/contentModelToDom.ts | 0 .../context/createModelToDomContext.ts | 0 .../context/defaultContentModelHandlers.ts | 0 .../lib/modelToDom/handlers/handleBlock.ts | 0 .../handlers/handleBlockGroupChildren.ts | 0 .../lib/modelToDom/handlers/handleBr.ts | 0 .../lib/modelToDom/handlers/handleDivider.ts | 0 .../lib/modelToDom/handlers/handleEntity.ts | 0 .../handlers/handleFormatContainer.ts | 0 .../modelToDom/handlers/handleGeneralModel.ts | 0 .../lib/modelToDom/handlers/handleImage.ts | 0 .../lib/modelToDom/handlers/handleList.ts | 0 .../lib/modelToDom/handlers/handleListItem.ts | 0 .../modelToDom/handlers/handleParagraph.ts | 0 .../lib/modelToDom/handlers/handleSegment.ts | 0 .../handlers/handleSegmentDecorator.ts | 0 .../lib/modelToDom/handlers/handleTable.ts | 0 .../lib/modelToDom/handlers/handleText.ts | 0 .../lib/modelToDom/optimizers/mergeNode.ts | 0 .../lib/modelToDom/optimizers/optimize.ts | 0 .../optimizers/removeUnnecessarySpan.ts | 0 .../lib/modelToDom/utils/applyFormat.ts | 0 .../lib/modelToDom/utils/applyMetadata.ts | 0 .../modelToDom/utils/handleSegmentCommon.ts | 0 .../lib/modelToDom/utils/stackFormat.ts | 0 .../lib/modelToText/contentModelToText.ts | 0 .../roosterjs-content-model-dom/package.json | 0 .../context/createDomToModelContextTest.ts | 0 .../test/domToModel/domToContentModelTest.ts | 0 .../processors/blockProcessorTest.ts | 0 .../domToModel/processors/brProcessorTest.ts | 0 .../processors/childProcessorTest.ts | 0 .../processors/codeProcessorTest.ts | 0 .../processors/delimiterProcessorTest.ts | 0 .../processors/elementProcessorTest.ts | 0 .../processors/entityProcessorTest.ts | 0 .../processors/fontProcessorTest.ts | 0 .../formatContainerProcessorTest.ts | 0 .../processors/generalProcessorTest.ts | 0 .../processors/headingProcessorTest.ts | 0 .../domToModel/processors/hrProcessorTest.ts | 0 .../processors/imageProcessorTest.ts | 0 .../processors/knownElementProcessorTest.ts | 0 .../processors/linkProcessorTest.ts | 0 .../processors/listItemProcessorTest.ts | 0 .../processors/listProcessorTest.ts | 0 .../domToModel/processors/pProcessorTest.ts | 0 .../processors/tableProcessorTest.ts | 0 .../processors/textProcessorTest.ts | 0 .../utils/addSelectionMarkerTest.ts | 0 .../domToModel/utils/areSameFormatsTest.ts | 0 .../domToModel/utils/getDefaultStyleTest.ts | 0 .../domToModel/utils/isBlockElementTest.ts | 0 .../test/domToModel/utils/parseFormatTest.ts | 0 .../test/domToModel/utils/stackFormatTest.ts | 0 .../test/domUtils/entityUtilTest.ts | 0 .../domUtils/isWhiteSpacePreservedTest.ts | 0 .../domUtils/metadata/updateMetadataTest.ts | 0 .../test/domUtils/metadata/validateTest.ts | 0 .../test/domUtils/moveChildNodesTest.ts | 0 .../test/domUtils/reuseCachedElementTest.ts | 0 .../test/endToEndTest.ts | 0 .../block/directionFormatHandlerTest.ts | 0 .../block/displayFormatHandlerTest.ts | 0 .../block/htmlAlignFormatHandlerTest.ts | 0 .../block/lineHeightFormatHandlerTest.ts | 0 .../block/marginFormatHandlerTest.ts | 0 .../block/paddingFormatHandlerTest.ts | 0 .../block/textAlignFormatHandlerTest.ts | 0 .../block/textIndentFormatHandlerTest.ts | 0 .../block/whiteSpaceFormatHandlerTest.ts | 0 .../backgroundColorFormatHandlerTest.ts | 0 .../common/borderBoxFormatHandlerTest.ts | 0 .../common/borderFormatHandlerTest.ts | 0 .../common/boxShadowFormatHandlerTest.ts | 0 .../common/datasetFormatHandlerTest.ts | 0 .../common/floatFormatHandlerTest.ts | 0 .../common/idFormatHandlerTest.ts | 0 .../common/sizeFormatHandlerTest.ts | 0 .../common/verticalAlignFormatHandlerTest.ts | 0 .../common/wordBreakFormatHandlerTest.ts | 0 .../entity/entityFormatHandlerTest.ts | 0 .../list/listItemThreadFormatHandlerTest.ts | 0 .../list/listLevelThreadFormatHandlerTest.ts | 0 .../list/listStyleFormatHandlerTest.ts | 0 .../segment/boldFormatHandlerTest.ts | 0 .../segment/fontFamilyFormatHandlerTest.ts | 0 .../segment/fontSizeFormatHandlerTest.ts | 0 .../segment/italicFormatHandlerTest.ts | 0 .../segment/letterSpacingFormatHandlerTest.ts | 0 .../segment/linkFormatHandlerTest.ts | 0 .../segment/strikeFormatHandleTest.ts | 0 .../superOrSubScriptFormatHandlerTest.ts | 0 .../segment/textColorFormatHandlerTest.ts | 0 .../segment/underlineFormatHandlerTest.ts | 0 .../table/tableLayoutFormatHandlerTest.ts | 0 .../table/tableSpacingFormatHandlerTest.ts | 0 .../textColorOnTableCellFormatHandlerTest.ts | 0 .../test/formatHandlers/utils/colorTest.ts | 0 .../utils/parseValueWithUnitTest.ts | 0 .../utils/shouldSetValueTest.ts | 0 .../block/setParagraphNotImplicitTest.ts | 0 .../test/modelApi/common/addBlockTest.ts | 0 .../test/modelApi/common/addDecoratorsTest.ts | 0 .../test/modelApi/common/addSegmentTest.ts | 0 .../modelApi/common/ensureParagraphTest.ts | 0 .../test/modelApi/common/isEmptyTest.ts | 0 .../modelApi/common/isGeneralSegmentTest.ts | 0 .../common/normalizeContentModelTest.ts | 0 .../modelApi/common/normalizeParagraphTest.ts | 0 .../modelApi/common/normalizeSegmentTest.ts | 0 .../test/modelApi/common/unwrapBlockTest.ts | 0 .../modelApi/creators/createEmptyModelTest.ts | 0 .../test/modelApi/creators/creatorsTest.ts | 0 .../test/modelToDom/contentModelToDomTest.ts | 0 .../context/createModelToDomContextTest.ts | 0 .../handlers/handleBlockGroupChildrenTest.ts | 0 .../modelToDom/handlers/handleBlockTest.ts | 0 .../test/modelToDom/handlers/handleBrTest.ts | 0 .../modelToDom/handlers/handleDividerTest.ts | 0 .../modelToDom/handlers/handleEntityTest.ts | 0 .../handlers/handleFormatContainerTest.ts | 0 .../handlers/handleGeneralModelTest.ts | 0 .../modelToDom/handlers/handleImageTest.ts | 0 .../modelToDom/handlers/handleListItemTest.ts | 0 .../modelToDom/handlers/handleListTest.ts | 0 .../handlers/handleParagraphTest.ts | 0 .../handlers/handleSegmentDecoratorTest.ts | 0 .../modelToDom/handlers/handleSegmentTest.ts | 0 .../modelToDom/handlers/handleTableTest.ts | 0 .../modelToDom/handlers/handleTextTest.ts | 0 .../modelToDom/optimizers/mergeNodeTest.ts | 0 .../modelToDom/optimizers/optimizeTest.ts | 0 .../optimizers/removeUnnecessarySpanTest.ts | 0 .../modelToDom/utils/applyMetadataTest.ts | 0 .../utils/handleSegmentCommonTest.ts | 0 .../test/modelToDom/utils/stackFormatTest.ts | 0 .../modelToText/contentModelToTextTest.ts | 0 .../test/testUtils.ts | 0 .../lib/autoFormat/AutoFormatPlugin.ts | 0 .../lib/autoFormat/keyboardListTrigger.ts | 0 .../utils/convertAlphaToDecimals.ts | 0 .../lib/autoFormat/utils/getIndex.ts | 0 .../lib/autoFormat/utils/getListTypeStyle.ts | 0 .../autoFormat/utils/getNumberingListStyle.ts | 0 .../lib/edit/EditPlugin.ts | 0 .../deleteSteps/deleteAllSegmentBefore.ts | 0 .../deleteSteps/deleteCollapsedSelection.ts | 0 .../lib/edit/deleteSteps/deleteList.ts | 0 .../edit/deleteSteps/deleteWordSelection.ts | 0 .../lib/edit/handleKeyboardEventCommon.ts | 0 .../lib/edit/inputSteps/handleEnterOnList.ts | 0 .../lib/edit/keyboardDelete.ts | 0 .../lib/edit/keyboardInput.ts | 0 .../lib/edit/keyboardTab.ts | 0 .../lib/edit/tabUtils/handleTabOnList.ts | 0 .../lib/edit/tabUtils/handleTabOnParagraph.ts | 0 .../lib/edit/utils/getLeafSiblingBlock.ts | 0 .../lib/index.ts | 0 .../Excel/processPastedContentFromExcel.ts | 0 .../lib/paste/PastePlugin.ts | 0 .../processPastedContentFromPowerPoint.ts | 0 .../lib/paste/WacComponents/constants.ts | 0 .../processPastedContentWacComponents.ts | 0 .../lib/paste/WordDesktop/WordMetadata.ts | 0 .../lib/paste/WordDesktop/getStyleMetadata.ts | 0 .../processPastedContentFromWordDesktop.ts | 0 .../paste/WordDesktop/processWordComments.ts | 0 .../lib/paste/WordDesktop/processWordLists.ts | 0 .../removeNegativeTextIndentParser.ts | 0 .../paste/pasteSourceValidations/constants.ts | 0 .../documentContainWacElements.ts | 0 .../pasteSourceValidations/getPasteSource.ts | 0 .../isExcelDesktopDocument.ts | 0 .../isExcelOnlineDocument.ts | 0 .../isGoogleSheetDocument.ts | 0 .../isPowerPointDesktopDocument.ts | 0 .../isWordDesktopDocument.ts | 0 .../shouldConvertToSingleImage.ts | 0 .../lib/paste/utils/addParser.ts | 0 .../lib/paste/utils/deprecatedColorParser.ts | 0 .../lib/paste/utils/getStyles.ts | 0 .../lib/paste/utils/linkParser.ts | 0 .../lib/paste/utils/setProcessor.ts | 0 .../CreateElement/CreateElementData.ts | 0 .../CreateElement/createElement.ts | 0 .../lib/pluginUtils/Disposable.ts | 0 .../DragAndDrop/DragAndDropHandler.ts | 0 .../DragAndDrop/DragAndDropHelper.ts | 0 .../pluginUtils/Rect/getIntersectedRect.ts | 0 .../lib/pluginUtils/Rect/normalizeRect.ts | 0 .../lib/shortcut/ShortcutCommand.ts | 0 .../lib/shortcut/ShortcutPlugin.ts | 0 .../lib/shortcut/shortcuts.ts | 0 .../lib/tableEdit/TableEditPlugin.ts | 0 .../lib/tableEdit/editors/TableEditor.ts | 0 .../tableEdit/editors/features/CellResizer.ts | 0 .../editors/features/TableEditorFeature.ts | 0 .../editors/features/TableInserter.ts | 0 .../tableEdit/editors/features/TableMover.ts | 0 .../editors/features/TableResizer.ts | 0 .../package.json | 0 .../test/TestHelper.ts | 0 .../test/autoFormat/AutoFormatPluginTest.ts | 0 .../autoFormat/keyboardListTriggerTest.ts | 0 .../utils/convertAlphaToDecimalsTest.ts | 0 .../test/autoFormat/utils/getIndexTest.ts | 0 .../autoFormat/utils/getListTypeStyleTest.ts | 0 .../utils/getNumberingListStyleTest.ts | 0 .../test/edit/EditPluginTest.ts | 0 .../deleteCollapsedSelectionTest.ts | 0 .../test/edit/deleteSteps/deleteListTest.ts | 0 .../deleteSteps/deleteWordSelectionTest.ts | 0 .../test/edit/editingTestCommon.ts | 0 .../edit/handleKeyboardEventCommonTest.ts | 0 .../edit/inputSteps/handleEnterOnListTest.ts | 0 .../test/edit/keyboardDeleteTest.ts | 0 .../test/edit/keyboardInputTest.ts | 0 .../test/edit/keyboardTabTest.ts | 0 .../test/edit/tabUtils/handleTabOnListTest.ts | 0 .../edit/tabUtils/handleTabOnParagraphTest.ts | 0 .../edit/utils/getLeafSiblingBlockTest.ts | 0 .../test/paste/ContentModelPastePluginTest.ts | 0 .../test/paste/deprecatedColorParserTest.ts | 0 .../paste/e2e/cmPasteFromExcelOnlineTest.ts | 0 .../test/paste/e2e/cmPasteFromExcelTest.ts | 0 .../test/paste/e2e/cmPasteFromWacTest.ts | 0 .../test/paste/e2e/cmPasteFromWordTest.ts | 0 .../test/paste/e2e/cmPasteTest.ts | 0 .../test/paste/e2e/testUtils.ts | 0 .../test/paste/getStyleMetadataTest.ts | 0 .../test/paste/linkParserTest.ts | 0 .../documentContainWacElementsTest.ts | 0 .../getPasteSourceTest.ts | 0 .../isExcelDesktopDocumentTest.ts | 0 .../isExcelOnlineDocumentTest.ts | 0 .../isGoogleSheetDocumentTest.ts | 0 .../isPowerPointDesktopDocumentTest.ts | 0 .../isWordDesktopDocumentTest.ts | 0 .../pasteSourceValidations/pasteTestUtils.ts | 0 .../shouldConvertToSingleImageTest.ts | 0 .../processPastedContentFromExcelTest.ts | 0 .../processPastedContentFromPowerPointTest.ts | 0 .../paste/processPastedContentFromWacTest.ts | 0 ...processPastedContentFromWordDesktopTest.ts | 0 .../test/paste/utils/getStylesTest.ts | 0 .../test/pluginUtils/DragAndDropHelperTest.ts | 0 .../test/pluginUtils/createElementTest.ts | 0 .../test/shortcut/ShortcutPluginTest.ts | 0 .../test/tableEdit/TableEditTestHelper.ts | 0 .../test/tableEdit/cellResizerTest.ts | 0 .../test/tableEdit/tableData.ts | 0 .../test/tableEdit/tableEditPluginTest.ts | 0 .../test/tableEdit/tableInserterTest.ts | 0 .../test/tableEdit/tableMoverTest.ts | 0 .../test/tableEdit/teableResizerTest.ts | 0 .../lib/block/ContentModelBlock.ts | 0 .../lib/block/ContentModelBlockBase.ts | 0 .../lib/block/ContentModelBlockWithCache.ts | 0 .../lib/block/ContentModelDivider.ts | 0 .../lib/block/ContentModelParagraph.ts | 0 .../lib/block/ContentModelTable.ts | 0 .../lib/block/ContentModelTableRow.ts | 0 .../lib/context/ContentModelHandler.ts | 0 .../lib/context/DarkColorHandler.ts | 0 .../lib/context/DomIndexer.ts | 0 .../lib/context/DomToModelContext.ts | 0 .../lib/context/DomToModelFormatContext.ts | 0 .../lib/context/DomToModelOption.ts | 0 .../lib/context/DomToModelSelectionContext.ts | 0 .../lib/context/DomToModelSettings.ts | 0 .../lib/context/EditorContext.ts | 0 .../lib/context/ElementProcessor.ts | 0 .../lib/context/ModelToDomContext.ts | 0 .../lib/context/ModelToDomFormatContext.ts | 0 .../lib/context/ModelToDomOption.ts | 0 .../lib/context/ModelToDomSelectionContext.ts | 0 .../lib/context/ModelToDomSettings.ts | 0 .../lib/context/TextMutationObserver.ts | 0 .../lib/decorator/ContentModelCode.ts | 0 .../lib/decorator/ContentModelDecorator.ts | 0 .../lib/decorator/ContentModelLink.ts | 0 .../lib/decorator/ContentModelListLevel.ts | 0 .../ContentModelParagraphDecorator.ts | 0 .../lib/editor/ContextMenuProvider.ts | 0 .../lib/editor/EditorCore.ts | 0 .../lib/editor/EditorCorePlugins.ts | 0 .../lib/editor/EditorOptions.ts | 0 .../lib/editor/EditorPlugin.ts | 0 .../lib/editor/IEditor.ts | 0 .../lib/editor/PluginWithState.ts | 0 .../lib/entity/ContentModelEntity.ts | 0 .../lib/enum/BlockGroupType.ts | 0 .../lib/enum/BlockType.ts | 0 .../lib/enum/BorderOperations.ts | 0 .../lib/enum/DeleteResult.ts | 0 .../lib/enum/EntityOperation.ts | 0 .../lib/enum/ExportContentMode.ts | 0 .../lib/enum/InsertEntityPosition.ts | 0 .../lib/enum/PasteType.ts | 0 .../lib/enum/SegmentType.ts | 0 .../lib/enum/TableOperation.ts | 0 .../lib/event/BasePluginEvent.ts | 0 .../lib/event/BeforeCutCopyEvent.ts | 0 .../lib/event/BeforeDisposeEvent.ts | 0 .../lib/event/BeforeKeyboardEditingEvent.ts | 0 .../lib/event/BeforePasteEvent.ts | 0 .../lib/event/BeforeSetContentEvent.ts | 0 .../lib/event/ContentChangedEvent.ts | 0 .../lib/event/ContextMenuEvent.ts | 0 .../lib/event/EditImageEvent.ts | 0 .../lib/event/EditorInputEvent.ts | 0 .../lib/event/EditorReadyEvent.ts | 0 .../lib/event/EntityOperationEvent.ts | 0 .../lib/event/ExtractContentWithDomEvent.ts | 0 .../lib/event/KeyboardEvent.ts | 0 .../lib/event/MouseEvent.ts | 0 .../lib/event/PluginEvent.ts | 0 .../lib/event/PluginEventData.ts | 0 .../lib/event/PluginEventType.ts | 0 .../lib/event/ScrollEvent.ts | 0 .../lib/event/SelectionChangedEvent.ts | 0 .../lib/event/ShadowEditEvent.ts | 0 .../lib/event/ZoomChangedEvent.ts | 0 .../lib/format/ContentModelBlockFormat.ts | 0 .../lib/format/ContentModelCodeFormat.ts | 0 .../lib/format/ContentModelDividerFormat.ts | 0 .../lib/format/ContentModelEntityFormat.ts | 0 .../lib/format/ContentModelFormatBase.ts | 0 .../ContentModelFormatContainerFormat.ts | 0 .../lib/format/ContentModelFormatMap.ts | 0 .../lib/format/ContentModelHyperLinkFormat.ts | 0 .../lib/format/ContentModelImageFormat.ts | 0 .../lib/format/ContentModelListItemFormat.ts | 0 .../format/ContentModelListItemLevelFormat.ts | 0 .../lib/format/ContentModelSegmentFormat.ts | 0 .../lib/format/ContentModelTableCellFormat.ts | 0 .../lib/format/ContentModelTableFormat.ts | 0 .../lib/format/ContentModelWithDataset.ts | 0 .../lib/format/ContentModelWithFormat.ts | 0 .../lib/format/FormatHandlerTypeMap.ts | 0 .../formatParts/BackgroundColorFormat.ts | 0 .../lib/format/formatParts/BoldFormat.ts | 0 .../lib/format/formatParts/BorderBoxFormat.ts | 0 .../lib/format/formatParts/BorderFormat.ts | 0 .../lib/format/formatParts/BoxShadowFormat.ts | 0 .../lib/format/formatParts/DirectionFormat.ts | 0 .../lib/format/formatParts/DisplayFormat.ts | 0 .../format/formatParts/EntityInfoFormat.ts | 0 .../lib/format/formatParts/FloatFormat.ts | 0 .../format/formatParts/FontFamilyFormat.ts | 0 .../lib/format/formatParts/FontSizeFormat.ts | 0 .../lib/format/formatParts/HtmlAlignFormat.ts | 0 .../lib/format/formatParts/IdFormat.ts | 0 .../lib/format/formatParts/ItalicFormat.ts | 0 .../format/formatParts/LetterSpacingFormat.ts | 0 .../format/formatParts/LineHeightFormat.ts | 0 .../lib/format/formatParts/LinkFormat.ts | 0 .../lib/format/formatParts/ListStyleFormat.ts | 0 .../format/formatParts/ListThreadFormat.ts | 0 .../lib/format/formatParts/MarginFormat.ts | 0 .../lib/format/formatParts/PaddingFormat.ts | 0 .../lib/format/formatParts/SizeFormat.ts | 0 .../lib/format/formatParts/SpacingFormat.ts | 0 .../lib/format/formatParts/StrikeFormat.ts | 0 .../formatParts/SuperOrSubScriptFormat.ts | 0 .../format/formatParts/TableLayoutFormat.ts | 0 .../lib/format/formatParts/TextAlignFormat.ts | 0 .../lib/format/formatParts/TextColorFormat.ts | 0 .../format/formatParts/TextIndentFormat.ts | 0 .../lib/format/formatParts/UnderlineFormat.ts | 0 .../format/formatParts/VerticalAlignFormat.ts | 0 .../format/formatParts/WhiteSpaceFormat.ts | 0 .../lib/format/formatParts/WordBreakFormat.ts | 0 .../lib/format/metadata/DatasetFormat.ts | 0 .../format/metadata/ImageMetadataFormat.ts | 0 .../lib/format/metadata/ListMetadataFormat.ts | 0 .../metadata/TableCellMetadataFormat.ts | 0 .../format/metadata/TableMetadataFormat.ts | 0 .../lib/group/ContentModelBlockGroup.ts | 0 .../lib/group/ContentModelBlockGroupBase.ts | 0 .../lib/group/ContentModelDocument.ts | 0 .../lib/group/ContentModelFormatContainer.ts | 0 .../lib/group/ContentModelGeneralBlock.ts | 0 .../lib/group/ContentModelListItem.ts | 0 .../lib/group/ContentModelTableCell.ts | 0 .../lib/index.ts | 0 .../lib/metadata/Definition.ts | 0 .../lib/metadata/DefinitionType.ts | 0 .../lib/parameter/AnnounceData.ts | 0 .../lib/parameter/Border.ts | 0 .../lib/parameter/ClipboardData.ts | 0 .../lib/parameter/ContentModelFormatState.ts | 0 .../lib/parameter/DOMEventRecord.ts | 0 .../lib/parameter/DOMHelper.ts | 0 .../lib/parameter/DeleteSelectionStep.ts | 0 .../lib/parameter/EdgeLinkPreview.ts | 0 .../lib/parameter/EditorEnvironment.ts | 0 .../parameter/FormatContentModelContext.ts | 0 .../parameter/FormatContentModelOptions.ts | 0 .../lib/parameter/ImageFormatState.ts | 0 .../lib/parameter/InsertEntityOptions.ts | 0 .../lib/parameter/Rect.ts | 0 .../lib/parameter/Snapshot.ts | 0 .../lib/parameter/SnapshotsManager.ts | 0 .../lib/parameter/TrustedHTMLHandler.ts | 0 .../lib/parameter/ValueSanitizer.ts | 0 .../lib/pluginState/CachePluginState.ts | 0 .../lib/pluginState/ContextMenuPluginState.ts | 0 .../lib/pluginState/CopyPastePluginState.ts | 0 .../lib/pluginState/DOMEventPluginState.ts | 0 .../lib/pluginState/EntityPluginState.ts | 0 .../lib/pluginState/FormatPluginState.ts | 0 .../lib/pluginState/LifecyclePluginState.ts | 0 .../lib/pluginState/PluginState.ts | 0 .../lib/pluginState/SelectionPluginState.ts | 0 .../lib/pluginState/UndoPluginState.ts | 0 .../lib/segment/ContentModelBr.ts | 0 .../lib/segment/ContentModelGeneralSegment.ts | 0 .../lib/segment/ContentModelImage.ts | 0 .../lib/segment/ContentModelSegment.ts | 0 .../lib/segment/ContentModelSegmentBase.ts | 0 .../segment/ContentModelSelectionMarker.ts | 0 .../lib/segment/ContentModelText.ts | 0 .../lib/selection/DOMSelection.ts | 0 .../lib/selection/InsertPoint.ts | 0 .../lib/selection/Selectable.ts | 0 .../lib/selection/TableSelectionContext.ts | 0 .../selection/TableSelectionCoordinates.ts | 0 .../package.json | 0 .../lib/createEditor.ts | 0 .../roosterjs-content-model/lib/index.ts | 0 .../roosterjs-content-model/package.json | 0 .../component/renderColorPicker.tsx | 0 .../roosterjs-react/lib/colorPicker/index.ts | 0 .../lib/colorPicker/types/stringKeys.ts | 0 .../lib/colorPicker/utils/backgroundColors.ts | 0 .../utils/getClassNamesForColorPicker.ts | 0 .../lib/colorPicker/utils/textColors.ts | 0 .../roosterjs-react/lib/common/index.ts | 0 .../lib/common/type/LocalizedStrings.ts | 0 .../lib/common/type/ReactEditorPlugin.ts | 0 .../lib/common/type/RibbonPluginOptions.ts | 0 .../lib/common/type/UIUtilities.ts | 0 .../lib/common/utils/createUIUtilities.tsx | 0 .../lib/common/utils/getLocalizedString.ts | 0 .../lib/common/utils/renderReactComponent.ts | 0 .../roosterjs-react/lib/contextMenu/index.ts | 0 .../menus/createImageEditMenuProvider.tsx | 0 .../menus/createListEditMenuProvider.ts | 0 .../menus/createTableEditMenuProvider.ts | 0 .../plugin/createContextMenuPlugin.tsx | 0 .../lib/contextMenu/types/ContextMenuItem.ts | 0 .../types/ContextMenuItemStringKeys.ts | 0 .../utils/createContextMenuProvider.ts | 0 .../lib/emoji/components/EmojiIcon.tsx | 0 .../lib/emoji/components/EmojiNavBar.tsx | 0 .../lib/emoji/components/EmojiPane.tsx | 0 .../lib/emoji/components/EmojiStatusBar.tsx | 0 .../lib/emoji/components/showEmojiCallout.tsx | 0 .../roosterjs-react/lib/emoji/index.ts | 0 .../lib/emoji/plugin/createEmojiPlugin.ts | 0 .../roosterjs-react/lib/emoji/type/Emoji.ts | 0 .../lib/emoji/type/EmojiPaneStyles.ts | 0 .../lib/emoji/type/EmojiStringKeys.ts | 0 .../lib/emoji/type/EmojiStrings.ts | 0 .../lib/emoji/utils/emojiList.ts | 0 .../lib/emoji/utils/searchEmojis.ts | 0 .../roosterjs-react/lib/index.ts | 0 .../lib/inputDialog/component/InputDialog.tsx | 0 .../inputDialog/component/InputDialogItem.tsx | 0 .../roosterjs-react/lib/inputDialog/index.ts | 0 .../lib/inputDialog/type/DialogItem.ts | 0 .../lib/inputDialog/utils/showInputDialog.tsx | 0 .../component/showPasteOptionPane.tsx | 0 .../roosterjs-react/lib/pasteOptions/index.ts | 0 .../plugin/createPasteOptionPlugin.ts | 0 .../type/PasteOptionStringKeys.ts | 0 .../lib/pasteOptions/utils/buttons.ts | 0 .../lib/ribbon/component/Ribbon.tsx | 0 .../ribbon/component/buttons/alignCenter.ts | 0 .../lib/ribbon/component/buttons/alignLeft.ts | 0 .../ribbon/component/buttons/alignRight.ts | 0 .../component/buttons/backgroundColor.ts | 0 .../lib/ribbon/component/buttons/bold.ts | 0 .../ribbon/component/buttons/bulletedList.ts | 0 .../ribbon/component/buttons/clearFormat.ts | 0 .../lib/ribbon/component/buttons/code.ts | 0 .../component/buttons/decreaseFontSize.ts | 0 .../component/buttons/decreaseIndent.ts | 0 .../lib/ribbon/component/buttons/font.ts | 0 .../lib/ribbon/component/buttons/fontSize.ts | 0 .../lib/ribbon/component/buttons/heading.ts | 0 .../component/buttons/increaseFontSize.ts | 0 .../component/buttons/increaseIndent.ts | 0 .../ribbon/component/buttons/insertImage.ts | 0 .../ribbon/component/buttons/insertLink.ts | 0 .../ribbon/component/buttons/insertTable.tsx | 0 .../lib/ribbon/component/buttons/italic.ts | 0 .../lib/ribbon/component/buttons/ltr.ts | 0 .../ribbon/component/buttons/moreCommands.ts | 0 .../ribbon/component/buttons/numberedList.ts | 0 .../lib/ribbon/component/buttons/quote.ts | 0 .../lib/ribbon/component/buttons/redo.ts | 0 .../ribbon/component/buttons/removeLink.ts | 0 .../lib/ribbon/component/buttons/rtl.ts | 0 .../ribbon/component/buttons/strikethrough.ts | 0 .../lib/ribbon/component/buttons/subscript.ts | 0 .../ribbon/component/buttons/superscript.ts | 0 .../lib/ribbon/component/buttons/textColor.ts | 0 .../lib/ribbon/component/buttons/underline.ts | 0 .../lib/ribbon/component/buttons/undo.ts | 0 .../lib/ribbon/component/getButtons.ts | 0 .../roosterjs-react/lib/ribbon/index.ts | 0 .../lib/ribbon/plugin/createRibbonPlugin.ts | 0 .../lib/ribbon/type/KnownRibbonButton.ts | 0 .../lib/ribbon/type/RibbonButton.ts | 0 .../lib/ribbon/type/RibbonButtonDropDown.ts | 0 .../lib/ribbon/type/RibbonButtonStringKeys.ts | 0 .../lib/ribbon/type/RibbonPlugin.ts | 0 .../lib/ribbon/type/RibbonProps.ts | 0 .../lib/rooster/component/Rooster.tsx | 0 .../roosterjs-react/lib/rooster/index.ts | 0 .../plugin/createUpdateContentPlugin.ts | 0 .../lib/rooster/type/RoosterProps.ts | 0 .../lib/rooster/type/UpdateContentPlugin.ts | 0 .../lib/rooster/type/UpdateMode.ts | 0 .../roosterjs-react/package.json | 0 .../roosterjs-react/test/emptyTest.ts | 0 packages/tsconfig.json | 3 +- packages/tsconfig.test.json | 3 +- tools/build.js | 22 +++-- tools/buildTools/buildAmd.js | 28 +----- tools/buildTools/buildCommonJs.js | 8 +- tools/buildTools/buildDemo.js | 64 ++++-------- tools/buildTools/buildMjs.js | 28 +----- tools/buildTools/buildTest.js | 23 +---- tools/buildTools/checkDependency.js | 12 +-- tools/buildTools/common.js | 99 ++++++++++--------- tools/buildTools/dts.js | 42 +++++--- tools/buildTools/eslint.js | 19 +--- tools/buildTools/normalize.js | 24 ++++- tools/buildTools/pack.js | 74 +++++--------- tools/buildTools/publish.js | 4 +- tools/karma.test.all.js | 4 +- tools/karma.test.contentmodel.js | 5 - tools/karma.test.roosterjs.js | 4 - tools/karma.test.ui.js | 4 - tools/tsconfig.doc.json | 14 +-- versions.json | 7 +- webpack.config.js | 8 +- 1030 files changed, 255 insertions(+), 504 deletions(-) delete mode 100644 packages-content-model/tsconfig.json delete mode 100644 packages-content-model/tsconfig.test.json delete mode 100644 packages-ui/tsconfig.json delete mode 100644 packages-ui/tsconfig.test.json rename {packages-content-model => packages}/roosterjs-content-model-api/lib/index.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/block/setModelAlignment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/block/setModelDirection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/block/setModelIndentation.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/block/toggleModelBlockQuote.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/common/clearModelFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/common/wrapBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/entity/insertEntityModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/image/applyImageBorderFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/list/findListItemsInSameThread.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/list/setListType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/selection/adjustSegmentSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/selection/adjustTrailingSpaceSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/selection/adjustWordSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/selection/collapseTableSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/alignTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/alignTableCell.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/canMergeCells.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/clearSelectedCells.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/createTableStructure.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/deleteTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/deleteTableColumn.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/deleteTableRow.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/ensureFocusableParagraphForTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/insertTableColumn.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/insertTableRow.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/mergeTableCells.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/mergeTableColumn.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/mergeTableRow.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/splitTableCellHorizontally.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/modelApi/table/splitTableCellVertically.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/block/setDirection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/table/editTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/lib/publicApi/utils/formatTableWithContentModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/package.json (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/block/setModelAlignmentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/block/setModelDirectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/block/setModelIndentationTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/block/toggleModelBlockQuoteTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/common/clearModelFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/common/wrapBlockTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/entity/insertEntityModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/image/applyImageBorderFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/link/matchLinkTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/list/findListItemsInSameThreadTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/selection/adjustSegmentSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/selection/adjustTrailingSpaceSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/selection/adjustWordSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/selection/collapseTableSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/alignTableCellTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/alignTableTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/canMergeCellsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/clearSelectedCellsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/createTableStructureTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/deleteTableColumnTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/deleteTableRowTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/deleteTableTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/ensureFocusableParagraphForTableTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/insertTableColumnTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/insertTableRowTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/mergeTableCellsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/mergeTableColumnTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/mergeTableRowTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/splitTableCellHorizontallyTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/modelApi/table/splitTableCellVerticallyTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/block/paragraphTestCommon.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/block/setDirectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/block/setHeadingLevelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/block/setParagraphMarginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/block/setSpacingTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/image/adjustImageSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/image/setImageAltTextTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/image/setImageBorderTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/image/setImageBoxShadowTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/list/setListStartNumberTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/list/setListStyleTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/changeCapitalizationTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/segmentTestCommon.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/utils/formatParagraphWithContentModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/utils/formatSegmentWithContentModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/constants/BulletListType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/constants/ChangeSource.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/constants/NumberingListType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/constants/TableBorderFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/createContentModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/focus.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/hasFocus.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/paste.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/setContentModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/coreApi/triggerEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/CachePlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/ContextMenuPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/DOMEventPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/FormatPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/LifecyclePlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/UndoPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/createEditorCorePlugins.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/utils/addRangeToSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/utils/applyDefaultFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/utils/applyPendingFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/utils/areSameSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/utils/deleteEmptyList.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/utils/domIndexerImpl.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/utils/entityDelimiterUtils.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/utils/findAllEntities.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/corePlugin/utils/textMutationObserver.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/editor/DarkColorHandlerImpl.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/editor/Editor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/editor/coreApiMap.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/editor/createEditorCore.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/editor/createEditorDefaultSettings.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/index.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/metadata/definitionCreators.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/metadata/updateImageMetadata.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/metadata/updateListMetadata.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/metadata/updateTableCellMetadata.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/metadata/updateTableMetadata.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/modelApi/edit/deleteExpandedSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/modelApi/edit/deleteSingleChar.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/override/containerSizeFormatParser.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/override/pasteCopyBlockEntityParser.ts (96%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/override/pasteDisplayFormatParser.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/override/pasteEntityProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/override/pasteGeneralProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/override/pasteTextProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/override/reducedModelChildProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/override/tablePreProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/color/transformColor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/domUtils/borderValues.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/domUtils/cacheGetEventData.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/domUtils/eventUtils.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/domUtils/getSegmentTextFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/domUtils/readFile.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/domUtils/stringUtil.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/domUtils/tableCellUtils.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/format/retrieveModelFormatState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/model/cloneModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/model/createModelFromHtml.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/model/getClosestAncestorBlockGroupIndex.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/model/isBlockGroupOfType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/model/isBold.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/collectSelections.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/deleteBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/deleteSegment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/deleteSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/getSelectionRootNode.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlockGroup.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInSegment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/iterateSelections.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/selection/setSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/table/applyTableFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/table/getSelectedCells.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/table/normalizeTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/table/setTableCellBackgroundColor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/undo/redo.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/publicApi/undo/undo.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/convertInlineCss.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/createDomToModelContextForSanitizing.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/extractClipboardItems.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/getRootComputedStyleForContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/paste/createPasteFragment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/paste/retrieveHtmlInfo.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/lib/utils/sanitizeElement.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/package.json (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/focusTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/getVisibleViewportTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/pasteTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshotTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/CachePluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/ContextMenuPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/DomEventPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/FormatPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/LifecyclePluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/SelectionPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/UndoPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/utils/applyDefaultFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/utils/applyPendingFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/utils/areSameRangeExTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/utils/delimiterUtilsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/utils/domIndexerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/utils/findAllEntitiesTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/corePlugin/utils/textMutationObserverTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/editor/DarkColorHandlerImplTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/editor/EditorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/editor/SnapshotsManagerImplTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/editor/createEditorDefaultSettingsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/metadata/handleListItemWithMetadataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/metadata/handleListWithMetadataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/metadata/updateImageMetadataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/metadata/updateListMetadataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/metadata/updateTableCellMetadataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/metadata/updateTableMetadataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/modelApi/edit/deleteSingleCharTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/overrides/containerSizeFormatParserTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/overrides/pasteCopyBlockEntityParserTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/overrides/pasteDisplayFormatParserTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/overrides/pasteEntityProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/overrides/pasteGeneralProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/overrides/pasteTextProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/overrides/reducedModelChildProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/overrides/tablePreProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/color/transformColorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/domUtils/borderValuesTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/domUtils/cacheGetEventDataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/domUtils/getSegmentTextFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/domUtils/tableCellUtilsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/format/retrieveModelFormatStateTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/model/cloneModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/model/getClosestAncestorBlockGroupIndexTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/model/isBlockGroupOfTypeTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/selection/collectSelectionsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/selection/deleteSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/selection/getSelectedSegmentsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/selection/getSelectionRootNodeTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInBlockTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInSegmentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/selection/iterateSelectionsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/selection/setSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/table/applyTableFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/table/getSelectedCellsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/table/normalizeTableTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/table/setTableCellBackgroundColorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/undo/redoTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/publicApi/undo/undoTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/convertInlineCssTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/createDomToModelContextForSanitizingTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/extractClipboardItemsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/paste/createPasteFragmentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/paste/retrieveHtmlInfoTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-core/test/utils/sanitizeElementTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/config/defaultContentModelFormatMap.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/config/defaultHTMLStyleMap.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/context/createDomToModelContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/context/defaultProcessors.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/domToContentModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/blockProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/brProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/childProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/codeProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/delimiterProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/elementProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/entityProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/fontProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/formatContainerProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/generalProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/headingProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/hrProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/imageProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/knownElementProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/linkProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/listItemProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/listProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/pProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/tableProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/processors/textProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/utils/addSelectionMarker.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/utils/areSameFormats.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/utils/getBoundingClientRect.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/utils/getDefaultStyle.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/utils/getRegularSelectionOffsets.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/utils/isBlockElement.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/utils/parseFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domToModel/utils/stackFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/getObjectKeys.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/isElementOfType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/isNodeOfType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/isWhiteSpacePreserved.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/metadata/updateMetadata.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/metadata/validate.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/moveChildNodes.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/reuseCachedElement.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/toArray.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/unwrap.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/domUtils/wrap.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/FormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/block/directionFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/block/displayFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/block/htmlAlignFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/block/lineHeightFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/block/marginFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/block/paddingFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/block/textAlignFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/block/textIndentFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/block/whiteSpaceFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/backgroundColorFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/borderBoxFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/borderFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/boxShadowFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/datasetFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/floatFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/idFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/sizeFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/verticalAlignFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/common/wordBreakFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/entity/entityFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/list/listItemThreadFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/list/listLevelThreadFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/list/listStyleFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/boldFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/fontFamilyFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/fontSizeFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/italicFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/letterSpacingFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/linkFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/strikeFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/superOrSubScriptFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/textColorFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/segment/underlineFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/table/tableLayoutFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/table/tableSpacingFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/table/textColorOnTableCellFormatHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/utils/color.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/utils/dir.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/utils/parseValueWithUnit.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/formatHandlers/utils/shouldSetValue.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/index.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/block/setParagraphNotImplicit.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/addBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/addDecorators.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/addSegment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/ensureParagraph.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnly.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnlyTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/isGeneralSegment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/normalizeSegment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/common/unwrapBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createBr.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createContentModelDocument.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createDivider.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createEmptyModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createEntity.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createFormatContainer.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralSegment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createImage.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createListItem.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createListLevel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createParagraph.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createParagraphDecorator.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createSelectionMarker.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createTableCell.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelApi/creators/createText.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/context/createModelToDomContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/context/defaultContentModelHandlers.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBr.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleDivider.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleEntity.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleFormatContainer.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleGeneralModel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleImage.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleList.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleParagraph.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegmentDecorator.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/handlers/handleText.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/optimizers/mergeNode.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/optimizers/optimize.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/optimizers/removeUnnecessarySpan.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/utils/applyFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/utils/applyMetadata.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/utils/handleSegmentCommon.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToDom/utils/stackFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/lib/modelToText/contentModelToText.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/package.json (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/context/createDomToModelContextTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/domToContentModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/blockProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/brProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/childProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/codeProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/delimiterProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/elementProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/entityProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/fontProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/formatContainerProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/generalProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/headingProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/hrProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/imageProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/knownElementProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/linkProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/listItemProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/listProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/pProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/tableProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/processors/textProcessorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/utils/addSelectionMarkerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/utils/areSameFormatsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/utils/getDefaultStyleTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/utils/isBlockElementTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/utils/parseFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domToModel/utils/stackFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domUtils/entityUtilTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domUtils/isWhiteSpacePreservedTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domUtils/metadata/updateMetadataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domUtils/metadata/validateTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domUtils/moveChildNodesTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/domUtils/reuseCachedElementTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/endToEndTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/block/directionFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/block/displayFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/block/htmlAlignFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/block/lineHeightFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/block/marginFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/block/paddingFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/block/textAlignFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/block/textIndentFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/block/whiteSpaceFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/backgroundColorFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/borderBoxFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/borderFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/boxShadowFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/datasetFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/floatFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/idFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/sizeFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/verticalAlignFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/common/wordBreakFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/entity/entityFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/list/listItemThreadFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/list/listLevelThreadFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/list/listStyleFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/boldFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/fontFamilyFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/fontSizeFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/italicFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/letterSpacingFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/linkFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/strikeFormatHandleTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/superOrSubScriptFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/textColorFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/segment/underlineFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/table/tableLayoutFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/table/tableSpacingFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/table/textColorOnTableCellFormatHandlerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/utils/colorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/utils/parseValueWithUnitTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/formatHandlers/utils/shouldSetValueTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/block/setParagraphNotImplicitTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/addBlockTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/addDecoratorsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/addSegmentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/ensureParagraphTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/isEmptyTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/isGeneralSegmentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/normalizeContentModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/normalizeSegmentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/common/unwrapBlockTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/creators/createEmptyModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelApi/creators/creatorsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/contentModelToDomTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/context/createModelToDomContextTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleBrTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleDividerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleEntityTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleFormatContainerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleGeneralModelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleImageTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleListTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleParagraphTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentDecoratorTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleTableTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/handlers/handleTextTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/optimizers/mergeNodeTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/optimizers/optimizeTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/optimizers/removeUnnecessarySpanTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/utils/applyMetadataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/utils/handleSegmentCommonTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToDom/utils/stackFormatTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/modelToText/contentModelToTextTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-dom/test/testUtils.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/autoFormat/utils/convertAlphaToDecimals.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/autoFormat/utils/getIndex.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/autoFormat/utils/getNumberingListStyle.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteAllSegmentBefore.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteCollapsedSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteWordSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/handleKeyboardEventCommon.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/edit/utils/getLeafSiblingBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/index.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/WacComponents/constants.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/WordDesktop/WordMetadata.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordComments.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordLists.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/WordDesktop/removeNegativeTextIndentParser.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/constants.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/documentContainWacElements.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/getPasteSource.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelDesktopDocument.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelOnlineDocument.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isGoogleSheetDocument.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isPowerPointDesktopDocument.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isWordDesktopDocument.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/shouldConvertToSingleImage.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/utils/addParser.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/utils/deprecatedColorParser.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/utils/getStyles.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/utils/linkParser.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/paste/utils/setProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/CreateElementData.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/createElement.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/pluginUtils/Disposable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHelper.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/package.json (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/TestHelper.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/autoFormat/utils/convertAlphaToDecimalsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/autoFormat/utils/getIndexTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/autoFormat/utils/getNumberingListStyleTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteCollapsedSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteListTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteWordSelectionTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/editingTestCommon.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/handleKeyboardEventCommonTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/edit/utils/getLeafSiblingBlockTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/deprecatedColorParserTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/e2e/testUtils.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/linkParserTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/documentContainWacElementsTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/getPasteSourceTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelDesktopDocumentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelOnlineDocumentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isGoogleSheetDocumentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isPowerPointDesktopDocumentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isWordDesktopDocumentTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/pasteTestUtils.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/shouldConvertToSingleImageTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/processPastedContentFromExcelTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/processPastedContentFromPowerPointTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/processPastedContentFromWacTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/paste/utils/getStylesTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/pluginUtils/DragAndDropHelperTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/pluginUtils/createElementTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/tableEdit/cellResizerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/tableEdit/tableData.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/tableEdit/tableInserterTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/block/ContentModelBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/block/ContentModelBlockBase.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/block/ContentModelBlockWithCache.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/block/ContentModelDivider.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/block/ContentModelParagraph.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/block/ContentModelTable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/block/ContentModelTableRow.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/ContentModelHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/DarkColorHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/DomIndexer.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/DomToModelContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/DomToModelFormatContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/DomToModelOption.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/DomToModelSelectionContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/DomToModelSettings.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/EditorContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/ElementProcessor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/ModelToDomContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/ModelToDomFormatContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/ModelToDomOption.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/ModelToDomSelectionContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/ModelToDomSettings.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/context/TextMutationObserver.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/decorator/ContentModelCode.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/decorator/ContentModelDecorator.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/decorator/ContentModelLink.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/decorator/ContentModelListLevel.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/decorator/ContentModelParagraphDecorator.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/editor/ContextMenuProvider.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/editor/EditorCore.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/editor/EditorCorePlugins.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/editor/EditorOptions.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/editor/EditorPlugin.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/editor/IEditor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/editor/PluginWithState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/entity/ContentModelEntity.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/BlockGroupType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/BlockType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/BorderOperations.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/DeleteResult.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/EntityOperation.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/ExportContentMode.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/InsertEntityPosition.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/PasteType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/SegmentType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/enum/TableOperation.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/BasePluginEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/BeforeCutCopyEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/BeforeDisposeEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/BeforeKeyboardEditingEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/BeforePasteEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/BeforeSetContentEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/ContentChangedEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/ContextMenuEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/EditImageEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/EditorInputEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/EditorReadyEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/EntityOperationEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/ExtractContentWithDomEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/KeyboardEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/MouseEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/PluginEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/PluginEventData.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/PluginEventType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/ScrollEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/SelectionChangedEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/ShadowEditEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/event/ZoomChangedEvent.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelBlockFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelCodeFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelDividerFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelEntityFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelFormatBase.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelFormatContainerFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelFormatMap.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelHyperLinkFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelImageFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelListItemFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelListItemLevelFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelSegmentFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelTableCellFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelTableFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelWithDataset.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/ContentModelWithFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/FormatHandlerTypeMap.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/BackgroundColorFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/BoldFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/BorderBoxFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/BorderFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/BoxShadowFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/DirectionFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/DisplayFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/EntityInfoFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/FloatFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/FontFamilyFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/FontSizeFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/HtmlAlignFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/IdFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/ItalicFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/LetterSpacingFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/LineHeightFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/LinkFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/ListStyleFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/ListThreadFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/MarginFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/PaddingFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/SizeFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/SpacingFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/StrikeFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/SuperOrSubScriptFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/TableLayoutFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/TextAlignFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/TextColorFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/TextIndentFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/UnderlineFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/VerticalAlignFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/WhiteSpaceFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/formatParts/WordBreakFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/metadata/DatasetFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/metadata/ImageMetadataFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/metadata/ListMetadataFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/metadata/TableCellMetadataFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/format/metadata/TableMetadataFormat.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/group/ContentModelBlockGroup.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/group/ContentModelBlockGroupBase.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/group/ContentModelDocument.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/group/ContentModelFormatContainer.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/group/ContentModelGeneralBlock.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/group/ContentModelListItem.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/group/ContentModelTableCell.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/index.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/metadata/Definition.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/metadata/DefinitionType.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/AnnounceData.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/Border.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/ClipboardData.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/ContentModelFormatState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/DOMEventRecord.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/DOMHelper.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/DeleteSelectionStep.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/EdgeLinkPreview.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/EditorEnvironment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/FormatContentModelContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/FormatContentModelOptions.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/ImageFormatState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/InsertEntityOptions.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/Rect.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/Snapshot.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/SnapshotsManager.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/TrustedHTMLHandler.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/parameter/ValueSanitizer.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/CachePluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/ContextMenuPluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/CopyPastePluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/DOMEventPluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/EntityPluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/FormatPluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/LifecyclePluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/PluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/SelectionPluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/pluginState/UndoPluginState.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/segment/ContentModelBr.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/segment/ContentModelGeneralSegment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/segment/ContentModelImage.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/segment/ContentModelSegment.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/segment/ContentModelSegmentBase.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/segment/ContentModelSelectionMarker.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/segment/ContentModelText.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/selection/DOMSelection.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/selection/InsertPoint.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/selection/Selectable.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/selection/TableSelectionContext.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/lib/selection/TableSelectionCoordinates.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model-types/package.json (100%) rename {packages-content-model => packages}/roosterjs-content-model/lib/createEditor.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model/lib/index.ts (100%) rename {packages-content-model => packages}/roosterjs-content-model/package.json (100%) rename {packages-ui => packages}/roosterjs-react/lib/colorPicker/component/renderColorPicker.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/colorPicker/index.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/colorPicker/types/stringKeys.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/colorPicker/utils/backgroundColors.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/colorPicker/utils/getClassNamesForColorPicker.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/colorPicker/utils/textColors.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/common/index.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/common/type/LocalizedStrings.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/common/type/ReactEditorPlugin.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/common/type/RibbonPluginOptions.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/common/type/UIUtilities.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/common/utils/createUIUtilities.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/common/utils/getLocalizedString.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/common/utils/renderReactComponent.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/contextMenu/index.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/contextMenu/menus/createImageEditMenuProvider.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/contextMenu/menus/createListEditMenuProvider.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/contextMenu/menus/createTableEditMenuProvider.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/contextMenu/plugin/createContextMenuPlugin.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/contextMenu/types/ContextMenuItem.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/contextMenu/types/ContextMenuItemStringKeys.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/contextMenu/utils/createContextMenuProvider.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/components/EmojiIcon.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/components/EmojiNavBar.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/components/EmojiPane.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/components/EmojiStatusBar.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/components/showEmojiCallout.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/index.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/plugin/createEmojiPlugin.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/type/Emoji.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/type/EmojiPaneStyles.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/type/EmojiStringKeys.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/type/EmojiStrings.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/utils/emojiList.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/emoji/utils/searchEmojis.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/index.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/inputDialog/component/InputDialog.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/inputDialog/component/InputDialogItem.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/inputDialog/index.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/inputDialog/type/DialogItem.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/inputDialog/utils/showInputDialog.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/pasteOptions/component/showPasteOptionPane.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/pasteOptions/index.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/pasteOptions/plugin/createPasteOptionPlugin.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/pasteOptions/type/PasteOptionStringKeys.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/pasteOptions/utils/buttons.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/Ribbon.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/alignCenter.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/alignLeft.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/alignRight.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/backgroundColor.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/bold.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/bulletedList.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/clearFormat.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/code.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/decreaseFontSize.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/decreaseIndent.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/font.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/fontSize.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/heading.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/increaseFontSize.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/increaseIndent.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/insertImage.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/insertLink.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/insertTable.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/italic.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/ltr.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/moreCommands.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/numberedList.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/quote.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/redo.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/removeLink.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/rtl.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/strikethrough.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/subscript.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/superscript.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/textColor.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/underline.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/buttons/undo.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/component/getButtons.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/index.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/plugin/createRibbonPlugin.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/type/KnownRibbonButton.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/type/RibbonButton.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/type/RibbonButtonDropDown.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/type/RibbonButtonStringKeys.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/type/RibbonPlugin.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/ribbon/type/RibbonProps.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/rooster/component/Rooster.tsx (100%) rename {packages-ui => packages}/roosterjs-react/lib/rooster/index.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/rooster/plugin/createUpdateContentPlugin.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/rooster/type/RoosterProps.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/rooster/type/UpdateContentPlugin.ts (100%) rename {packages-ui => packages}/roosterjs-react/lib/rooster/type/UpdateMode.ts (100%) rename {packages-ui => packages}/roosterjs-react/package.json (100%) rename {packages-ui => packages}/roosterjs-react/test/emptyTest.ts (100%) delete mode 100644 tools/karma.test.contentmodel.js delete mode 100644 tools/karma.test.roosterjs.js delete mode 100644 tools/karma.test.ui.js diff --git a/demo/index.html b/demo/index.html index 74c769bbb3f..b63af748584 100644 --- a/demo/index.html +++ b/demo/index.html @@ -23,6 +23,7 @@ + diff --git a/demo/scripts/tsconfig.json b/demo/scripts/tsconfig.json index 80082fd426a..4514c38a674 100644 --- a/demo/scripts/tsconfig.json +++ b/demo/scripts/tsconfig.json @@ -25,40 +25,24 @@ "roosterjs-editor-types/lib/*": ["packages/roosterjs-editor-types/lib/*"], "roosterjs-color-utils": ["packages/roosterjs-color-utils/lib/index"], "roosterjs-color-utils/lib/*": ["packages/roosterjs-color-utils/lib/*"], - "roosterjs-content-model-types": [ - "packages-content-model/roosterjs-content-model-types/lib/index" - ], - "roosterjs-content-model-types/lib/*": [ - "packages-content-model/roosterjs-content-model-types/lib/*" - ], - "roosterjs-content-model-dom": [ - "packages-content-model/roosterjs-content-model-dom/lib/index" - ], - "roosterjs-content-model-dom/lib/*": [ - "packages-content-model/roosterjs-content-model-dom/lib/*" - ], - "roosterjs-content-model-core": [ - "packages-content-model/roosterjs-content-model-core/lib/index" - ], - "roosterjs-content-model-core/lib/*": [ - "packages-content-model/roosterjs-content-model-core/lib/*" - ], - "roosterjs-content-model-api": [ - "packages-content-model/roosterjs-content-model-api/lib/index" - ], - "roosterjs-content-model-api/lib/*": [ - "packages-content-model/roosterjs-content-model-api/lib/*" - ], + "roosterjs-content-model-types": ["packages/roosterjs-content-model-types/lib/index"], + "roosterjs-content-model-types/lib/*": ["packages/roosterjs-content-model-types/lib/*"], + "roosterjs-content-model-dom": ["packages/roosterjs-content-model-dom/lib/index"], + "roosterjs-content-model-dom/lib/*": ["packages/roosterjs-content-model-dom/lib/*"], + "roosterjs-content-model-core": ["packages/roosterjs-content-model-core/lib/index"], + "roosterjs-content-model-core/lib/*": ["packages/roosterjs-content-model-core/lib/*"], + "roosterjs-content-model-api": ["packages/roosterjs-content-model-api/lib/index"], + "roosterjs-content-model-api/lib/*": ["packages/roosterjs-content-model-api/lib/*"], "roosterjs-editor-adapter": ["packages/roosterjs-editor-adapter/lib/index"], "roosterjs-editor-adapter/lib/*": ["packages/roosterjs-editor-adapter/lib/*"], "roosterjs-content-model-plugins": [ - "packages-content-model/roosterjs-content-model-plugins/lib/index" + "packages/roosterjs-content-model-plugins/lib/index" ], "roosterjs-content-model-plugins/lib/*": [ - "packages-content-model/roosterjs-content-model-plugins/lib/*" + "packages/roosterjs-content-model-plugins/lib/*" ], - "roosterjs-react": ["packages-ui/roosterjs-react/lib/index"], - "roosterjs-react/lib/*": ["packages-ui/roosterjs-react/lib/*"] + "roosterjs-react": ["packages/roosterjs-react/lib/index"], + "roosterjs-react/lib/*": ["packages/roosterjs-react/lib/*"] } }, "include": ["./**/*.ts", "./**/*.tsx"] diff --git a/karma.conf.js b/karma.conf.js index 6adfeff75f0..31d7d396bf2 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -4,26 +4,6 @@ const runCoverage = typeof argv.coverage !== 'undefined'; const runFirefox = typeof argv.firefox !== 'undefined'; const runChrome = typeof argv.chrome !== 'undefined'; -const testEntries = { - 'Original RoosterJs': 'tools/karma.test.roosterjs.js', - UI: 'tools/karma.test.ui.js', - 'Content Model': 'tools/karma.test.contentmodel.js', - All: 'tools/karma.test.all.js', -}; -const currentEntry = - typeof argv.contentmodel !== 'undefined' - ? 'Content Model' - : typeof argv.roosterjs !== 'undefined' - ? 'Original RoosterJs' - : typeof argv.ui !== 'undefined' - ? 'UI' - : 'All'; -const currentFile = testEntries[currentEntry]; -const allPreprocessors = Object.keys(testEntries).reduce((value, entry) => { - value[testEntries[entry]] = ['webpack', 'sourcemap']; - return value; -}, {}); - const rootPath = __dirname; module.exports = function (config) { @@ -56,12 +36,7 @@ module.exports = function (config) { strict: false, downlevelIteration: true, paths: { - '*': [ - '*', - rootPath + '/packages/*', - rootPath + '/packages-ui/*', - rootPath + '/packages-content-model/*', - ], + '*': ['*', rootPath + '/packages/*'], }, }, }; @@ -100,9 +75,11 @@ module.exports = function (config) { clearContext: false, }, browsers: launcher, - files: [currentFile], + files: ['tools/karma.test.all.js'], frameworks: ['jasmine'], - preprocessors: allPreprocessors, + preprocessors: { + 'tools/karma.test.all.js': ['webpack', 'sourcemap'], + }, port: 9876, colors: true, logLevel: config.LOG_INFO, @@ -151,7 +128,5 @@ module.exports = function (config) { }; } - console.log('Run ' + currentEntry + ' test cases...'); - config.set(settings); }; diff --git a/package.json b/package.json index 76cdc6ac7d5..a27a74d139b 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,6 @@ "test:firefox": "node tools/build.js normalize & karma start --firefox", "test:debug": "node tools/build.js normalize & karma start --no-single-run --chrome", "test:coverage": "node tools/build.js normalize & karma start --coverage --firefox --chrome", - "test:cm": "node tools/build.js normalize & karma start --no-single-run --chrome --contentmodel", - "test:cm:firefox": "node tools/build.js normalize & karma start --no-single-run --firefox --contentmodel", - "test:r": "node tools/build.js normalize & karma start --no-single-run --chrome --roosterjs", - "test:ui": "node tools/build.js normalize & karma start --no-single-run --chrome --ui", "publish": "node tools/build.js clean normalize eslint buildcommonjs buildamd buildmjs dts pack packprod builddemo builddoc publish" }, "devDependencies": { diff --git a/packages-content-model/tsconfig.json b/packages-content-model/tsconfig.json deleted file mode 100644 index d4681dd4529..00000000000 --- a/packages-content-model/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "target": "es5", - "module": "commonjs", - "outDir": "../dist", - "sourceMap": true, - "inlineSources": true, - "declaration": true, - "removeComments": false, - "noImplicitAny": true, - "preserveConstEnums": true, - "noUnusedLocals": true, - "downlevelIteration": true, - "importHelpers": true, - "baseUrl": ".", - "paths": { - "*": ["*", "../dist/*"] - }, - "rootDir": ".", - "lib": ["es6", "dom"] - }, - "include": ["./*/lib/**/*.ts"] -} diff --git a/packages-content-model/tsconfig.test.json b/packages-content-model/tsconfig.test.json deleted file mode 100644 index 98b02b80873..00000000000 --- a/packages-content-model/tsconfig.test.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "strict": false, - "target": "es5", - "module": "commonjs", - "noEmit": true, - "sourceMap": true, - "inlineSources": true, - "declaration": true, - "removeComments": false, - "noImplicitAny": true, - "preserveConstEnums": true, - "noUnusedLocals": true, - "downlevelIteration": true, - "importHelpers": true, - "baseUrl": ".", - "paths": { - "*": ["*", "../packages/*"] - }, - "rootDir": "..", - "lib": ["es6", "dom"] - }, - "include": ["./*/test/**/*.ts"] -} diff --git a/packages-ui/tsconfig.json b/packages-ui/tsconfig.json deleted file mode 100644 index 27bcb1f969a..00000000000 --- a/packages-ui/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "jsx": "react", - "target": "es5", - "module": "commonjs", - "outDir": "../dist", - "sourceMap": true, - "inlineSources": true, - "declaration": true, - "removeComments": false, - "noImplicitAny": true, - "preserveConstEnums": true, - "noUnusedLocals": true, - "baseUrl": ".", - "paths": { - "*": ["*", "../dist/*"] - }, - "rootDir": ".", - "lib": ["es6", "dom"] - }, - "include": ["./*/lib/**/*.ts", "./*/lib/**/*.tsx"] -} diff --git a/packages-ui/tsconfig.test.json b/packages-ui/tsconfig.test.json deleted file mode 100644 index 18d64d0348c..00000000000 --- a/packages-ui/tsconfig.test.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "strict": false, - "jsx": "react", - "target": "es5", - "module": "commonjs", - "noEmit": true, - "sourceMap": true, - "inlineSources": true, - "declaration": true, - "removeComments": false, - "noImplicitAny": true, - "preserveConstEnums": true, - "noUnusedLocals": true, - "baseUrl": ".", - "paths": { - "*": ["*", "../packages/*"] - }, - "rootDir": "..", - "lib": ["es6", "dom"] - }, - "include": ["./*/test/**/*.ts", "./*/test/**/*.tsx"] -} diff --git a/packages-content-model/roosterjs-content-model-api/lib/index.ts b/packages/roosterjs-content-model-api/lib/index.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/index.ts rename to packages/roosterjs-content-model-api/lib/index.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/block/setModelAlignment.ts b/packages/roosterjs-content-model-api/lib/modelApi/block/setModelAlignment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/block/setModelAlignment.ts rename to packages/roosterjs-content-model-api/lib/modelApi/block/setModelAlignment.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/block/setModelDirection.ts b/packages/roosterjs-content-model-api/lib/modelApi/block/setModelDirection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/block/setModelDirection.ts rename to packages/roosterjs-content-model-api/lib/modelApi/block/setModelDirection.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/block/setModelIndentation.ts b/packages/roosterjs-content-model-api/lib/modelApi/block/setModelIndentation.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/block/setModelIndentation.ts rename to packages/roosterjs-content-model-api/lib/modelApi/block/setModelIndentation.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/block/toggleModelBlockQuote.ts b/packages/roosterjs-content-model-api/lib/modelApi/block/toggleModelBlockQuote.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/block/toggleModelBlockQuote.ts rename to packages/roosterjs-content-model-api/lib/modelApi/block/toggleModelBlockQuote.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/common/clearModelFormat.ts b/packages/roosterjs-content-model-api/lib/modelApi/common/clearModelFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/common/clearModelFormat.ts rename to packages/roosterjs-content-model-api/lib/modelApi/common/clearModelFormat.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/common/wrapBlock.ts b/packages/roosterjs-content-model-api/lib/modelApi/common/wrapBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/common/wrapBlock.ts rename to packages/roosterjs-content-model-api/lib/modelApi/common/wrapBlock.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/entity/insertEntityModel.ts b/packages/roosterjs-content-model-api/lib/modelApi/entity/insertEntityModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/entity/insertEntityModel.ts rename to packages/roosterjs-content-model-api/lib/modelApi/entity/insertEntityModel.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/image/applyImageBorderFormat.ts b/packages/roosterjs-content-model-api/lib/modelApi/image/applyImageBorderFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/image/applyImageBorderFormat.ts rename to packages/roosterjs-content-model-api/lib/modelApi/image/applyImageBorderFormat.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts b/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts rename to packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/findListItemsInSameThread.ts b/packages/roosterjs-content-model-api/lib/modelApi/list/findListItemsInSameThread.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/list/findListItemsInSameThread.ts rename to packages/roosterjs-content-model-api/lib/modelApi/list/findListItemsInSameThread.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts b/packages/roosterjs-content-model-api/lib/modelApi/list/setListType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/list/setListType.ts rename to packages/roosterjs-content-model-api/lib/modelApi/list/setListType.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/selection/adjustSegmentSelection.ts b/packages/roosterjs-content-model-api/lib/modelApi/selection/adjustSegmentSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/selection/adjustSegmentSelection.ts rename to packages/roosterjs-content-model-api/lib/modelApi/selection/adjustSegmentSelection.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/selection/adjustTrailingSpaceSelection.ts b/packages/roosterjs-content-model-api/lib/modelApi/selection/adjustTrailingSpaceSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/selection/adjustTrailingSpaceSelection.ts rename to packages/roosterjs-content-model-api/lib/modelApi/selection/adjustTrailingSpaceSelection.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/selection/adjustWordSelection.ts b/packages/roosterjs-content-model-api/lib/modelApi/selection/adjustWordSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/selection/adjustWordSelection.ts rename to packages/roosterjs-content-model-api/lib/modelApi/selection/adjustWordSelection.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/selection/collapseTableSelection.ts b/packages/roosterjs-content-model-api/lib/modelApi/selection/collapseTableSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/selection/collapseTableSelection.ts rename to packages/roosterjs-content-model-api/lib/modelApi/selection/collapseTableSelection.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/alignTable.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/alignTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/alignTable.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/alignTable.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/alignTableCell.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/alignTableCell.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/alignTableCell.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/alignTableCell.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/canMergeCells.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/canMergeCells.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/canMergeCells.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/canMergeCells.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/clearSelectedCells.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/clearSelectedCells.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/clearSelectedCells.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/clearSelectedCells.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/createTableStructure.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/createTableStructure.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/createTableStructure.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/createTableStructure.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/deleteTable.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/deleteTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/deleteTable.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/deleteTable.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/deleteTableColumn.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/deleteTableColumn.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/deleteTableColumn.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/deleteTableColumn.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/deleteTableRow.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/deleteTableRow.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/deleteTableRow.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/deleteTableRow.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/ensureFocusableParagraphForTable.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/ensureFocusableParagraphForTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/ensureFocusableParagraphForTable.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/ensureFocusableParagraphForTable.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/insertTableColumn.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/insertTableColumn.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/insertTableColumn.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/insertTableColumn.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/insertTableRow.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/insertTableRow.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/insertTableRow.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/insertTableRow.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/mergeTableCells.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/mergeTableCells.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/mergeTableCells.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/mergeTableCells.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/mergeTableColumn.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/mergeTableColumn.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/mergeTableColumn.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/mergeTableColumn.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/mergeTableRow.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/mergeTableRow.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/mergeTableRow.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/mergeTableRow.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/splitTableCellHorizontally.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/splitTableCellHorizontally.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/splitTableCellHorizontally.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/splitTableCellHorizontally.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/modelApi/table/splitTableCellVertically.ts b/packages/roosterjs-content-model-api/lib/modelApi/table/splitTableCellVertically.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/modelApi/table/splitTableCellVertically.ts rename to packages/roosterjs-content-model-api/lib/modelApi/table/splitTableCellVertically.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts rename to packages/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setDirection.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setDirection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setDirection.ts rename to packages/roosterjs-content-model-api/lib/publicApi/block/setDirection.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts rename to packages/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts rename to packages/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts rename to packages/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts rename to packages/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts rename to packages/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts b/packages/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts rename to packages/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts b/packages/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts rename to packages/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts b/packages/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts rename to packages/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts rename to packages/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts rename to packages/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts rename to packages/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts rename to packages/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts rename to packages/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts rename to packages/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts b/packages/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts rename to packages/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts b/packages/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts rename to packages/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts b/packages/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts rename to packages/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts b/packages/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts rename to packages/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts b/packages/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts rename to packages/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts b/packages/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts rename to packages/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts b/packages/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts rename to packages/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts rename to packages/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts rename to packages/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/editTable.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/editTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/table/editTable.ts rename to packages/roosterjs-content-model-api/lib/publicApi/table/editTable.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts rename to packages/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts rename to packages/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts rename to packages/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts rename to packages/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts rename to packages/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts rename to packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts diff --git a/packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatTableWithContentModel.ts b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatTableWithContentModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/lib/publicApi/utils/formatTableWithContentModel.ts rename to packages/roosterjs-content-model-api/lib/publicApi/utils/formatTableWithContentModel.ts diff --git a/packages-content-model/roosterjs-content-model-api/package.json b/packages/roosterjs-content-model-api/package.json similarity index 100% rename from packages-content-model/roosterjs-content-model-api/package.json rename to packages/roosterjs-content-model-api/package.json diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/block/setModelAlignmentTest.ts b/packages/roosterjs-content-model-api/test/modelApi/block/setModelAlignmentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/block/setModelAlignmentTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/block/setModelAlignmentTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/block/setModelDirectionTest.ts b/packages/roosterjs-content-model-api/test/modelApi/block/setModelDirectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/block/setModelDirectionTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/block/setModelDirectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/block/setModelIndentationTest.ts b/packages/roosterjs-content-model-api/test/modelApi/block/setModelIndentationTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/block/setModelIndentationTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/block/setModelIndentationTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/block/toggleModelBlockQuoteTest.ts b/packages/roosterjs-content-model-api/test/modelApi/block/toggleModelBlockQuoteTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/block/toggleModelBlockQuoteTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/block/toggleModelBlockQuoteTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/common/clearModelFormatTest.ts b/packages/roosterjs-content-model-api/test/modelApi/common/clearModelFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/common/clearModelFormatTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/common/clearModelFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/common/wrapBlockTest.ts b/packages/roosterjs-content-model-api/test/modelApi/common/wrapBlockTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/common/wrapBlockTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/common/wrapBlockTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/entity/insertEntityModelTest.ts b/packages/roosterjs-content-model-api/test/modelApi/entity/insertEntityModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/entity/insertEntityModelTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/entity/insertEntityModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/image/applyImageBorderFormatTest.ts b/packages/roosterjs-content-model-api/test/modelApi/image/applyImageBorderFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/image/applyImageBorderFormatTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/image/applyImageBorderFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/link/matchLinkTest.ts b/packages/roosterjs-content-model-api/test/modelApi/link/matchLinkTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/link/matchLinkTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/link/matchLinkTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/list/findListItemsInSameThreadTest.ts b/packages/roosterjs-content-model-api/test/modelApi/list/findListItemsInSameThreadTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/list/findListItemsInSameThreadTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/list/findListItemsInSameThreadTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts b/packages/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/list/setListTypeTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/selection/adjustSegmentSelectionTest.ts b/packages/roosterjs-content-model-api/test/modelApi/selection/adjustSegmentSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/selection/adjustSegmentSelectionTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/selection/adjustSegmentSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/selection/adjustTrailingSpaceSelectionTest.ts b/packages/roosterjs-content-model-api/test/modelApi/selection/adjustTrailingSpaceSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/selection/adjustTrailingSpaceSelectionTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/selection/adjustTrailingSpaceSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/selection/adjustWordSelectionTest.ts b/packages/roosterjs-content-model-api/test/modelApi/selection/adjustWordSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/selection/adjustWordSelectionTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/selection/adjustWordSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/selection/collapseTableSelectionTest.ts b/packages/roosterjs-content-model-api/test/modelApi/selection/collapseTableSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/selection/collapseTableSelectionTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/selection/collapseTableSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/alignTableCellTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/alignTableCellTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/alignTableCellTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/alignTableCellTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/alignTableTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/alignTableTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/alignTableTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/alignTableTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/canMergeCellsTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/canMergeCellsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/canMergeCellsTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/canMergeCellsTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/clearSelectedCellsTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/clearSelectedCellsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/clearSelectedCellsTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/clearSelectedCellsTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/createTableStructureTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/createTableStructureTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/createTableStructureTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/createTableStructureTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/deleteTableColumnTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/deleteTableColumnTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/deleteTableColumnTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/deleteTableColumnTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/deleteTableRowTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/deleteTableRowTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/deleteTableRowTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/deleteTableRowTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/deleteTableTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/deleteTableTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/deleteTableTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/deleteTableTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/ensureFocusableParagraphForTableTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/ensureFocusableParagraphForTableTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/ensureFocusableParagraphForTableTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/ensureFocusableParagraphForTableTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/insertTableColumnTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/insertTableColumnTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/insertTableColumnTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/insertTableColumnTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/insertTableRowTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/insertTableRowTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/insertTableRowTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/insertTableRowTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/mergeTableCellsTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/mergeTableCellsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/mergeTableCellsTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/mergeTableCellsTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/mergeTableColumnTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/mergeTableColumnTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/mergeTableColumnTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/mergeTableColumnTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/mergeTableRowTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/mergeTableRowTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/mergeTableRowTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/mergeTableRowTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/splitTableCellHorizontallyTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/splitTableCellHorizontallyTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/splitTableCellHorizontallyTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/splitTableCellHorizontallyTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/modelApi/table/splitTableCellVerticallyTest.ts b/packages/roosterjs-content-model-api/test/modelApi/table/splitTableCellVerticallyTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/modelApi/table/splitTableCellVerticallyTest.ts rename to packages/roosterjs-content-model-api/test/modelApi/table/splitTableCellVerticallyTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/paragraphTestCommon.ts b/packages/roosterjs-content-model-api/test/publicApi/block/paragraphTestCommon.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/block/paragraphTestCommon.ts rename to packages/roosterjs-content-model-api/test/publicApi/block/paragraphTestCommon.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setDirectionTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setDirectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/block/setDirectionTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/block/setDirectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setHeadingLevelTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setHeadingLevelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/block/setHeadingLevelTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/block/setHeadingLevelTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setParagraphMarginTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setParagraphMarginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/block/setParagraphMarginTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/block/setParagraphMarginTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/setSpacingTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setSpacingTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/block/setSpacingTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/block/setSpacingTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts b/packages/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts b/packages/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts b/packages/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/adjustImageSelectionTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/adjustImageSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/image/adjustImageSelectionTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/image/adjustImageSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/setImageAltTextTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/setImageAltTextTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/image/setImageAltTextTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/image/setImageAltTextTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/setImageBorderTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/setImageBorderTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/image/setImageBorderTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/image/setImageBorderTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/image/setImageBoxShadowTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/setImageBoxShadowTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/image/setImageBoxShadowTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/image/setImageBoxShadowTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts b/packages/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts b/packages/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts b/packages/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/setListStartNumberTest.ts b/packages/roosterjs-content-model-api/test/publicApi/list/setListStartNumberTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/list/setListStartNumberTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/list/setListStartNumberTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/setListStyleTest.ts b/packages/roosterjs-content-model-api/test/publicApi/list/setListStyleTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/list/setListStyleTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/list/setListStyleTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts b/packages/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts b/packages/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/changeCapitalizationTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/changeCapitalizationTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/changeCapitalizationTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/changeCapitalizationTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/segmentTestCommon.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/segmentTestCommon.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/segmentTestCommon.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/segmentTestCommon.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts b/packages/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts b/packages/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts b/packages/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatParagraphWithContentModelTest.ts b/packages/roosterjs-content-model-api/test/publicApi/utils/formatParagraphWithContentModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatParagraphWithContentModelTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/utils/formatParagraphWithContentModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatSegmentWithContentModelTest.ts b/packages/roosterjs-content-model-api/test/publicApi/utils/formatSegmentWithContentModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatSegmentWithContentModelTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/utils/formatSegmentWithContentModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts b/packages/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts rename to packages/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/constants/BulletListType.ts b/packages/roosterjs-content-model-core/lib/constants/BulletListType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/constants/BulletListType.ts rename to packages/roosterjs-content-model-core/lib/constants/BulletListType.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/constants/ChangeSource.ts b/packages/roosterjs-content-model-core/lib/constants/ChangeSource.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/constants/ChangeSource.ts rename to packages/roosterjs-content-model-core/lib/constants/ChangeSource.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/constants/NumberingListType.ts b/packages/roosterjs-content-model-core/lib/constants/NumberingListType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/constants/NumberingListType.ts rename to packages/roosterjs-content-model-core/lib/constants/NumberingListType.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/constants/TableBorderFormat.ts b/packages/roosterjs-content-model-core/lib/constants/TableBorderFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/constants/TableBorderFormat.ts rename to packages/roosterjs-content-model-core/lib/constants/TableBorderFormat.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts b/packages/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts rename to packages/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts b/packages/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts rename to packages/roosterjs-content-model-core/lib/coreApi/attachDomEvent.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createContentModel.ts b/packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/createContentModel.ts rename to packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts b/packages/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts rename to packages/roosterjs-content-model-core/lib/coreApi/createEditorContext.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts b/packages/roosterjs-content-model-core/lib/coreApi/focus.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/focus.ts rename to packages/roosterjs-content-model-core/lib/coreApi/focus.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts b/packages/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts rename to packages/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts b/packages/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts rename to packages/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts b/packages/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts rename to packages/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts b/packages/roosterjs-content-model-core/lib/coreApi/hasFocus.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/hasFocus.ts rename to packages/roosterjs-content-model-core/lib/coreApi/hasFocus.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts b/packages/roosterjs-content-model-core/lib/coreApi/paste.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/paste.ts rename to packages/roosterjs-content-model-core/lib/coreApi/paste.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot.ts b/packages/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot.ts rename to packages/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/setContentModel.ts b/packages/roosterjs-content-model-core/lib/coreApi/setContentModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/setContentModel.ts rename to packages/roosterjs-content-model-core/lib/coreApi/setContentModel.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts b/packages/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts rename to packages/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts b/packages/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts rename to packages/roosterjs-content-model-core/lib/coreApi/switchShadowEdit.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/coreApi/triggerEvent.ts b/packages/roosterjs-content-model-core/lib/coreApi/triggerEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/coreApi/triggerEvent.ts rename to packages/roosterjs-content-model-core/lib/coreApi/triggerEvent.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CachePlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/CachePlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/CachePlugin.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/CachePlugin.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/ContextMenuPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/ContextMenuPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/ContextMenuPlugin.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/ContextMenuPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/DOMEventPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/DOMEventPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/DOMEventPlugin.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/DOMEventPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/FormatPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/FormatPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/FormatPlugin.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/FormatPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/LifecyclePlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/LifecyclePlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/LifecyclePlugin.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/LifecyclePlugin.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/SelectionPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/UndoPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/UndoPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/UndoPlugin.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/UndoPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/createEditorCorePlugins.ts b/packages/roosterjs-content-model-core/lib/corePlugin/createEditorCorePlugins.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/createEditorCorePlugins.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/createEditorCorePlugins.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/addRangeToSelection.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/addRangeToSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/addRangeToSelection.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/utils/addRangeToSelection.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyDefaultFormat.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/applyDefaultFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyDefaultFormat.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/utils/applyDefaultFormat.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyPendingFormat.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/applyPendingFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/applyPendingFormat.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/utils/applyPendingFormat.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/areSameSelection.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/areSameSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/areSameSelection.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/utils/areSameSelection.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/deleteEmptyList.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/deleteEmptyList.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/deleteEmptyList.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/utils/deleteEmptyList.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/domIndexerImpl.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/domIndexerImpl.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/domIndexerImpl.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/utils/domIndexerImpl.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/entityDelimiterUtils.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/entityDelimiterUtils.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/entityDelimiterUtils.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/utils/entityDelimiterUtils.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/findAllEntities.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/findAllEntities.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/findAllEntities.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/utils/findAllEntities.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/textMutationObserver.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/textMutationObserver.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/corePlugin/utils/textMutationObserver.ts rename to packages/roosterjs-content-model-core/lib/corePlugin/utils/textMutationObserver.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts b/packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts rename to packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/DarkColorHandlerImpl.ts b/packages/roosterjs-content-model-core/lib/editor/DarkColorHandlerImpl.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/editor/DarkColorHandlerImpl.ts rename to packages/roosterjs-content-model-core/lib/editor/DarkColorHandlerImpl.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts b/packages/roosterjs-content-model-core/lib/editor/Editor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/editor/Editor.ts rename to packages/roosterjs-content-model-core/lib/editor/Editor.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts b/packages/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts rename to packages/roosterjs-content-model-core/lib/editor/SnapshotsManagerImpl.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/coreApiMap.ts b/packages/roosterjs-content-model-core/lib/editor/coreApiMap.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/editor/coreApiMap.ts rename to packages/roosterjs-content-model-core/lib/editor/coreApiMap.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorCore.ts b/packages/roosterjs-content-model-core/lib/editor/createEditorCore.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/editor/createEditorCore.ts rename to packages/roosterjs-content-model-core/lib/editor/createEditorCore.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/editor/createEditorDefaultSettings.ts b/packages/roosterjs-content-model-core/lib/editor/createEditorDefaultSettings.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/editor/createEditorDefaultSettings.ts rename to packages/roosterjs-content-model-core/lib/editor/createEditorDefaultSettings.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/index.ts b/packages/roosterjs-content-model-core/lib/index.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/index.ts rename to packages/roosterjs-content-model-core/lib/index.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/metadata/definitionCreators.ts b/packages/roosterjs-content-model-core/lib/metadata/definitionCreators.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/metadata/definitionCreators.ts rename to packages/roosterjs-content-model-core/lib/metadata/definitionCreators.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/metadata/updateImageMetadata.ts b/packages/roosterjs-content-model-core/lib/metadata/updateImageMetadata.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/metadata/updateImageMetadata.ts rename to packages/roosterjs-content-model-core/lib/metadata/updateImageMetadata.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/metadata/updateListMetadata.ts b/packages/roosterjs-content-model-core/lib/metadata/updateListMetadata.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/metadata/updateListMetadata.ts rename to packages/roosterjs-content-model-core/lib/metadata/updateListMetadata.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/metadata/updateTableCellMetadata.ts b/packages/roosterjs-content-model-core/lib/metadata/updateTableCellMetadata.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/metadata/updateTableCellMetadata.ts rename to packages/roosterjs-content-model-core/lib/metadata/updateTableCellMetadata.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/metadata/updateTableMetadata.ts b/packages/roosterjs-content-model-core/lib/metadata/updateTableMetadata.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/metadata/updateTableMetadata.ts rename to packages/roosterjs-content-model-core/lib/metadata/updateTableMetadata.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/modelApi/edit/deleteExpandedSelection.ts b/packages/roosterjs-content-model-core/lib/modelApi/edit/deleteExpandedSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/modelApi/edit/deleteExpandedSelection.ts rename to packages/roosterjs-content-model-core/lib/modelApi/edit/deleteExpandedSelection.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/modelApi/edit/deleteSingleChar.ts b/packages/roosterjs-content-model-core/lib/modelApi/edit/deleteSingleChar.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/modelApi/edit/deleteSingleChar.ts rename to packages/roosterjs-content-model-core/lib/modelApi/edit/deleteSingleChar.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/override/containerSizeFormatParser.ts b/packages/roosterjs-content-model-core/lib/override/containerSizeFormatParser.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/override/containerSizeFormatParser.ts rename to packages/roosterjs-content-model-core/lib/override/containerSizeFormatParser.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/override/pasteCopyBlockEntityParser.ts b/packages/roosterjs-content-model-core/lib/override/pasteCopyBlockEntityParser.ts similarity index 96% rename from packages-content-model/roosterjs-content-model-core/lib/override/pasteCopyBlockEntityParser.ts rename to packages/roosterjs-content-model-core/lib/override/pasteCopyBlockEntityParser.ts index 8a4a567c542..04fb2ae1408 100644 --- a/packages-content-model/roosterjs-content-model-core/lib/override/pasteCopyBlockEntityParser.ts +++ b/packages/roosterjs-content-model-core/lib/override/pasteCopyBlockEntityParser.ts @@ -1,43 +1,43 @@ -import { isBlockElement, isElementOfType, isNodeOfType } from 'roosterjs-content-model-dom'; -import type { - ContentModelEntity, - EntityInfoFormat, - FormatParser, - OnNodeCreated, -} from 'roosterjs-content-model-types'; - -const BlockEntityClass = '_EBlock'; -const OneHundredPercent = '100%'; -const InlineBlock = 'inline-block'; - -/** - * @internal - */ -export const onCreateCopyEntityNode: OnNodeCreated = (model, node) => { - const entityModel = model as ContentModelEntity; - if ( - entityModel && - entityModel.wrapper && - entityModel.blockType == 'Entity' && - isNodeOfType(node, 'ELEMENT_NODE') && - isElementOfType(node, 'div') && - !isBlockElement(entityModel.wrapper) && - entityModel.wrapper.style.display == InlineBlock && - entityModel.wrapper.style.width == OneHundredPercent - ) { - node.classList.add(BlockEntityClass); - node.style.display = 'block'; - node.style.width = ''; - } -}; - -/** - * @internal - */ -export const pasteBlockEntityParser: FormatParser = (_, element) => { - if (element.classList.contains(BlockEntityClass)) { - element.classList.remove(BlockEntityClass); - element.style.display = InlineBlock; - element.style.width = OneHundredPercent; - } -}; +import { isBlockElement, isElementOfType, isNodeOfType } from 'roosterjs-content-model-dom'; +import type { + ContentModelEntity, + EntityInfoFormat, + FormatParser, + OnNodeCreated, +} from 'roosterjs-content-model-types'; + +const BlockEntityClass = '_EBlock'; +const OneHundredPercent = '100%'; +const InlineBlock = 'inline-block'; + +/** + * @internal + */ +export const onCreateCopyEntityNode: OnNodeCreated = (model, node) => { + const entityModel = model as ContentModelEntity; + if ( + entityModel && + entityModel.wrapper && + entityModel.blockType == 'Entity' && + isNodeOfType(node, 'ELEMENT_NODE') && + isElementOfType(node, 'div') && + !isBlockElement(entityModel.wrapper) && + entityModel.wrapper.style.display == InlineBlock && + entityModel.wrapper.style.width == OneHundredPercent + ) { + node.classList.add(BlockEntityClass); + node.style.display = 'block'; + node.style.width = ''; + } +}; + +/** + * @internal + */ +export const pasteBlockEntityParser: FormatParser = (_, element) => { + if (element.classList.contains(BlockEntityClass)) { + element.classList.remove(BlockEntityClass); + element.style.display = InlineBlock; + element.style.width = OneHundredPercent; + } +}; diff --git a/packages-content-model/roosterjs-content-model-core/lib/override/pasteDisplayFormatParser.ts b/packages/roosterjs-content-model-core/lib/override/pasteDisplayFormatParser.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/override/pasteDisplayFormatParser.ts rename to packages/roosterjs-content-model-core/lib/override/pasteDisplayFormatParser.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/override/pasteEntityProcessor.ts b/packages/roosterjs-content-model-core/lib/override/pasteEntityProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/override/pasteEntityProcessor.ts rename to packages/roosterjs-content-model-core/lib/override/pasteEntityProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/override/pasteGeneralProcessor.ts b/packages/roosterjs-content-model-core/lib/override/pasteGeneralProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/override/pasteGeneralProcessor.ts rename to packages/roosterjs-content-model-core/lib/override/pasteGeneralProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/override/pasteTextProcessor.ts b/packages/roosterjs-content-model-core/lib/override/pasteTextProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/override/pasteTextProcessor.ts rename to packages/roosterjs-content-model-core/lib/override/pasteTextProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/override/reducedModelChildProcessor.ts b/packages/roosterjs-content-model-core/lib/override/reducedModelChildProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/override/reducedModelChildProcessor.ts rename to packages/roosterjs-content-model-core/lib/override/reducedModelChildProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/override/tablePreProcessor.ts b/packages/roosterjs-content-model-core/lib/override/tablePreProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/override/tablePreProcessor.ts rename to packages/roosterjs-content-model-core/lib/override/tablePreProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/color/transformColor.ts b/packages/roosterjs-content-model-core/lib/publicApi/color/transformColor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/color/transformColor.ts rename to packages/roosterjs-content-model-core/lib/publicApi/color/transformColor.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/borderValues.ts b/packages/roosterjs-content-model-core/lib/publicApi/domUtils/borderValues.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/borderValues.ts rename to packages/roosterjs-content-model-core/lib/publicApi/domUtils/borderValues.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/cacheGetEventData.ts b/packages/roosterjs-content-model-core/lib/publicApi/domUtils/cacheGetEventData.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/cacheGetEventData.ts rename to packages/roosterjs-content-model-core/lib/publicApi/domUtils/cacheGetEventData.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/eventUtils.ts b/packages/roosterjs-content-model-core/lib/publicApi/domUtils/eventUtils.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/eventUtils.ts rename to packages/roosterjs-content-model-core/lib/publicApi/domUtils/eventUtils.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/getSegmentTextFormat.ts b/packages/roosterjs-content-model-core/lib/publicApi/domUtils/getSegmentTextFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/getSegmentTextFormat.ts rename to packages/roosterjs-content-model-core/lib/publicApi/domUtils/getSegmentTextFormat.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/readFile.ts b/packages/roosterjs-content-model-core/lib/publicApi/domUtils/readFile.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/readFile.ts rename to packages/roosterjs-content-model-core/lib/publicApi/domUtils/readFile.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/stringUtil.ts b/packages/roosterjs-content-model-core/lib/publicApi/domUtils/stringUtil.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/stringUtil.ts rename to packages/roosterjs-content-model-core/lib/publicApi/domUtils/stringUtil.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/tableCellUtils.ts b/packages/roosterjs-content-model-core/lib/publicApi/domUtils/tableCellUtils.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/domUtils/tableCellUtils.ts rename to packages/roosterjs-content-model-core/lib/publicApi/domUtils/tableCellUtils.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/format/retrieveModelFormatState.ts b/packages/roosterjs-content-model-core/lib/publicApi/format/retrieveModelFormatState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/format/retrieveModelFormatState.ts rename to packages/roosterjs-content-model-core/lib/publicApi/format/retrieveModelFormatState.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/cloneModel.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/cloneModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/model/cloneModel.ts rename to packages/roosterjs-content-model-core/lib/publicApi/model/cloneModel.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/createModelFromHtml.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/createModelFromHtml.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/model/createModelFromHtml.ts rename to packages/roosterjs-content-model-core/lib/publicApi/model/createModelFromHtml.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts rename to packages/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/getClosestAncestorBlockGroupIndex.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/getClosestAncestorBlockGroupIndex.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/model/getClosestAncestorBlockGroupIndex.ts rename to packages/roosterjs-content-model-core/lib/publicApi/model/getClosestAncestorBlockGroupIndex.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/isBlockGroupOfType.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/isBlockGroupOfType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/model/isBlockGroupOfType.ts rename to packages/roosterjs-content-model-core/lib/publicApi/model/isBlockGroupOfType.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/isBold.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/isBold.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/model/isBold.ts rename to packages/roosterjs-content-model-core/lib/publicApi/model/isBold.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts rename to packages/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/collectSelections.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/collectSelections.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/collectSelections.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/collectSelections.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteBlock.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/deleteBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteBlock.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/deleteBlock.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteSegment.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/deleteSegment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteSegment.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/deleteSegment.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteSelection.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/deleteSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/deleteSelection.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/deleteSelection.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/getSelectionRootNode.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/getSelectionRootNode.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/getSelectionRootNode.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/getSelectionRootNode.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlockGroup.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlockGroup.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlockGroup.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlockGroup.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInSegment.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInSegment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInSegment.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInSegment.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/iterateSelections.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/iterateSelections.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/iterateSelections.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/iterateSelections.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/setSelection.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/setSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/selection/setSelection.ts rename to packages/roosterjs-content-model-core/lib/publicApi/selection/setSelection.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/table/applyTableFormat.ts b/packages/roosterjs-content-model-core/lib/publicApi/table/applyTableFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/table/applyTableFormat.ts rename to packages/roosterjs-content-model-core/lib/publicApi/table/applyTableFormat.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/table/getSelectedCells.ts b/packages/roosterjs-content-model-core/lib/publicApi/table/getSelectedCells.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/table/getSelectedCells.ts rename to packages/roosterjs-content-model-core/lib/publicApi/table/getSelectedCells.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/table/normalizeTable.ts b/packages/roosterjs-content-model-core/lib/publicApi/table/normalizeTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/table/normalizeTable.ts rename to packages/roosterjs-content-model-core/lib/publicApi/table/normalizeTable.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/table/setTableCellBackgroundColor.ts b/packages/roosterjs-content-model-core/lib/publicApi/table/setTableCellBackgroundColor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/table/setTableCellBackgroundColor.ts rename to packages/roosterjs-content-model-core/lib/publicApi/table/setTableCellBackgroundColor.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/redo.ts b/packages/roosterjs-content-model-core/lib/publicApi/undo/redo.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/redo.ts rename to packages/roosterjs-content-model-core/lib/publicApi/undo/redo.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/undo.ts b/packages/roosterjs-content-model-core/lib/publicApi/undo/undo.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/publicApi/undo/undo.ts rename to packages/roosterjs-content-model-core/lib/publicApi/undo/undo.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/convertInlineCss.ts b/packages/roosterjs-content-model-core/lib/utils/convertInlineCss.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/convertInlineCss.ts rename to packages/roosterjs-content-model-core/lib/utils/convertInlineCss.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/createDomToModelContextForSanitizing.ts b/packages/roosterjs-content-model-core/lib/utils/createDomToModelContextForSanitizing.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/createDomToModelContextForSanitizing.ts rename to packages/roosterjs-content-model-core/lib/utils/createDomToModelContextForSanitizing.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts b/packages/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts rename to packages/roosterjs-content-model-core/lib/utils/createSnapshotSelection.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/extractClipboardItems.ts b/packages/roosterjs-content-model-core/lib/utils/extractClipboardItems.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/extractClipboardItems.ts rename to packages/roosterjs-content-model-core/lib/utils/extractClipboardItems.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/getRootComputedStyleForContext.ts b/packages/roosterjs-content-model-core/lib/utils/getRootComputedStyleForContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/getRootComputedStyleForContext.ts rename to packages/roosterjs-content-model-core/lib/utils/getRootComputedStyleForContext.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/createPasteFragment.ts b/packages/roosterjs-content-model-core/lib/utils/paste/createPasteFragment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/paste/createPasteFragment.ts rename to packages/roosterjs-content-model-core/lib/utils/paste/createPasteFragment.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts b/packages/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts rename to packages/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts b/packages/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts rename to packages/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/paste/retrieveHtmlInfo.ts b/packages/roosterjs-content-model-core/lib/utils/paste/retrieveHtmlInfo.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/paste/retrieveHtmlInfo.ts rename to packages/roosterjs-content-model-core/lib/utils/paste/retrieveHtmlInfo.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts b/packages/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts rename to packages/roosterjs-content-model-core/lib/utils/restoreSnapshotColors.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts b/packages/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts rename to packages/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts b/packages/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts rename to packages/roosterjs-content-model-core/lib/utils/restoreSnapshotSelection.ts diff --git a/packages-content-model/roosterjs-content-model-core/lib/utils/sanitizeElement.ts b/packages/roosterjs-content-model-core/lib/utils/sanitizeElement.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/lib/utils/sanitizeElement.ts rename to packages/roosterjs-content-model-core/lib/utils/sanitizeElement.ts diff --git a/packages-content-model/roosterjs-content-model-core/package.json b/packages/roosterjs-content-model-core/package.json similarity index 100% rename from packages-content-model/roosterjs-content-model-core/package.json rename to packages/roosterjs-content-model-core/package.json diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts b/packages/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/addUndoSnapshotTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts b/packages/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/attachDomEventTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts b/packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts b/packages/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/createEditorContextTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts b/packages/roosterjs-content-model-core/test/coreApi/focusTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/focusTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/focusTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts b/packages/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts b/packages/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/getVisibleViewportTest.ts b/packages/roosterjs-content-model-core/test/coreApi/getVisibleViewportTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/getVisibleViewportTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/getVisibleViewportTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts b/packages/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts b/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/pasteTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshotTest.ts b/packages/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshotTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshotTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshotTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts b/packages/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts b/packages/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts b/packages/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/switchShadowEditTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts b/packages/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts rename to packages/roosterjs-content-model-core/test/coreApi/triggerEventTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/CachePluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/CachePluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/CachePluginTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/CachePluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/ContextMenuPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/ContextMenuPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/ContextMenuPluginTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/ContextMenuPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/DomEventPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/DomEventPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/DomEventPluginTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/DomEventPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/FormatPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/FormatPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/FormatPluginTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/FormatPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/LifecyclePluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/LifecyclePluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/LifecyclePluginTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/LifecyclePluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/SelectionPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/SelectionPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/SelectionPluginTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/SelectionPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/UndoPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/UndoPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/UndoPluginTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/UndoPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyDefaultFormatTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/utils/applyDefaultFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyDefaultFormatTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/utils/applyDefaultFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyPendingFormatTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/utils/applyPendingFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/applyPendingFormatTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/utils/applyPendingFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/areSameRangeExTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/utils/areSameRangeExTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/areSameRangeExTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/utils/areSameRangeExTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/delimiterUtilsTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/utils/delimiterUtilsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/delimiterUtilsTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/utils/delimiterUtilsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/domIndexerTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/utils/domIndexerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/domIndexerTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/utils/domIndexerTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/findAllEntitiesTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/utils/findAllEntitiesTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/findAllEntitiesTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/utils/findAllEntitiesTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/textMutationObserverTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/utils/textMutationObserverTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/corePlugin/utils/textMutationObserverTest.ts rename to packages/roosterjs-content-model-core/test/corePlugin/utils/textMutationObserverTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts b/packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts rename to packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/DarkColorHandlerImplTest.ts b/packages/roosterjs-content-model-core/test/editor/DarkColorHandlerImplTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/editor/DarkColorHandlerImplTest.ts rename to packages/roosterjs-content-model-core/test/editor/DarkColorHandlerImplTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts b/packages/roosterjs-content-model-core/test/editor/EditorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/editor/EditorTest.ts rename to packages/roosterjs-content-model-core/test/editor/EditorTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/SnapshotsManagerImplTest.ts b/packages/roosterjs-content-model-core/test/editor/SnapshotsManagerImplTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/editor/SnapshotsManagerImplTest.ts rename to packages/roosterjs-content-model-core/test/editor/SnapshotsManagerImplTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts b/packages/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts rename to packages/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/editor/createEditorDefaultSettingsTest.ts b/packages/roosterjs-content-model-core/test/editor/createEditorDefaultSettingsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/editor/createEditorDefaultSettingsTest.ts rename to packages/roosterjs-content-model-core/test/editor/createEditorDefaultSettingsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/metadata/handleListItemWithMetadataTest.ts b/packages/roosterjs-content-model-core/test/metadata/handleListItemWithMetadataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/metadata/handleListItemWithMetadataTest.ts rename to packages/roosterjs-content-model-core/test/metadata/handleListItemWithMetadataTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/metadata/handleListWithMetadataTest.ts b/packages/roosterjs-content-model-core/test/metadata/handleListWithMetadataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/metadata/handleListWithMetadataTest.ts rename to packages/roosterjs-content-model-core/test/metadata/handleListWithMetadataTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/metadata/updateImageMetadataTest.ts b/packages/roosterjs-content-model-core/test/metadata/updateImageMetadataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/metadata/updateImageMetadataTest.ts rename to packages/roosterjs-content-model-core/test/metadata/updateImageMetadataTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/metadata/updateListMetadataTest.ts b/packages/roosterjs-content-model-core/test/metadata/updateListMetadataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/metadata/updateListMetadataTest.ts rename to packages/roosterjs-content-model-core/test/metadata/updateListMetadataTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/metadata/updateTableCellMetadataTest.ts b/packages/roosterjs-content-model-core/test/metadata/updateTableCellMetadataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/metadata/updateTableCellMetadataTest.ts rename to packages/roosterjs-content-model-core/test/metadata/updateTableCellMetadataTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/metadata/updateTableMetadataTest.ts b/packages/roosterjs-content-model-core/test/metadata/updateTableMetadataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/metadata/updateTableMetadataTest.ts rename to packages/roosterjs-content-model-core/test/metadata/updateTableMetadataTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/modelApi/edit/deleteSingleCharTest.ts b/packages/roosterjs-content-model-core/test/modelApi/edit/deleteSingleCharTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/modelApi/edit/deleteSingleCharTest.ts rename to packages/roosterjs-content-model-core/test/modelApi/edit/deleteSingleCharTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/overrides/containerSizeFormatParserTest.ts b/packages/roosterjs-content-model-core/test/overrides/containerSizeFormatParserTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/overrides/containerSizeFormatParserTest.ts rename to packages/roosterjs-content-model-core/test/overrides/containerSizeFormatParserTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/overrides/pasteCopyBlockEntityParserTest.ts b/packages/roosterjs-content-model-core/test/overrides/pasteCopyBlockEntityParserTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/overrides/pasteCopyBlockEntityParserTest.ts rename to packages/roosterjs-content-model-core/test/overrides/pasteCopyBlockEntityParserTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/overrides/pasteDisplayFormatParserTest.ts b/packages/roosterjs-content-model-core/test/overrides/pasteDisplayFormatParserTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/overrides/pasteDisplayFormatParserTest.ts rename to packages/roosterjs-content-model-core/test/overrides/pasteDisplayFormatParserTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/overrides/pasteEntityProcessorTest.ts b/packages/roosterjs-content-model-core/test/overrides/pasteEntityProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/overrides/pasteEntityProcessorTest.ts rename to packages/roosterjs-content-model-core/test/overrides/pasteEntityProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/overrides/pasteGeneralProcessorTest.ts b/packages/roosterjs-content-model-core/test/overrides/pasteGeneralProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/overrides/pasteGeneralProcessorTest.ts rename to packages/roosterjs-content-model-core/test/overrides/pasteGeneralProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/overrides/pasteTextProcessorTest.ts b/packages/roosterjs-content-model-core/test/overrides/pasteTextProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/overrides/pasteTextProcessorTest.ts rename to packages/roosterjs-content-model-core/test/overrides/pasteTextProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/overrides/reducedModelChildProcessorTest.ts b/packages/roosterjs-content-model-core/test/overrides/reducedModelChildProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/overrides/reducedModelChildProcessorTest.ts rename to packages/roosterjs-content-model-core/test/overrides/reducedModelChildProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/overrides/tablePreProcessorTest.ts b/packages/roosterjs-content-model-core/test/overrides/tablePreProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/overrides/tablePreProcessorTest.ts rename to packages/roosterjs-content-model-core/test/overrides/tablePreProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/color/transformColorTest.ts b/packages/roosterjs-content-model-core/test/publicApi/color/transformColorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/color/transformColorTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/color/transformColorTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/borderValuesTest.ts b/packages/roosterjs-content-model-core/test/publicApi/domUtils/borderValuesTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/borderValuesTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/domUtils/borderValuesTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/cacheGetEventDataTest.ts b/packages/roosterjs-content-model-core/test/publicApi/domUtils/cacheGetEventDataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/cacheGetEventDataTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/domUtils/cacheGetEventDataTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/getSegmentTextFormatTest.ts b/packages/roosterjs-content-model-core/test/publicApi/domUtils/getSegmentTextFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/getSegmentTextFormatTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/domUtils/getSegmentTextFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/tableCellUtilsTest.ts b/packages/roosterjs-content-model-core/test/publicApi/domUtils/tableCellUtilsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/domUtils/tableCellUtilsTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/domUtils/tableCellUtilsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/format/retrieveModelFormatStateTest.ts b/packages/roosterjs-content-model-core/test/publicApi/format/retrieveModelFormatStateTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/format/retrieveModelFormatStateTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/format/retrieveModelFormatStateTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/cloneModelTest.ts b/packages/roosterjs-content-model-core/test/publicApi/model/cloneModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/model/cloneModelTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/model/cloneModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts b/packages/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/model/createModelFromHtmlTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts b/packages/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/getClosestAncestorBlockGroupIndexTest.ts b/packages/roosterjs-content-model-core/test/publicApi/model/getClosestAncestorBlockGroupIndexTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/model/getClosestAncestorBlockGroupIndexTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/model/getClosestAncestorBlockGroupIndexTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/isBlockGroupOfTypeTest.ts b/packages/roosterjs-content-model-core/test/publicApi/model/isBlockGroupOfTypeTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/model/isBlockGroupOfTypeTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/model/isBlockGroupOfTypeTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts b/packages/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/selection/collectSelectionsTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/collectSelectionsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/selection/collectSelectionsTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/selection/collectSelectionsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/selection/deleteSelectionTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/deleteSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/selection/deleteSelectionTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/selection/deleteSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/selection/getSelectedSegmentsTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/getSelectedSegmentsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/selection/getSelectedSegmentsTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/selection/getSelectedSegmentsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/selection/getSelectionRootNodeTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/getSelectionRootNodeTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/selection/getSelectionRootNodeTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/selection/getSelectionRootNodeTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInBlockTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInBlockTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInBlockTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInBlockTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInSegmentTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInSegmentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInSegmentTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInSegmentTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/selection/iterateSelectionsTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/iterateSelectionsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/selection/iterateSelectionsTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/selection/iterateSelectionsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/selection/setSelectionTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/setSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/selection/setSelectionTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/selection/setSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/table/applyTableFormatTest.ts b/packages/roosterjs-content-model-core/test/publicApi/table/applyTableFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/table/applyTableFormatTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/table/applyTableFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/table/getSelectedCellsTest.ts b/packages/roosterjs-content-model-core/test/publicApi/table/getSelectedCellsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/table/getSelectedCellsTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/table/getSelectedCellsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/table/normalizeTableTest.ts b/packages/roosterjs-content-model-core/test/publicApi/table/normalizeTableTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/table/normalizeTableTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/table/normalizeTableTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/table/setTableCellBackgroundColorTest.ts b/packages/roosterjs-content-model-core/test/publicApi/table/setTableCellBackgroundColorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/table/setTableCellBackgroundColorTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/table/setTableCellBackgroundColorTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/redoTest.ts b/packages/roosterjs-content-model-core/test/publicApi/undo/redoTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/undo/redoTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/undo/redoTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/publicApi/undo/undoTest.ts b/packages/roosterjs-content-model-core/test/publicApi/undo/undoTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/publicApi/undo/undoTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/undo/undoTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/convertInlineCssTest.ts b/packages/roosterjs-content-model-core/test/utils/convertInlineCssTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/convertInlineCssTest.ts rename to packages/roosterjs-content-model-core/test/utils/convertInlineCssTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/createDomToModelContextForSanitizingTest.ts b/packages/roosterjs-content-model-core/test/utils/createDomToModelContextForSanitizingTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/createDomToModelContextForSanitizingTest.ts rename to packages/roosterjs-content-model-core/test/utils/createDomToModelContextForSanitizingTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts b/packages/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts rename to packages/roosterjs-content-model-core/test/utils/createSnapshotSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/extractClipboardItemsTest.ts b/packages/roosterjs-content-model-core/test/utils/extractClipboardItemsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/extractClipboardItemsTest.ts rename to packages/roosterjs-content-model-core/test/utils/extractClipboardItemsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/paste/createPasteFragmentTest.ts b/packages/roosterjs-content-model-core/test/utils/paste/createPasteFragmentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/paste/createPasteFragmentTest.ts rename to packages/roosterjs-content-model-core/test/utils/paste/createPasteFragmentTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts b/packages/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts rename to packages/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts b/packages/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts rename to packages/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/paste/retrieveHtmlInfoTest.ts b/packages/roosterjs-content-model-core/test/utils/paste/retrieveHtmlInfoTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/paste/retrieveHtmlInfoTest.ts rename to packages/roosterjs-content-model-core/test/utils/paste/retrieveHtmlInfoTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts b/packages/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts rename to packages/roosterjs-content-model-core/test/utils/restoreSnapshotColorsTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts b/packages/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts rename to packages/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts b/packages/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts rename to packages/roosterjs-content-model-core/test/utils/restoreSnapshotSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-core/test/utils/sanitizeElementTest.ts b/packages/roosterjs-content-model-core/test/utils/sanitizeElementTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-core/test/utils/sanitizeElementTest.ts rename to packages/roosterjs-content-model-core/test/utils/sanitizeElementTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/config/defaultContentModelFormatMap.ts b/packages/roosterjs-content-model-dom/lib/config/defaultContentModelFormatMap.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/config/defaultContentModelFormatMap.ts rename to packages/roosterjs-content-model-dom/lib/config/defaultContentModelFormatMap.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/config/defaultHTMLStyleMap.ts b/packages/roosterjs-content-model-dom/lib/config/defaultHTMLStyleMap.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/config/defaultHTMLStyleMap.ts rename to packages/roosterjs-content-model-dom/lib/config/defaultHTMLStyleMap.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/context/createDomToModelContext.ts b/packages/roosterjs-content-model-dom/lib/domToModel/context/createDomToModelContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/context/createDomToModelContext.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/context/createDomToModelContext.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/context/defaultProcessors.ts b/packages/roosterjs-content-model-dom/lib/domToModel/context/defaultProcessors.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/context/defaultProcessors.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/context/defaultProcessors.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/domToContentModel.ts b/packages/roosterjs-content-model-dom/lib/domToModel/domToContentModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/domToContentModel.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/domToContentModel.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/blockProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/blockProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/blockProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/blockProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/brProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/brProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/brProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/brProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/childProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/childProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/childProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/childProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/codeProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/codeProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/codeProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/codeProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/delimiterProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/delimiterProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/delimiterProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/delimiterProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/elementProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/elementProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/elementProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/elementProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/entityProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/entityProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/entityProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/entityProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/fontProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/fontProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/fontProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/fontProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/formatContainerProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/formatContainerProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/formatContainerProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/formatContainerProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/generalProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/generalProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/generalProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/generalProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/headingProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/headingProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/headingProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/headingProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/hrProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/hrProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/hrProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/hrProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/imageProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/imageProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/imageProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/imageProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/knownElementProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/knownElementProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/knownElementProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/knownElementProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/linkProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/linkProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/linkProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/linkProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/listItemProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/listItemProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/listItemProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/listItemProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/listProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/listProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/listProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/listProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/pProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/pProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/pProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/pProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/tableProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/tableProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/tableProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/tableProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/textProcessor.ts b/packages/roosterjs-content-model-dom/lib/domToModel/processors/textProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/processors/textProcessor.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/processors/textProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/addSelectionMarker.ts b/packages/roosterjs-content-model-dom/lib/domToModel/utils/addSelectionMarker.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/addSelectionMarker.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/utils/addSelectionMarker.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/areSameFormats.ts b/packages/roosterjs-content-model-dom/lib/domToModel/utils/areSameFormats.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/areSameFormats.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/utils/areSameFormats.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/getBoundingClientRect.ts b/packages/roosterjs-content-model-dom/lib/domToModel/utils/getBoundingClientRect.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/getBoundingClientRect.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/utils/getBoundingClientRect.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/getDefaultStyle.ts b/packages/roosterjs-content-model-dom/lib/domToModel/utils/getDefaultStyle.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/getDefaultStyle.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/utils/getDefaultStyle.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/getRegularSelectionOffsets.ts b/packages/roosterjs-content-model-dom/lib/domToModel/utils/getRegularSelectionOffsets.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/getRegularSelectionOffsets.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/utils/getRegularSelectionOffsets.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/isBlockElement.ts b/packages/roosterjs-content-model-dom/lib/domToModel/utils/isBlockElement.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/isBlockElement.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/utils/isBlockElement.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/parseFormat.ts b/packages/roosterjs-content-model-dom/lib/domToModel/utils/parseFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/parseFormat.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/utils/parseFormat.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/stackFormat.ts b/packages/roosterjs-content-model-dom/lib/domToModel/utils/stackFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domToModel/utils/stackFormat.ts rename to packages/roosterjs-content-model-dom/lib/domToModel/utils/stackFormat.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts b/packages/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/getObjectKeys.ts b/packages/roosterjs-content-model-dom/lib/domUtils/getObjectKeys.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/getObjectKeys.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/getObjectKeys.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/isElementOfType.ts b/packages/roosterjs-content-model-dom/lib/domUtils/isElementOfType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/isElementOfType.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/isElementOfType.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/isNodeOfType.ts b/packages/roosterjs-content-model-dom/lib/domUtils/isNodeOfType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/isNodeOfType.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/isNodeOfType.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/isWhiteSpacePreserved.ts b/packages/roosterjs-content-model-dom/lib/domUtils/isWhiteSpacePreserved.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/isWhiteSpacePreserved.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/isWhiteSpacePreserved.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/metadata/updateMetadata.ts b/packages/roosterjs-content-model-dom/lib/domUtils/metadata/updateMetadata.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/metadata/updateMetadata.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/metadata/updateMetadata.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/metadata/validate.ts b/packages/roosterjs-content-model-dom/lib/domUtils/metadata/validate.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/metadata/validate.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/metadata/validate.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/moveChildNodes.ts b/packages/roosterjs-content-model-dom/lib/domUtils/moveChildNodes.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/moveChildNodes.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/moveChildNodes.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/reuseCachedElement.ts b/packages/roosterjs-content-model-dom/lib/domUtils/reuseCachedElement.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/reuseCachedElement.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/reuseCachedElement.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/toArray.ts b/packages/roosterjs-content-model-dom/lib/domUtils/toArray.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/toArray.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/toArray.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/unwrap.ts b/packages/roosterjs-content-model-dom/lib/domUtils/unwrap.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/unwrap.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/unwrap.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/domUtils/wrap.ts b/packages/roosterjs-content-model-dom/lib/domUtils/wrap.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/domUtils/wrap.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/wrap.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/FormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/FormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/FormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/FormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/directionFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/block/directionFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/directionFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/block/directionFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/displayFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/block/displayFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/displayFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/block/displayFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/htmlAlignFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/block/htmlAlignFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/htmlAlignFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/block/htmlAlignFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/lineHeightFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/block/lineHeightFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/lineHeightFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/block/lineHeightFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/marginFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/block/marginFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/marginFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/block/marginFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/paddingFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/block/paddingFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/paddingFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/block/paddingFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/textAlignFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/block/textAlignFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/textAlignFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/block/textAlignFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/textIndentFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/block/textIndentFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/textIndentFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/block/textIndentFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/whiteSpaceFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/block/whiteSpaceFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/block/whiteSpaceFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/block/whiteSpaceFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/backgroundColorFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/backgroundColorFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/backgroundColorFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/backgroundColorFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/borderBoxFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/borderBoxFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/borderBoxFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/borderBoxFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/borderFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/borderFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/borderFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/borderFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/boxShadowFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/boxShadowFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/boxShadowFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/boxShadowFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/datasetFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/datasetFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/datasetFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/datasetFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/floatFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/floatFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/floatFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/floatFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/idFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/idFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/idFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/idFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/sizeFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/sizeFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/sizeFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/sizeFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/verticalAlignFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/verticalAlignFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/verticalAlignFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/verticalAlignFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/wordBreakFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/wordBreakFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/common/wordBreakFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/common/wordBreakFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/entity/entityFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/entity/entityFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/entity/entityFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/entity/entityFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/list/listItemThreadFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/list/listItemThreadFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/list/listItemThreadFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/list/listItemThreadFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/list/listLevelThreadFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/list/listLevelThreadFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/list/listLevelThreadFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/list/listLevelThreadFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/list/listStyleFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/list/listStyleFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/list/listStyleFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/list/listStyleFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/boldFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/boldFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/boldFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/boldFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/fontFamilyFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/fontFamilyFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/fontFamilyFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/fontFamilyFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/fontSizeFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/fontSizeFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/fontSizeFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/fontSizeFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/italicFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/italicFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/italicFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/italicFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/letterSpacingFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/letterSpacingFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/letterSpacingFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/letterSpacingFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/linkFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/linkFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/linkFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/linkFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/strikeFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/strikeFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/strikeFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/strikeFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/superOrSubScriptFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/superOrSubScriptFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/superOrSubScriptFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/superOrSubScriptFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/textColorFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/textColorFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/textColorFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/textColorFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/underlineFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/segment/underlineFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/segment/underlineFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/segment/underlineFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/table/tableLayoutFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/table/tableLayoutFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/table/tableLayoutFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/table/tableLayoutFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/table/tableSpacingFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/table/tableSpacingFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/table/tableSpacingFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/table/tableSpacingFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/table/textColorOnTableCellFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/table/textColorOnTableCellFormatHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/table/textColorOnTableCellFormatHandler.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/table/textColorOnTableCellFormatHandler.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/utils/color.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/utils/color.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/utils/color.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/utils/color.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/utils/dir.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/utils/dir.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/utils/dir.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/utils/dir.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/utils/parseValueWithUnit.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/utils/parseValueWithUnit.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/utils/parseValueWithUnit.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/utils/parseValueWithUnit.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/utils/shouldSetValue.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/utils/shouldSetValue.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/formatHandlers/utils/shouldSetValue.ts rename to packages/roosterjs-content-model-dom/lib/formatHandlers/utils/shouldSetValue.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/index.ts b/packages/roosterjs-content-model-dom/lib/index.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/index.ts rename to packages/roosterjs-content-model-dom/lib/index.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/block/setParagraphNotImplicit.ts b/packages/roosterjs-content-model-dom/lib/modelApi/block/setParagraphNotImplicit.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/block/setParagraphNotImplicit.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/block/setParagraphNotImplicit.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/addBlock.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/addBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/addBlock.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/addBlock.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/addDecorators.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/addDecorators.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/addDecorators.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/addDecorators.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/addSegment.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/addSegment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/addSegment.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/addSegment.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/ensureParagraph.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/ensureParagraph.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/ensureParagraph.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/ensureParagraph.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnly.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnly.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnly.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnly.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnlyTest.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnlyTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnlyTest.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/hasSpacesOnlyTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/isGeneralSegment.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/isGeneralSegment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/isGeneralSegment.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/isGeneralSegment.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeSegment.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeSegment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/normalizeSegment.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeSegment.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/unwrapBlock.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/unwrapBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/common/unwrapBlock.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/common/unwrapBlock.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createBr.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createBr.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createBr.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createBr.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createContentModelDocument.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createContentModelDocument.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createContentModelDocument.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createContentModelDocument.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createDivider.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createDivider.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createDivider.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createDivider.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createEmptyModel.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createEmptyModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createEmptyModel.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createEmptyModel.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createEntity.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createEntity.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createEntity.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createEntity.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createFormatContainer.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createFormatContainer.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createFormatContainer.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createFormatContainer.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralBlock.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralBlock.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralBlock.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralSegment.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralSegment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralSegment.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createGeneralSegment.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createImage.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createImage.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createImage.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createImage.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createListItem.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createListItem.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createListItem.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createListItem.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createListLevel.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createListLevel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createListLevel.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createListLevel.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createParagraph.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createParagraph.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createParagraph.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createParagraph.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createParagraphDecorator.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createParagraphDecorator.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createParagraphDecorator.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createParagraphDecorator.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createSelectionMarker.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createSelectionMarker.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createSelectionMarker.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createSelectionMarker.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createTable.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createTable.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createTable.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createTableCell.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createTableCell.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createTableCell.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createTableCell.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createText.ts b/packages/roosterjs-content-model-dom/lib/modelApi/creators/createText.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createText.ts rename to packages/roosterjs-content-model-dom/lib/modelApi/creators/createText.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/context/createModelToDomContext.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/context/createModelToDomContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/context/createModelToDomContext.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/context/createModelToDomContext.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/context/defaultContentModelHandlers.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/context/defaultContentModelHandlers.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/context/defaultContentModelHandlers.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/context/defaultContentModelHandlers.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlock.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlock.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlock.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBlockGroupChildren.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBr.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBr.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBr.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleBr.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleDivider.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleDivider.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleDivider.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleDivider.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleEntity.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleEntity.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleEntity.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleEntity.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleFormatContainer.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleFormatContainer.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleFormatContainer.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleFormatContainer.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleGeneralModel.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleGeneralModel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleGeneralModel.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleGeneralModel.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleImage.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleImage.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleImage.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleImage.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleList.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleList.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleList.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleList.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleParagraph.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleParagraph.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleParagraph.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleParagraph.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegment.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegment.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegment.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegmentDecorator.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegmentDecorator.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegmentDecorator.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleSegmentDecorator.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleTable.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleTable.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleTable.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleText.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleText.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/handlers/handleText.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleText.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/optimizers/mergeNode.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/mergeNode.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/optimizers/mergeNode.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/mergeNode.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/optimizers/optimize.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/optimize.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/optimizers/optimize.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/optimize.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/optimizers/removeUnnecessarySpan.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/removeUnnecessarySpan.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/optimizers/removeUnnecessarySpan.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/optimizers/removeUnnecessarySpan.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/utils/applyFormat.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/utils/applyFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/utils/applyFormat.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/utils/applyFormat.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/utils/applyMetadata.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/utils/applyMetadata.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/utils/applyMetadata.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/utils/applyMetadata.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/utils/handleSegmentCommon.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/utils/handleSegmentCommon.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/utils/handleSegmentCommon.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/utils/handleSegmentCommon.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToDom/utils/stackFormat.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/utils/stackFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToDom/utils/stackFormat.ts rename to packages/roosterjs-content-model-dom/lib/modelToDom/utils/stackFormat.ts diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelToText/contentModelToText.ts b/packages/roosterjs-content-model-dom/lib/modelToText/contentModelToText.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/lib/modelToText/contentModelToText.ts rename to packages/roosterjs-content-model-dom/lib/modelToText/contentModelToText.ts diff --git a/packages-content-model/roosterjs-content-model-dom/package.json b/packages/roosterjs-content-model-dom/package.json similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/package.json rename to packages/roosterjs-content-model-dom/package.json diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/context/createDomToModelContextTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/context/createDomToModelContextTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/context/createDomToModelContextTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/context/createDomToModelContextTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/domToContentModelTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/domToContentModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/domToContentModelTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/domToContentModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/blockProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/blockProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/blockProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/blockProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/brProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/brProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/brProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/brProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/childProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/childProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/childProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/childProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/codeProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/codeProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/codeProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/codeProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/delimiterProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/delimiterProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/delimiterProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/delimiterProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/elementProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/elementProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/elementProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/elementProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/entityProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/entityProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/entityProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/entityProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/fontProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/fontProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/fontProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/fontProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/formatContainerProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/formatContainerProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/formatContainerProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/formatContainerProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/generalProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/generalProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/generalProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/generalProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/headingProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/headingProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/headingProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/headingProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/hrProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/hrProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/hrProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/hrProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/imageProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/imageProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/imageProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/imageProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/knownElementProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/knownElementProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/knownElementProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/knownElementProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/linkProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/linkProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/linkProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/linkProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/listItemProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/listItemProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/listItemProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/listItemProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/listProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/listProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/listProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/listProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/pProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/pProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/pProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/pProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/tableProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/tableProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/tableProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/tableProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/textProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/textProcessorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/processors/textProcessorTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/processors/textProcessorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/addSelectionMarkerTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/utils/addSelectionMarkerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/addSelectionMarkerTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/utils/addSelectionMarkerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/areSameFormatsTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/utils/areSameFormatsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/areSameFormatsTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/utils/areSameFormatsTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/getDefaultStyleTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/utils/getDefaultStyleTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/getDefaultStyleTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/utils/getDefaultStyleTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/isBlockElementTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/utils/isBlockElementTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/isBlockElementTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/utils/isBlockElementTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/parseFormatTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/utils/parseFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/parseFormatTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/utils/parseFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/stackFormatTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/utils/stackFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domToModel/utils/stackFormatTest.ts rename to packages/roosterjs-content-model-dom/test/domToModel/utils/stackFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domUtils/entityUtilTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/entityUtilTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domUtils/entityUtilTest.ts rename to packages/roosterjs-content-model-dom/test/domUtils/entityUtilTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domUtils/isWhiteSpacePreservedTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/isWhiteSpacePreservedTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domUtils/isWhiteSpacePreservedTest.ts rename to packages/roosterjs-content-model-dom/test/domUtils/isWhiteSpacePreservedTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domUtils/metadata/updateMetadataTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/metadata/updateMetadataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domUtils/metadata/updateMetadataTest.ts rename to packages/roosterjs-content-model-dom/test/domUtils/metadata/updateMetadataTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domUtils/metadata/validateTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/metadata/validateTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domUtils/metadata/validateTest.ts rename to packages/roosterjs-content-model-dom/test/domUtils/metadata/validateTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domUtils/moveChildNodesTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/moveChildNodesTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domUtils/moveChildNodesTest.ts rename to packages/roosterjs-content-model-dom/test/domUtils/moveChildNodesTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/domUtils/reuseCachedElementTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/reuseCachedElementTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/domUtils/reuseCachedElementTest.ts rename to packages/roosterjs-content-model-dom/test/domUtils/reuseCachedElementTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/endToEndTest.ts b/packages/roosterjs-content-model-dom/test/endToEndTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/endToEndTest.ts rename to packages/roosterjs-content-model-dom/test/endToEndTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/directionFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/block/directionFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/directionFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/block/directionFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/displayFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/block/displayFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/displayFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/block/displayFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/htmlAlignFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/block/htmlAlignFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/htmlAlignFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/block/htmlAlignFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/lineHeightFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/block/lineHeightFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/lineHeightFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/block/lineHeightFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/marginFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/block/marginFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/marginFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/block/marginFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/paddingFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/block/paddingFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/paddingFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/block/paddingFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/textAlignFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/block/textAlignFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/textAlignFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/block/textAlignFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/textIndentFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/block/textIndentFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/textIndentFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/block/textIndentFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/whiteSpaceFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/block/whiteSpaceFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/block/whiteSpaceFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/block/whiteSpaceFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/backgroundColorFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/backgroundColorFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/backgroundColorFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/backgroundColorFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/borderBoxFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/borderBoxFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/borderBoxFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/borderBoxFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/borderFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/borderFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/borderFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/borderFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/boxShadowFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/boxShadowFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/boxShadowFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/boxShadowFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/datasetFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/datasetFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/datasetFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/datasetFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/floatFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/floatFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/floatFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/floatFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/idFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/idFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/idFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/idFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/sizeFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/sizeFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/sizeFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/sizeFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/verticalAlignFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/verticalAlignFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/verticalAlignFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/verticalAlignFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/wordBreakFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/common/wordBreakFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/common/wordBreakFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/common/wordBreakFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/entity/entityFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/entity/entityFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/entity/entityFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/entity/entityFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/list/listItemThreadFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/list/listItemThreadFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/list/listItemThreadFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/list/listItemThreadFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/list/listLevelThreadFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/list/listLevelThreadFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/list/listLevelThreadFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/list/listLevelThreadFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/list/listStyleFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/list/listStyleFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/list/listStyleFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/list/listStyleFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/boldFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/boldFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/boldFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/boldFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/fontFamilyFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/fontFamilyFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/fontFamilyFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/fontFamilyFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/fontSizeFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/fontSizeFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/fontSizeFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/fontSizeFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/italicFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/italicFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/italicFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/italicFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/letterSpacingFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/letterSpacingFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/letterSpacingFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/letterSpacingFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/linkFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/linkFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/linkFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/linkFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/strikeFormatHandleTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/strikeFormatHandleTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/strikeFormatHandleTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/strikeFormatHandleTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/superOrSubScriptFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/superOrSubScriptFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/superOrSubScriptFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/superOrSubScriptFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/textColorFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/textColorFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/textColorFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/textColorFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/underlineFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/segment/underlineFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/segment/underlineFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/segment/underlineFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/table/tableLayoutFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/table/tableLayoutFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/table/tableLayoutFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/table/tableLayoutFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/table/tableSpacingFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/table/tableSpacingFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/table/tableSpacingFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/table/tableSpacingFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/table/textColorOnTableCellFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/table/textColorOnTableCellFormatHandlerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/table/textColorOnTableCellFormatHandlerTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/table/textColorOnTableCellFormatHandlerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/utils/colorTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/utils/colorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/utils/colorTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/utils/colorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/utils/parseValueWithUnitTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/utils/parseValueWithUnitTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/utils/parseValueWithUnitTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/utils/parseValueWithUnitTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/formatHandlers/utils/shouldSetValueTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/utils/shouldSetValueTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/formatHandlers/utils/shouldSetValueTest.ts rename to packages/roosterjs-content-model-dom/test/formatHandlers/utils/shouldSetValueTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/block/setParagraphNotImplicitTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/block/setParagraphNotImplicitTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/block/setParagraphNotImplicitTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/block/setParagraphNotImplicitTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/addBlockTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/addBlockTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/addBlockTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/addBlockTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/addDecoratorsTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/addDecoratorsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/addDecoratorsTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/addDecoratorsTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/addSegmentTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/addSegmentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/addSegmentTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/addSegmentTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/ensureParagraphTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/ensureParagraphTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/ensureParagraphTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/ensureParagraphTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/isEmptyTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/isEmptyTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/isEmptyTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/isEmptyTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/isGeneralSegmentTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/isGeneralSegmentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/isGeneralSegmentTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/isGeneralSegmentTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeContentModelTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeContentModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeContentModelTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/normalizeContentModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeSegmentTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeSegmentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/normalizeSegmentTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/normalizeSegmentTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/common/unwrapBlockTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/unwrapBlockTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/common/unwrapBlockTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/common/unwrapBlockTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/creators/createEmptyModelTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/creators/createEmptyModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/creators/createEmptyModelTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/creators/createEmptyModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/creators/creatorsTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/creators/creatorsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelApi/creators/creatorsTest.ts rename to packages/roosterjs-content-model-dom/test/modelApi/creators/creatorsTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/contentModelToDomTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/contentModelToDomTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/contentModelToDomTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/contentModelToDomTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/context/createModelToDomContextTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/context/createModelToDomContextTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/context/createModelToDomContextTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/context/createModelToDomContextTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockGroupChildrenTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBlockTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleBrTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBrTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleBrTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleBrTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleDividerTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleDividerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleDividerTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleDividerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleEntityTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleEntityTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleEntityTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleEntityTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleFormatContainerTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleFormatContainerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleFormatContainerTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleFormatContainerTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleGeneralModelTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleGeneralModelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleGeneralModelTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleGeneralModelTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleImageTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleImageTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleImageTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleImageTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleListTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleListTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleParagraphTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleParagraphTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleParagraphTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleParagraphTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentDecoratorTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentDecoratorTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentDecoratorTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentDecoratorTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleSegmentTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleTableTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleTableTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleTableTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleTableTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleTextTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleTextTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/handlers/handleTextTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleTextTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/optimizers/mergeNodeTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/optimizers/mergeNodeTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/optimizers/mergeNodeTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/optimizers/mergeNodeTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/optimizers/optimizeTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/optimizers/optimizeTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/optimizers/optimizeTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/optimizers/optimizeTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/optimizers/removeUnnecessarySpanTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/optimizers/removeUnnecessarySpanTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/optimizers/removeUnnecessarySpanTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/optimizers/removeUnnecessarySpanTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/utils/applyMetadataTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/utils/applyMetadataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/utils/applyMetadataTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/utils/applyMetadataTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/utils/handleSegmentCommonTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/utils/handleSegmentCommonTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/utils/handleSegmentCommonTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/utils/handleSegmentCommonTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToDom/utils/stackFormatTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/utils/stackFormatTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToDom/utils/stackFormatTest.ts rename to packages/roosterjs-content-model-dom/test/modelToDom/utils/stackFormatTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelToText/contentModelToTextTest.ts b/packages/roosterjs-content-model-dom/test/modelToText/contentModelToTextTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/modelToText/contentModelToTextTest.ts rename to packages/roosterjs-content-model-dom/test/modelToText/contentModelToTextTest.ts diff --git a/packages-content-model/roosterjs-content-model-dom/test/testUtils.ts b/packages/roosterjs-content-model-dom/test/testUtils.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-dom/test/testUtils.ts rename to packages/roosterjs-content-model-dom/test/testUtils.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/utils/convertAlphaToDecimals.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/convertAlphaToDecimals.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/utils/convertAlphaToDecimals.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/utils/convertAlphaToDecimals.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/utils/getIndex.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getIndex.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/utils/getIndex.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getIndex.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/utils/getNumberingListStyle.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getNumberingListStyle.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/autoFormat/utils/getNumberingListStyle.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getNumberingListStyle.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts b/packages/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts rename to packages/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteAllSegmentBefore.ts b/packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteAllSegmentBefore.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteAllSegmentBefore.ts rename to packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteAllSegmentBefore.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteCollapsedSelection.ts b/packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteCollapsedSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteCollapsedSelection.ts rename to packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteCollapsedSelection.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts b/packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts rename to packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteList.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteWordSelection.ts b/packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteWordSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteWordSelection.ts rename to packages/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteWordSelection.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/handleKeyboardEventCommon.ts b/packages/roosterjs-content-model-plugins/lib/edit/handleKeyboardEventCommon.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/handleKeyboardEventCommon.ts rename to packages/roosterjs-content-model-plugins/lib/edit/handleKeyboardEventCommon.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts b/packages/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts rename to packages/roosterjs-content-model-plugins/lib/edit/inputSteps/handleEnterOnList.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts b/packages/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts rename to packages/roosterjs-content-model-plugins/lib/edit/keyboardDelete.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts b/packages/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts rename to packages/roosterjs-content-model-plugins/lib/edit/keyboardInput.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts b/packages/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts rename to packages/roosterjs-content-model-plugins/lib/edit/keyboardTab.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts b/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts rename to packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts b/packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts rename to packages/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/utils/getLeafSiblingBlock.ts b/packages/roosterjs-content-model-plugins/lib/edit/utils/getLeafSiblingBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/edit/utils/getLeafSiblingBlock.ts rename to packages/roosterjs-content-model-plugins/lib/edit/utils/getLeafSiblingBlock.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/index.ts b/packages/roosterjs-content-model-plugins/lib/index.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/index.ts rename to packages/roosterjs-content-model-plugins/lib/index.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts b/packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts rename to packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts b/packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts rename to packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts b/packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts rename to packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/WacComponents/constants.ts b/packages/roosterjs-content-model-plugins/lib/paste/WacComponents/constants.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/WacComponents/constants.ts rename to packages/roosterjs-content-model-plugins/lib/paste/WacComponents/constants.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts b/packages/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts rename to packages/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/WordMetadata.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/WordMetadata.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/WordMetadata.ts rename to packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/WordMetadata.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts rename to packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts rename to packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordComments.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordComments.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordComments.ts rename to packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordComments.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordLists.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordLists.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordLists.ts rename to packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processWordLists.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/removeNegativeTextIndentParser.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/removeNegativeTextIndentParser.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/WordDesktop/removeNegativeTextIndentParser.ts rename to packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/removeNegativeTextIndentParser.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/constants.ts b/packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/constants.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/constants.ts rename to packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/constants.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/documentContainWacElements.ts b/packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/documentContainWacElements.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/documentContainWacElements.ts rename to packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/documentContainWacElements.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/getPasteSource.ts b/packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/getPasteSource.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/getPasteSource.ts rename to packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/getPasteSource.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelDesktopDocument.ts b/packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelDesktopDocument.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelDesktopDocument.ts rename to packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelDesktopDocument.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelOnlineDocument.ts b/packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelOnlineDocument.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelOnlineDocument.ts rename to packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isExcelOnlineDocument.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isGoogleSheetDocument.ts b/packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isGoogleSheetDocument.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isGoogleSheetDocument.ts rename to packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isGoogleSheetDocument.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isPowerPointDesktopDocument.ts b/packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isPowerPointDesktopDocument.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isPowerPointDesktopDocument.ts rename to packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isPowerPointDesktopDocument.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isWordDesktopDocument.ts b/packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isWordDesktopDocument.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isWordDesktopDocument.ts rename to packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/isWordDesktopDocument.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/shouldConvertToSingleImage.ts b/packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/shouldConvertToSingleImage.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/shouldConvertToSingleImage.ts rename to packages/roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/shouldConvertToSingleImage.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/addParser.ts b/packages/roosterjs-content-model-plugins/lib/paste/utils/addParser.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/addParser.ts rename to packages/roosterjs-content-model-plugins/lib/paste/utils/addParser.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/deprecatedColorParser.ts b/packages/roosterjs-content-model-plugins/lib/paste/utils/deprecatedColorParser.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/deprecatedColorParser.ts rename to packages/roosterjs-content-model-plugins/lib/paste/utils/deprecatedColorParser.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/getStyles.ts b/packages/roosterjs-content-model-plugins/lib/paste/utils/getStyles.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/getStyles.ts rename to packages/roosterjs-content-model-plugins/lib/paste/utils/getStyles.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/linkParser.ts b/packages/roosterjs-content-model-plugins/lib/paste/utils/linkParser.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/linkParser.ts rename to packages/roosterjs-content-model-plugins/lib/paste/utils/linkParser.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/setProcessor.ts b/packages/roosterjs-content-model-plugins/lib/paste/utils/setProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/paste/utils/setProcessor.ts rename to packages/roosterjs-content-model-plugins/lib/paste/utils/setProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/CreateElementData.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/CreateElementData.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/CreateElementData.ts rename to packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/CreateElementData.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/createElement.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/createElement.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/createElement.ts rename to packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/createElement.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/Disposable.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Disposable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/Disposable.ts rename to packages/roosterjs-content-model-plugins/lib/pluginUtils/Disposable.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHandler.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHandler.ts rename to packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHandler.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHelper.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHelper.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHelper.ts rename to packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHelper.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts rename to packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts rename to packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts b/packages/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts rename to packages/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts b/packages/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts rename to packages/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts b/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts rename to packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts rename to packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts rename to packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts rename to packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts rename to packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts rename to packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts rename to packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts rename to packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/package.json b/packages/roosterjs-content-model-plugins/package.json similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/package.json rename to packages/roosterjs-content-model-plugins/package.json diff --git a/packages-content-model/roosterjs-content-model-plugins/test/TestHelper.ts b/packages/roosterjs-content-model-plugins/test/TestHelper.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/TestHelper.ts rename to packages/roosterjs-content-model-plugins/test/TestHelper.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/utils/convertAlphaToDecimalsTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/utils/convertAlphaToDecimalsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/autoFormat/utils/convertAlphaToDecimalsTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/utils/convertAlphaToDecimalsTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/utils/getIndexTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getIndexTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/autoFormat/utils/getIndexTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/utils/getIndexTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/autoFormat/utils/getNumberingListStyleTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getNumberingListStyleTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/autoFormat/utils/getNumberingListStyleTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/utils/getNumberingListStyleTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts b/packages/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/EditPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteCollapsedSelectionTest.ts b/packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteCollapsedSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteCollapsedSelectionTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteCollapsedSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteListTest.ts b/packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteListTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteListTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteListTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteWordSelectionTest.ts b/packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteWordSelectionTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteWordSelectionTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteWordSelectionTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/editingTestCommon.ts b/packages/roosterjs-content-model-plugins/test/edit/editingTestCommon.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/editingTestCommon.ts rename to packages/roosterjs-content-model-plugins/test/edit/editingTestCommon.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/handleKeyboardEventCommonTest.ts b/packages/roosterjs-content-model-plugins/test/edit/handleKeyboardEventCommonTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/handleKeyboardEventCommonTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/handleKeyboardEventCommonTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts b/packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/inputSteps/handleEnterOnListTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts b/packages/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/keyboardDeleteTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts b/packages/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/keyboardInputTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts b/packages/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/keyboardTabTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts b/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnListTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts b/packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/tabUtils/handleTabOnParagraphTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/utils/getLeafSiblingBlockTest.ts b/packages/roosterjs-content-model-plugins/test/edit/utils/getLeafSiblingBlockTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/edit/utils/getLeafSiblingBlockTest.ts rename to packages/roosterjs-content-model-plugins/test/edit/utils/getLeafSiblingBlockTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts b/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/deprecatedColorParserTest.ts b/packages/roosterjs-content-model-plugins/test/paste/deprecatedColorParserTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/deprecatedColorParserTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/deprecatedColorParserTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/testUtils.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/testUtils.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/e2e/testUtils.ts rename to packages/roosterjs-content-model-plugins/test/paste/e2e/testUtils.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts b/packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/linkParserTest.ts b/packages/roosterjs-content-model-plugins/test/paste/linkParserTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/linkParserTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/linkParserTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/documentContainWacElementsTest.ts b/packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/documentContainWacElementsTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/documentContainWacElementsTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/documentContainWacElementsTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/getPasteSourceTest.ts b/packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/getPasteSourceTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/getPasteSourceTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/getPasteSourceTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelDesktopDocumentTest.ts b/packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelDesktopDocumentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelDesktopDocumentTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelDesktopDocumentTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelOnlineDocumentTest.ts b/packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelOnlineDocumentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelOnlineDocumentTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isExcelOnlineDocumentTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isGoogleSheetDocumentTest.ts b/packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isGoogleSheetDocumentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isGoogleSheetDocumentTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isGoogleSheetDocumentTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isPowerPointDesktopDocumentTest.ts b/packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isPowerPointDesktopDocumentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isPowerPointDesktopDocumentTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isPowerPointDesktopDocumentTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isWordDesktopDocumentTest.ts b/packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isWordDesktopDocumentTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isWordDesktopDocumentTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/isWordDesktopDocumentTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/pasteTestUtils.ts b/packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/pasteTestUtils.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/pasteTestUtils.ts rename to packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/pasteTestUtils.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/shouldConvertToSingleImageTest.ts b/packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/shouldConvertToSingleImageTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/shouldConvertToSingleImageTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/pasteSourceValidations/shouldConvertToSingleImageTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromExcelTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromExcelTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromExcelTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromExcelTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromPowerPointTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromPowerPointTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromPowerPointTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromPowerPointTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromWacTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWacTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromWacTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWacTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/paste/utils/getStylesTest.ts b/packages/roosterjs-content-model-plugins/test/paste/utils/getStylesTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/paste/utils/getStylesTest.ts rename to packages/roosterjs-content-model-plugins/test/paste/utils/getStylesTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/pluginUtils/DragAndDropHelperTest.ts b/packages/roosterjs-content-model-plugins/test/pluginUtils/DragAndDropHelperTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/pluginUtils/DragAndDropHelperTest.ts rename to packages/roosterjs-content-model-plugins/test/pluginUtils/DragAndDropHelperTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/pluginUtils/createElementTest.ts b/packages/roosterjs-content-model-plugins/test/pluginUtils/createElementTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/pluginUtils/createElementTest.ts rename to packages/roosterjs-content-model-plugins/test/pluginUtils/createElementTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts rename to packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts rename to packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/cellResizerTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/cellResizerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/tableEdit/cellResizerTest.ts rename to packages/roosterjs-content-model-plugins/test/tableEdit/cellResizerTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableData.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableData.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableData.ts rename to packages/roosterjs-content-model-plugins/test/tableEdit/tableData.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts rename to packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableInserterTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableInserterTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableInserterTest.ts rename to packages/roosterjs-content-model-plugins/test/tableEdit/tableInserterTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts rename to packages/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts diff --git a/packages-content-model/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts rename to packages/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/block/ContentModelBlock.ts b/packages/roosterjs-content-model-types/lib/block/ContentModelBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/block/ContentModelBlock.ts rename to packages/roosterjs-content-model-types/lib/block/ContentModelBlock.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/block/ContentModelBlockBase.ts b/packages/roosterjs-content-model-types/lib/block/ContentModelBlockBase.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/block/ContentModelBlockBase.ts rename to packages/roosterjs-content-model-types/lib/block/ContentModelBlockBase.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/block/ContentModelBlockWithCache.ts b/packages/roosterjs-content-model-types/lib/block/ContentModelBlockWithCache.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/block/ContentModelBlockWithCache.ts rename to packages/roosterjs-content-model-types/lib/block/ContentModelBlockWithCache.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/block/ContentModelDivider.ts b/packages/roosterjs-content-model-types/lib/block/ContentModelDivider.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/block/ContentModelDivider.ts rename to packages/roosterjs-content-model-types/lib/block/ContentModelDivider.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/block/ContentModelParagraph.ts b/packages/roosterjs-content-model-types/lib/block/ContentModelParagraph.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/block/ContentModelParagraph.ts rename to packages/roosterjs-content-model-types/lib/block/ContentModelParagraph.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/block/ContentModelTable.ts b/packages/roosterjs-content-model-types/lib/block/ContentModelTable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/block/ContentModelTable.ts rename to packages/roosterjs-content-model-types/lib/block/ContentModelTable.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/block/ContentModelTableRow.ts b/packages/roosterjs-content-model-types/lib/block/ContentModelTableRow.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/block/ContentModelTableRow.ts rename to packages/roosterjs-content-model-types/lib/block/ContentModelTableRow.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/ContentModelHandler.ts b/packages/roosterjs-content-model-types/lib/context/ContentModelHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/ContentModelHandler.ts rename to packages/roosterjs-content-model-types/lib/context/ContentModelHandler.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/DarkColorHandler.ts b/packages/roosterjs-content-model-types/lib/context/DarkColorHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/DarkColorHandler.ts rename to packages/roosterjs-content-model-types/lib/context/DarkColorHandler.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/DomIndexer.ts b/packages/roosterjs-content-model-types/lib/context/DomIndexer.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/DomIndexer.ts rename to packages/roosterjs-content-model-types/lib/context/DomIndexer.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/DomToModelContext.ts b/packages/roosterjs-content-model-types/lib/context/DomToModelContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/DomToModelContext.ts rename to packages/roosterjs-content-model-types/lib/context/DomToModelContext.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/DomToModelFormatContext.ts b/packages/roosterjs-content-model-types/lib/context/DomToModelFormatContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/DomToModelFormatContext.ts rename to packages/roosterjs-content-model-types/lib/context/DomToModelFormatContext.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/DomToModelOption.ts b/packages/roosterjs-content-model-types/lib/context/DomToModelOption.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/DomToModelOption.ts rename to packages/roosterjs-content-model-types/lib/context/DomToModelOption.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/DomToModelSelectionContext.ts b/packages/roosterjs-content-model-types/lib/context/DomToModelSelectionContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/DomToModelSelectionContext.ts rename to packages/roosterjs-content-model-types/lib/context/DomToModelSelectionContext.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/DomToModelSettings.ts b/packages/roosterjs-content-model-types/lib/context/DomToModelSettings.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/DomToModelSettings.ts rename to packages/roosterjs-content-model-types/lib/context/DomToModelSettings.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/EditorContext.ts b/packages/roosterjs-content-model-types/lib/context/EditorContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/EditorContext.ts rename to packages/roosterjs-content-model-types/lib/context/EditorContext.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/ElementProcessor.ts b/packages/roosterjs-content-model-types/lib/context/ElementProcessor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/ElementProcessor.ts rename to packages/roosterjs-content-model-types/lib/context/ElementProcessor.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomContext.ts b/packages/roosterjs-content-model-types/lib/context/ModelToDomContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomContext.ts rename to packages/roosterjs-content-model-types/lib/context/ModelToDomContext.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomFormatContext.ts b/packages/roosterjs-content-model-types/lib/context/ModelToDomFormatContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomFormatContext.ts rename to packages/roosterjs-content-model-types/lib/context/ModelToDomFormatContext.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomOption.ts b/packages/roosterjs-content-model-types/lib/context/ModelToDomOption.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomOption.ts rename to packages/roosterjs-content-model-types/lib/context/ModelToDomOption.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomSelectionContext.ts b/packages/roosterjs-content-model-types/lib/context/ModelToDomSelectionContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomSelectionContext.ts rename to packages/roosterjs-content-model-types/lib/context/ModelToDomSelectionContext.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomSettings.ts b/packages/roosterjs-content-model-types/lib/context/ModelToDomSettings.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/ModelToDomSettings.ts rename to packages/roosterjs-content-model-types/lib/context/ModelToDomSettings.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/context/TextMutationObserver.ts b/packages/roosterjs-content-model-types/lib/context/TextMutationObserver.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/context/TextMutationObserver.ts rename to packages/roosterjs-content-model-types/lib/context/TextMutationObserver.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelCode.ts b/packages/roosterjs-content-model-types/lib/decorator/ContentModelCode.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelCode.ts rename to packages/roosterjs-content-model-types/lib/decorator/ContentModelCode.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelDecorator.ts b/packages/roosterjs-content-model-types/lib/decorator/ContentModelDecorator.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelDecorator.ts rename to packages/roosterjs-content-model-types/lib/decorator/ContentModelDecorator.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelLink.ts b/packages/roosterjs-content-model-types/lib/decorator/ContentModelLink.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelLink.ts rename to packages/roosterjs-content-model-types/lib/decorator/ContentModelLink.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelListLevel.ts b/packages/roosterjs-content-model-types/lib/decorator/ContentModelListLevel.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelListLevel.ts rename to packages/roosterjs-content-model-types/lib/decorator/ContentModelListLevel.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelParagraphDecorator.ts b/packages/roosterjs-content-model-types/lib/decorator/ContentModelParagraphDecorator.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/decorator/ContentModelParagraphDecorator.ts rename to packages/roosterjs-content-model-types/lib/decorator/ContentModelParagraphDecorator.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/ContextMenuProvider.ts b/packages/roosterjs-content-model-types/lib/editor/ContextMenuProvider.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/editor/ContextMenuProvider.ts rename to packages/roosterjs-content-model-types/lib/editor/ContextMenuProvider.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCore.ts b/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/editor/EditorCore.ts rename to packages/roosterjs-content-model-types/lib/editor/EditorCore.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorCorePlugins.ts b/packages/roosterjs-content-model-types/lib/editor/EditorCorePlugins.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/editor/EditorCorePlugins.ts rename to packages/roosterjs-content-model-types/lib/editor/EditorCorePlugins.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorOptions.ts b/packages/roosterjs-content-model-types/lib/editor/EditorOptions.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/editor/EditorOptions.ts rename to packages/roosterjs-content-model-types/lib/editor/EditorOptions.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/EditorPlugin.ts b/packages/roosterjs-content-model-types/lib/editor/EditorPlugin.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/editor/EditorPlugin.ts rename to packages/roosterjs-content-model-types/lib/editor/EditorPlugin.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/IEditor.ts b/packages/roosterjs-content-model-types/lib/editor/IEditor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/editor/IEditor.ts rename to packages/roosterjs-content-model-types/lib/editor/IEditor.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/editor/PluginWithState.ts b/packages/roosterjs-content-model-types/lib/editor/PluginWithState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/editor/PluginWithState.ts rename to packages/roosterjs-content-model-types/lib/editor/PluginWithState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/entity/ContentModelEntity.ts b/packages/roosterjs-content-model-types/lib/entity/ContentModelEntity.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/entity/ContentModelEntity.ts rename to packages/roosterjs-content-model-types/lib/entity/ContentModelEntity.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/BlockGroupType.ts b/packages/roosterjs-content-model-types/lib/enum/BlockGroupType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/BlockGroupType.ts rename to packages/roosterjs-content-model-types/lib/enum/BlockGroupType.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/BlockType.ts b/packages/roosterjs-content-model-types/lib/enum/BlockType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/BlockType.ts rename to packages/roosterjs-content-model-types/lib/enum/BlockType.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/BorderOperations.ts b/packages/roosterjs-content-model-types/lib/enum/BorderOperations.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/BorderOperations.ts rename to packages/roosterjs-content-model-types/lib/enum/BorderOperations.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/DeleteResult.ts b/packages/roosterjs-content-model-types/lib/enum/DeleteResult.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/DeleteResult.ts rename to packages/roosterjs-content-model-types/lib/enum/DeleteResult.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/EntityOperation.ts b/packages/roosterjs-content-model-types/lib/enum/EntityOperation.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/EntityOperation.ts rename to packages/roosterjs-content-model-types/lib/enum/EntityOperation.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/ExportContentMode.ts b/packages/roosterjs-content-model-types/lib/enum/ExportContentMode.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/ExportContentMode.ts rename to packages/roosterjs-content-model-types/lib/enum/ExportContentMode.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/InsertEntityPosition.ts b/packages/roosterjs-content-model-types/lib/enum/InsertEntityPosition.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/InsertEntityPosition.ts rename to packages/roosterjs-content-model-types/lib/enum/InsertEntityPosition.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/PasteType.ts b/packages/roosterjs-content-model-types/lib/enum/PasteType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/PasteType.ts rename to packages/roosterjs-content-model-types/lib/enum/PasteType.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/SegmentType.ts b/packages/roosterjs-content-model-types/lib/enum/SegmentType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/SegmentType.ts rename to packages/roosterjs-content-model-types/lib/enum/SegmentType.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/enum/TableOperation.ts b/packages/roosterjs-content-model-types/lib/enum/TableOperation.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/enum/TableOperation.ts rename to packages/roosterjs-content-model-types/lib/enum/TableOperation.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/BasePluginEvent.ts b/packages/roosterjs-content-model-types/lib/event/BasePluginEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/BasePluginEvent.ts rename to packages/roosterjs-content-model-types/lib/event/BasePluginEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/BeforeCutCopyEvent.ts b/packages/roosterjs-content-model-types/lib/event/BeforeCutCopyEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/BeforeCutCopyEvent.ts rename to packages/roosterjs-content-model-types/lib/event/BeforeCutCopyEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/BeforeDisposeEvent.ts b/packages/roosterjs-content-model-types/lib/event/BeforeDisposeEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/BeforeDisposeEvent.ts rename to packages/roosterjs-content-model-types/lib/event/BeforeDisposeEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/BeforeKeyboardEditingEvent.ts b/packages/roosterjs-content-model-types/lib/event/BeforeKeyboardEditingEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/BeforeKeyboardEditingEvent.ts rename to packages/roosterjs-content-model-types/lib/event/BeforeKeyboardEditingEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/BeforePasteEvent.ts b/packages/roosterjs-content-model-types/lib/event/BeforePasteEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/BeforePasteEvent.ts rename to packages/roosterjs-content-model-types/lib/event/BeforePasteEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/BeforeSetContentEvent.ts b/packages/roosterjs-content-model-types/lib/event/BeforeSetContentEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/BeforeSetContentEvent.ts rename to packages/roosterjs-content-model-types/lib/event/BeforeSetContentEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/ContentChangedEvent.ts b/packages/roosterjs-content-model-types/lib/event/ContentChangedEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/ContentChangedEvent.ts rename to packages/roosterjs-content-model-types/lib/event/ContentChangedEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/ContextMenuEvent.ts b/packages/roosterjs-content-model-types/lib/event/ContextMenuEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/ContextMenuEvent.ts rename to packages/roosterjs-content-model-types/lib/event/ContextMenuEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/EditImageEvent.ts b/packages/roosterjs-content-model-types/lib/event/EditImageEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/EditImageEvent.ts rename to packages/roosterjs-content-model-types/lib/event/EditImageEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/EditorInputEvent.ts b/packages/roosterjs-content-model-types/lib/event/EditorInputEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/EditorInputEvent.ts rename to packages/roosterjs-content-model-types/lib/event/EditorInputEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/EditorReadyEvent.ts b/packages/roosterjs-content-model-types/lib/event/EditorReadyEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/EditorReadyEvent.ts rename to packages/roosterjs-content-model-types/lib/event/EditorReadyEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/EntityOperationEvent.ts b/packages/roosterjs-content-model-types/lib/event/EntityOperationEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/EntityOperationEvent.ts rename to packages/roosterjs-content-model-types/lib/event/EntityOperationEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/ExtractContentWithDomEvent.ts b/packages/roosterjs-content-model-types/lib/event/ExtractContentWithDomEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/ExtractContentWithDomEvent.ts rename to packages/roosterjs-content-model-types/lib/event/ExtractContentWithDomEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/KeyboardEvent.ts b/packages/roosterjs-content-model-types/lib/event/KeyboardEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/KeyboardEvent.ts rename to packages/roosterjs-content-model-types/lib/event/KeyboardEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/MouseEvent.ts b/packages/roosterjs-content-model-types/lib/event/MouseEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/MouseEvent.ts rename to packages/roosterjs-content-model-types/lib/event/MouseEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/PluginEvent.ts b/packages/roosterjs-content-model-types/lib/event/PluginEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/PluginEvent.ts rename to packages/roosterjs-content-model-types/lib/event/PluginEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/PluginEventData.ts b/packages/roosterjs-content-model-types/lib/event/PluginEventData.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/PluginEventData.ts rename to packages/roosterjs-content-model-types/lib/event/PluginEventData.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/PluginEventType.ts b/packages/roosterjs-content-model-types/lib/event/PluginEventType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/PluginEventType.ts rename to packages/roosterjs-content-model-types/lib/event/PluginEventType.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/ScrollEvent.ts b/packages/roosterjs-content-model-types/lib/event/ScrollEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/ScrollEvent.ts rename to packages/roosterjs-content-model-types/lib/event/ScrollEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/SelectionChangedEvent.ts b/packages/roosterjs-content-model-types/lib/event/SelectionChangedEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/SelectionChangedEvent.ts rename to packages/roosterjs-content-model-types/lib/event/SelectionChangedEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/ShadowEditEvent.ts b/packages/roosterjs-content-model-types/lib/event/ShadowEditEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/ShadowEditEvent.ts rename to packages/roosterjs-content-model-types/lib/event/ShadowEditEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/event/ZoomChangedEvent.ts b/packages/roosterjs-content-model-types/lib/event/ZoomChangedEvent.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/event/ZoomChangedEvent.ts rename to packages/roosterjs-content-model-types/lib/event/ZoomChangedEvent.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelBlockFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelBlockFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelBlockFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelBlockFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelCodeFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelCodeFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelCodeFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelCodeFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelDividerFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelDividerFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelDividerFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelDividerFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelEntityFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelEntityFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelEntityFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelEntityFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelFormatBase.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelFormatBase.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelFormatBase.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelFormatBase.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelFormatContainerFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelFormatContainerFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelFormatContainerFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelFormatContainerFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelFormatMap.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelFormatMap.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelFormatMap.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelFormatMap.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelHyperLinkFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelHyperLinkFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelHyperLinkFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelHyperLinkFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelImageFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelImageFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelImageFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelImageFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelListItemFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelListItemFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelListItemFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelListItemFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelListItemLevelFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelListItemLevelFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelListItemLevelFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelListItemLevelFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelSegmentFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelSegmentFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelSegmentFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelSegmentFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelTableCellFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelTableCellFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelTableCellFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelTableCellFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelTableFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelTableFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelTableFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelTableFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelWithDataset.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelWithDataset.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelWithDataset.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelWithDataset.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/ContentModelWithFormat.ts b/packages/roosterjs-content-model-types/lib/format/ContentModelWithFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/ContentModelWithFormat.ts rename to packages/roosterjs-content-model-types/lib/format/ContentModelWithFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/FormatHandlerTypeMap.ts b/packages/roosterjs-content-model-types/lib/format/FormatHandlerTypeMap.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/FormatHandlerTypeMap.ts rename to packages/roosterjs-content-model-types/lib/format/FormatHandlerTypeMap.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BackgroundColorFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/BackgroundColorFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BackgroundColorFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/BackgroundColorFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BoldFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/BoldFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BoldFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/BoldFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BorderBoxFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/BorderBoxFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BorderBoxFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/BorderBoxFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BorderFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/BorderFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BorderFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/BorderFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BoxShadowFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/BoxShadowFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/BoxShadowFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/BoxShadowFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/DirectionFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/DirectionFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/DirectionFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/DirectionFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/DisplayFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/DisplayFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/DisplayFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/DisplayFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/EntityInfoFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/EntityInfoFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/EntityInfoFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/EntityInfoFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/FloatFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/FloatFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/FloatFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/FloatFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/FontFamilyFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/FontFamilyFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/FontFamilyFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/FontFamilyFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/FontSizeFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/FontSizeFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/FontSizeFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/FontSizeFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/HtmlAlignFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/HtmlAlignFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/HtmlAlignFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/HtmlAlignFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/IdFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/IdFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/IdFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/IdFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/ItalicFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/ItalicFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/ItalicFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/ItalicFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/LetterSpacingFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/LetterSpacingFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/LetterSpacingFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/LetterSpacingFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/LineHeightFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/LineHeightFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/LineHeightFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/LineHeightFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/LinkFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/LinkFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/LinkFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/LinkFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/ListStyleFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/ListStyleFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/ListStyleFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/ListStyleFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/ListThreadFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/ListThreadFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/ListThreadFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/ListThreadFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/MarginFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/MarginFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/MarginFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/MarginFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/PaddingFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/PaddingFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/PaddingFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/PaddingFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/SizeFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/SizeFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/SizeFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/SizeFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/SpacingFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/SpacingFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/SpacingFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/SpacingFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/StrikeFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/StrikeFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/StrikeFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/StrikeFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/SuperOrSubScriptFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/SuperOrSubScriptFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/SuperOrSubScriptFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/SuperOrSubScriptFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/TableLayoutFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/TableLayoutFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/TableLayoutFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/TableLayoutFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/TextAlignFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/TextAlignFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/TextAlignFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/TextAlignFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/TextColorFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/TextColorFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/TextColorFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/TextColorFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/TextIndentFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/TextIndentFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/TextIndentFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/TextIndentFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/UnderlineFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/UnderlineFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/UnderlineFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/UnderlineFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/VerticalAlignFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/VerticalAlignFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/VerticalAlignFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/VerticalAlignFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/WhiteSpaceFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/WhiteSpaceFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/WhiteSpaceFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/WhiteSpaceFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/formatParts/WordBreakFormat.ts b/packages/roosterjs-content-model-types/lib/format/formatParts/WordBreakFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/formatParts/WordBreakFormat.ts rename to packages/roosterjs-content-model-types/lib/format/formatParts/WordBreakFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/metadata/DatasetFormat.ts b/packages/roosterjs-content-model-types/lib/format/metadata/DatasetFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/metadata/DatasetFormat.ts rename to packages/roosterjs-content-model-types/lib/format/metadata/DatasetFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/metadata/ImageMetadataFormat.ts b/packages/roosterjs-content-model-types/lib/format/metadata/ImageMetadataFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/metadata/ImageMetadataFormat.ts rename to packages/roosterjs-content-model-types/lib/format/metadata/ImageMetadataFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/metadata/ListMetadataFormat.ts b/packages/roosterjs-content-model-types/lib/format/metadata/ListMetadataFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/metadata/ListMetadataFormat.ts rename to packages/roosterjs-content-model-types/lib/format/metadata/ListMetadataFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/metadata/TableCellMetadataFormat.ts b/packages/roosterjs-content-model-types/lib/format/metadata/TableCellMetadataFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/metadata/TableCellMetadataFormat.ts rename to packages/roosterjs-content-model-types/lib/format/metadata/TableCellMetadataFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/format/metadata/TableMetadataFormat.ts b/packages/roosterjs-content-model-types/lib/format/metadata/TableMetadataFormat.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/format/metadata/TableMetadataFormat.ts rename to packages/roosterjs-content-model-types/lib/format/metadata/TableMetadataFormat.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/group/ContentModelBlockGroup.ts b/packages/roosterjs-content-model-types/lib/group/ContentModelBlockGroup.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/group/ContentModelBlockGroup.ts rename to packages/roosterjs-content-model-types/lib/group/ContentModelBlockGroup.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/group/ContentModelBlockGroupBase.ts b/packages/roosterjs-content-model-types/lib/group/ContentModelBlockGroupBase.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/group/ContentModelBlockGroupBase.ts rename to packages/roosterjs-content-model-types/lib/group/ContentModelBlockGroupBase.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/group/ContentModelDocument.ts b/packages/roosterjs-content-model-types/lib/group/ContentModelDocument.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/group/ContentModelDocument.ts rename to packages/roosterjs-content-model-types/lib/group/ContentModelDocument.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/group/ContentModelFormatContainer.ts b/packages/roosterjs-content-model-types/lib/group/ContentModelFormatContainer.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/group/ContentModelFormatContainer.ts rename to packages/roosterjs-content-model-types/lib/group/ContentModelFormatContainer.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/group/ContentModelGeneralBlock.ts b/packages/roosterjs-content-model-types/lib/group/ContentModelGeneralBlock.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/group/ContentModelGeneralBlock.ts rename to packages/roosterjs-content-model-types/lib/group/ContentModelGeneralBlock.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/group/ContentModelListItem.ts b/packages/roosterjs-content-model-types/lib/group/ContentModelListItem.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/group/ContentModelListItem.ts rename to packages/roosterjs-content-model-types/lib/group/ContentModelListItem.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/group/ContentModelTableCell.ts b/packages/roosterjs-content-model-types/lib/group/ContentModelTableCell.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/group/ContentModelTableCell.ts rename to packages/roosterjs-content-model-types/lib/group/ContentModelTableCell.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/index.ts b/packages/roosterjs-content-model-types/lib/index.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/index.ts rename to packages/roosterjs-content-model-types/lib/index.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/metadata/Definition.ts b/packages/roosterjs-content-model-types/lib/metadata/Definition.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/metadata/Definition.ts rename to packages/roosterjs-content-model-types/lib/metadata/Definition.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/metadata/DefinitionType.ts b/packages/roosterjs-content-model-types/lib/metadata/DefinitionType.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/metadata/DefinitionType.ts rename to packages/roosterjs-content-model-types/lib/metadata/DefinitionType.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/AnnounceData.ts b/packages/roosterjs-content-model-types/lib/parameter/AnnounceData.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/AnnounceData.ts rename to packages/roosterjs-content-model-types/lib/parameter/AnnounceData.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/Border.ts b/packages/roosterjs-content-model-types/lib/parameter/Border.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/Border.ts rename to packages/roosterjs-content-model-types/lib/parameter/Border.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/ClipboardData.ts b/packages/roosterjs-content-model-types/lib/parameter/ClipboardData.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/ClipboardData.ts rename to packages/roosterjs-content-model-types/lib/parameter/ClipboardData.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/ContentModelFormatState.ts b/packages/roosterjs-content-model-types/lib/parameter/ContentModelFormatState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/ContentModelFormatState.ts rename to packages/roosterjs-content-model-types/lib/parameter/ContentModelFormatState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/DOMEventRecord.ts b/packages/roosterjs-content-model-types/lib/parameter/DOMEventRecord.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/DOMEventRecord.ts rename to packages/roosterjs-content-model-types/lib/parameter/DOMEventRecord.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/DOMHelper.ts b/packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/DOMHelper.ts rename to packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/DeleteSelectionStep.ts b/packages/roosterjs-content-model-types/lib/parameter/DeleteSelectionStep.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/DeleteSelectionStep.ts rename to packages/roosterjs-content-model-types/lib/parameter/DeleteSelectionStep.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/EdgeLinkPreview.ts b/packages/roosterjs-content-model-types/lib/parameter/EdgeLinkPreview.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/EdgeLinkPreview.ts rename to packages/roosterjs-content-model-types/lib/parameter/EdgeLinkPreview.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/EditorEnvironment.ts b/packages/roosterjs-content-model-types/lib/parameter/EditorEnvironment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/EditorEnvironment.ts rename to packages/roosterjs-content-model-types/lib/parameter/EditorEnvironment.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/FormatContentModelContext.ts b/packages/roosterjs-content-model-types/lib/parameter/FormatContentModelContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/FormatContentModelContext.ts rename to packages/roosterjs-content-model-types/lib/parameter/FormatContentModelContext.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/FormatContentModelOptions.ts b/packages/roosterjs-content-model-types/lib/parameter/FormatContentModelOptions.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/FormatContentModelOptions.ts rename to packages/roosterjs-content-model-types/lib/parameter/FormatContentModelOptions.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/ImageFormatState.ts b/packages/roosterjs-content-model-types/lib/parameter/ImageFormatState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/ImageFormatState.ts rename to packages/roosterjs-content-model-types/lib/parameter/ImageFormatState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/InsertEntityOptions.ts b/packages/roosterjs-content-model-types/lib/parameter/InsertEntityOptions.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/InsertEntityOptions.ts rename to packages/roosterjs-content-model-types/lib/parameter/InsertEntityOptions.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/Rect.ts b/packages/roosterjs-content-model-types/lib/parameter/Rect.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/Rect.ts rename to packages/roosterjs-content-model-types/lib/parameter/Rect.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/Snapshot.ts b/packages/roosterjs-content-model-types/lib/parameter/Snapshot.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/Snapshot.ts rename to packages/roosterjs-content-model-types/lib/parameter/Snapshot.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/SnapshotsManager.ts b/packages/roosterjs-content-model-types/lib/parameter/SnapshotsManager.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/SnapshotsManager.ts rename to packages/roosterjs-content-model-types/lib/parameter/SnapshotsManager.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/TrustedHTMLHandler.ts b/packages/roosterjs-content-model-types/lib/parameter/TrustedHTMLHandler.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/TrustedHTMLHandler.ts rename to packages/roosterjs-content-model-types/lib/parameter/TrustedHTMLHandler.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/parameter/ValueSanitizer.ts b/packages/roosterjs-content-model-types/lib/parameter/ValueSanitizer.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/parameter/ValueSanitizer.ts rename to packages/roosterjs-content-model-types/lib/parameter/ValueSanitizer.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/CachePluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/CachePluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/CachePluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/CachePluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/ContextMenuPluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/ContextMenuPluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/ContextMenuPluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/ContextMenuPluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/CopyPastePluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/CopyPastePluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/CopyPastePluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/CopyPastePluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/DOMEventPluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/DOMEventPluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/DOMEventPluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/DOMEventPluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/EntityPluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/EntityPluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/EntityPluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/EntityPluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/FormatPluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/FormatPluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/FormatPluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/FormatPluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/LifecyclePluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/LifecyclePluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/LifecyclePluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/LifecyclePluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/PluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/PluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/PluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/PluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/SelectionPluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/SelectionPluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/SelectionPluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/SelectionPluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/pluginState/UndoPluginState.ts b/packages/roosterjs-content-model-types/lib/pluginState/UndoPluginState.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/pluginState/UndoPluginState.ts rename to packages/roosterjs-content-model-types/lib/pluginState/UndoPluginState.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelBr.ts b/packages/roosterjs-content-model-types/lib/segment/ContentModelBr.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelBr.ts rename to packages/roosterjs-content-model-types/lib/segment/ContentModelBr.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelGeneralSegment.ts b/packages/roosterjs-content-model-types/lib/segment/ContentModelGeneralSegment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelGeneralSegment.ts rename to packages/roosterjs-content-model-types/lib/segment/ContentModelGeneralSegment.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelImage.ts b/packages/roosterjs-content-model-types/lib/segment/ContentModelImage.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelImage.ts rename to packages/roosterjs-content-model-types/lib/segment/ContentModelImage.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelSegment.ts b/packages/roosterjs-content-model-types/lib/segment/ContentModelSegment.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelSegment.ts rename to packages/roosterjs-content-model-types/lib/segment/ContentModelSegment.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelSegmentBase.ts b/packages/roosterjs-content-model-types/lib/segment/ContentModelSegmentBase.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelSegmentBase.ts rename to packages/roosterjs-content-model-types/lib/segment/ContentModelSegmentBase.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelSelectionMarker.ts b/packages/roosterjs-content-model-types/lib/segment/ContentModelSelectionMarker.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelSelectionMarker.ts rename to packages/roosterjs-content-model-types/lib/segment/ContentModelSelectionMarker.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelText.ts b/packages/roosterjs-content-model-types/lib/segment/ContentModelText.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/segment/ContentModelText.ts rename to packages/roosterjs-content-model-types/lib/segment/ContentModelText.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/selection/DOMSelection.ts b/packages/roosterjs-content-model-types/lib/selection/DOMSelection.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/selection/DOMSelection.ts rename to packages/roosterjs-content-model-types/lib/selection/DOMSelection.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/selection/InsertPoint.ts b/packages/roosterjs-content-model-types/lib/selection/InsertPoint.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/selection/InsertPoint.ts rename to packages/roosterjs-content-model-types/lib/selection/InsertPoint.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/selection/Selectable.ts b/packages/roosterjs-content-model-types/lib/selection/Selectable.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/selection/Selectable.ts rename to packages/roosterjs-content-model-types/lib/selection/Selectable.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/selection/TableSelectionContext.ts b/packages/roosterjs-content-model-types/lib/selection/TableSelectionContext.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/selection/TableSelectionContext.ts rename to packages/roosterjs-content-model-types/lib/selection/TableSelectionContext.ts diff --git a/packages-content-model/roosterjs-content-model-types/lib/selection/TableSelectionCoordinates.ts b/packages/roosterjs-content-model-types/lib/selection/TableSelectionCoordinates.ts similarity index 100% rename from packages-content-model/roosterjs-content-model-types/lib/selection/TableSelectionCoordinates.ts rename to packages/roosterjs-content-model-types/lib/selection/TableSelectionCoordinates.ts diff --git a/packages-content-model/roosterjs-content-model-types/package.json b/packages/roosterjs-content-model-types/package.json similarity index 100% rename from packages-content-model/roosterjs-content-model-types/package.json rename to packages/roosterjs-content-model-types/package.json diff --git a/packages-content-model/roosterjs-content-model/lib/createEditor.ts b/packages/roosterjs-content-model/lib/createEditor.ts similarity index 100% rename from packages-content-model/roosterjs-content-model/lib/createEditor.ts rename to packages/roosterjs-content-model/lib/createEditor.ts diff --git a/packages-content-model/roosterjs-content-model/lib/index.ts b/packages/roosterjs-content-model/lib/index.ts similarity index 100% rename from packages-content-model/roosterjs-content-model/lib/index.ts rename to packages/roosterjs-content-model/lib/index.ts diff --git a/packages-content-model/roosterjs-content-model/package.json b/packages/roosterjs-content-model/package.json similarity index 100% rename from packages-content-model/roosterjs-content-model/package.json rename to packages/roosterjs-content-model/package.json diff --git a/packages-ui/roosterjs-react/lib/colorPicker/component/renderColorPicker.tsx b/packages/roosterjs-react/lib/colorPicker/component/renderColorPicker.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/colorPicker/component/renderColorPicker.tsx rename to packages/roosterjs-react/lib/colorPicker/component/renderColorPicker.tsx diff --git a/packages-ui/roosterjs-react/lib/colorPicker/index.ts b/packages/roosterjs-react/lib/colorPicker/index.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/colorPicker/index.ts rename to packages/roosterjs-react/lib/colorPicker/index.ts diff --git a/packages-ui/roosterjs-react/lib/colorPicker/types/stringKeys.ts b/packages/roosterjs-react/lib/colorPicker/types/stringKeys.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/colorPicker/types/stringKeys.ts rename to packages/roosterjs-react/lib/colorPicker/types/stringKeys.ts diff --git a/packages-ui/roosterjs-react/lib/colorPicker/utils/backgroundColors.ts b/packages/roosterjs-react/lib/colorPicker/utils/backgroundColors.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/colorPicker/utils/backgroundColors.ts rename to packages/roosterjs-react/lib/colorPicker/utils/backgroundColors.ts diff --git a/packages-ui/roosterjs-react/lib/colorPicker/utils/getClassNamesForColorPicker.ts b/packages/roosterjs-react/lib/colorPicker/utils/getClassNamesForColorPicker.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/colorPicker/utils/getClassNamesForColorPicker.ts rename to packages/roosterjs-react/lib/colorPicker/utils/getClassNamesForColorPicker.ts diff --git a/packages-ui/roosterjs-react/lib/colorPicker/utils/textColors.ts b/packages/roosterjs-react/lib/colorPicker/utils/textColors.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/colorPicker/utils/textColors.ts rename to packages/roosterjs-react/lib/colorPicker/utils/textColors.ts diff --git a/packages-ui/roosterjs-react/lib/common/index.ts b/packages/roosterjs-react/lib/common/index.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/common/index.ts rename to packages/roosterjs-react/lib/common/index.ts diff --git a/packages-ui/roosterjs-react/lib/common/type/LocalizedStrings.ts b/packages/roosterjs-react/lib/common/type/LocalizedStrings.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/common/type/LocalizedStrings.ts rename to packages/roosterjs-react/lib/common/type/LocalizedStrings.ts diff --git a/packages-ui/roosterjs-react/lib/common/type/ReactEditorPlugin.ts b/packages/roosterjs-react/lib/common/type/ReactEditorPlugin.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/common/type/ReactEditorPlugin.ts rename to packages/roosterjs-react/lib/common/type/ReactEditorPlugin.ts diff --git a/packages-ui/roosterjs-react/lib/common/type/RibbonPluginOptions.ts b/packages/roosterjs-react/lib/common/type/RibbonPluginOptions.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/common/type/RibbonPluginOptions.ts rename to packages/roosterjs-react/lib/common/type/RibbonPluginOptions.ts diff --git a/packages-ui/roosterjs-react/lib/common/type/UIUtilities.ts b/packages/roosterjs-react/lib/common/type/UIUtilities.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/common/type/UIUtilities.ts rename to packages/roosterjs-react/lib/common/type/UIUtilities.ts diff --git a/packages-ui/roosterjs-react/lib/common/utils/createUIUtilities.tsx b/packages/roosterjs-react/lib/common/utils/createUIUtilities.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/common/utils/createUIUtilities.tsx rename to packages/roosterjs-react/lib/common/utils/createUIUtilities.tsx diff --git a/packages-ui/roosterjs-react/lib/common/utils/getLocalizedString.ts b/packages/roosterjs-react/lib/common/utils/getLocalizedString.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/common/utils/getLocalizedString.ts rename to packages/roosterjs-react/lib/common/utils/getLocalizedString.ts diff --git a/packages-ui/roosterjs-react/lib/common/utils/renderReactComponent.ts b/packages/roosterjs-react/lib/common/utils/renderReactComponent.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/common/utils/renderReactComponent.ts rename to packages/roosterjs-react/lib/common/utils/renderReactComponent.ts diff --git a/packages-ui/roosterjs-react/lib/contextMenu/index.ts b/packages/roosterjs-react/lib/contextMenu/index.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/contextMenu/index.ts rename to packages/roosterjs-react/lib/contextMenu/index.ts diff --git a/packages-ui/roosterjs-react/lib/contextMenu/menus/createImageEditMenuProvider.tsx b/packages/roosterjs-react/lib/contextMenu/menus/createImageEditMenuProvider.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/contextMenu/menus/createImageEditMenuProvider.tsx rename to packages/roosterjs-react/lib/contextMenu/menus/createImageEditMenuProvider.tsx diff --git a/packages-ui/roosterjs-react/lib/contextMenu/menus/createListEditMenuProvider.ts b/packages/roosterjs-react/lib/contextMenu/menus/createListEditMenuProvider.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/contextMenu/menus/createListEditMenuProvider.ts rename to packages/roosterjs-react/lib/contextMenu/menus/createListEditMenuProvider.ts diff --git a/packages-ui/roosterjs-react/lib/contextMenu/menus/createTableEditMenuProvider.ts b/packages/roosterjs-react/lib/contextMenu/menus/createTableEditMenuProvider.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/contextMenu/menus/createTableEditMenuProvider.ts rename to packages/roosterjs-react/lib/contextMenu/menus/createTableEditMenuProvider.ts diff --git a/packages-ui/roosterjs-react/lib/contextMenu/plugin/createContextMenuPlugin.tsx b/packages/roosterjs-react/lib/contextMenu/plugin/createContextMenuPlugin.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/contextMenu/plugin/createContextMenuPlugin.tsx rename to packages/roosterjs-react/lib/contextMenu/plugin/createContextMenuPlugin.tsx diff --git a/packages-ui/roosterjs-react/lib/contextMenu/types/ContextMenuItem.ts b/packages/roosterjs-react/lib/contextMenu/types/ContextMenuItem.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/contextMenu/types/ContextMenuItem.ts rename to packages/roosterjs-react/lib/contextMenu/types/ContextMenuItem.ts diff --git a/packages-ui/roosterjs-react/lib/contextMenu/types/ContextMenuItemStringKeys.ts b/packages/roosterjs-react/lib/contextMenu/types/ContextMenuItemStringKeys.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/contextMenu/types/ContextMenuItemStringKeys.ts rename to packages/roosterjs-react/lib/contextMenu/types/ContextMenuItemStringKeys.ts diff --git a/packages-ui/roosterjs-react/lib/contextMenu/utils/createContextMenuProvider.ts b/packages/roosterjs-react/lib/contextMenu/utils/createContextMenuProvider.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/contextMenu/utils/createContextMenuProvider.ts rename to packages/roosterjs-react/lib/contextMenu/utils/createContextMenuProvider.ts diff --git a/packages-ui/roosterjs-react/lib/emoji/components/EmojiIcon.tsx b/packages/roosterjs-react/lib/emoji/components/EmojiIcon.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/components/EmojiIcon.tsx rename to packages/roosterjs-react/lib/emoji/components/EmojiIcon.tsx diff --git a/packages-ui/roosterjs-react/lib/emoji/components/EmojiNavBar.tsx b/packages/roosterjs-react/lib/emoji/components/EmojiNavBar.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/components/EmojiNavBar.tsx rename to packages/roosterjs-react/lib/emoji/components/EmojiNavBar.tsx diff --git a/packages-ui/roosterjs-react/lib/emoji/components/EmojiPane.tsx b/packages/roosterjs-react/lib/emoji/components/EmojiPane.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/components/EmojiPane.tsx rename to packages/roosterjs-react/lib/emoji/components/EmojiPane.tsx diff --git a/packages-ui/roosterjs-react/lib/emoji/components/EmojiStatusBar.tsx b/packages/roosterjs-react/lib/emoji/components/EmojiStatusBar.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/components/EmojiStatusBar.tsx rename to packages/roosterjs-react/lib/emoji/components/EmojiStatusBar.tsx diff --git a/packages-ui/roosterjs-react/lib/emoji/components/showEmojiCallout.tsx b/packages/roosterjs-react/lib/emoji/components/showEmojiCallout.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/components/showEmojiCallout.tsx rename to packages/roosterjs-react/lib/emoji/components/showEmojiCallout.tsx diff --git a/packages-ui/roosterjs-react/lib/emoji/index.ts b/packages/roosterjs-react/lib/emoji/index.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/index.ts rename to packages/roosterjs-react/lib/emoji/index.ts diff --git a/packages-ui/roosterjs-react/lib/emoji/plugin/createEmojiPlugin.ts b/packages/roosterjs-react/lib/emoji/plugin/createEmojiPlugin.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/plugin/createEmojiPlugin.ts rename to packages/roosterjs-react/lib/emoji/plugin/createEmojiPlugin.ts diff --git a/packages-ui/roosterjs-react/lib/emoji/type/Emoji.ts b/packages/roosterjs-react/lib/emoji/type/Emoji.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/type/Emoji.ts rename to packages/roosterjs-react/lib/emoji/type/Emoji.ts diff --git a/packages-ui/roosterjs-react/lib/emoji/type/EmojiPaneStyles.ts b/packages/roosterjs-react/lib/emoji/type/EmojiPaneStyles.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/type/EmojiPaneStyles.ts rename to packages/roosterjs-react/lib/emoji/type/EmojiPaneStyles.ts diff --git a/packages-ui/roosterjs-react/lib/emoji/type/EmojiStringKeys.ts b/packages/roosterjs-react/lib/emoji/type/EmojiStringKeys.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/type/EmojiStringKeys.ts rename to packages/roosterjs-react/lib/emoji/type/EmojiStringKeys.ts diff --git a/packages-ui/roosterjs-react/lib/emoji/type/EmojiStrings.ts b/packages/roosterjs-react/lib/emoji/type/EmojiStrings.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/type/EmojiStrings.ts rename to packages/roosterjs-react/lib/emoji/type/EmojiStrings.ts diff --git a/packages-ui/roosterjs-react/lib/emoji/utils/emojiList.ts b/packages/roosterjs-react/lib/emoji/utils/emojiList.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/utils/emojiList.ts rename to packages/roosterjs-react/lib/emoji/utils/emojiList.ts diff --git a/packages-ui/roosterjs-react/lib/emoji/utils/searchEmojis.ts b/packages/roosterjs-react/lib/emoji/utils/searchEmojis.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/emoji/utils/searchEmojis.ts rename to packages/roosterjs-react/lib/emoji/utils/searchEmojis.ts diff --git a/packages-ui/roosterjs-react/lib/index.ts b/packages/roosterjs-react/lib/index.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/index.ts rename to packages/roosterjs-react/lib/index.ts diff --git a/packages-ui/roosterjs-react/lib/inputDialog/component/InputDialog.tsx b/packages/roosterjs-react/lib/inputDialog/component/InputDialog.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/inputDialog/component/InputDialog.tsx rename to packages/roosterjs-react/lib/inputDialog/component/InputDialog.tsx diff --git a/packages-ui/roosterjs-react/lib/inputDialog/component/InputDialogItem.tsx b/packages/roosterjs-react/lib/inputDialog/component/InputDialogItem.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/inputDialog/component/InputDialogItem.tsx rename to packages/roosterjs-react/lib/inputDialog/component/InputDialogItem.tsx diff --git a/packages-ui/roosterjs-react/lib/inputDialog/index.ts b/packages/roosterjs-react/lib/inputDialog/index.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/inputDialog/index.ts rename to packages/roosterjs-react/lib/inputDialog/index.ts diff --git a/packages-ui/roosterjs-react/lib/inputDialog/type/DialogItem.ts b/packages/roosterjs-react/lib/inputDialog/type/DialogItem.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/inputDialog/type/DialogItem.ts rename to packages/roosterjs-react/lib/inputDialog/type/DialogItem.ts diff --git a/packages-ui/roosterjs-react/lib/inputDialog/utils/showInputDialog.tsx b/packages/roosterjs-react/lib/inputDialog/utils/showInputDialog.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/inputDialog/utils/showInputDialog.tsx rename to packages/roosterjs-react/lib/inputDialog/utils/showInputDialog.tsx diff --git a/packages-ui/roosterjs-react/lib/pasteOptions/component/showPasteOptionPane.tsx b/packages/roosterjs-react/lib/pasteOptions/component/showPasteOptionPane.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/pasteOptions/component/showPasteOptionPane.tsx rename to packages/roosterjs-react/lib/pasteOptions/component/showPasteOptionPane.tsx diff --git a/packages-ui/roosterjs-react/lib/pasteOptions/index.ts b/packages/roosterjs-react/lib/pasteOptions/index.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/pasteOptions/index.ts rename to packages/roosterjs-react/lib/pasteOptions/index.ts diff --git a/packages-ui/roosterjs-react/lib/pasteOptions/plugin/createPasteOptionPlugin.ts b/packages/roosterjs-react/lib/pasteOptions/plugin/createPasteOptionPlugin.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/pasteOptions/plugin/createPasteOptionPlugin.ts rename to packages/roosterjs-react/lib/pasteOptions/plugin/createPasteOptionPlugin.ts diff --git a/packages-ui/roosterjs-react/lib/pasteOptions/type/PasteOptionStringKeys.ts b/packages/roosterjs-react/lib/pasteOptions/type/PasteOptionStringKeys.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/pasteOptions/type/PasteOptionStringKeys.ts rename to packages/roosterjs-react/lib/pasteOptions/type/PasteOptionStringKeys.ts diff --git a/packages-ui/roosterjs-react/lib/pasteOptions/utils/buttons.ts b/packages/roosterjs-react/lib/pasteOptions/utils/buttons.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/pasteOptions/utils/buttons.ts rename to packages/roosterjs-react/lib/pasteOptions/utils/buttons.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/Ribbon.tsx b/packages/roosterjs-react/lib/ribbon/component/Ribbon.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/Ribbon.tsx rename to packages/roosterjs-react/lib/ribbon/component/Ribbon.tsx diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/alignCenter.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/alignCenter.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/alignCenter.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/alignCenter.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/alignLeft.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/alignLeft.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/alignLeft.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/alignLeft.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/alignRight.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/alignRight.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/alignRight.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/alignRight.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/backgroundColor.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/backgroundColor.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/backgroundColor.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/backgroundColor.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/bold.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/bold.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/bold.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/bold.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/bulletedList.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/bulletedList.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/bulletedList.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/bulletedList.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/clearFormat.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/clearFormat.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/clearFormat.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/clearFormat.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/code.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/code.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/code.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/code.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/decreaseFontSize.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/decreaseFontSize.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/decreaseFontSize.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/decreaseFontSize.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/decreaseIndent.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/decreaseIndent.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/decreaseIndent.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/decreaseIndent.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/font.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/font.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/font.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/font.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/fontSize.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/fontSize.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/fontSize.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/fontSize.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/heading.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/heading.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/heading.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/heading.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/increaseFontSize.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/increaseFontSize.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/increaseFontSize.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/increaseFontSize.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/increaseIndent.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/increaseIndent.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/increaseIndent.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/increaseIndent.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/insertImage.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/insertImage.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/insertImage.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/insertImage.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/insertLink.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/insertLink.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/insertLink.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/insertLink.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/insertTable.tsx b/packages/roosterjs-react/lib/ribbon/component/buttons/insertTable.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/insertTable.tsx rename to packages/roosterjs-react/lib/ribbon/component/buttons/insertTable.tsx diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/italic.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/italic.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/italic.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/italic.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/ltr.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/ltr.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/ltr.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/ltr.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/moreCommands.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/moreCommands.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/moreCommands.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/moreCommands.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/numberedList.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/numberedList.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/numberedList.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/numberedList.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/quote.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/quote.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/quote.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/quote.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/redo.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/redo.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/redo.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/redo.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/removeLink.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/removeLink.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/removeLink.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/removeLink.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/rtl.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/rtl.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/rtl.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/rtl.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/strikethrough.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/strikethrough.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/strikethrough.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/strikethrough.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/subscript.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/subscript.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/subscript.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/subscript.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/superscript.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/superscript.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/superscript.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/superscript.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/textColor.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/textColor.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/textColor.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/textColor.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/underline.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/underline.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/underline.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/underline.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/buttons/undo.ts b/packages/roosterjs-react/lib/ribbon/component/buttons/undo.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/buttons/undo.ts rename to packages/roosterjs-react/lib/ribbon/component/buttons/undo.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/component/getButtons.ts b/packages/roosterjs-react/lib/ribbon/component/getButtons.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/component/getButtons.ts rename to packages/roosterjs-react/lib/ribbon/component/getButtons.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/index.ts b/packages/roosterjs-react/lib/ribbon/index.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/index.ts rename to packages/roosterjs-react/lib/ribbon/index.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/plugin/createRibbonPlugin.ts b/packages/roosterjs-react/lib/ribbon/plugin/createRibbonPlugin.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/plugin/createRibbonPlugin.ts rename to packages/roosterjs-react/lib/ribbon/plugin/createRibbonPlugin.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/type/KnownRibbonButton.ts b/packages/roosterjs-react/lib/ribbon/type/KnownRibbonButton.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/type/KnownRibbonButton.ts rename to packages/roosterjs-react/lib/ribbon/type/KnownRibbonButton.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/type/RibbonButton.ts b/packages/roosterjs-react/lib/ribbon/type/RibbonButton.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/type/RibbonButton.ts rename to packages/roosterjs-react/lib/ribbon/type/RibbonButton.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/type/RibbonButtonDropDown.ts b/packages/roosterjs-react/lib/ribbon/type/RibbonButtonDropDown.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/type/RibbonButtonDropDown.ts rename to packages/roosterjs-react/lib/ribbon/type/RibbonButtonDropDown.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/type/RibbonButtonStringKeys.ts b/packages/roosterjs-react/lib/ribbon/type/RibbonButtonStringKeys.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/type/RibbonButtonStringKeys.ts rename to packages/roosterjs-react/lib/ribbon/type/RibbonButtonStringKeys.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/type/RibbonPlugin.ts b/packages/roosterjs-react/lib/ribbon/type/RibbonPlugin.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/type/RibbonPlugin.ts rename to packages/roosterjs-react/lib/ribbon/type/RibbonPlugin.ts diff --git a/packages-ui/roosterjs-react/lib/ribbon/type/RibbonProps.ts b/packages/roosterjs-react/lib/ribbon/type/RibbonProps.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/ribbon/type/RibbonProps.ts rename to packages/roosterjs-react/lib/ribbon/type/RibbonProps.ts diff --git a/packages-ui/roosterjs-react/lib/rooster/component/Rooster.tsx b/packages/roosterjs-react/lib/rooster/component/Rooster.tsx similarity index 100% rename from packages-ui/roosterjs-react/lib/rooster/component/Rooster.tsx rename to packages/roosterjs-react/lib/rooster/component/Rooster.tsx diff --git a/packages-ui/roosterjs-react/lib/rooster/index.ts b/packages/roosterjs-react/lib/rooster/index.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/rooster/index.ts rename to packages/roosterjs-react/lib/rooster/index.ts diff --git a/packages-ui/roosterjs-react/lib/rooster/plugin/createUpdateContentPlugin.ts b/packages/roosterjs-react/lib/rooster/plugin/createUpdateContentPlugin.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/rooster/plugin/createUpdateContentPlugin.ts rename to packages/roosterjs-react/lib/rooster/plugin/createUpdateContentPlugin.ts diff --git a/packages-ui/roosterjs-react/lib/rooster/type/RoosterProps.ts b/packages/roosterjs-react/lib/rooster/type/RoosterProps.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/rooster/type/RoosterProps.ts rename to packages/roosterjs-react/lib/rooster/type/RoosterProps.ts diff --git a/packages-ui/roosterjs-react/lib/rooster/type/UpdateContentPlugin.ts b/packages/roosterjs-react/lib/rooster/type/UpdateContentPlugin.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/rooster/type/UpdateContentPlugin.ts rename to packages/roosterjs-react/lib/rooster/type/UpdateContentPlugin.ts diff --git a/packages-ui/roosterjs-react/lib/rooster/type/UpdateMode.ts b/packages/roosterjs-react/lib/rooster/type/UpdateMode.ts similarity index 100% rename from packages-ui/roosterjs-react/lib/rooster/type/UpdateMode.ts rename to packages/roosterjs-react/lib/rooster/type/UpdateMode.ts diff --git a/packages-ui/roosterjs-react/package.json b/packages/roosterjs-react/package.json similarity index 100% rename from packages-ui/roosterjs-react/package.json rename to packages/roosterjs-react/package.json diff --git a/packages-ui/roosterjs-react/test/emptyTest.ts b/packages/roosterjs-react/test/emptyTest.ts similarity index 100% rename from packages-ui/roosterjs-react/test/emptyTest.ts rename to packages/roosterjs-react/test/emptyTest.ts diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 8bab19b7bcb..29ab12039af 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "strict": true, + "jsx": "react", "target": "es5", "module": "commonjs", "outDir": "../dist", @@ -20,5 +21,5 @@ "rootDir": ".", "lib": ["es6", "dom"] }, - "include": ["./*/lib/**/*.ts"] + "include": ["./*/lib/**/*.ts", "./*/lib/**/*.tsx"] } diff --git a/packages/tsconfig.test.json b/packages/tsconfig.test.json index 1500bf7be24..b524c6a70bc 100644 --- a/packages/tsconfig.test.json +++ b/packages/tsconfig.test.json @@ -1,6 +1,7 @@ { "compilerOptions": { "strict": false, + "jsx": "react", "target": "es5", "module": "commonjs", "noEmit": true, @@ -20,5 +21,5 @@ "rootDir": "..", "lib": ["es6", "dom"] }, - "include": ["./*/test/**/*.ts"] + "include": ["./*/lib/**/*.ts", "./*/lib/**/*.tsx"] } diff --git a/tools/build.js b/tools/build.js index 2a12391c59a..59b5b294c0c 100644 --- a/tools/build.js +++ b/tools/build.js @@ -27,23 +27,29 @@ const allTasks = [ buildCommonJsStep, buildTestStep, pack.commonJsDebug, - pack.commonJsProduction, + pack.commonJsProd, pack.amdDebug, pack.amdProduction, pack.commonJsDebugUi, - pack.commonJsProductionUi, + pack.commonJsProdUi, pack.amdDebugUi, pack.amdProductionUi, - pack.commonJsDebugContentModel, - pack.commonJsProductionContentModel, - pack.amdDebugContentModel, - pack.amdProductionContentModel, + pack.commonJsDebugMain, + pack.commonJsProdMain, + pack.amdDebugMain, + pack.amdProdMain, + pack.commonJsDebugAdapter, + pack.commonJsProdAdapter, + pack.amdDebugAdapter, + pack.amdProdAdapter, dts.dtsCommonJs, dts.dtsAmd, dts.dtsCommonJsUi, dts.dtsAmdUi, - dts.dtsCommonJsContentModel, - dts.dtsAmdContentModel, + dts.dtsCommonJsMain, + dts.dtsAmdMain, + // dts.dtsCommonJsAdapter, + // dts.dtsAmdAdapter, buildDemoStep, buildDocumentStep, publishStep, diff --git a/tools/buildTools/buildAmd.js b/tools/buildTools/buildAmd.js index 2d4a109348e..fc409433a9a 100644 --- a/tools/buildTools/buildAmd.js +++ b/tools/buildTools/buildAmd.js @@ -2,15 +2,7 @@ const path = require('path'); const fs = require('fs'); -const { - packagesPath, - packagesUiPath, - packagesContentModelPath, - nodeModulesPath, - allPackages, - distPath, - runNode, -} = require('./common'); +const { packagesPath, nodeModulesPath, distPath, packages, runNode } = require('./common'); function buildAmd() { const typescriptPath = path.join(nodeModulesPath, 'typescript/lib/tsc.js'); @@ -20,24 +12,8 @@ function buildAmd() { ` -p ${path.join(packagesPath, 'tsconfig.json')} -t es5 --moduleResolution node -m amd`, packagesPath ); - runNode( - typescriptPath + - ` -p ${path.join( - packagesUiPath, - 'tsconfig.json' - )} -t es5 --moduleResolution node -m amd`, - packagesPath - ); - runNode( - typescriptPath + - ` -p ${path.join( - packagesContentModelPath, - 'tsconfig.json' - )} -t es5 --moduleResolution node -m amd`, - packagesPath - ); - allPackages.forEach(packageName => { + packages.forEach(packageName => { const packagePath = path.join(distPath, packageName); fs.renameSync(`${packagePath}/lib`, `${packagePath}/lib-amd`); }); diff --git a/tools/buildTools/buildCommonJs.js b/tools/buildTools/buildCommonJs.js index 58ade553c3f..63be5759358 100644 --- a/tools/buildTools/buildCommonJs.js +++ b/tools/buildTools/buildCommonJs.js @@ -8,9 +8,7 @@ const { nodeModulesPath, distPath, packagesPath, - packagesUiPath, - packagesContentModelPath, - allPackages, + packages, } = require('./common'); function buildCommonJs() { @@ -23,10 +21,8 @@ function buildCommonJs() { 'tsconfig.json' )} -t es5 --moduleResolution node -m commonjs` ); - runNode(typescriptPath, packagesUiPath); - runNode(typescriptPath, packagesContentModelPath); - allPackages.forEach(packageName => { + packages.forEach(packageName => { const copy = fileName => { const source = path.join(rootPath, fileName); const target = path.join(distPath, packageName, fileName); diff --git a/tools/buildTools/buildDemo.js b/tools/buildTools/buildDemo.js index 478c9151d33..f03a54c8784 100644 --- a/tools/buildTools/buildDemo.js +++ b/tools/buildTools/buildDemo.js @@ -8,14 +8,13 @@ const { packagesPath, deployPath, roosterJsDistPath, - packagesUiPath, - roosterJsUiDistPath, runWebPack, getWebpackExternalCallback, - contentModelDistPath, - packagesContentModelPath, + buildConfig, } = require('./common'); +const filesToCopy = Object.values(buildConfig).map(x => x.jsFileBaseName); + async function buildDemoSite() { const sourcePathRoot = path.join(rootPath, 'demo'); const sourcePath = path.join(sourcePathRoot, 'scripts'); @@ -29,13 +28,7 @@ async function buildDemoSite() { }, resolve: { extensions: ['.ts', '.tsx', '.js', '.svg', '.scss', '.'], - modules: [ - sourcePath, - packagesPath, - packagesUiPath, - packagesContentModelPath, - nodeModulesPath, - ], + modules: [sourcePath, packagesPath, nodeModulesPath], }, module: { rules: [ @@ -72,15 +65,14 @@ async function buildDemoSite() { }, ], }, - externals: getWebpackExternalCallback( - [ - [/^roosterjs-editor-plugins\/.*$/, 'roosterjs'], - [/^roosterjs-react\/.*$/, 'roosterjsReact'], - [/^roosterjs-react$/, 'roosterjsReact'], - [/^roosterjs-content-model((?!-editor).)*\/.*$/, 'roosterjsContentModel'], - ], - ['roosterjs-editor-adapter'] - ), + externals: getWebpackExternalCallback([ + [/^roosterjs-editor-plugins\/.*$/, 'roosterjs'], + [/^roosterjs-editor-adapter\/.*$/, 'roosterjs'], + [/^roosterjs-react\/.*$/, 'roosterjsReact'], + [/^roosterjs-react$/, 'roosterjsReact'], + [/^roosterjs-content-model.*/, 'roosterjsContentModel'], + [/^roosterjs-adapter\/.*$/, 'roosterjsAdapter'], + ]), stats: 'minimal', mode: 'production', optimization: { @@ -90,30 +82,14 @@ async function buildDemoSite() { await runWebPack(webpackConfig); - fs.copyFileSync( - path.resolve(roosterJsDistPath, 'rooster-min.js'), - path.resolve(deployPath, 'rooster-min.js') - ); - fs.copyFileSync( - path.resolve(roosterJsDistPath, 'rooster-min.js.map'), - path.resolve(deployPath, 'rooster-min.js.map') - ); - fs.copyFileSync( - path.resolve(roosterJsUiDistPath, 'rooster-react-min.js'), - path.resolve(deployPath, 'rooster-react-min.js') - ); - fs.copyFileSync( - path.resolve(roosterJsUiDistPath, 'rooster-react-min.js.map'), - path.resolve(deployPath, 'rooster-react-min.js.map') - ); - fs.copyFileSync( - path.resolve(contentModelDistPath, 'rooster-content-model-min.js'), - path.resolve(deployPath, 'rooster-content-model-min.js') - ); - fs.copyFileSync( - path.resolve(contentModelDistPath, 'rooster-content-model-min.js.map'), - path.resolve(deployPath, 'rooster-content-model-min.js.map') - ); + filesToCopy.forEach(file => { + const source = path.resolve(roosterJsDistPath, `${file}-min.js`); + const target = path.resolve(deployPath, `${file}-min.js`); + + fs.copyFileSync(source, target); + fs.copyFileSync(source + '.map', target + '.map'); + }); + fs.copyFileSync( path.resolve(sourcePathRoot, 'index.html'), path.resolve(deployPath, 'index.html') diff --git a/tools/buildTools/buildMjs.js b/tools/buildTools/buildMjs.js index e9844c56df7..9a2199c09d3 100644 --- a/tools/buildTools/buildMjs.js +++ b/tools/buildTools/buildMjs.js @@ -2,15 +2,7 @@ const path = require('path'); const fs = require('fs'); -const { - packagesPath, - packagesUiPath, - nodeModulesPath, - allPackages, - distPath, - runNode, - packagesContentModelPath, -} = require('./common'); +const { packagesPath, nodeModulesPath, distPath, runNode, packages } = require('./common'); function buildMjs() { const typescriptPath = path.join(nodeModulesPath, 'typescript/lib/tsc.js'); @@ -23,23 +15,7 @@ function buildMjs() { )} -t es5 --moduleResolution node -m esnext`, packagesPath ); - runNode( - typescriptPath + - ` -p ${path.join( - packagesUiPath, - 'tsconfig.json' - )} -t es5 --moduleResolution node -m esnext`, - packagesPath - ); - runNode( - typescriptPath + - ` -p ${path.join( - packagesContentModelPath, - 'tsconfig.json' - )} -t es5 --moduleResolution node -m esnext`, - packagesPath - ); - allPackages.forEach(packageName => { + packages.forEach(packageName => { const packagePath = path.join(distPath, packageName); fs.renameSync(`${packagePath}/lib`, `${packagePath}/lib-mjs`); }); diff --git a/tools/buildTools/buildTest.js b/tools/buildTools/buildTest.js index 6caec39cfd7..5fb183c65ad 100644 --- a/tools/buildTools/buildTest.js +++ b/tools/buildTools/buildTest.js @@ -1,14 +1,7 @@ 'use strict'; -const fs = require('fs'); const path = require('path'); -const { - runNode, - nodeModulesPath, - packagesPath, - packagesUiPath, - packagesContentModelPath, -} = require('./common'); +const { runNode, nodeModulesPath, packagesPath } = require('./common'); function buildTest() { const typescriptPath = path.join(nodeModulesPath, 'typescript/lib/tsc.js'); @@ -20,20 +13,6 @@ function buildTest() { 'tsconfig.test.json' )} -t es5 --moduleResolution node -m commonjs` ); - runNode( - typescriptPath + - ` -p ${path.join( - packagesUiPath, - 'tsconfig.test.json' - )} -t es5 --moduleResolution node -m commonjs` - ); - runNode( - typescriptPath + - ` -p ${path.join( - packagesContentModelPath, - 'tsconfig.test.json' - )} -t es5 --moduleResolution node -m commonjs` - ); } module.exports = { diff --git a/tools/buildTools/checkDependency.js b/tools/buildTools/checkDependency.js index 2c292a30b3f..60e7a7e05d3 100644 --- a/tools/buildTools/checkDependency.js +++ b/tools/buildTools/checkDependency.js @@ -2,7 +2,7 @@ const path = require('path'); const fs = require('fs'); -const { allPackages, readPackageJson, findPackageRoot, err, rootPath } = require('./common'); +const { readPackageJson, err, rootPath, packages } = require('./common'); function getPossibleNames(dir, objectName) { return [ @@ -73,9 +73,9 @@ function processFile(dir, filename, files, externalDependencies, fromFile) { } function findPackageName(filename) { - for (let i = 0; i < allPackages.length; i++) { - if (filename.indexOf(allPackages[i])) { - return allPackages[i]; + for (let i = 0; i < packages.length; i++) { + if (filename.indexOf(packages[i])) { + return packages[i]; } } @@ -88,8 +88,8 @@ const GlobalAllowedCrossPackageDependency = [ ]; function checkDependency() { - allPackages.forEach(packageName => { - const packageRoot = path.join(rootPath, findPackageRoot(packageName)); + packages.forEach(packageName => { + const packageRoot = path.join(rootPath, 'packages'); var packageJson = readPackageJson(packageName, true /*readFromSourceFolder*/); var dependencies = Object.keys(packageJson.dependencies); diff --git a/tools/buildTools/common.js b/tools/buildTools/common.js index 0dccde03d6c..78e6a10d246 100644 --- a/tools/buildTools/common.js +++ b/tools/buildTools/common.js @@ -9,19 +9,12 @@ const toposort = require('toposort'); const webpack = require('webpack'); const packagesName = 'packages'; -const packagesUiName = 'packages-ui'; -const packagesContentModelName = 'packages-content-model'; - const rootPath = path.join(__dirname, '../..'); const packagesPath = path.join(rootPath, packagesName); -const packagesUiPath = path.join(rootPath, packagesUiName); -const packagesContentModelPath = path.join(rootPath, packagesContentModelName); const nodeModulesPath = path.join(rootPath, 'node_modules'); const typescriptPath = path.join(nodeModulesPath, 'typescript/lib/tsc.js'); const distPath = path.join(rootPath, 'dist'); const roosterJsDistPath = path.join(distPath, 'roosterjs/dist'); -const roosterJsUiDistPath = path.join(distPath, 'roosterjs-react/dist'); -const contentModelDistPath = path.join(distPath, 'roosterjs-content-model/dist'); const deployPath = path.join(distPath, 'deploy'); const compatibleEnumPath = path.join( packagesPath, @@ -68,9 +61,6 @@ function collectPackages(startPath) { } const packages = collectPackages(packagesPath); -const packagesUI = collectPackages(packagesUiPath); -const packagesContentModel = collectPackages(packagesContentModelPath); -const allPackages = packages.concat(packagesUI).concat(packagesContentModel); function runNode(command, cwd, stdio) { exec('node ' + command, { @@ -85,19 +75,9 @@ function err(message) { throw ex; } -function findPackageRoot(packageName) { - return packages.indexOf(packageName) >= 0 - ? packagesName - : packagesUI.indexOf(packageName) >= 0 - ? packagesUiName - : packagesContentModel.indexOf(packageName) >= 0 - ? packagesContentModelName - : null; -} - function readPackageJson(packageName, readFromSourceFolder) { const packageJsonFilePath = path.join( - readFromSourceFolder ? rootPath + '/' + findPackageRoot(packageName) : distPath, + readFromSourceFolder ? rootPath + '/packages' : distPath, packageName, 'package.json' ); @@ -124,13 +104,13 @@ async function runWebPack(config) { }); } -function getWebpackExternalCallback(externalLibraryPairs, internalLibraries) { +function getWebpackExternalCallback(externalLibraryPairs) { const externalMap = new Map([ ['react', 'React'], ['react-dom', 'ReactDOM'], [/^office-ui-fabric-react(\/.*)?$/, 'FluentUIReact'], [/^@fluentui(\/.*)?$/, 'FluentUIReact'], - ...packages.filter(x => internalLibraries.indexOf(x) < 0).map(p => [p, 'roosterjs']), + ...legacyPackages.map(p => [p, 'roosterjs']), ...externalLibraryPairs, ]); @@ -147,8 +127,29 @@ function getWebpackExternalCallback(externalLibraryPairs, internalLibraries) { }; } +const legacyPackages = [ + 'roosterjs', + 'roosterjs-editor-types', + 'roosterjs-editor-types-compatible', + 'roosterjs-editor-dom', + 'roosterjs-editor-core', + 'roosterjs-editor-api', + 'roosterjs-editor-plugins', + 'roosterjs-color-utils', +]; +const reactPackages = ['roosterjs-react']; +const mainPackages = [ + 'roosterjs-content-model', + 'roosterjs-content-model-types', + 'roosterjs-content-model-dom', + 'roosterjs-content-model-core', + 'roosterjs-content-model-api', + 'roosterjs-content-model-plugins', +]; +const legacyAdapterPackages = ['roosterjs-editor-adapter']; + const buildConfig = { - packages: { + legacy: { targetPath: roosterJsDistPath, packEntry: path.join(packagesPath, 'roosterjs/lib/index.ts'), jsFileBaseName: 'rooster', @@ -157,32 +158,48 @@ const buildConfig = { libraryName: 'roosterjs', targetFileName: 'rooster', externalHandler: undefined, + packages: legacyPackages, }, - 'packages-ui': { - targetPath: roosterJsUiDistPath, - packEntry: path.join(packagesUiPath, 'roosterjs-react/lib/index.ts'), + react: { + targetPath: roosterJsDistPath, + packEntry: path.join(packagesPath, 'roosterjs-react/lib/index.ts'), jsFileBaseName: 'rooster-react', - targetPackages: packagesUI, + targetPackages: ['roosterjs-react'], startFileName: 'roosterjs-react/lib/index.d.ts', libraryName: 'roosterjsReact', targetFileName: 'rooster-react', - externalHandler: getWebpackExternalCallback([], []), + externalHandler: getWebpackExternalCallback([]), dependsOnRoosterJs: true, dependsOnReact: true, + packages: reactPackages, }, - 'packages-content-model': { - targetPath: contentModelDistPath, - packEntry: path.join(packagesContentModelPath, 'roosterjs-content-model/lib/index.ts'), + main: { + targetPath: roosterJsDistPath, + packEntry: path.join(packagesPath, 'roosterjs-content-model/lib/index.ts'), jsFileBaseName: 'rooster-content-model', - targetPackages: packagesContentModel, + targetPackages: ['roosterjs-content-model'], startFileName: 'roosterjs-content-model/lib/index.d.ts', libraryName: 'roosterjsContentModel', targetFileName: 'rooster-content-model', - externalHandler: getWebpackExternalCallback( - [[/^roosterjs-editor-types\/lib\/compatibleTypes/, 'roosterjs']], - packagesContentModel - ), + externalHandler: undefined, + dependsOnRoosterJs: false, + packages: mainPackages, + }, + legacyAdapter: { + targetPath: roosterJsDistPath, + packEntry: path.join(packagesPath, 'roosterjs-editor-adapter/lib/index.ts'), + jsFileBaseName: 'rooster-adapter', + targetPackages: ['roosterjs-editor-adapter'], + startFileName: 'roosterjs-editor-adapter/lib/index.d.ts', + libraryName: 'roosterjsAdapter', + targetFileName: 'rooster-adapter', + externalHandler: getWebpackExternalCallback([ + [/^roosterjs-editor-types\/lib\/compatibleTypes/, 'roosterjs'], + [/^roosterjs-content-model.*/, 'roosterjsContentModel'], + ]), dependsOnRoosterJs: true, + dependsOnMain: true, + packages: legacyAdapterPackages, }, }; @@ -191,27 +208,19 @@ const versions = JSON.parse(fs.readFileSync(path.join(rootPath, 'versions.json') module.exports = { rootPath, packagesPath, - packagesUiPath, - packagesContentModelPath, nodeModulesPath, typescriptPath, distPath, roosterJsDistPath, - roosterJsUiDistPath, compatibleEnumPath, deployPath, runNode, err, packages, - packagesUI, - packagesContentModel, - allPackages, readPackageJson, mainPackageJson, - findPackageRoot, runWebPack, getWebpackExternalCallback, - contentModelDistPath, buildConfig, versions, }; diff --git a/tools/buildTools/dts.js b/tools/buildTools/dts.js index ad3ee37fdb3..35d96ba0096 100644 --- a/tools/buildTools/dts.js +++ b/tools/buildTools/dts.js @@ -7,7 +7,6 @@ const mkdirp = require('mkdirp'); const { rootPath, distPath, - roosterJsDistPath, nodeModulesPath, runNode, err, @@ -427,6 +426,7 @@ function dts(isAmd, target) { externalHandler, dependsOnRoosterJs, dependsOnReact, + dependsOnMain, } = buildConfig[target]; mkdirp.sync(targetPath); @@ -451,13 +451,15 @@ function dts(isAmd, target) { let dependencies = ''; if (dependsOnRoosterJs) { - const roosterjsDtsFileName = `rooster${isAmd ? '-amd' : ''}.d.ts`; - fs.copyFileSync( - path.join(roosterJsDistPath, roosterjsDtsFileName), - path.join(targetPath, roosterjsDtsFileName) - ); + dependencies += `/// \n`; + } - dependencies += `/// \n`; + if (dependsOnMain) { + dependencies += `/// \n`; } if (dependsOnReact) { @@ -475,32 +477,42 @@ function dts(isAmd, target) { module.exports = { dtsCommonJs: { message: `Generating type definition file (rooster.d.ts) for CommonJs...`, - callback: () => dts(false /*isAmd*/, 'packages'), + callback: () => dts(false /*isAmd*/, 'legacy'), enabled: options => options.dts, }, dtsAmd: { message: `Generating type definition file (rooster-amd.d.ts) for AMD...`, - callback: () => dts(true /*isAmd*/, 'packages'), + callback: () => dts(true /*isAmd*/, 'legacy'), enabled: options => options.dts, }, dtsCommonJsUi: { message: `Generating type definition file (rooster-react.d.ts) for CommonJs...`, - callback: () => dts(false /*isAmd*/, 'packages-ui'), + callback: () => dts(false /*isAmd*/, 'react'), enabled: options => options.dts, }, dtsAmdUi: { message: `Generating type definition file (rooster-react-amd.d.ts) for AMD...`, - callback: () => dts(true /*isAmd*/, 'packages-ui'), + callback: () => dts(true /*isAmd*/, 'react'), enabled: options => options.dts, }, - dtsCommonJsContentModel: { + dtsCommonJsMain: { message: `Generating type definition file (rooster-content-model.d.ts) for CommonJs...`, - callback: () => dts(false /*isAmd*/, 'packages-content-model'), + callback: () => dts(false /*isAmd*/, 'main'), enabled: options => options.dts, }, - dtsAmdContentModel: { + dtsAmdMain: { message: `Generating type definition file (rooster-content-model-amd.d.ts) for AMD...`, - callback: () => dts(true /*isAmd*/, 'packages-content-model'), + callback: () => dts(true /*isAmd*/, 'main'), enabled: options => options.dts, }, + // dtsCommonJsAdapter: { + // message: `Generating type definition file (rooster-adapter.d.ts) for CommonJs...`, + // callback: () => dts(false /*isAmd*/, 'legacyAdapter'), + // enabled: options => options.dts, + // }, + // dtsAmdAdapter: { + // message: `Generating type definition file (rooster-adapter-amd.d.ts) for AMD...`, + // callback: () => dts(true /*isAmd*/, 'legacyAdapter'), + // enabled: options => options.dts, + // }, }; diff --git a/tools/buildTools/eslint.js b/tools/buildTools/eslint.js index 090117c5553..8cf7b3ea745 100644 --- a/tools/buildTools/eslint.js +++ b/tools/buildTools/eslint.js @@ -1,23 +1,14 @@ 'use strict'; const path = require('path'); -const { - nodeModulesPath, - runNode, - packagesPath, - packagesUiPath, - packagesContentModelPath, - rootPath, -} = require('./common'); +const { nodeModulesPath, runNode, packagesPath, rootPath } = require('./common'); function eslint() { const eslintPath = path.join(nodeModulesPath, 'eslint/bin/eslint.js'); - [packagesPath, packagesUiPath, packagesContentModelPath].forEach(p => { - runNode( - eslintPath + ' -c ' + path.join(rootPath, '.eslintrc.js') + ' ./**/lib/**/*.{ts,tsx}', - p - ); - }); + runNode( + eslintPath + ' -c ' + path.join(rootPath, '.eslintrc.js') + ' ./**/lib/**/*.{ts,tsx}', + packagesPath + ); } module.exports = { diff --git a/tools/buildTools/normalize.js b/tools/buildTools/normalize.js index 8236c64979c..43516a42a99 100644 --- a/tools/buildTools/normalize.js +++ b/tools/buildTools/normalize.js @@ -5,20 +5,34 @@ const mkdirp = require('mkdirp'); const fs = require('fs'); const processConstEnum = require('./processConstEnum'); const { - allPackages, + packages, distPath, readPackageJson, mainPackageJson, err, - findPackageRoot, versions, + buildConfig, } = require('./common'); +function findPackageKey(packageName) { + const key = Object.keys(buildConfig).find( + x => buildConfig[x].packages.indexOf(packageName) >= 0 + ); + + if (!key) { + throw new Error( + `Unrecognized package name ${packageName}. If this is a new package, please update buildConfig in tools/common.js to include this package in proper config.` + ); + } + + return key; +} + function normalize() { const knownCustomizedPackages = {}; - allPackages.forEach(packageName => { - const versionKey = findPackageRoot(packageName); + packages.forEach(packageName => { + const versionKey = findPackageKey(packageName); const version = versions.overrides?.[packageName] ?? versions[versionKey]; const packageJson = readPackageJson(packageName, true /*readFromSourceFolder*/); @@ -27,7 +41,7 @@ function normalize() { // No op, keep the specified value } else if (knownCustomizedPackages[dep]) { packageJson.dependencies[dep] = '^' + knownCustomizedPackages[dep]; - } else if (allPackages.indexOf(dep) > -1) { + } else if (packages.indexOf(dep) > -1) { var depKey = findPackageRoot(dep); var depVersion = versions[depKey]; packageJson.dependencies[dep] = '^' + depVersion; diff --git a/tools/buildTools/pack.js b/tools/buildTools/pack.js index 8715cd34654..d4666b06ea5 100644 --- a/tools/buildTools/pack.js +++ b/tools/buildTools/pack.js @@ -1,14 +1,6 @@ 'use strict'; -const { - packagesPath, - nodeModulesPath, - packagesUiPath, - rootPath, - runWebPack, - buildConfig, - packagesContentModelPath, -} = require('./common'); +const { packagesPath, nodeModulesPath, rootPath, runWebPack, buildConfig } = require('./common'); async function pack(isProduction, isAmd, target, filename) { const { packEntry, targetPath, libraryName, externalHandler } = buildConfig[target]; @@ -23,7 +15,7 @@ async function pack(isProduction, isAmd, target, filename) { }, resolve: { extensions: ['.ts', '.tsx', '.js'], - modules: [packagesPath, packagesUiPath, packagesContentModelPath, nodeModulesPath], + modules: [packagesPath, nodeModulesPath], }, module: { rules: [ @@ -53,7 +45,7 @@ async function pack(isProduction, isAmd, target, filename) { await runWebPack(webpackConfig); } -function createStep(isProduction, isAmd, target, enableForDemoSite) { +function createStep(isProduction, isAmd, target) { const fileName = `${buildConfig[target].jsFileBaseName}${isAmd ? '-amd' : ''}${ isProduction ? '-min' : '' }.js`; @@ -61,49 +53,29 @@ function createStep(isProduction, isAmd, target, enableForDemoSite) { message: `Packing ${fileName}...`, callback: async () => pack(isProduction, isAmd, target, fileName), enabled: options => - (enableForDemoSite && options.builddemo) || + (options.builddemo && isProduction && !isAmd) || (isProduction ? options.packprod : options.pack), }; } module.exports = { - commonJsDebug: createStep(false /*isProduction*/, false /*isAmd*/, 'packages'), - commonJsProduction: createStep( - true /*isProduction*/, - false /*isAmd*/, - 'packages', - true /*enableForDemoSite*/ - ), - amdDebug: createStep(false /*isProduction*/, true /*isAmd*/, 'packages'), - amdProduction: createStep(true /*isProduction*/, true /*isAmd*/, 'packages'), - commonJsDebugUi: createStep(false /*isProduction*/, false /*isAmd*/, 'packages-ui'), - commonJsProductionUi: createStep( - true /*isProduction*/, - false /*isAmd*/, - 'packages-ui', - true /*enableForDemoSite*/ - ), - amdDebugUi: createStep(false /*isProduction*/, true /*isAmd*/, 'packages-ui'), - amdProductionUi: createStep(true /*isProduction*/, true /*isAmd*/, 'packages-ui'), - commonJsDebugContentModel: createStep( - false /*isProduction*/, - false /*isAmd*/, - 'packages-content-model' - ), - commonJsProductionContentModel: createStep( - true /*isProduction*/, - false /*isAmd*/, - 'packages-content-model', - true /*enableForDemoSite*/ - ), - amdDebugContentModel: createStep( - false /*isProduction*/, - true /*isAmd*/, - 'packages-content-model' - ), - amdProductionContentModel: createStep( - true /*isProduction*/, - true /*isAmd*/, - 'packages-content-model' - ), + commonJsDebug: createStep(false /*isProduction*/, false /*isAmd*/, 'legacy'), + commonJsProd: createStep(true /*isProduction*/, false /*isAmd*/, 'legacy'), + amdDebug: createStep(false /*isProduction*/, true /*isAmd*/, 'legacy'), + amdProduction: createStep(true /*isProduction*/, true /*isAmd*/, 'legacy'), + + commonJsDebugUi: createStep(false /*isProduction*/, false /*isAmd*/, 'react'), + commonJsProdUi: createStep(true /*isProduction*/, false /*isAmd*/, 'react'), + amdDebugUi: createStep(false /*isProduction*/, true /*isAmd*/, 'react'), + amdProductionUi: createStep(true /*isProduction*/, true /*isAmd*/, 'react'), + + commonJsDebugMain: createStep(false /*isProduction*/, false /*isAmd*/, 'main'), + commonJsProdMain: createStep(true /*isProduction*/, false /*isAmd*/, 'main'), + amdDebugMain: createStep(false /*isProduction*/, true /*isAmd*/, 'main'), + amdProdMain: createStep(true /*isProduction*/, true /*isAmd*/, 'main'), + + commonJsDebugAdapter: createStep(false /*isProduction*/, false /*isAmd*/, 'legacyAdapter'), + commonJsProdAdapter: createStep(true /*isProduction*/, false /*isAmd*/, 'legacyAdapter'), + amdDebugAdapter: createStep(false /*isProduction*/, true /*isAmd*/, 'legacyAdapter'), + amdProdAdapter: createStep(true /*isProduction*/, true /*isAmd*/, 'legacyAdapter'), }; diff --git a/tools/buildTools/publish.js b/tools/buildTools/publish.js index 24a5016adf7..5b2a06c602d 100644 --- a/tools/buildTools/publish.js +++ b/tools/buildTools/publish.js @@ -3,13 +3,13 @@ const path = require('path'); const fs = require('fs'); const exec = require('child_process').execSync; -const { allPackages, distPath, readPackageJson } = require('./common'); +const { distPath, readPackageJson, packages } = require('./common'); const VersionRegex = /\d+\.\d+\.\d+(-([^\.]+)(\.\d+)?)?/; const NpmrcContent = 'registry=https://registry.npmjs.com/\n//registry.npmjs.com/:_authToken='; function publish(options) { - allPackages.forEach(packageName => { + packages.forEach(packageName => { const json = readPackageJson(packageName, false /*readFromSourceFolder*/); const localVersion = json.version; const versionMatch = VersionRegex.exec(localVersion); diff --git a/tools/karma.test.all.js b/tools/karma.test.all.js index 56cb34c2bb6..cb8e041d7d5 100644 --- a/tools/karma.test.all.js +++ b/tools/karma.test.all.js @@ -1,6 +1,4 @@ var contextRoosterjs = require.context('../packages', true, /test\/.+\.ts?$/); -var contextUI = require.context('../packages-ui', true, /test\/.+\.ts?$/); -var contextContentModel = require.context('../packages-content-model', true, /test\/.+\.ts?$/); var karmaTest = require('./karma.test'); -module.exports = karmaTest([contextRoosterjs, contextUI, contextContentModel]); +module.exports = karmaTest([contextRoosterjs]); diff --git a/tools/karma.test.contentmodel.js b/tools/karma.test.contentmodel.js deleted file mode 100644 index 6b4c1224594..00000000000 --- a/tools/karma.test.contentmodel.js +++ /dev/null @@ -1,5 +0,0 @@ -var contextContentModel = require.context('../packages-content-model', true, /test\/.+\.ts?$/); - -var karmaTest = require('./karma.test'); - -module.exports = karmaTest([contextContentModel]); diff --git a/tools/karma.test.roosterjs.js b/tools/karma.test.roosterjs.js deleted file mode 100644 index cb8e041d7d5..00000000000 --- a/tools/karma.test.roosterjs.js +++ /dev/null @@ -1,4 +0,0 @@ -var contextRoosterjs = require.context('../packages', true, /test\/.+\.ts?$/); -var karmaTest = require('./karma.test'); - -module.exports = karmaTest([contextRoosterjs]); diff --git a/tools/karma.test.ui.js b/tools/karma.test.ui.js deleted file mode 100644 index 0f7cfcd6978..00000000000 --- a/tools/karma.test.ui.js +++ /dev/null @@ -1,4 +0,0 @@ -var contextUI = require.context('../packages-ui', true, /test\/.+\.ts?$/); -var karmaTest = require('./karma.test'); - -module.exports = karmaTest([contextUI]); diff --git a/tools/tsconfig.doc.json b/tools/tsconfig.doc.json index b5da40f7c30..ee60a9f9079 100644 --- a/tools/tsconfig.doc.json +++ b/tools/tsconfig.doc.json @@ -29,15 +29,15 @@ "../packages/roosterjs-editor-api/lib/index.ts", "../packages/roosterjs-editor-plugins/lib/index.ts", "../packages/roosterjs-color-utils/lib/index.ts", - "../packages-ui/roosterjs-react/lib/index.ts", + "../packages/roosterjs-react/lib/index.ts", "../packages/roosterjs/lib/index.ts", - "../packages-content-model/roosterjs-content-model-types/lib/index.ts", - "../packages-content-model/roosterjs-content-model-dom/lib/index.ts", - "../packages-content-model/roosterjs-content-model-core/lib/index.ts", - "../packages-content-model/roosterjs-content-model-api/lib/index.ts", - "../packages-content-model/roosterjs-content-model-plugins/lib/index.ts", + "../packages/roosterjs-content-model-types/lib/index.ts", + "../packages/roosterjs-content-model-dom/lib/index.ts", + "../packages/roosterjs-content-model-core/lib/index.ts", + "../packages/roosterjs-content-model-api/lib/index.ts", + "../packages/roosterjs-content-model-plugins/lib/index.ts", "../packages/roosterjs-editor-adapter/lib/index.ts", - "../packages-content-model/roosterjs-content-model/lib/index.ts" + "../packages/roosterjs-content-model/lib/index.ts" ], "plugin": ["typedoc-plugin-remove-references", "typedoc-plugin-external-module-map"], "out": "../dist/deploy/docs", diff --git a/versions.json b/versions.json index b11ce22809a..f9e5564a6fc 100644 --- a/versions.json +++ b/versions.json @@ -1,6 +1,7 @@ { - "packages": "0.0.0", - "packages-ui": "0.0.0", - "packages-content-model": "0.0.0", + "legacy": "0.0.0", + "react": "0.0.0", + "main": "0.0.0", + "legacyAdapter": "0.0.0", "overrides": {} } diff --git a/webpack.config.js b/webpack.config.js index d3763ed691b..cfdb0031578 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,13 +19,7 @@ module.exports = { }, resolve: { extensions: ['.ts', '.tsx', '.js', '.svg', '.scss', '.'], - modules: [ - './demo/scripts', - 'packages', - 'packages-ui', - 'packages-content-model', - './node_modules', - ], + modules: ['./demo/scripts', 'packages', './node_modules'], }, mode: 'development', module: { From 2d9d5f09e25a914ac9a30ec869870c274fc04115 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Mon, 4 Mar 2024 09:30:08 -0800 Subject: [PATCH 40/80] Port demo site 1: Move Content Model pane files (#2462) --- .../controls/ContentModelEditorMainPane.tsx | 6 +- .../controls/StandaloneEditorMainPane.tsx | 4 +- .../contentModel/ContentModelPane.tsx | 2 +- .../rooster/component/Rooster.tsx} | 6 +- .../contentModel/components/ButtonGroup.scss | 0 .../contentModel/components/ButtonGroup.tsx | 0 .../components/ContentModelView.scss | 0 .../components/ContentModelView.tsx | 0 .../components/format/BlockFormatView.tsx | 0 .../components/format/FormatView.scss | 0 .../components/format/FormatView.tsx | 0 .../components/format/LinkFormatView.tsx | 0 .../components/format/MetadataView.tsx | 0 .../components/format/SegmentFormatView.tsx | 0 .../BackgroundColorFormatRenderer.ts | 0 .../format/formatPart/BasicFormatRenderers.ts | 0 .../formatPart/BorderBoxFormatRenderer.ts | 0 .../formatPart/BorderFormatRenderers.ts | 0 .../formatPart/DirectionFormatRenderer.ts | 0 .../formatPart/DisplayFormatRenderer.ts | 0 .../format/formatPart/FloatFormatRenderer.ts | 0 .../formatPart/FontFamilyFormatRenderer.ts | 0 .../formatPart/FontSizeFormatRenderer.ts | 0 .../formatPart/HtmlAlignFormatRenderer.ts | 0 .../format/formatPart/IdFormatRenderer.ts | 0 .../ImageMetadataFormatRenderers.ts | 0 .../formatPart/LetterSpacingFormatRenderer.ts | 0 .../formatPart/LineHeightFormatRenderer.ts | 0 .../formatPart/ListMetadataFormatRenderers.ts | 0 .../ListStylePositionFormatRenderers.ts | 0 .../formatPart/ListThreadFormatRenderer.ts | 0 .../format/formatPart/MarginFormatRenderer.ts | 0 .../formatPart/PaddingFormatRenderer.ts | 0 .../format/formatPart/SizeFormatRenderers.ts | 0 .../formatPart/SpacingFormatRenderer.ts | 0 .../TableCellMetadataFormatRenders.ts | 0 .../formatPart/TableLayoutFormatRenderer.ts | 0 .../formatPart/TableMetadataFormatRenders.ts | 0 .../formatPart/TextAlignFormatRenderer.ts | 0 .../formatPart/TextColorFormatRenderer.ts | 0 .../formatPart/TextIndentFormatRenderer.ts | 0 .../formatPart/VerticalAlignFormatRenderer.ts | 0 .../formatPart/WhiteSpaceFormatRenderer.ts | 0 .../formatPart/WordBreakFormatRenderer.ts | 16 +- .../components/format/utils/FormatRenderer.ts | 0 .../utils/createCheckboxFormatRenderer.tsx | 0 .../format/utils/createColorFormatRender.tsx | 0 .../utils/createDropDownFormatRenderer.tsx | 0 .../format/utils/createTextFormatRenderer.tsx | 0 .../model/BlockGroupContentView.tsx | 0 .../model/ContentModelBlockGroupView.tsx | 0 .../model/ContentModelBlockView.tsx | 0 .../components/model/ContentModelBrView.scss | 0 .../components/model/ContentModelBrView.tsx | 0 .../model/ContentModelCodeView.scss | 0 .../components/model/ContentModelCodeView.tsx | 0 .../model/ContentModelDividerView.scss | 0 .../model/ContentModelDividerView.tsx | 0 .../model/ContentModelDocumentView.scss | 0 .../model/ContentModelDocumentView.tsx | 0 .../model/ContentModelEntityView.scss | 0 .../model/ContentModelEntityView.tsx | 0 .../ContentModelFormatContainerView.scss | 0 .../model/ContentModelFormatContainerView.tsx | 0 .../model/ContentModelGeneralView.scss | 0 .../model/ContentModelGeneralView.tsx | 0 .../model/ContentModelImageView.scss | 0 .../model/ContentModelImageView.tsx | 0 .../components/model/ContentModelJson.scss | 0 .../components/model/ContentModelJson.tsx | 0 .../model/ContentModelLinkView.scss | 0 .../components/model/ContentModelLinkView.tsx | 0 .../model/ContentModelListItemView.scss | 0 .../model/ContentModelListItemView.tsx | 0 .../model/ContentModelListLevelView.scss | 0 .../model/ContentModelListLevelView.tsx | 0 .../model/ContentModelParagraphView.scss | 0 .../model/ContentModelParagraphView.tsx | 0 .../model/ContentModelSegmentView.tsx | 0 .../ContentModelSelectionMarkerView.scss | 0 .../model/ContentModelSelectionMarkerView.tsx | 0 .../model/ContentModelTableCellView.scss | 0 .../model/ContentModelTableCellView.tsx | 262 +++++++++--------- .../model/ContentModelTableRowView.scss | 0 .../model/ContentModelTableRowView.tsx | 0 .../model/ContentModelTableView.scss | 0 .../model/ContentModelTableView.tsx | 0 .../model/ContentModelTextView.scss | 0 .../components/model/ContentModelTextView.tsx | 0 .../contentModel/hooks/useProperty.ts | 0 90 files changed, 147 insertions(+), 149 deletions(-) rename demo/scripts/{controls/contentModel/editor/ContentModelRooster.tsx => controlsV2/roosterjsReact/rooster/component/Rooster.tsx} (93%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/ButtonGroup.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/ButtonGroup.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/ContentModelView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/ContentModelView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/BlockFormatView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/FormatView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/FormatView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/LinkFormatView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/MetadataView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/SegmentFormatView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/BackgroundColorFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/BasicFormatRenderers.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/BorderBoxFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/BorderFormatRenderers.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/DirectionFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/DisplayFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/FloatFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/FontFamilyFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/FontSizeFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/HtmlAlignFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/IdFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/ImageMetadataFormatRenderers.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/LetterSpacingFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/LineHeightFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/ListMetadataFormatRenderers.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/ListStylePositionFormatRenderers.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/ListThreadFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/MarginFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/PaddingFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/SizeFormatRenderers.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/SpacingFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/TableCellMetadataFormatRenders.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/TableLayoutFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/TableMetadataFormatRenders.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/TextAlignFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/TextColorFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/TextIndentFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/VerticalAlignFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/WhiteSpaceFormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/formatPart/WordBreakFormatRenderer.ts (97%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/utils/FormatRenderer.ts (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/utils/createCheckboxFormatRenderer.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/utils/createColorFormatRender.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/utils/createDropDownFormatRenderer.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/format/utils/createTextFormatRenderer.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/BlockGroupContentView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelBlockGroupView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelBlockView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelBrView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelBrView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelCodeView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelCodeView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelDividerView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelDividerView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelDocumentView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelDocumentView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelEntityView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelEntityView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelFormatContainerView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelFormatContainerView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelGeneralView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelGeneralView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelImageView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelImageView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelJson.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelJson.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelLinkView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelLinkView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelListItemView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelListItemView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelListLevelView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelListLevelView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelParagraphView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelParagraphView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelSegmentView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelSelectionMarkerView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelSelectionMarkerView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelTableCellView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelTableCellView.tsx (97%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelTableRowView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelTableRowView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelTableView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelTableView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelTextView.scss (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/components/model/ContentModelTextView.tsx (100%) rename demo/scripts/{controls => controlsV2/sidePane}/contentModel/hooks/useProperty.ts (100%) diff --git a/demo/scripts/controls/ContentModelEditorMainPane.tsx b/demo/scripts/controls/ContentModelEditorMainPane.tsx index b09021fd353..4dd6f479db1 100644 --- a/demo/scripts/controls/ContentModelEditorMainPane.tsx +++ b/demo/scripts/controls/ContentModelEditorMainPane.tsx @@ -7,7 +7,6 @@ import ContentModelFormatPainterPlugin from './contentModel/plugins/ContentModel import ContentModelFormatStatePlugin from './sidePane/formatState/ContentModelFormatStatePlugin'; import ContentModelPanePlugin from './sidePane/contentModel/ContentModelPanePlugin'; import ContentModelRibbonButton from './ribbonButtons/contentModel/ContentModelRibbonButton'; -import ContentModelRooster from './contentModel/editor/ContentModelRooster'; import ContentModelSnapshotPlugin from './sidePane/snapshot/ContentModelSnapshotPlugin'; import getToggleablePlugins from './getToggleablePlugins'; import MainPaneBase, { MainPaneBaseState } from './MainPaneBase'; @@ -29,6 +28,7 @@ import { clearFormatButton } from './ribbonButtons/contentModel/clearFormatButto import { codeButton } from './ribbonButtons/contentModel/codeButton'; import { ContentModelRibbon } from './ribbonButtons/contentModel/ContentModelRibbon'; import { ContentModelRibbonPlugin } from './ribbonButtons/contentModel/ContentModelRibbonPlugin'; +import { ContentModelSegmentFormat, IEditor, Snapshots } from 'roosterjs-content-model-types'; import { createEmojiPlugin, createPasteOptionPlugin } from 'roosterjs-react'; import { darkMode } from './ribbonButtons/contentModel/darkMode'; import { decreaseFontSizeButton } from './ribbonButtons/contentModel/decreaseFontSizeButton'; @@ -60,6 +60,7 @@ import { pasteButton } from './ribbonButtons/contentModel/pasteButton'; import { popout } from './ribbonButtons/contentModel/popout'; import { redoButton } from './ribbonButtons/contentModel/redoButton'; import { removeLinkButton } from './ribbonButtons/contentModel/removeLinkButton'; +import { Rooster } from '../controlsV2/roosterjsReact/rooster/component/Rooster'; import { rtlButton } from './ribbonButtons/contentModel/rtlButton'; import { setBulletedListStyleButton } from './ribbonButtons/contentModel/setBulletedListStyleButton'; import { setHeadingLevelButton } from './ribbonButtons/contentModel/setHeadingLevelButton'; @@ -79,7 +80,6 @@ import { trustedHTMLHandler } from '../utils/trustedHTMLHandler'; import { underlineButton } from './ribbonButtons/contentModel/underlineButton'; import { undoButton } from './ribbonButtons/contentModel/undoButton'; import { zoom } from './ribbonButtons/contentModel/zoom'; -import { ContentModelSegmentFormat, IEditor, Snapshots } from 'roosterjs-content-model-types'; import { AutoFormatPlugin, EditPlugin, @@ -377,7 +377,7 @@ class ContentModelEditorMainPane extends MainPaneBase
{this.state.editorCreator && ( -
{this.state.editorCreator && ( - { +export interface RoosterProps extends EditorAdapterOptions, React.HTMLAttributes { /** * Creator function used for creating the instance of roosterjs editor. * Use this callback when you have your own sub class of roosterjs Editor or force trigging a reset of editor @@ -30,7 +28,7 @@ export interface ContentModelRoosterProps * @param props Properties of this component * @returns The react component */ -export default function ContentModelRooster(props: ContentModelRoosterProps) { +export function Rooster(props: RoosterProps) { const editorDiv = React.useRef(null); const editor = React.useRef(null); const theme = useTheme(); diff --git a/demo/scripts/controls/contentModel/components/ButtonGroup.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/ButtonGroup.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/ButtonGroup.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/ButtonGroup.scss diff --git a/demo/scripts/controls/contentModel/components/ButtonGroup.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/ButtonGroup.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/ButtonGroup.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/ButtonGroup.tsx diff --git a/demo/scripts/controls/contentModel/components/ContentModelView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/ContentModelView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/ContentModelView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/ContentModelView.scss diff --git a/demo/scripts/controls/contentModel/components/ContentModelView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/ContentModelView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/ContentModelView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/ContentModelView.tsx diff --git a/demo/scripts/controls/contentModel/components/format/BlockFormatView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/BlockFormatView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/format/BlockFormatView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/BlockFormatView.tsx diff --git a/demo/scripts/controls/contentModel/components/format/FormatView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/format/FormatView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/format/FormatView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/FormatView.scss diff --git a/demo/scripts/controls/contentModel/components/format/FormatView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/FormatView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/format/FormatView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/FormatView.tsx diff --git a/demo/scripts/controls/contentModel/components/format/LinkFormatView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/LinkFormatView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/format/LinkFormatView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/LinkFormatView.tsx diff --git a/demo/scripts/controls/contentModel/components/format/MetadataView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/MetadataView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/format/MetadataView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/MetadataView.tsx diff --git a/demo/scripts/controls/contentModel/components/format/SegmentFormatView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/SegmentFormatView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/format/SegmentFormatView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/SegmentFormatView.tsx diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/BackgroundColorFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BackgroundColorFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/BackgroundColorFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BackgroundColorFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/BasicFormatRenderers.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BasicFormatRenderers.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/BasicFormatRenderers.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BasicFormatRenderers.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/BorderBoxFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BorderBoxFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/BorderBoxFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BorderBoxFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/BorderFormatRenderers.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BorderFormatRenderers.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/BorderFormatRenderers.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BorderFormatRenderers.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/DirectionFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/DirectionFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/DirectionFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/DirectionFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/DisplayFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/DisplayFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/DisplayFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/DisplayFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/FloatFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/FloatFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/FloatFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/FloatFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/FontFamilyFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/FontFamilyFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/FontFamilyFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/FontFamilyFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/FontSizeFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/FontSizeFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/FontSizeFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/FontSizeFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/HtmlAlignFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/HtmlAlignFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/HtmlAlignFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/HtmlAlignFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/IdFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/IdFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/IdFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/IdFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/ImageMetadataFormatRenderers.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/ImageMetadataFormatRenderers.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/ImageMetadataFormatRenderers.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/ImageMetadataFormatRenderers.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/LetterSpacingFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/LetterSpacingFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/LetterSpacingFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/LetterSpacingFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/LineHeightFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/LineHeightFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/LineHeightFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/LineHeightFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/ListMetadataFormatRenderers.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/ListMetadataFormatRenderers.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/ListMetadataFormatRenderers.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/ListMetadataFormatRenderers.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/ListStylePositionFormatRenderers.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/ListStylePositionFormatRenderers.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/ListStylePositionFormatRenderers.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/ListStylePositionFormatRenderers.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/ListThreadFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/ListThreadFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/ListThreadFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/ListThreadFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/MarginFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/MarginFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/MarginFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/MarginFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/PaddingFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/PaddingFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/PaddingFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/PaddingFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/SizeFormatRenderers.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/SizeFormatRenderers.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/SizeFormatRenderers.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/SizeFormatRenderers.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/SpacingFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/SpacingFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/SpacingFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/SpacingFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/TableCellMetadataFormatRenders.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableCellMetadataFormatRenders.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/TableCellMetadataFormatRenders.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableCellMetadataFormatRenders.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/TableLayoutFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableLayoutFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/TableLayoutFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableLayoutFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/TableMetadataFormatRenders.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableMetadataFormatRenders.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/TableMetadataFormatRenders.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableMetadataFormatRenders.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/TextAlignFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextAlignFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/TextAlignFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextAlignFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/TextColorFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextColorFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/TextColorFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextColorFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/TextIndentFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextIndentFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/TextIndentFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextIndentFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/VerticalAlignFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/VerticalAlignFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/VerticalAlignFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/VerticalAlignFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/WhiteSpaceFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/WhiteSpaceFormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/formatPart/WhiteSpaceFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/WhiteSpaceFormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/formatPart/WordBreakFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/WordBreakFormatRenderer.ts similarity index 97% rename from demo/scripts/controls/contentModel/components/format/formatPart/WordBreakFormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/WordBreakFormatRenderer.ts index 137f013dee8..32942cb40a5 100644 --- a/demo/scripts/controls/contentModel/components/format/formatPart/WordBreakFormatRenderer.ts +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/WordBreakFormatRenderer.ts @@ -1,8 +1,8 @@ -import { createTextFormatRenderer } from '../utils/createTextFormatRenderer'; -import { WordBreakFormat } from 'roosterjs-content-model-types'; - -export const WordBreakFormatRenderer = createTextFormatRenderer( - 'Word break', - format => format.wordBreak, - (format, value) => (format.wordBreak = value) -); +import { createTextFormatRenderer } from '../utils/createTextFormatRenderer'; +import { WordBreakFormat } from 'roosterjs-content-model-types'; + +export const WordBreakFormatRenderer = createTextFormatRenderer( + 'Word break', + format => format.wordBreak, + (format, value) => (format.wordBreak = value) +); diff --git a/demo/scripts/controls/contentModel/components/format/utils/FormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/FormatRenderer.ts similarity index 100% rename from demo/scripts/controls/contentModel/components/format/utils/FormatRenderer.ts rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/FormatRenderer.ts diff --git a/demo/scripts/controls/contentModel/components/format/utils/createCheckboxFormatRenderer.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createCheckboxFormatRenderer.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/format/utils/createCheckboxFormatRenderer.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createCheckboxFormatRenderer.tsx diff --git a/demo/scripts/controls/contentModel/components/format/utils/createColorFormatRender.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createColorFormatRender.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/format/utils/createColorFormatRender.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createColorFormatRender.tsx diff --git a/demo/scripts/controls/contentModel/components/format/utils/createDropDownFormatRenderer.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createDropDownFormatRenderer.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/format/utils/createDropDownFormatRenderer.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createDropDownFormatRenderer.tsx diff --git a/demo/scripts/controls/contentModel/components/format/utils/createTextFormatRenderer.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createTextFormatRenderer.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/format/utils/createTextFormatRenderer.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createTextFormatRenderer.tsx diff --git a/demo/scripts/controls/contentModel/components/model/BlockGroupContentView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/BlockGroupContentView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/BlockGroupContentView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/BlockGroupContentView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelBlockGroupView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelBlockGroupView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelBlockGroupView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelBlockGroupView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelBlockView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelBlockView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelBlockView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelBlockView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelBrView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelBrView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelBrView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelBrView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelBrView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelBrView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelBrView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelBrView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelCodeView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelCodeView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelCodeView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelCodeView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelCodeView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelCodeView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelCodeView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelCodeView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelDividerView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDividerView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelDividerView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDividerView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelDividerView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDividerView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelDividerView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDividerView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelDocumentView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelDocumentView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelDocumentView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelDocumentView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelEntityView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelEntityView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelEntityView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelEntityView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelEntityView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelEntityView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelEntityView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelEntityView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelFormatContainerView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelFormatContainerView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelFormatContainerView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelFormatContainerView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelFormatContainerView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelFormatContainerView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelFormatContainerView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelFormatContainerView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelGeneralView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelGeneralView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelGeneralView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelGeneralView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelGeneralView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelGeneralView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelGeneralView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelGeneralView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelImageView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelImageView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelImageView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelImageView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelImageView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelImageView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelImageView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelImageView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelJson.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelJson.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelJson.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelJson.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelJson.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelJson.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelJson.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelJson.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelLinkView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelLinkView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelLinkView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelLinkView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelLinkView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelLinkView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelLinkView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelLinkView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelListItemView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListItemView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelListItemView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListItemView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelListItemView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListItemView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelListItemView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListItemView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelListLevelView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListLevelView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelListLevelView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListLevelView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelListLevelView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListLevelView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelListLevelView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListLevelView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelParagraphView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelParagraphView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelParagraphView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelParagraphView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelParagraphView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelParagraphView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelParagraphView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelParagraphView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelSegmentView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelSegmentView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelSegmentView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelSegmentView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelSelectionMarkerView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelSelectionMarkerView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelSelectionMarkerView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelSelectionMarkerView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelSelectionMarkerView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelSelectionMarkerView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelSelectionMarkerView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelSelectionMarkerView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelTableCellView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableCellView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelTableCellView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableCellView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelTableCellView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableCellView.tsx similarity index 97% rename from demo/scripts/controls/contentModel/components/model/ContentModelTableCellView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableCellView.tsx index a932ab5ba96..3c9987da8c2 100644 --- a/demo/scripts/controls/contentModel/components/model/ContentModelTableCellView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableCellView.tsx @@ -1,131 +1,131 @@ -import * as React from 'react'; -import { BackgroundColorFormatRenderer } from '../format/formatPart/BackgroundColorFormatRenderer'; -import { BlockGroupContentView } from './BlockGroupContentView'; -import { BorderBoxFormatRenderer } from '../format/formatPart/BorderBoxFormatRenderer'; -import { BorderFormatRenderers } from '../format/formatPart/BorderFormatRenderers'; -import { ContentModelTableCell, ContentModelTableCellFormat } from 'roosterjs-content-model-types'; -import { ContentModelView } from '../ContentModelView'; -import { DirectionFormatRenderer } from '../format/formatPart/DirectionFormatRenderer'; -import { FormatRenderer } from '../format/utils/FormatRenderer'; -import { FormatView } from '../format/FormatView'; -import { hasSelectionInBlockGroup, updateTableCellMetadata } from 'roosterjs-content-model-core'; -import { HtmlAlignFormatRenderer } from '../format/formatPart/HtmlAlignFormatRenderer'; -import { MetadataView } from '../format/MetadataView'; -import { PaddingFormatRenderer } from '../format/formatPart/PaddingFormatRenderer'; -import { SizeFormatRenderers } from '../format/formatPart/SizeFormatRenderers'; -import { TableCellMetadataFormatRenders } from '../format/formatPart/TableCellMetadataFormatRenders'; -import { TextAlignFormatRenderer } from '../format/formatPart/TextAlignFormatRenderer'; -import { TextColorFormatRenderer } from '../format/formatPart/TextColorFormatRenderer'; -import { useProperty } from '../../hooks/useProperty'; -import { VerticalAlignFormatRenderer } from '../format/formatPart/VerticalAlignFormatRenderer'; -import { WordBreakFormatRenderer } from '../format/formatPart/WordBreakFormatRenderer'; - -const styles = require('./ContentModelTableCellView.scss'); - -const TableCellFormatRenderers: FormatRenderer[] = [ - ...BorderFormatRenderers, - DirectionFormatRenderer, - TextAlignFormatRenderer, - HtmlAlignFormatRenderer, - BorderBoxFormatRenderer, - BackgroundColorFormatRenderer, - PaddingFormatRenderer, - VerticalAlignFormatRenderer, - WordBreakFormatRenderer, - TextColorFormatRenderer, - ...SizeFormatRenderers, -]; - -export function ContentModelTableCellView(props: { cell: ContentModelTableCell }) { - const { cell } = props; - const checkboxHeader = React.useRef(null); - const checkboxSpanLeft = React.useRef(null); - const checkboxSpanAbove = React.useRef(null); - const [isHeader, setIsHeader] = useProperty(cell.isHeader); - const [spanLeft, setSpanLeft] = useProperty(cell.spanLeft); - const [spanAbove, setSpanAbove] = useProperty(cell.spanAbove); - - const onHeaderChanged = React.useCallback(() => { - const value = checkboxHeader.current.checked; - cell.isHeader = value; - setIsHeader(value); - }, [cell, setIsHeader]); - - const onSpanLeftChanged = React.useCallback(() => { - const value = checkboxSpanLeft.current.checked; - cell.spanLeft = value; - setSpanLeft(value); - }, [cell, setSpanLeft]); - - const onSpanAboveChanged = React.useCallback(() => { - const value = checkboxSpanAbove.current.checked; - cell.spanAbove = value; - setSpanAbove(value); - }, [cell, setSpanAbove]); - - const getContent = React.useCallback(() => { - return ( - <> -
- - Header -
-
- - Span Left -
-
- - Span Above -
- - - ); - }, [cell, isHeader, spanAbove, spanLeft]); - - const getMetadata = React.useCallback(() => { - return ( - - ); - }, [cell]); - - const getFormat = React.useCallback(() => { - return ; - }, [cell.format]); - - const subTitle = - cell.spanAbove && cell.spanLeft ? '↖' : cell.spanLeft ? '←' : cell.spanAbove ? '↑' : ''; - - return ( - - ); -} +import * as React from 'react'; +import { BackgroundColorFormatRenderer } from '../format/formatPart/BackgroundColorFormatRenderer'; +import { BlockGroupContentView } from './BlockGroupContentView'; +import { BorderBoxFormatRenderer } from '../format/formatPart/BorderBoxFormatRenderer'; +import { BorderFormatRenderers } from '../format/formatPart/BorderFormatRenderers'; +import { ContentModelTableCell, ContentModelTableCellFormat } from 'roosterjs-content-model-types'; +import { ContentModelView } from '../ContentModelView'; +import { DirectionFormatRenderer } from '../format/formatPart/DirectionFormatRenderer'; +import { FormatRenderer } from '../format/utils/FormatRenderer'; +import { FormatView } from '../format/FormatView'; +import { hasSelectionInBlockGroup, updateTableCellMetadata } from 'roosterjs-content-model-core'; +import { HtmlAlignFormatRenderer } from '../format/formatPart/HtmlAlignFormatRenderer'; +import { MetadataView } from '../format/MetadataView'; +import { PaddingFormatRenderer } from '../format/formatPart/PaddingFormatRenderer'; +import { SizeFormatRenderers } from '../format/formatPart/SizeFormatRenderers'; +import { TableCellMetadataFormatRenders } from '../format/formatPart/TableCellMetadataFormatRenders'; +import { TextAlignFormatRenderer } from '../format/formatPart/TextAlignFormatRenderer'; +import { TextColorFormatRenderer } from '../format/formatPart/TextColorFormatRenderer'; +import { useProperty } from '../../hooks/useProperty'; +import { VerticalAlignFormatRenderer } from '../format/formatPart/VerticalAlignFormatRenderer'; +import { WordBreakFormatRenderer } from '../format/formatPart/WordBreakFormatRenderer'; + +const styles = require('./ContentModelTableCellView.scss'); + +const TableCellFormatRenderers: FormatRenderer[] = [ + ...BorderFormatRenderers, + DirectionFormatRenderer, + TextAlignFormatRenderer, + HtmlAlignFormatRenderer, + BorderBoxFormatRenderer, + BackgroundColorFormatRenderer, + PaddingFormatRenderer, + VerticalAlignFormatRenderer, + WordBreakFormatRenderer, + TextColorFormatRenderer, + ...SizeFormatRenderers, +]; + +export function ContentModelTableCellView(props: { cell: ContentModelTableCell }) { + const { cell } = props; + const checkboxHeader = React.useRef(null); + const checkboxSpanLeft = React.useRef(null); + const checkboxSpanAbove = React.useRef(null); + const [isHeader, setIsHeader] = useProperty(cell.isHeader); + const [spanLeft, setSpanLeft] = useProperty(cell.spanLeft); + const [spanAbove, setSpanAbove] = useProperty(cell.spanAbove); + + const onHeaderChanged = React.useCallback(() => { + const value = checkboxHeader.current.checked; + cell.isHeader = value; + setIsHeader(value); + }, [cell, setIsHeader]); + + const onSpanLeftChanged = React.useCallback(() => { + const value = checkboxSpanLeft.current.checked; + cell.spanLeft = value; + setSpanLeft(value); + }, [cell, setSpanLeft]); + + const onSpanAboveChanged = React.useCallback(() => { + const value = checkboxSpanAbove.current.checked; + cell.spanAbove = value; + setSpanAbove(value); + }, [cell, setSpanAbove]); + + const getContent = React.useCallback(() => { + return ( + <> +
+ + Header +
+
+ + Span Left +
+
+ + Span Above +
+ + + ); + }, [cell, isHeader, spanAbove, spanLeft]); + + const getMetadata = React.useCallback(() => { + return ( + + ); + }, [cell]); + + const getFormat = React.useCallback(() => { + return ; + }, [cell.format]); + + const subTitle = + cell.spanAbove && cell.spanLeft ? '↖' : cell.spanLeft ? '←' : cell.spanAbove ? '↑' : ''; + + return ( + + ); +} diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelTableRowView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableRowView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelTableRowView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableRowView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelTableRowView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableRowView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelTableRowView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableRowView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelTableView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelTableView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelTableView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelTableView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelTextView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTextView.scss similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelTextView.scss rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTextView.scss diff --git a/demo/scripts/controls/contentModel/components/model/ContentModelTextView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTextView.tsx similarity index 100% rename from demo/scripts/controls/contentModel/components/model/ContentModelTextView.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTextView.tsx diff --git a/demo/scripts/controls/contentModel/hooks/useProperty.ts b/demo/scripts/controlsV2/sidePane/contentModel/hooks/useProperty.ts similarity index 100% rename from demo/scripts/controls/contentModel/hooks/useProperty.ts rename to demo/scripts/controlsV2/sidePane/contentModel/hooks/useProperty.ts From 3f4928bad513c08383cf8ebaf8c549a1541f6761 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Mon, 4 Mar 2024 13:26:33 -0800 Subject: [PATCH 41/80] Port demo site step 2 (#2465) --- .../lib/editor/DOMHelperImpl.ts | 17 ++- .../roosterjs-content-model-core/lib/index.ts | 2 + .../lib/utils/extractClipboardItems.ts | 1 - .../test/editor/DOMHelperImplTest.ts | 56 +++++++++ .../contextMenuBase/ContextMenuPluginBase.ts | 112 ++++++++++++++++++ .../lib/index.ts | 1 + .../lib/index.ts | 1 + .../lib/parameter/DOMHelper.ts | 9 ++ .../lib/parameter/ImageEditor.ts | 54 +++++++++ .../lib/editor/EditorAdapter.ts | 4 +- 10 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 packages/roosterjs-content-model-plugins/lib/contextMenuBase/ContextMenuPluginBase.ts create mode 100644 packages/roosterjs-content-model-types/lib/parameter/ImageEditor.ts diff --git a/packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts b/packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts index 998887d351c..db6bbdb86eb 100644 --- a/packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts +++ b/packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts @@ -1,4 +1,4 @@ -import { toArray } from 'roosterjs-content-model-dom'; +import { isNodeOfType, toArray } from 'roosterjs-content-model-dom'; import type { DOMHelper } from 'roosterjs-content-model-types'; class DOMHelperImpl implements DOMHelper { @@ -40,6 +40,21 @@ class DOMHelperImpl implements DOMHelper { getDomStyle(style: T): CSSStyleDeclaration[T] { return this.contentDiv.style[style]; } + + findClosestElementAncestor(startFrom: Node, selector?: string): HTMLElement | null { + const startElement = isNodeOfType(startFrom, 'ELEMENT_NODE') + ? startFrom + : startFrom.parentElement; + const closestElement = selector + ? (startElement?.closest(selector) as HTMLElement | null) + : startElement; + + return closestElement && + this.isNodeInEditor(closestElement) && + closestElement != this.contentDiv + ? closestElement + : null; + } } /** diff --git a/packages/roosterjs-content-model-core/lib/index.ts b/packages/roosterjs-content-model-core/lib/index.ts index 6407e084e5f..ca10b6cde89 100644 --- a/packages/roosterjs-content-model-core/lib/index.ts +++ b/packages/roosterjs-content-model-core/lib/index.ts @@ -61,4 +61,6 @@ export { BulletListType } from './constants/BulletListType'; export { NumberingListType } from './constants/NumberingListType'; export { TableBorderFormat } from './constants/TableBorderFormat'; +export { extractClipboardItems } from './utils/extractClipboardItems'; + export { Editor } from './editor/Editor'; diff --git a/packages/roosterjs-content-model-core/lib/utils/extractClipboardItems.ts b/packages/roosterjs-content-model-core/lib/utils/extractClipboardItems.ts index 2657c9d17d9..85b1749cf0c 100644 --- a/packages/roosterjs-content-model-core/lib/utils/extractClipboardItems.ts +++ b/packages/roosterjs-content-model-core/lib/utils/extractClipboardItems.ts @@ -11,7 +11,6 @@ const ContentHandlers: { }; /** - * @internal * Extract clipboard items to be a ClipboardData object for IE * @param items The clipboard items retrieve from a DataTransfer object * @param allowedCustomPasteType Allowed custom content type when paste besides text/plain, text/html and images diff --git a/packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts b/packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts index 0774387e1e3..71c65b30d4b 100644 --- a/packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts +++ b/packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts @@ -123,4 +123,60 @@ describe('DOMHelperImpl', () => { expect(result).toBe(mockedValue); }); + + it('findClosestElementAncestor - text node, no parent, no selector', () => { + const startNode = document.createTextNode('test'); + const container = document.createElement('div'); + + container.appendChild(startNode); + + const domHelper = createDOMHelper(container); + const result = domHelper.findClosestElementAncestor(startNode); + + expect(result).toBeNull(); + }); + + it('findClosestElementAncestor - text node, has parent, no selector', () => { + const startNode = document.createTextNode('test'); + const parent = document.createElement('span'); + const container = document.createElement('div'); + + parent.appendChild(startNode); + container.appendChild(parent); + + const domHelper = createDOMHelper(container); + const result = domHelper.findClosestElementAncestor(startNode); + + expect(result).toBe(parent); + }); + + it('findClosestElementAncestor - element node, no selector', () => { + const startNode = document.createElement('span'); + const container = document.createElement('div'); + + container.appendChild(startNode); + + const domHelper = createDOMHelper(container); + const result = domHelper.findClosestElementAncestor(startNode); + + expect(result).toBe(startNode); + }); + + it('findClosestElementAncestor - has selector', () => { + const startNode = document.createElement('span'); + const parent1 = document.createElement('div'); + const parent2 = document.createElement('div'); + const container = document.createElement('div'); + + parent2.className = 'testClass'; + + parent1.appendChild(startNode); + parent2.appendChild(parent1); + container.appendChild(parent2); + + const domHelper = createDOMHelper(container); + const result = domHelper.findClosestElementAncestor(startNode, '.testClass'); + + expect(result).toBe(parent2); + }); }); diff --git a/packages/roosterjs-content-model-plugins/lib/contextMenuBase/ContextMenuPluginBase.ts b/packages/roosterjs-content-model-plugins/lib/contextMenuBase/ContextMenuPluginBase.ts new file mode 100644 index 00000000000..6ad505303a5 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/lib/contextMenuBase/ContextMenuPluginBase.ts @@ -0,0 +1,112 @@ +import type { EditorPlugin, IEditor, PluginEvent } from 'roosterjs-content-model-types'; + +/** + * Context Menu options for ContextMenu plugin + */ +export interface ContextMenuOptions { + /** + * Render function for the context menu + * @param container The container HTML element, it will be located at the mouse click position, + * so the callback just need to render menu content into this container + * @param onDismiss The onDismiss callback, some menu render need to know this callback so that + * it can handle the dismiss event + */ + render: (container: HTMLElement, items: (T | null)[], onDismiss: () => void) => void; + + /** + * Dismiss function for the context menu, it will be called when user wants to dismiss this context menu + * e.g. user click away so the menu should be dismissed + * @param container The container HTML element + */ + dismiss?: (container: HTMLElement) => void; + + /** + * Whether the default context menu is allowed. @default false + */ + allowDefaultMenu?: boolean; +} + +/** + * An editor plugin that support showing a context menu using render() function from options parameter + */ +export class ContextMenuPluginBase implements EditorPlugin { + private container: HTMLElement | null = null; + private editor: IEditor | null = null; + private isMenuShowing: boolean = false; + + /** + * Create a new instance of ContextMenu class + * @param options An options object to determine how to show/hide the context menu + */ + constructor(private options: ContextMenuOptions) {} + + /** + * Get a friendly name of this plugin + */ + getName() { + return 'ContextMenu'; + } + + /** + * Initialize this plugin + * @param editor The editor instance + */ + initialize(editor: IEditor) { + this.editor = editor; + } + + /** + * Dispose this plugin + */ + dispose() { + this.onDismiss(); + + if (this.container?.parentNode) { + this.container.parentNode.removeChild(this.container); + this.container = null; + } + this.editor = null; + } + + /** + * Handle events triggered from editor + * @param event PluginEvent object + */ + onPluginEvent(event: PluginEvent) { + if (event.eventType == 'contextMenu' && event.items.length > 0) { + const { rawEvent, items } = event; + + this.onDismiss(); + + if (!this.options.allowDefaultMenu) { + rawEvent.preventDefault(); + } + + if (this.initContainer(rawEvent.pageX, rawEvent.pageY)) { + this.options.render(this.container!, items as T[], this.onDismiss); + this.isMenuShowing = true; + } + } + } + + private initContainer(x: number, y: number) { + if (!this.container && this.editor) { + this.container = this.editor.getDocument().createElement('div'); + + this.container.style.position = 'fixed'; + this.container.style.width = '0'; + this.container.style.height = '0'; + this.editor.getDocument().body.appendChild(this.container); + } + this.container?.style.setProperty('left', x + 'px'); + this.container?.style.setProperty('top', y + 'px'); + return !!this.container; + } + + private onDismiss = () => { + if (this.container && this.isMenuShowing) { + this.options.dismiss?.(this.container); + this.isMenuShowing = false; + } + }; +} diff --git a/packages/roosterjs-content-model-plugins/lib/index.ts b/packages/roosterjs-content-model-plugins/lib/index.ts index 0f75ee9b442..7fe66a69c49 100644 --- a/packages/roosterjs-content-model-plugins/lib/index.ts +++ b/packages/roosterjs-content-model-plugins/lib/index.ts @@ -19,3 +19,4 @@ export { } from './shortcut/shortcuts'; export { ShortcutPlugin } from './shortcut/ShortcutPlugin'; export { ShortcutKeyDefinition, ShortcutCommand } from './shortcut/ShortcutCommand'; +export { ContextMenuPluginBase, ContextMenuOptions } from './contextMenuBase/ContextMenuPluginBase'; diff --git a/packages/roosterjs-content-model-types/lib/index.ts b/packages/roosterjs-content-model-types/lib/index.ts index d7ed66a4434..ea49333ccc1 100644 --- a/packages/roosterjs-content-model-types/lib/index.ts +++ b/packages/roosterjs-content-model-types/lib/index.ts @@ -284,6 +284,7 @@ export { TrustedHTMLHandler } from './parameter/TrustedHTMLHandler'; export { Rect } from './parameter/Rect'; export { ValueSanitizer } from './parameter/ValueSanitizer'; export { DOMHelper } from './parameter/DOMHelper'; +export { ImageEditOperation, ImageEditor } from './parameter/ImageEditor'; export { BasePluginEvent, BasePluginDomEvent } from './event/BasePluginEvent'; export { BeforeCutCopyEvent } from './event/BeforeCutCopyEvent'; diff --git a/packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts b/packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts index 65b2927178f..216a13af4a5 100644 --- a/packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts +++ b/packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts @@ -54,4 +54,13 @@ export interface DOMHelper { * @param style Name of the style */ getDomStyle(style: T): CSSStyleDeclaration[T]; + + /** + * Find closest element ancestor start from the given node which matches the given selector + * @param node Find ancestor start from this node + * @param selector The expected selector. If null, return the first HTML Element found from start node + * @returns An HTML element which matches the given selector. If the given start node matches the selector, + * returns the given node + */ + findClosestElementAncestor(node: Node, selector?: string): HTMLElement | null; } diff --git a/packages/roosterjs-content-model-types/lib/parameter/ImageEditor.ts b/packages/roosterjs-content-model-types/lib/parameter/ImageEditor.ts new file mode 100644 index 00000000000..d1b93b8a8ef --- /dev/null +++ b/packages/roosterjs-content-model-types/lib/parameter/ImageEditor.ts @@ -0,0 +1,54 @@ +/** + * Type of image editing operations + */ +export type ImageEditOperation = + /** + * Resize an image + */ + | 'resize' + + /** + * Rotate an image + */ + | 'rotate' + + /** + * Crop an image + */ + | 'crop'; + +/** + * Define the common operation of an image editor + */ +export interface ImageEditor { + /** + * Check if the given editing operation is allowed on current selected image + * @param operation The operation to check + * @returns True if the operation is allowed, otherwise false + */ + isOperationAllowed(operation: ImageEditOperation): boolean; + + /** + * Check if the given image can be regenerated by this image editor + * @param image The image to check + * @returns True if the image can be regenerated, otherwise false + */ + canRegenerateImage(image: HTMLImageElement): boolean; + + /** + * Rotate selected image to the given angle (in rad) + * @param angleRad The angle to rotate to + */ + rotateImage(angleRad: number): void; + + /** + * Flip the image. + * @param direction Direction of flip, can be vertical or horizontal + */ + flipImage(direction: 'vertical' | 'horizontal'): void; + + /** + * Start to crop selected image + */ + cropImage(): void; +} diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts index 84a85f712fc..47cdc2a1a4e 100644 --- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts +++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts @@ -78,7 +78,6 @@ import { collapseNodes, contains, deleteSelectedContent, - findClosestElementAncestor, getBlockElementAtNode, getRegionsFromRange, getSelectionPath, @@ -563,8 +562,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { startFrom = position?.node; } return ( - startFrom && - findClosestElementAncestor(startFrom, this.getCore().physicalRoot, selector) + startFrom && this.getDOMHelper().findClosestElementAncestor(startFrom, selector) ); }) ?? null ); From 92fafd147217a7eb9e8f1f8696ee4e46332c2f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Mon, 4 Mar 2024 20:20:11 -0300 Subject: [PATCH 42/80] WIP --- .../lib/index.ts | 2 + .../lib/shortcut/ShortcutPlugin.ts | 4 + .../lib/shortcut/shortcuts.ts | 49 ++++++- .../test/shortcut/ShortcutPluginTest.ts | 130 +++++++++++++++++- 4 files changed, 183 insertions(+), 2 deletions(-) diff --git a/packages/roosterjs-content-model-plugins/lib/index.ts b/packages/roosterjs-content-model-plugins/lib/index.ts index 7fe66a69c49..d84caa771f5 100644 --- a/packages/roosterjs-content-model-plugins/lib/index.ts +++ b/packages/roosterjs-content-model-plugins/lib/index.ts @@ -16,6 +16,8 @@ export { ShortcutNumbering, ShortcutIncreaseFont, ShortcutDecreaseFont, + ShortcutIndentList, + ShortcutOutdentList, } from './shortcut/shortcuts'; export { ShortcutPlugin } from './shortcut/ShortcutPlugin'; export { ShortcutKeyDefinition, ShortcutCommand } from './shortcut/ShortcutCommand'; diff --git a/packages/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts b/packages/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts index 9b7fe7a3f60..244fa268747 100644 --- a/packages/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts @@ -6,8 +6,10 @@ import { ShortcutClearFormat, ShortcutDecreaseFont, ShortcutIncreaseFont, + ShortcutIndentList, ShortcutItalic, ShortcutNumbering, + ShortcutOutdentList, ShortcutRedo, ShortcutRedoMacOS, ShortcutUnderline, @@ -34,6 +36,8 @@ const defaultShortcuts: ShortcutCommand[] = [ ShortcutNumbering, ShortcutIncreaseFont, ShortcutDecreaseFont, + ShortcutIndentList, + ShortcutOutdentList, ]; const CommandCacheKey = '__ShortcutCommandCache'; diff --git a/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts b/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts index 8c2520485c4..101936b58a4 100644 --- a/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts +++ b/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts @@ -1,7 +1,8 @@ -import { redo, undo } from 'roosterjs-content-model-core'; +import { getFirstSelectedListItem, redo, undo } from 'roosterjs-content-model-core'; import { changeFontSize, clearFormat, + setModelIndentation, toggleBold, toggleBullet, toggleItalic, @@ -9,6 +10,7 @@ import { toggleUnderline, } from 'roosterjs-content-model-api'; import type { ShortcutCommand } from './ShortcutCommand'; +import type { IEditor } from 'roosterjs-content-model-types'; const enum Keys { BACKSPACE = 8, @@ -21,6 +23,8 @@ const enum Keys { COMMA = 188, PERIOD = 190, FORWARD_SLASH = 191, + ArrowRight = 39, + ArrowLeft = 37, } /** @@ -193,3 +197,46 @@ export const ShortcutDecreaseFont: ShortcutCommand = { }, onClick: editor => changeFontSize(editor, 'decrease'), }; + +/** + * Shortcut command for Intent list + */ +export const ShortcutIndentList: ShortcutCommand = { + shortcutKey: { + modifierKey: 'alt', + shiftKey: true, + which: Keys.ArrowRight, + }, + onClick: editor => { + setShortcutIndentationCommand(editor, 'indent'); + }, +}; + +/** + * Shortcut command for Intent list + */ +export const ShortcutOutdentList: ShortcutCommand = { + shortcutKey: { + modifierKey: 'alt', + shiftKey: true, + which: Keys.ArrowLeft, + }, + onClick: editor => { + setShortcutIndentationCommand(editor, 'outdent'); + }, +}; + +function setShortcutIndentationCommand(editor: IEditor, operation: 'indent' | 'outdent') { + editor.formatContentModel(model => { + const listItem = getFirstSelectedListItem(model); + if ( + listItem && + listItem.blocks[0].blockType == 'Paragraph' && + listItem.blocks[0].segments[0].segmentType == 'SelectionMarker' + ) { + setModelIndentation(model, operation); + return true; + } + return false; + }); +} diff --git a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts index 0c4a69a47d0..220893bc20f 100644 --- a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts @@ -1,14 +1,22 @@ import * as changeFontSize from 'roosterjs-content-model-api/lib/publicApi/segment/changeFontSize'; import * as clearFormat from 'roosterjs-content-model-api/lib/publicApi/format/clearFormat'; import * as redo from 'roosterjs-content-model-core/lib/publicApi/undo/redo'; +import * as setModelIndentation from 'roosterjs-content-model-api/lib/modelApi/block/setModelIndentation'; import * as toggleBold from 'roosterjs-content-model-api/lib/publicApi/segment/toggleBold'; import * as toggleBullet from 'roosterjs-content-model-api/lib/publicApi/list/toggleBullet'; import * as toggleItalic from 'roosterjs-content-model-api/lib/publicApi/segment/toggleItalic'; import * as toggleNumbering from 'roosterjs-content-model-api/lib/publicApi/list/toggleNumbering'; import * as toggleUnderline from 'roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline'; import * as undo from 'roosterjs-content-model-core/lib/publicApi/undo/undo'; -import { EditorEnvironment, IEditor, PluginEvent } from 'roosterjs-content-model-types'; import { ShortcutPlugin } from '../../lib/shortcut/ShortcutPlugin'; +import { + ContentModelDocument, + ContentModelFormatter, + EditorEnvironment, + FormatContentModelOptions, + IEditor, + PluginEvent, +} from 'roosterjs-content-model-types'; const enum Keys { BACKSPACE = 8, @@ -22,17 +30,74 @@ const enum Keys { COMMA = 188, PERIOD = 190, FORWARD_SLASH = 191, + ArrowLeft = 37, + ArrowRight = 39, } describe('ShortcutPlugin', () => { let preventDefaultSpy: jasmine.Spy; let mockedEditor: IEditor; let mockedEnvironment: EditorEnvironment; + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Br', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + ], + format: {}, + }; + const formatContentModelSpy = jasmine + .createSpy('formatContentModel') + .and.callFake((callback: ContentModelFormatter, options: FormatContentModelOptions) => { + callback(model, { + newEntities: [], + deletedEntities: [], + newImages: [], + rawEvent: options.rawEvent, + }); + }); beforeEach(() => { preventDefaultSpy = jasmine.createSpy('preventDefault'); mockedEnvironment = {}; mockedEditor = { + formatContentModel: formatContentModelSpy, getEnvironment: () => mockedEnvironment, } as any; }); @@ -585,5 +650,68 @@ describe('ShortcutPlugin', () => { expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'decrease'); }); + + it('decrease font', () => { + const apiSpy = spyOn(changeFontSize, 'default'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.COMMA, false, false, true, true), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'decrease'); + }); + + it('indent list', () => { + const apiSpy = spyOn(setModelIndentation, 'setModelIndentation'); + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.ArrowRight, false, true, true, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledTimes(1); + expect(apiSpy).toHaveBeenCalledWith(model, 'indent'); + }); + + it('outdent list', () => { + const apiSpy = spyOn(setModelIndentation, 'setModelIndentation'); + + const plugin = new ShortcutPlugin(); + const event: PluginEvent = { + eventType: 'keyDown', + rawEvent: createMockedEvent(Keys.ArrowLeft, false, true, true, false), + }; + + plugin.initialize(mockedEditor); + + const exclusively = plugin.willHandleEventExclusively(event); + + expect(exclusively).toBeTrue(); + expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); + + plugin.onPluginEvent(event); + + expect(apiSpy).toHaveBeenCalledTimes(1); + expect(apiSpy).toHaveBeenCalledWith(model, 'outdent'); + }); }); }); From 234a9fbc6aef0b261366be37580b041e1d552edc Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Mon, 4 Mar 2024 15:41:30 -0800 Subject: [PATCH 43/80] Clean up some remaining old package paths (#2469) --- karma.conf.js | 7 +------ package.json | 4 +--- tools/tsconfig.doc.json | 8 +++----- tools/tsconfig.tslint.json | 10 ---------- 4 files changed, 5 insertions(+), 24 deletions(-) delete mode 100644 tools/tsconfig.tslint.json diff --git a/karma.conf.js b/karma.conf.js index 31d7d396bf2..85c8aeabadb 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -101,12 +101,7 @@ module.exports = function (config) { }, resolve: { extensions: ['.ts', '.tsx', '.js'], - modules: [ - './packages', - './packages-ui', - './packages-content-model', - './node_modules', - ], + modules: ['./packages', './node_modules'], }, // Workaround karma-webpack issue https://github.com/ryanclark/karma-webpack/issues/493 // Got this solution from https://github.com/ryanclark/karma-webpack/issues/493#issuecomment-780411348 diff --git a/package.json b/package.json index a27a74d139b..f4ff49dcfe0 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,7 @@ "license": "MIT", "private": true, "workspaces": [ - "packages/*", - "packages-ui/*", - "packages-content-model/*" + "packages/*" ], "scripts": { "clean": "node tools/build.js clean", diff --git a/tools/tsconfig.doc.json b/tools/tsconfig.doc.json index ee60a9f9079..db9be707df4 100644 --- a/tools/tsconfig.doc.json +++ b/tools/tsconfig.doc.json @@ -6,7 +6,7 @@ "baseUrl": "..", "jsx": "react", "paths": { - "*": ["packages/*", "packages-ui/*", "packages-content-model/*"] + "*": ["packages/*"] }, "rootDir": "..", "downlevelIteration": true, @@ -15,10 +15,8 @@ }, "include": [ "../packages/**/lib/**/*.ts", - "../packages-ui/**/lib/**/*.ts", - "../packages-ui/**/lib/**/*.tsx", - "../packages/roosterjs-editor-types-compatible/**/lib/**/*.ts", - "../packages-content-model/**/lib/**/*.ts" + "../packages/**/lib/**/*.tsx", + "../packages/roosterjs-editor-types-compatible/**/lib/**/*.ts" ], "typedocOptions": { "entryPoints": [ diff --git a/tools/tsconfig.tslint.json b/tools/tsconfig.tslint.json deleted file mode 100644 index 2712efb93a6..00000000000 --- a/tools/tsconfig.tslint.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "include": [ - "../packages/**/*.ts", - "../packages-ui/**/*.ts", - "../packages-ui/**/*.tsx", - "../packages-content-model/**/*.ts", - "../demo/scripts/**/*.ts", - "../demo/scripts/**/*.tsx" - ] -} From eff8f0028148b2a080fbf807189a427fb3f391cc Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Mon, 4 Mar 2024 16:14:31 -0800 Subject: [PATCH 44/80] Port demo site step 3: Port roosterjs-react (#2466) * Port demo site step 2 * Port demo site step 3 * fix build * remove ribbon --- .../component/renderColorPicker.tsx | 54 + .../roosterjsReact/colorPicker/index.ts | 3 + .../colorPicker/types/stringKeys.ts | 57 + .../colorPicker/utils/backgroundColors.ts | 62 + .../utils/getClassNamesForColorPicker.ts | 38 + .../colorPicker/utils/textColors.ts | 85 ++ .../controlsV2/roosterjsReact/common/index.ts | 10 + .../common/type/LocalizedStrings.ts | 22 + .../common/type/ReactEditorPlugin.ts | 13 + .../roosterjsReact/common/type/UIUtilities.ts | 16 + .../common/utils/createUIUtilities.tsx | 41 + .../common/utils/getLocalizedString.ts | 24 + .../common/utils/renderReactComponent.ts | 14 + .../roosterjsReact/contextMenu/index.ts | 18 + .../menus/createImageEditMenuProvider.tsx | 271 ++++ .../menus/createListEditMenuProvider.ts | 84 ++ .../menus/createTableEditMenuProvider.ts | 178 +++ .../plugin/createContextMenuPlugin.tsx | 57 + .../contextMenu/types/ContextMenuItem.ts | 81 ++ .../types/ContextMenuItemStringKeys.ts | 136 ++ .../utils/createContextMenuProvider.ts | 141 ++ .../emoji/components/EmojiIcon.tsx | 66 + .../emoji/components/EmojiNavBar.tsx | 71 + .../emoji/components/EmojiPane.tsx | 754 ++++++++++ .../emoji/components/EmojiStatusBar.tsx | 62 + .../emoji/components/showEmojiCallout.tsx | 120 ++ .../controlsV2/roosterjsReact/emoji/index.ts | 2 + .../emoji/plugin/createEmojiPlugin.ts | 328 +++++ .../roosterjsReact/emoji/type/Emoji.ts | 19 + .../emoji/type/EmojiPaneStyles.ts | 26 + .../emoji/type/EmojiStringKeys.ts | 4 + .../roosterjsReact/emoji/type/EmojiStrings.ts | 1231 +++++++++++++++++ .../roosterjsReact/emoji/utils/emojiList.ts | 795 +++++++++++ .../emoji/utils/searchEmojis.ts | 40 + .../inputDialog/component/InputDialog.tsx | 98 ++ .../inputDialog/component/InputDialogItem.tsx | 71 + .../roosterjsReact/inputDialog/index.ts | 2 + .../inputDialog/type/DialogItem.ts | 24 + .../inputDialog/utils/showInputDialog.tsx | 57 + .../component/showPasteOptionPane.tsx | 221 +++ .../roosterjsReact/pasteOptions/index.ts | 2 + .../plugin/createPasteOptionPlugin.ts | 187 +++ .../type/PasteOptionStringKeys.ts | 13 + .../pasteOptions/utils/buttons.ts | 41 + .../pasteOptions/utils/getPositionRect.ts | 74 + .../roosterjsReact/rooster/index.ts | 1 + 46 files changed, 5714 insertions(+) create mode 100644 demo/scripts/controlsV2/roosterjsReact/colorPicker/component/renderColorPicker.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/colorPicker/index.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/colorPicker/types/stringKeys.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/backgroundColors.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/getClassNamesForColorPicker.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/textColors.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/common/index.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/common/type/LocalizedStrings.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/common/type/ReactEditorPlugin.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/common/type/UIUtilities.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/common/utils/createUIUtilities.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/common/utils/getLocalizedString.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/common/utils/renderReactComponent.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/contextMenu/index.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createListEditMenuProvider.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createTableEditMenuProvider.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/contextMenu/plugin/createContextMenuPlugin.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItem.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItemStringKeys.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/contextMenu/utils/createContextMenuProvider.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiIcon.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiNavBar.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiPane.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiStatusBar.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/components/showEmojiCallout.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/index.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/plugin/createEmojiPlugin.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/type/Emoji.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiPaneStyles.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiStringKeys.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiStrings.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/utils/emojiList.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/emoji/utils/searchEmojis.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialog.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialogItem.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/inputDialog/index.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/inputDialog/type/DialogItem.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/inputDialog/utils/showInputDialog.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/pasteOptions/component/showPasteOptionPane.tsx create mode 100644 demo/scripts/controlsV2/roosterjsReact/pasteOptions/index.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/pasteOptions/type/PasteOptionStringKeys.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/buttons.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/getPositionRect.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/rooster/index.ts diff --git a/demo/scripts/controlsV2/roosterjsReact/colorPicker/component/renderColorPicker.tsx b/demo/scripts/controlsV2/roosterjsReact/colorPicker/component/renderColorPicker.tsx new file mode 100644 index 00000000000..7995cfe9d32 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/colorPicker/component/renderColorPicker.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { mergeStyleSets } from '@fluentui/react/lib/Styling'; +import type { IContextualMenuItem } from '@fluentui/react/lib/ContextualMenu'; +import type { Colors } from 'roosterjs-content-model-types'; + +const classNames = mergeStyleSets({ + colorSquare: { + width: '20px', + height: '20px', + margin: '4px', + borderStyle: 'solid', + borderWidth: '2px', + '&:hover': { + borderColor: 'red', + }, + }, + colorSquareBorder: { + borderColor: 'transparent', + }, + colorSquareBorderWhite: { + borderColor: '#bebebe', + }, +}); + +/** + * @internal + */ +export function renderColorPicker( + item: IContextualMenuItem, + colorDef: Record, + onClick: ( + e: React.MouseEvent | React.KeyboardEvent, + item: IContextualMenuItem + ) => void +) { + const key = item.key as Strings; + const buttonColor = colorDef[key].lightModeColor; + + return ( + + ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/colorPicker/index.ts b/demo/scripts/controlsV2/roosterjsReact/colorPicker/index.ts new file mode 100644 index 00000000000..f9dbdcfe6e4 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/colorPicker/index.ts @@ -0,0 +1,3 @@ +export { BackgroundColorKeys, TextColorKeys } from './types/stringKeys'; +export { getBackgroundColorValue } from './utils/backgroundColors'; +export { getTextColorValue } from './utils/textColors'; diff --git a/demo/scripts/controlsV2/roosterjsReact/colorPicker/types/stringKeys.ts b/demo/scripts/controlsV2/roosterjsReact/colorPicker/types/stringKeys.ts new file mode 100644 index 00000000000..6a4c57dc8c8 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/colorPicker/types/stringKeys.ts @@ -0,0 +1,57 @@ +/** + * Localized string keys for text colors + */ +export type TextColorKeys = + | 'textColorLightBlue' + | 'textColorLightGreen' + | 'textColorLightYellow' + | 'textColorLightOrange' + | 'textColorLightRed' + | 'textColorLightPurple' + | 'textColorBlue' + | 'textColorGreen' + | 'textColorYellow' + | 'textColorOrange' + | 'textColorRed' + | 'textColorPurple' + | 'textColorDarkBlue' + | 'textColorDarkGreen' + | 'textColorDarkYellow' + | 'textColorDarkOrange' + | 'textColorDarkRed' + | 'textColorDarkPurple' + | 'textColorDarkerBlue' + | 'textColorDarkerGreen' + | 'textColorDarkerYellow' + | 'textColorDarkerOrange' + | 'textColorDarkerRed' + | 'textColorDarkerPurple' + | 'textColorWhite' + | 'textColorLightGray' + | 'textColorGray' + | 'textColorDarkGray' + | 'textColorDarkerGray' + | 'textColorBlack'; + +/** + * Localized string keys for background colors + */ +export type BackgroundColorKeys = + | 'backgroundColorCyan' + | 'backgroundColorGreen' + | 'backgroundColorYellow' + | 'backgroundColorOrange' + | 'backgroundColorRed' + | 'backgroundColorMagenta' + | 'backgroundColorLightCyan' + | 'backgroundColorLightGreen' + | 'backgroundColorLightYellow' + | 'backgroundColorLightOrange' + | 'backgroundColorLightRed' + | 'backgroundColorLightMagenta' + | 'backgroundColorWhite' + | 'backgroundColorLightGray' + | 'backgroundColorGray' + | 'backgroundColorDarkGray' + | 'backgroundColorDarkerGray' + | 'backgroundColorBlack'; diff --git a/demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/backgroundColors.ts b/demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/backgroundColors.ts new file mode 100644 index 00000000000..9c2a539f8a5 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/backgroundColors.ts @@ -0,0 +1,62 @@ +import type { Colors } from 'roosterjs-content-model-types'; +import type { BackgroundColorKeys } from '../types/stringKeys'; + +/** + * @internal + */ +const BackgroundColors: Record = { + backgroundColorCyan: { lightModeColor: '#00ffff', darkModeColor: '#005357' }, + backgroundColorGreen: { lightModeColor: '#00ff00', darkModeColor: '#005e00' }, + backgroundColorYellow: { lightModeColor: '#ffff00', darkModeColor: '#383e00' }, + backgroundColorOrange: { lightModeColor: '#ff8000', darkModeColor: '#bf4c00' }, + backgroundColorRed: { lightModeColor: '#ff0000', darkModeColor: '#ff2711' }, + backgroundColorMagenta: { lightModeColor: '#ff00ff', darkModeColor: '#e700e8' }, + backgroundColorLightCyan: { lightModeColor: '#80ffff', darkModeColor: '#004c4f' }, + backgroundColorLightGreen: { lightModeColor: '#80ff80', darkModeColor: '#005400' }, + backgroundColorLightYellow: { lightModeColor: '#ffff80', darkModeColor: '#343c00' }, + backgroundColorLightOrange: { lightModeColor: '#ffc080', darkModeColor: '#77480b' }, + backgroundColorLightRed: { lightModeColor: '#ff8080', darkModeColor: '#bc454a' }, + backgroundColorLightMagenta: { lightModeColor: '#ff80ff', darkModeColor: '#aa2bad' }, + backgroundColorWhite: { lightModeColor: '#ffffff', darkModeColor: '#333333' }, + backgroundColorLightGray: { lightModeColor: '#cccccc', darkModeColor: '#535353' }, + backgroundColorGray: { lightModeColor: '#999999', darkModeColor: '#777777' }, + backgroundColorDarkGray: { lightModeColor: '#666666', darkModeColor: '#a0a0a0' }, + backgroundColorDarkerGray: { lightModeColor: '#333333', darkModeColor: '#cfcfcf' }, + backgroundColorBlack: { lightModeColor: '#000000', darkModeColor: '#ffffff' }, +}; + +/** + * @internal + * List of colors in drop down list + */ +const BackgroundColorDropDownItems: Record = { + backgroundColorCyan: 'Cyan', + backgroundColorGreen: 'Green', + backgroundColorYellow: 'Yellow', + backgroundColorOrange: 'Orange', + backgroundColorRed: 'Red', + backgroundColorMagenta: 'Magenta', + backgroundColorLightCyan: 'Light cyan', + backgroundColorLightGreen: 'Light green', + backgroundColorLightYellow: 'Light yellow', + backgroundColorLightOrange: 'Light orange', + backgroundColorLightRed: 'Light red', + backgroundColorLightMagenta: 'Light magenta', + backgroundColorWhite: 'White', + backgroundColorLightGray: 'Light gray', + backgroundColorGray: 'Gray', + backgroundColorDarkGray: 'Dark gray', + backgroundColorDarkerGray: 'Darker gray', + backgroundColorBlack: 'Black', +}; + +/** + * Get mode independent color value of background color from the given color key + * @param key The key to get color from + * @returns A model independent color value of the given key + */ +function getBackgroundColorValue(key: BackgroundColorKeys): Colors { + return BackgroundColors[key]; +} + +export { BackgroundColors, BackgroundColorDropDownItems, getBackgroundColorValue }; diff --git a/demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/getClassNamesForColorPicker.ts b/demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/getClassNamesForColorPicker.ts new file mode 100644 index 00000000000..152c8297435 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/getClassNamesForColorPicker.ts @@ -0,0 +1,38 @@ +import { mergeStyleSets } from '@fluentui/react/lib/Styling'; + +const classNames = mergeStyleSets({ + colorPickerContainer: { + width: '192px', + padding: '8px', + overflow: 'hidden', + '& ul': { + width: '192px', + overflow: 'hidden', + }, + }, + colorMenuItem: { + display: 'inline-block', + width: '32px', + height: '32px', + '& button': { + padding: '0px', + minWidth: '0px', + background: 'transparent', + border: 'none', + }, + }, +}); + +/** + * @internal + */ +export function getColorPickerContainerClassName() { + return classNames.colorPickerContainer; +} + +/** + * @internal + */ +export function getColorPickerItemClassName() { + return classNames.colorMenuItem; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/textColors.ts b/demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/textColors.ts new file mode 100644 index 00000000000..baf2723908b --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/colorPicker/utils/textColors.ts @@ -0,0 +1,85 @@ +import type { Colors } from 'roosterjs-content-model-types'; +import type { TextColorKeys } from '../types/stringKeys'; + +/** + * @internal + */ +const TextColors: Record = { + textColorLightBlue: { lightModeColor: '#51a7f9', darkModeColor: '#0075c2' }, + textColorLightGreen: { lightModeColor: '#6fc040', darkModeColor: '#207a00' }, + textColorLightYellow: { lightModeColor: '#f5d427', darkModeColor: '#5d4d00' }, + textColorLightOrange: { lightModeColor: '#f3901d', darkModeColor: '#ab5500' }, + textColorLightRed: { lightModeColor: '#ed5c57', darkModeColor: '#df504d' }, + textColorLightPurple: { lightModeColor: '#b36ae2', darkModeColor: '#ab63da' }, + textColorBlue: { lightModeColor: '#0c64c0', darkModeColor: '#6da0ff' }, + textColorGreen: { lightModeColor: '#0c882a', darkModeColor: '#3da848' }, + textColorYellow: { lightModeColor: '#dcbe22', darkModeColor: '#6d5c00' }, + textColorOrange: { lightModeColor: '#de6a19', darkModeColor: '#d3610c' }, + textColorRed: { lightModeColor: '#c82613', darkModeColor: '#ff6847' }, + textColorPurple: { lightModeColor: '#763e9b', darkModeColor: '#d394f9' }, + textColorDarkBlue: { lightModeColor: '#174e86', darkModeColor: '#93b8f9' }, + textColorDarkGreen: { lightModeColor: '#0f5c1a', darkModeColor: '#7fc57b' }, + textColorDarkYellow: { lightModeColor: '#c3971d', darkModeColor: '#946f00' }, + textColorDarkOrange: { lightModeColor: '#be5b17', darkModeColor: '#de7633' }, + textColorDarkRed: { lightModeColor: '#861106', darkModeColor: '#ff9b7c' }, + textColorDarkPurple: { lightModeColor: '#5e327c', darkModeColor: '#dea9fd' }, + textColorDarkerBlue: { lightModeColor: '#002451', darkModeColor: '#cedbff' }, + textColorDarkerGreen: { lightModeColor: '#06400c', darkModeColor: '#a3da9b' }, + textColorDarkerYellow: { lightModeColor: '#a37519', darkModeColor: '#b5852a' }, + textColorDarkerOrange: { lightModeColor: '#934511', darkModeColor: '#ef935c' }, + textColorDarkerRed: { lightModeColor: '#570606', darkModeColor: '#ffc0b1' }, + textColorDarkerPurple: { lightModeColor: '#3b204d', darkModeColor: '#eecaff' }, + textColorWhite: { lightModeColor: '#ffffff', darkModeColor: '#333333' }, + textColorLightGray: { lightModeColor: '#cccccc', darkModeColor: '#535353' }, + textColorGray: { lightModeColor: '#999999', darkModeColor: '#777777' }, + textColorDarkGray: { lightModeColor: '#666666', darkModeColor: '#a0a0a0' }, + textColorDarkerGray: { lightModeColor: '#333333', darkModeColor: '#cfcfcf' }, + textColorBlack: { lightModeColor: '#000000', darkModeColor: '#ffffff' }, +}; + +/** + * @internal + */ +const TextColorDropDownItems: Record = { + textColorLightBlue: 'Light blue', + textColorLightGreen: 'Light green', + textColorLightYellow: 'Light yellow', + textColorLightOrange: 'Light orange', + textColorLightRed: 'Light red', + textColorLightPurple: 'Light purple', + textColorBlue: 'Blue', + textColorGreen: 'Green', + textColorYellow: 'Yellow', + textColorOrange: 'Orange', + textColorRed: 'Red', + textColorPurple: 'Purple', + textColorDarkBlue: 'Dark blue', + textColorDarkGreen: 'Dark green', + textColorDarkYellow: 'Dark yellow', + textColorDarkOrange: 'Dark orange', + textColorDarkRed: 'Dark red', + textColorDarkPurple: 'Dark purple', + textColorDarkerBlue: 'Darker blue', + textColorDarkerGreen: 'Darker green', + textColorDarkerYellow: 'Darker yellow', + textColorDarkerOrange: 'Darker orange', + textColorDarkerRed: 'Darker red', + textColorDarkerPurple: 'Darker purple', + textColorWhite: 'White', + textColorLightGray: 'Light gray', + textColorGray: 'Gray', + textColorDarkGray: 'Dark gray', + textColorDarkerGray: 'Darker gray', + textColorBlack: 'Black', +}; + +/** + * Get mode independent color value of text color from the given color key + * @param key The key to get color from + * @returns A model independent color value of the given key + */ +function getTextColorValue(key: TextColorKeys): Colors { + return TextColors[key]; +} + +export { TextColors, TextColorDropDownItems, getTextColorValue }; diff --git a/demo/scripts/controlsV2/roosterjsReact/common/index.ts b/demo/scripts/controlsV2/roosterjsReact/common/index.ts new file mode 100644 index 00000000000..c044795dd71 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/common/index.ts @@ -0,0 +1,10 @@ +export { + LocalizedStrings, + OkButtonStringKey, + CancelButtonStringKey, + MenuItemSplitterKey0, +} from './type/LocalizedStrings'; +export { UIUtilities } from './type/UIUtilities'; +export { ReactEditorPlugin } from './type/ReactEditorPlugin'; +export { createUIUtilities } from './utils/createUIUtilities'; +export { getLocalizedString } from './utils/getLocalizedString'; diff --git a/demo/scripts/controlsV2/roosterjsReact/common/type/LocalizedStrings.ts b/demo/scripts/controlsV2/roosterjsReact/common/type/LocalizedStrings.ts new file mode 100644 index 00000000000..3fa370db2b9 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/common/type/LocalizedStrings.ts @@ -0,0 +1,22 @@ +/** + * Represents a localized string map from the string key to the localized string or a function returns localized string + */ +export type LocalizedStrings = { + [key in T]?: V | (() => V); +}; + +/** + * Localized string key for OK button + */ +export type OkButtonStringKey = 'buttonNameOK'; + +/** + * Localized string key for Cancel button + */ +export type CancelButtonStringKey = 'buttonNameCancel'; + +/** + * Localized string key for Cancel button menu splitter. + * No need to localize this one as it will be replaced with a horizontal line + */ +export type MenuItemSplitterKey0 = '-'; diff --git a/demo/scripts/controlsV2/roosterjsReact/common/type/ReactEditorPlugin.ts b/demo/scripts/controlsV2/roosterjsReact/common/type/ReactEditorPlugin.ts new file mode 100644 index 00000000000..5a296494160 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/common/type/ReactEditorPlugin.ts @@ -0,0 +1,13 @@ +import type { EditorPlugin } from 'roosterjs-content-model-types'; +import type { UIUtilities } from './UIUtilities'; + +/** + * A sub interface of EditorPlugin to provide additional functionalities for rendering react component from the plugin + */ +export interface ReactEditorPlugin extends EditorPlugin { + /** + * Set the UI utilities objects to this plugin to help render additional UI elements + * @param uiUtilities The UI utilities object to set + */ + setUIUtilities(uiUtilities: UIUtilities): void; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/common/type/UIUtilities.ts b/demo/scripts/controlsV2/roosterjsReact/common/type/UIUtilities.ts new file mode 100644 index 00000000000..0594aa5be7b --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/common/type/UIUtilities.ts @@ -0,0 +1,16 @@ +/** + * A set of UI Utilities to help render additional UI element from a plugin + */ +export interface UIUtilities { + /** + * Render additional react component from a plugin, with correlated theme and window context of Rooster + * @param element The UI element (JSX object) to render + * @returns A disposer function to help unmount this element + */ + renderComponent(element: JSX.Element): () => void; + + /** + * Check if editor is rendered in Right-to-left mode + */ + isRightToLeft(): boolean; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/common/utils/createUIUtilities.tsx b/demo/scripts/controlsV2/roosterjsReact/common/utils/createUIUtilities.tsx new file mode 100644 index 00000000000..3b633533992 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/common/utils/createUIUtilities.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { ThemeProvider } from '@fluentui/react/lib/Theme'; +import { WindowProvider } from '@fluentui/react/lib/WindowProvider'; +import type { UIUtilities } from '../type/UIUtilities'; +import type { PartialTheme } from '@fluentui/react/lib/Theme'; + +/** + * Create the UI Utilities object for plugins to render additional react components + * @param container Container DIV of editor + * @param theme Current theme used by editor + * @returns A UIUtilities object + */ +export function createUIUtilities(container: HTMLDivElement, theme: PartialTheme): UIUtilities { + return { + renderComponent: element => { + const doc = container.ownerDocument; + const div = doc.createElement('div'); + doc.body.appendChild(div); + + ReactDOM.render( + + {element} + , + div + ); + + return () => { + ReactDOM.unmountComponentAtNode(div); + doc.body.removeChild(div); + }; + }, + isRightToLeft: () => { + const dir = + container && + container.ownerDocument.defaultView?.getComputedStyle(container).direction; + + return dir == 'rtl'; + }, + }; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/common/utils/getLocalizedString.ts b/demo/scripts/controlsV2/roosterjsReact/common/utils/getLocalizedString.ts new file mode 100644 index 00000000000..7afb9733e09 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/common/utils/getLocalizedString.ts @@ -0,0 +1,24 @@ +import type { LocalizedStrings } from '../type/LocalizedStrings'; + +/** + * Get a localized string + * @param strings The LocalizedStrings map + * @param key Key of the string + * @param defaultString Default unlocalized string, will be used if strings is not specified or the give key doesn't exist in strings + * @returns A localized string from the string map, or defaultString + */ +export function getLocalizedString( + strings: LocalizedStrings | undefined, + key: T, + defaultString: R +) { + const str = strings?.[key]; + + if (typeof str == 'function') { + return str(); + } else if (typeof str == 'string') { + return str; + } else { + return defaultString; + } +} diff --git a/demo/scripts/controlsV2/roosterjsReact/common/utils/renderReactComponent.ts b/demo/scripts/controlsV2/roosterjsReact/common/utils/renderReactComponent.ts new file mode 100644 index 00000000000..5d49e0bdcdf --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/common/utils/renderReactComponent.ts @@ -0,0 +1,14 @@ +import type { UIUtilities } from '../type/UIUtilities'; + +/** + * @internal + */ +export function renderReactComponent(uiUtilities: UIUtilities | null, reactElement: JSX.Element) { + if (uiUtilities) { + return uiUtilities.renderComponent(reactElement); + } else { + throw new Error( + 'UIUtilities is required but not provided. Please call ReactEditorPlugin.setUIUtilities() to set a valid uiUtilities object' + ); + } +} diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/index.ts b/demo/scripts/controlsV2/roosterjsReact/contextMenu/index.ts new file mode 100644 index 00000000000..31ae875412b --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/index.ts @@ -0,0 +1,18 @@ +export { createContextMenuPlugin } from './plugin/createContextMenuPlugin'; +export { createContextMenuProvider } from './utils/createContextMenuProvider'; +export { createListEditMenuProvider } from './menus/createListEditMenuProvider'; +export { createImageEditMenuProvider } from './menus/createImageEditMenuProvider'; +export { createTableEditMenuProvider } from './menus/createTableEditMenuProvider'; +export { ContextMenuItem } from './types/ContextMenuItem'; +export { + ListNumberMenuItemStringKey, + ImageEditMenuItemStringKey, + TableEditInsertMenuItemStringKey, + TableEditDeleteMenuItemStringKey, + TableEditMergeMenuItemStringKey, + TableEditSplitMenuItemStringKey, + TableEditAlignMenuItemStringKey, + TableEditShadeMenuItemStringKey, + TableEditMenuItemStringKey, + TableEditAlignTableMenuItemStringKey, +} from './types/ContextMenuItemStringKeys'; diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx new file mode 100644 index 00000000000..8fc3189fd51 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx @@ -0,0 +1,271 @@ +import formatImageWithContentModel from 'roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel'; +import { createContextMenuProvider } from '../utils/createContextMenuProvider'; +import { EditorPlugin, IEditor, ImageEditor } from 'roosterjs-content-model-types'; +import { iterateSelections, updateImageMetadata } from 'roosterjs-content-model-core'; +import { setImageAltText } from 'roosterjs-content-model-api'; +import { showInputDialog } from '../../inputDialog/utils/showInputDialog'; +import type { ContextMenuItem } from '../types/ContextMenuItem'; +import type { ImageEditMenuItemStringKey } from '../types/ContextMenuItemStringKeys'; +import type { LocalizedStrings } from '../../common/type/LocalizedStrings'; + +const MIN_WIDTH = 10; +const MIN_HEIGHT = 10; + +const ImageAltTextMenuItem: ContextMenuItem = { + key: 'menuNameImageAltText', + unlocalizedText: 'Add alternate text', + onClick: (_, editor, node, strings, uiUtilities) => { + const image = node as HTMLImageElement; + const initValue = image.alt; + + showInputDialog( + uiUtilities, + 'menuNameImageAltText', + 'Add alternate text', + { + altText: { + labelKey: null, + unlocalizedLabel: null, + initValue: initValue, + }, + }, + strings + ).then(values => { + editor.focus(); + editor.setDOMSelection({ + type: 'image', + image: image, + }); + + if (values) { + setImageAltText(editor, values.altText); + } + }); + }, +}; + +const sizeMap: { [key in ImageEditMenuItemStringKey]?: number } = { + menuNameImageSizeBestFit: 0, + menuNameImageSizeSmall: 0.25, + menuNameImageSizeMedium: 0.5, + menuNameImageSizeOriginal: 1, +}; + +const ImageResizeMenuItem: ContextMenuItem = { + key: 'menuNameImageResize', + unlocalizedText: 'Size', + subItems: { + menuNameImageSizeBestFit: 'Best fit', + menuNameImageSizeSmall: 'Small', + menuNameImageSizeMedium: 'Medium', + menuNameImageSizeOriginal: 'Original', + }, + onClick: (key, editor, _) => { + const selection = editor.getDOMSelection(); + if (selection.type !== 'image') { + return; + } + + const percentage = sizeMap[key]; + + if (percentage > 0) { + const { naturalWidth, naturalHeight } = selection.image; + + resizeByPercentage(editor, percentage, naturalWidth, naturalHeight); + } else { + resetImage(editor); + } + }, +}; + +const ImageRotateMenuItem: ContextMenuItem = { + key: 'menuNameImageRotate', + unlocalizedText: 'Rotate image', + subItems: { + menuNameImageRotateLeft: 'Left', + menuNameImageRotateRight: 'Right', + }, + shouldShow: (_, node, imageEditor) => { + return ( + !!imageEditor?.isOperationAllowed('rotate') && + imageEditor.canRegenerateImage(node as HTMLImageElement) + ); + }, + onClick: (key, editor, node, strings, uiUtilities, imageEdit) => { + switch (key) { + case 'menuNameImageRotateLeft': + imageEdit?.rotateImage(-Math.PI / 2); + break; + case 'menuNameImageRotateRight': + imageEdit?.rotateImage(Math.PI / 2); + break; + } + }, +}; + +const ImageFlipMenuItem: ContextMenuItem = { + key: 'menuNameImageFlip', + unlocalizedText: 'Flip image', + subItems: { + menuNameImageRotateFlipHorizontally: 'Flip Horizontally', + menuNameImageRotateFlipVertically: 'Flip Vertically', + }, + shouldShow: (_, node, imageEditor) => { + return ( + !!imageEditor?.isOperationAllowed('rotate') && + imageEditor.canRegenerateImage(node as HTMLImageElement) + ); + }, + onClick: (key, editor, node, strings, uiUtilities, imageEdit) => { + switch (key) { + case 'menuNameImageRotateFlipHorizontally': + imageEdit?.flipImage('horizontal'); + break; + case 'menuNameImageRotateFlipVertically': + imageEdit?.flipImage('vertical'); + break; + } + }, +}; + +const ImageCropMenuItem: ContextMenuItem = { + key: 'menuNameImageCrop', + unlocalizedText: 'Crop image', + shouldShow: (_, node, imageEditor) => { + return ( + !!imageEditor?.isOperationAllowed('crop') && + imageEditor.canRegenerateImage(node as HTMLImageElement) + ); + }, + onClick: (_, editor, node, strings, uiUtilities, imageEdit) => { + imageEdit?.cropImage(); + }, +}; + +const ImageRemoveMenuItem: ContextMenuItem = { + key: 'menuNameImageRemove', + unlocalizedText: 'Remove image', + onClick: (_, editor, node) => { + removeImage(editor); + }, +}; + +const ImageCopyMenuItem: ContextMenuItem = { + key: 'menuNameImageCopy', + unlocalizedText: 'Copy image', + onClick: (_, editor) => { + editor.getDocument()?.execCommand('copy'); + }, +}; + +const ImageCutMenuItem: ContextMenuItem = { + key: 'menuNameImageCut', + unlocalizedText: 'Cut image', + onClick: (_, editor) => { + editor.getDocument()?.execCommand('cut'); + }, +}; + +function shouldShowImageEditItems(editor: IEditor, _: Node) { + const selection = editor.getDOMSelection(); + return selection.type === 'image' && !!selection.image; +} + +/** + * Create a new instance of ContextMenuProvider to support image editing functionalities in context menu + * @returns A new ContextMenuProvider + */ +export function createImageEditMenuProvider( + imageEditor?: ImageEditor, + strings?: LocalizedStrings +): EditorPlugin { + return createContextMenuProvider( + 'imageEdit', + [ + ImageAltTextMenuItem, + ImageResizeMenuItem, + ImageCropMenuItem, + ImageRemoveMenuItem, + ImageRotateMenuItem, + ImageFlipMenuItem, + ImageCopyMenuItem, + ImageCutMenuItem, + ], + strings, + shouldShowImageEditItems, + imageEditor + ); +} + +function removeImage(editor: IEditor) { + editor.formatContentModel( + model => { + let changed = false; + + iterateSelections(model, (_, __, block, segments) => { + segments?.forEach(segment => { + if (segment.segmentType == 'Image' && block?.blockType == 'Paragraph') { + const index = block.segments.indexOf(segment); + + if (index >= 0) { + block.segments.splice(index, 1); + changed = true; + } + } + }); + }); + + return changed; + }, + { + apiName: 'DeleteImage', + } + ); +} + +function resizeByPercentage( + editor: IEditor, + percentage: number, + naturalWidth: number, + naturalHeight: number +) { + formatImageWithContentModel(editor, 'resizeImage', segment => { + updateImageMetadata(segment, format => { + format = format || { + naturalWidth, + naturalHeight, + leftPercent: 0, + rightPercent: 0, + topPercent: 0, + bottomPercent: 0, + angleRad: 0, + }; + + const newWidth = Math.max( + MIN_WIDTH, + format.naturalWidth * (1 - format.leftPercent - format.rightPercent) * percentage + ); + const newHeight = Math.max( + MIN_HEIGHT, + format.naturalHeight * (1 - format.topPercent - format.bottomPercent) * percentage + ); + format.widthPx = newWidth; + format.heightPx = newHeight; + segment.format.width = newWidth + 'px'; + segment.format.height = newHeight + 'px'; + + return format; + }); + }); +} + +function resetImage(editor: IEditor) { + formatImageWithContentModel(editor, 'resizeImage', segment => { + updateImageMetadata(segment, () => null); + + delete segment.format.width; + delete segment.format.height; + + segment.format.maxWidth = '100%'; + }); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createListEditMenuProvider.ts b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createListEditMenuProvider.ts new file mode 100644 index 00000000000..797fc93c375 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createListEditMenuProvider.ts @@ -0,0 +1,84 @@ +import { createContextMenuProvider } from '../utils/createContextMenuProvider'; +import { EditorPlugin, IEditor } from 'roosterjs-content-model-types'; +import { isElementOfType, isNodeOfType } from 'roosterjs-content-model-dom'; +import { setListStartNumber } from 'roosterjs-content-model-api'; +import { showInputDialog } from '../../inputDialog/utils/showInputDialog'; +import type { ContextMenuItem } from '../types/ContextMenuItem'; +import type { ListNumberMenuItemStringKey } from '../types/ContextMenuItemStringKeys'; +import type { LocalizedStrings } from '../../common/type/LocalizedStrings'; + +const ListNumberResetMenuItem: ContextMenuItem = { + key: 'menuNameListNumberReset', + unlocalizedText: 'Restart at 1', + onClick: (_, editor, node) => { + setListStartNumber(editor, 1); + }, +}; + +const ListNumberEditMenuItem: ContextMenuItem = { + key: 'menuNameListNumberEdit', + unlocalizedText: 'Set numbering value', + onClick: (_, editor, node, strings, uiUtilities) => { + const listAndLi = getEditingList(editor, node); + + if (listAndLi) { + const { list, li } = listAndLi; + let startNumber = list.start; + + for (let child = list.firstChild; child; child = child.nextSibling) { + if (child === li) { + break; + } else if (isNodeOfType(child, 'ELEMENT_NODE') && isElementOfType(child, 'li')) { + startNumber += 1; + } + } + + showInputDialog( + uiUtilities, + 'menuNameListNumberEdit', + 'Set numbering value', + { + value: { + labelKey: 'dialogTextSetListNumber', + unlocalizedLabel: 'Set value to', + initValue: startNumber.toString(), + }, + }, + strings + ).then(values => { + editor.focus(); + + if (values) { + const result = parseInt(values.value); + + if (result > 0 && result != startNumber) { + setListStartNumber(editor, Math.floor(result)); + } + } + }); + } + }, +}; + +function getEditingList(editor: IEditor, node: Node) { + const domHelper = editor.getDOMHelper(); + const li = domHelper.findClosestElementAncestor(node, 'LI'); + const list = li && (domHelper.findClosestElementAncestor(li, 'ol') as HTMLOListElement | null); + + return list?.isContentEditable ? { list, li } : null; +} + +/** + * Create a new instance of ContextMenuProvider to support list number editing functionalities in context menu + * @returns A new ContextMenuProvider + */ +export function createListEditMenuProvider( + strings?: LocalizedStrings +): EditorPlugin { + return createContextMenuProvider( + 'listEdit', + [ListNumberResetMenuItem, ListNumberEditMenuItem], + strings, + (editor, node) => !!getEditingList(editor, node) + ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createTableEditMenuProvider.ts b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createTableEditMenuProvider.ts new file mode 100644 index 00000000000..09771202e49 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createTableEditMenuProvider.ts @@ -0,0 +1,178 @@ +import { Colors, EditorPlugin, IEditor, TableOperation } from 'roosterjs-content-model-types'; +import { createContextMenuProvider } from '../utils/createContextMenuProvider'; +import { editTable, setTableCellShade } from 'roosterjs-content-model-api'; +import { renderColorPicker } from '../../colorPicker/component/renderColorPicker'; +import type { ContextMenuItem } from '../types/ContextMenuItem'; +import type { LocalizedStrings } from '../../common/type/LocalizedStrings'; +import { + getColorPickerContainerClassName, + getColorPickerItemClassName, +} from '../../colorPicker/utils/getClassNamesForColorPicker'; +import type { + TableEditMenuItemStringKey, + TableEditInsertMenuItemStringKey, + TableEditDeleteMenuItemStringKey, + TableEditMergeMenuItemStringKey, + TableEditSplitMenuItemStringKey, + TableEditAlignMenuItemStringKey, + TableEditShadeMenuItemStringKey, + TableEditAlignTableMenuItemStringKey, +} from '../types/ContextMenuItemStringKeys'; +import { + BackgroundColorDropDownItems, + BackgroundColors, +} from '../../colorPicker/utils/backgroundColors'; + +const TableEditOperationMap: Partial> = { + menuNameTableInsertAbove: 'insertAbove', + menuNameTableInsertBelow: 'insertBelow', + menuNameTableInsertLeft: 'insertLeft', + menuNameTableInsertRight: 'insertRight', + menuNameTableDeleteTable: 'deleteTable', + menuNameTableDeleteColumn: 'deleteColumn', + menuNameTableDeleteRow: 'deleteRow', + menuNameTableMergeAbove: 'mergeAbove', + menuNameTableMergeBelow: 'mergeBelow', + menuNameTableMergeLeft: 'mergeLeft', + menuNameTableMergeRight: 'mergeRight', + menuNameTableMergeCells: 'mergeCells', + menuNameTableSplitHorizontally: 'splitHorizontally', + menuNameTableSplitVertically: 'splitVertically', + menuNameTableAlignLeft: 'alignCellLeft', + menuNameTableAlignCenter: 'alignCellCenter', + menuNameTableAlignRight: 'alignCellRight', + menuNameTableAlignTop: 'alignCellTop', + menuNameTableAlignMiddle: 'alignCellMiddle', + menuNameTableAlignBottom: 'alignCellBottom', + menuNameTableAlignTableLeft: 'alignLeft', + menuNameTableAlignTableCenter: 'alignCenter', + menuNameTableAlignTableRight: 'alignRight', +}; + +const ColorValues = { + ...BackgroundColors, + // Add this value to satisfy compiler + menuNameTableCellShade: (null), +}; + +function onClick(key: TableEditMenuItemStringKey, editor: IEditor) { + editTable(editor, TableEditOperationMap[key]); +} + +const TableEditInsertMenuItem: ContextMenuItem = { + key: 'menuNameTableInsert', + unlocalizedText: 'Insert', + subItems: { + menuNameTableInsertAbove: 'Insert above', + menuNameTableInsertBelow: 'Insert below', + menuNameTableInsertLeft: 'Insert left', + menuNameTableInsertRight: 'Insert right', + }, + onClick, +}; + +const TableEditDeleteMenuItem: ContextMenuItem = { + key: 'menuNameTableDelete', + unlocalizedText: 'Delete', + subItems: { + menuNameTableDeleteColumn: 'Delete column', + menuNameTableDeleteRow: 'Delete row', + menuNameTableDeleteTable: 'Delete table', + }, + onClick, +}; + +const TableEditMergeMenuItem: ContextMenuItem = { + key: 'menuNameTableMerge', + unlocalizedText: 'Merge', + subItems: { + menuNameTableMergeAbove: 'Merge above', + menuNameTableMergeBelow: 'Merge below', + menuNameTableMergeLeft: 'Merge left', + menuNameTableMergeRight: 'Merge right', + '-': '-', + menuNameTableMergeCells: 'Merge selected cells', + }, + onClick, +}; + +const TableEditSplitMenuItem: ContextMenuItem = { + key: 'menuNameTableSplit', + unlocalizedText: 'Split', + subItems: { + menuNameTableSplitHorizontally: 'Split horizontally', + menuNameTableSplitVertically: 'Split vertically', + }, + onClick, +}; + +const TableEditAlignMenuItem: ContextMenuItem = { + key: 'menuNameTableAlign', + unlocalizedText: 'Align cell', + subItems: { + menuNameTableAlignLeft: 'Align left', + menuNameTableAlignCenter: 'Align center', + menuNameTableAlignRight: 'Align right', + '-': '-', + menuNameTableAlignTop: 'Align top', + menuNameTableAlignMiddle: 'Align middle', + menuNameTableAlignBottom: 'Align bottom', + }, + onClick, +}; + +const TableEditAlignTableMenuItem: ContextMenuItem = { + key: 'menuNameTableAlignTable', + unlocalizedText: 'Align table', + subItems: { + menuNameTableAlignTableLeft: 'Align left', + menuNameTableAlignTableCenter: 'Align center', + menuNameTableAlignTableRight: 'Align right', + }, + onClick, +}; + +const TableEditCellShadeMenuItem: ContextMenuItem = { + key: 'menuNameTableCellShade', + unlocalizedText: 'Shading', + subItems: BackgroundColorDropDownItems, + onClick: (key, editor) => { + setTableCellShade(editor, ColorValues[key].lightModeColor); + }, + itemRender: (item, click) => renderColorPicker(item, ColorValues, click), + itemClassName: getColorPickerItemClassName(), + commandBarSubMenuProperties: { + className: getColorPickerContainerClassName(), + }, +}; + +function getEditingTable(editor: IEditor, node: Node) { + const domHelper = editor.getDOMHelper(); + const td = domHelper.findClosestElementAncestor(node, 'TD,TH'); + const table = td && domHelper.findClosestElementAncestor(td, 'table'); + + return table?.isContentEditable ? { table, td } : null; +} + +/** + * Create a new instance of ContextMenuProvider to support table editing functionalities in context menu + * @returns A new ContextMenuProvider + */ +export function createTableEditMenuProvider( + strings?: LocalizedStrings +): EditorPlugin { + return createContextMenuProvider( + 'tableEdit', + []>[ + TableEditInsertMenuItem, + TableEditDeleteMenuItem, + TableEditMergeMenuItem, + TableEditSplitMenuItem, + TableEditAlignMenuItem, + TableEditAlignTableMenuItem, + TableEditCellShadeMenuItem, + ], + strings, + (editor, node) => !!getEditingTable(editor, node) + ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/plugin/createContextMenuPlugin.tsx b/demo/scripts/controlsV2/roosterjsReact/contextMenu/plugin/createContextMenuPlugin.tsx new file mode 100644 index 00000000000..0b98ed3ec48 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/plugin/createContextMenuPlugin.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { ContextMenuPluginBase } from 'roosterjs-content-model-plugins'; +import { ContextualMenu } from '@fluentui/react/lib/ContextualMenu'; +import { renderReactComponent } from '../../common/utils/renderReactComponent'; +import type { IContextualMenuItem } from '@fluentui/react/lib/ContextualMenu'; +import type { ReactEditorPlugin, UIUtilities } from '../../common/index'; + +function normalizeItems(items: (IContextualMenuItem | null)[]) { + let dividerKey = 0; + return items.map( + item => + item || { + name: '-', + key: 'divider_' + (dividerKey++).toString(), + } + ); +} + +class ContextMenuPlugin extends ContextMenuPluginBase + implements ReactEditorPlugin { + private uiUtilities: UIUtilities | null = null; + private disposer: (() => void) | null = null; + + constructor() { + super({ + render: (container, items, onDismiss) => { + const normalizedItems = normalizeItems(items); + + if (normalizedItems.length > 0) { + this.disposer = renderReactComponent( + this.uiUtilities, + + ); + } + }, + dismiss: _ => { + this.disposer?.(); + this.disposer = null; + }, + }); + } + + setUIUtilities(uiUtilities: UIUtilities) { + this.uiUtilities = uiUtilities; + } +} + +/** + * Create a new instance of ContextMenu plugin with context menu implementation based on FluentUI. + */ +export function createContextMenuPlugin(): ContextMenuPluginBase { + return new ContextMenuPlugin(); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItem.ts b/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItem.ts new file mode 100644 index 00000000000..1300a4879e4 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItem.ts @@ -0,0 +1,81 @@ +import type { IContextualMenuItem, IContextualMenuProps } from '@fluentui/react/lib/ContextualMenu'; +import type { LocalizedStrings, UIUtilities } from '../../common/index'; +import type { IEditor } from 'roosterjs-content-model-types'; + +/** + * Represent a context menu item + */ +export interface ContextMenuItem { + /** + * key of this button, needs to be unique + */ + key: TString; + + /** + * Text of the button. This text is not localized. To show a localized text, pass a dictionary to Ribbon component via RibbonProps.strings. + */ + unlocalizedText: string; + + /** + * Click event handler + * @param key Key of the menu item that is clicked + * @param editor The editor object that triggers this event + * @param targetNode The node that user is clicking onto + * @param strings The strings object used by getLocalizedString() function + * @param uiUtilities UI Utilities to help render additional react component from this click event + * @param context A context object that passed in from context menu provider, can be anything + */ + onClick: ( + key: TString, + editor: IEditor, + targetNode: Node, + strings: LocalizedStrings | undefined, + uiUtilities: UIUtilities, + context?: TContext + ) => void; + + /** + * A callback function to check whether this menu item should show now + * @param editor The editor object that triggers this event + * @param targetNode The node that user is clicking onto + * @param context A context object that passed in from context menu provider, can be anything + */ + shouldShow?: (editor: IEditor, targetNode: Node, context?: TContext) => boolean; + + /** + * A callback function to verify which subitem ID should have a checkmark + * @param editor The editor object that triggers this event + * @param targetNode The node that user is clicking onto + * @returns ID to be shown as selected, null for none + */ + getSelectedId?: (editor: IEditor, targetNode: Node) => TString | null; + + /** + * A key-value map for sub menu items, key is the key of menu item, value is unlocalized string + * When click on a child item, onClick handler will be triggered with the key of the clicked child item passed in as the second parameter + */ + subItems?: { [key in TString]?: string }; + + /** + * Custom render of drop down item + * @param item This menu item + * @param onClick click handler of this menu item + */ + itemRender?: ( + item: IContextualMenuItem, + onClick: ( + e: React.MouseEvent | React.KeyboardEvent, + item: IContextualMenuItem + ) => void + ) => React.ReactNode; + + /** + * CSS class name for drop down menu item + */ + itemClassName?: string; + + /** + * Use this property to pass in Fluent UI ContextMenu property directly. It will overwrite the values of other conflict properties + */ + commandBarSubMenuProperties?: Partial; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItemStringKeys.ts b/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItemStringKeys.ts new file mode 100644 index 00000000000..bd0f74fdf80 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItemStringKeys.ts @@ -0,0 +1,136 @@ +import type { BackgroundColorKeys } from '../../colorPicker/index'; +import type { + CancelButtonStringKey, + MenuItemSplitterKey0, + OkButtonStringKey, +} from '../../common/type/LocalizedStrings'; + +/** + * Key of localized strings of List Number menu items and its dialog. + * Including: + * - Menu item "Set numbering value" + * - Menu item "Restart at 1" + * - Dialog text "Set value to" + * - Ok button + * - Cancel button + */ +export type ListNumberMenuItemStringKey = + | 'menuNameListNumberEdit' + | 'menuNameListNumberReset' + | 'dialogTextSetListNumber' + | OkButtonStringKey + | CancelButtonStringKey; + +/** + * Key of localized strings of Image Alt Text menu item. + * Including: + * - Menu item "Add alternate text" + * - Menu item "Size" and sub menus" + * - Menu item "Crop image" + * - Menu item "Remove image" + * - Ok button + * - Cancel button + */ +export type ImageEditMenuItemStringKey = + | 'menuNameImageAltText' + | 'menuNameImageResize' + | 'menuNameImageCrop' + | 'menuNameImageRotate' + | 'menuNameImageRemove' + | 'menuNameImageFlip' + | 'menuNameImageSizeBestFit' + | 'menuNameImageSizeSmall' + | 'menuNameImageSizeMedium' + | 'menuNameImageSizeOriginal' + | 'menuNameImageRotateLeft' + | 'menuNameImageRotateRight' + | 'menuNameImageRotateFlipHorizontally' + | 'menuNameImageRotateFlipVertically' + | 'menuNameImageCopy' + | 'menuNameImageCut' + | OkButtonStringKey + | CancelButtonStringKey; + +/** + * Key of localized strings of Table Edit Insert menu item. + */ +export type TableEditInsertMenuItemStringKey = + | 'menuNameTableInsert' + | 'menuNameTableInsertAbove' + | 'menuNameTableInsertBelow' + | 'menuNameTableInsertLeft' + | 'menuNameTableInsertRight'; + +/** + * Key of localized strings of Table Edit Delete menu item. + */ +export type TableEditDeleteMenuItemStringKey = + | 'menuNameTableDelete' + | 'menuNameTableDeleteTable' + | 'menuNameTableDeleteColumn' + | 'menuNameTableDeleteRow'; + +/** + * Key of localized strings of Table Edit Merge menu item. + */ +export type TableEditMergeMenuItemStringKey = + | 'menuNameTableMerge' + | 'menuNameTableMergeAbove' + | 'menuNameTableMergeBelow' + | 'menuNameTableMergeLeft' + | 'menuNameTableMergeRight' + | 'menuNameTableMergeCells' + | MenuItemSplitterKey0; + +/** + * Key of localized strings of Table Edit Split menu item. + */ +export type TableEditSplitMenuItemStringKey = + | 'menuNameTableSplit' + | 'menuNameTableSplitHorizontally' + | 'menuNameTableSplitVertically'; + +/** + * Key of localized strings of Table Edit Align menu item. + */ +export type TableEditAlignMenuItemStringKey = + | 'menuNameTableAlign' + | 'menuNameTableAlignLeft' + | 'menuNameTableAlignCenter' + | 'menuNameTableAlignRight' + | 'menuNameTableAlignTop' + | 'menuNameTableAlignMiddle' + | 'menuNameTableAlignBottom' + | MenuItemSplitterKey0; + +/** + * Key of localized strings of Table Edit Align table menu item. + */ +export type TableEditAlignTableMenuItemStringKey = + | 'menuNameTableAlignTable' + | 'menuNameTableAlignTableLeft' + | 'menuNameTableAlignTableCenter' + | 'menuNameTableAlignTableRight'; + +/** + * Key of localized strings of Table Edit Cell Shade menu item. + */ +export type TableEditShadeMenuItemStringKey = 'menuNameTableCellShade' | BackgroundColorKeys; + +/** + * Key of localized strings of Table Edit menu item. + * Including: + * - Menu item "Insert" + * - Menu item "Delete" + * - Menu item "Merge" + * - Menu item "Split" + * - Menu item "Align cell" + */ +export type TableEditMenuItemStringKey = + | TableEditInsertMenuItemStringKey + | TableEditDeleteMenuItemStringKey + | TableEditMergeMenuItemStringKey + | TableEditSplitMenuItemStringKey + | TableEditAlignMenuItemStringKey + | TableEditShadeMenuItemStringKey + | TableEditAlignTableMenuItemStringKey; diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/utils/createContextMenuProvider.ts b/demo/scripts/controlsV2/roosterjsReact/contextMenu/utils/createContextMenuProvider.ts new file mode 100644 index 00000000000..e8904f25c67 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/utils/createContextMenuProvider.ts @@ -0,0 +1,141 @@ +import { getLocalizedString } from '../../common/utils/getLocalizedString'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import type { ContextMenuItem } from '../types/ContextMenuItem'; +import type { IContextualMenuItem } from '@fluentui/react/lib/ContextualMenu'; +import type { LocalizedStrings, ReactEditorPlugin, UIUtilities } from '../../common/index'; +import type { ContextMenuProvider, EditorPlugin, IEditor } from 'roosterjs-content-model-types'; + +/** + * A plugin of editor to provide context menu items + */ +class ContextMenuProviderImpl + implements ContextMenuProvider, ReactEditorPlugin { + private editor: IEditor | null = null; + private targetNode: Node | null = null; + private uiUtilities: UIUtilities | null = null; + + /** + * Create a new instance of ContextMenuProviderImpl class + * @param menuName Name of this group of menus + * @param items Menu items that will be show + * @param strings Localized strings of these menu items + * @param shouldAddMenuItems A general checker to decide if we should add this group of menu items + */ + constructor( + private menuName: string, + private items: ContextMenuItem[], + private strings?: LocalizedStrings, + private shouldAddMenuItems?: (editor: IEditor, node: Node) => boolean, + private context?: TContext + ) {} + + /** + * Get a friendly name of this plugin + */ + getName() { + return this.menuName; + } + + /** + * Initialize this plugin. This should only be called from Editor + * @param editor Editor instance + */ + initialize(editor: IEditor) { + this.editor = editor; + } + + /** + * Dispose this plugin + */ + dispose() { + this.editor = null; + } + + getContextMenuItems(node: Node) { + const editor = this.editor; + + this.targetNode = node; + + return editor && this.shouldAddMenuItems?.(this.editor, node) + ? this.items + .filter(item => !item.shouldShow || item.shouldShow(editor, node, this.context)) + .map(item => this.convertMenuItems(item, node)) + : []; + } + + setUIUtilities(uiUtilities: UIUtilities) { + this.uiUtilities = uiUtilities; + } + + private convertMenuItems( + item: ContextMenuItem, + node: Node + ): IContextualMenuItem { + const selectedId = this.editor && item.getSelectedId?.(this.editor, node); + + return { + key: item.key, + data: item, + text: getLocalizedString(this.strings, item.key, item.unlocalizedText), + ariaLabel: getLocalizedString(this.strings, item.key, item.unlocalizedText), + onClick: () => this.onClick(item, item.key), + subMenuProps: item.subItems + ? { + onItemClick: (_, menuItem) => menuItem && this.onClick(item, menuItem.data), + items: getObjectKeys(item.subItems).map(key => ({ + key: key, + data: key, + text: getLocalizedString(this.strings, key, item.subItems?.[key]), + className: item.itemClassName, + onRender: item.itemRender + ? subItem => item.itemRender?.(subItem, () => this.onClick(item, key)) + : undefined, + iconProps: + key == selectedId + ? { + iconName: 'Checkmark', + } + : undefined, + })), + ...(item.commandBarSubMenuProperties || {}), + } + : undefined, + }; + } + + private onClick(item: ContextMenuItem, key: TString) { + if (this.editor && this.targetNode && this.uiUtilities) { + item.onClick( + key, + this.editor, + this.targetNode, + this.strings, + this.uiUtilities, + this.context + ); + } + } +} + +/** + * Create a new instance of ContextMenuProviderImpl class + * @param menuName Name of this group of menus + * @param items Menu items that will be show + * @param strings Localized strings of these menu items + * @param shouldAddMenuItems A general checker to decide if we should add this group of menu items + */ +export function createContextMenuProvider( + menuName: string, + items: ContextMenuItem[], + strings?: LocalizedStrings, + shouldAddMenuItems?: (editor: IEditor, node: Node) => boolean, + context?: TContext +): EditorPlugin { + return new ContextMenuProviderImpl( + menuName, + items, + strings, + shouldAddMenuItems, + context + ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiIcon.tsx b/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiIcon.tsx new file mode 100644 index 00000000000..8b2c4d4cc57 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiIcon.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import { css } from '@fluentui/react/lib/Utilities'; +import type { Emoji } from '../type/Emoji'; +import type { EmojiPaneStyle } from '../type/EmojiPaneStyles'; +import type { IProcessedStyleSet, IStyleSet } from '@fluentui/react/lib/Styling'; +/** + * @internal + * Emoji icon data + */ +export interface EmojiIconProps { + id: string; + emoji: Emoji; + strings: Record; + classNames: IProcessedStyleSet>; + onClick?: (e: React.MouseEvent) => void; + onMouseOver?: (e: React.MouseEvent) => void; + onFocus?: React.FocusEventHandler; + isSelected?: boolean; +} + +/** + * @internal + * Emoji icon component + */ +export function EmojiIcon(props: EmojiIconProps) { + const { emoji, onClick, isSelected, onMouseOver, onFocus, strings, id, classNames } = props; + const content = emoji.description && strings[emoji.description]; + + return ( + + ); +} + +function reduceObject(object: any, callback: (key: string) => boolean): T { + if (!object) { + return object; + } + + return Object.keys(object).reduce((result: any, key: string) => { + if (callback(key)) { + result[key] = object[key]; + } + return result; + }, {} as T); +} + +function getDataAndAriaProps(props: EmojiIconProps): T { + return reduceObject( + props || {}, + propName => propName.indexOf('data-') === 0 || propName.indexOf('aria-') === 0 + ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiNavBar.tsx b/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiNavBar.tsx new file mode 100644 index 00000000000..3c81f8f397b --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiNavBar.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { css } from '@fluentui/react/lib/Utilities'; +import { EmojiFabricIconCharacterMap, EmojiList } from '../utils/emojiList'; +import { FocusZone, FocusZoneDirection } from '@fluentui/react/lib/FocusZone'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import { Icon } from '@fluentui/react/lib/Icon'; +import { TooltipHost } from '@fluentui/react/lib/Tooltip'; +import type { EmojiFamilyKeys } from '../utils/emojiList'; +import type { EmojiPaneStyle } from '../type/EmojiPaneStyles'; +import type { IProcessedStyleSet, IStyleSet } from '@fluentui/react/lib/Styling'; + +/** + * @internal + * Emoji Nav Bar data + */ +export interface EmojiNavBarProps { + onClick?: (selected: string) => void; + currentSelected?: string; + getTabId?: (selected: EmojiFamilyKeys) => string; + strings: Record; + classNames: IProcessedStyleSet>; +} + +/** + * @internal + */ +export function EmojiNavBar(props: EmojiNavBarProps) { + const { currentSelected, getTabId, strings = {}, classNames } = props; + const keys = getObjectKeys(EmojiList); + const onFamilyClick = (key: string) => { + if (props.onClick) { + props.onClick(key); + } + }; + + return ( + // for each emoji family key, create a button to use as nav bar +
+ + {keys.map((key, index) => { + const selected = key === currentSelected; + const friendlyName = strings[key]; + + return ( + + + + ); + })} + +
+ ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiPane.tsx b/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiPane.tsx new file mode 100644 index 00000000000..17db04b65c1 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiPane.tsx @@ -0,0 +1,754 @@ +import * as React from 'react'; +import { Callout, DirectionalHint } from '@fluentui/react/lib/Callout'; +import { CommonEmojis, EmojiFamilyKeys, EmojiList, MoreEmoji } from '../utils/emojiList'; +import { css, KeyCodes, memoizeFunction } from '@fluentui/react/lib/Utilities'; +import { EmojiIcon } from './EmojiIcon'; +import { EmojiNavBar } from './EmojiNavBar'; +import { EmojiStatusBar } from './EmojiStatusBar'; +import { FocusZone } from '@fluentui/react/lib/FocusZone'; +import { getLocalizedString } from '../../common/index'; +import { mergeStyleSets } from '@fluentui/react/lib/Styling'; +import { searchEmojis } from '../utils/searchEmojis'; +import { TextField } from '@fluentui/react/lib/TextField'; +import { useTheme } from '@fluentui/react/lib/Theme'; +import type { EmojiIconProps } from './EmojiIcon'; +import type { EmojiNavBarProps } from './EmojiNavBar'; +import type { EmojiStatusBarProps } from './EmojiStatusBar'; +import type { ICalloutProps } from '@fluentui/react/lib/Callout'; +import type { Emoji } from '../type/Emoji'; +import type { EmojiStringKeys } from '../type/EmojiStringKeys'; +import type { LocalizedStrings } from '../../common/index'; +import type { ITextField } from '@fluentui/react/lib/TextField'; +import type { Theme } from '@fluentui/react/lib/Theme'; + +// "When a div contains an element that is bigger (either taller or wider) than the parent and has the property +// overflow-x or overflow-y set to any value, then it can receive the focus." +// https://bugzilla.mozilla.org/show_bug.cgi?id=1069739 +const TabIndexForFirefoxBug = -1; +const EmojisPerRow = 7; +const EmojiVisibleRowCount = 5; +const EmojiVisibleWithoutNavBarRowCount = 6; +const EmojiHeightPx = 40; +const VerticalDirectionKeys: number[] = [KeyCodes.up, KeyCodes.down]; +const DirectionKeys: number[] = [ + KeyCodes.left, + KeyCodes.right, + KeyCodes.up, + KeyCodes.down, + KeyCodes.home, + KeyCodes.end, +]; + +const TooltipCalloutProps: ICalloutProps = { + isBeakVisible: true, + beakWidth: 16, + gapSpace: 0, + setInitialFocus: true, + doNotLayer: false, + directionalHint: DirectionalHint.bottomCenter, +}; + +/** + * @internal + * Types of emoji pane size + */ +export type EmojiPaneMode = 'Quick' | 'Partial' | 'Full'; + +/** + * @internal + * Types of emoji Navigation direction + */ +export type EmojiPaneNavigateDirection = 'Horizontal' | 'Vertical'; + +/** + * @internal + * Emoji Pane data + */ +export interface EmojiPaneState { + index: number; + mode: EmojiPaneMode; + currentEmojiList: Emoji[]; + currentFamily: EmojiFamilyKeys; + search: string; + searchInBox: string; +} + +/** + * @internal + * Emoji Pane customizable data + */ +export interface EmojiPaneProps { + searchDisabled?: boolean; + hideStatusBar?: boolean; + navBarProps?: Partial; + statusBarProps?: Partial; + emojiIconProps?: Partial; + searchBoxString?: LocalizedStrings; + onSelect: (emoji: Emoji, wordBeforeCursor: string) => void; + baseId: number; + strings: Record; +} + +const AriaAttributes = { + ActiveDescendant: 'aria-activedescendant', + AutoComplete: 'aria-autocomplete', + Controls: 'aria-controls', + Expanded: 'aria-expanded', + HasPopup: 'aria-haspopup', + Owns: 'aria-owns', + Pressed: 'aria-pressed', +}; + +/** + * @internal + * Emoji pane component functions + */ +export interface EmojiPane { + navigate: (change: number, direction?: EmojiPaneNavigateDirection) => number; + getEmojiElementIdByIndex: (index: number) => string | null; + getSelectedEmoji: () => Emoji; + showFullPicker: (fullSearchText: string) => void; + setSearch: (value: string) => void; + normalizeSearchText: (text: string, colonIncluded: boolean) => string; + getSearchResult: (searchValue: string, mode: EmojiPaneMode) => Emoji[]; + getEmojiIconId: (emoji: Emoji) => string; +} + +const EmojiPane = React.forwardRef(function EmojiPaneFunc( + props: EmojiPaneProps, + ref: React.Ref +) { + let searchBox: ITextField; + let emojiBody: HTMLElement; + let input: HTMLInputElement; + + const [index, setIndex] = React.useState(0); + const [mode, setMode] = React.useState('Quick'); + const [currentEmojiList, setCurrentEmojiList] = React.useState(CommonEmojis); + const [currentFamily, setCurrentFamily] = React.useState('People'); + const [search, setSearchString] = React.useState(':'); + const [searchInBox, setSearchInBox] = React.useState(''); + + const theme = useTheme(); + const classNames = getEmojiPaneClassName(theme); + const listId = `EmojiPane${props.baseId}`; + + const navigate = React.useCallback( + (change: number, direction?: EmojiPaneNavigateDirection): number => { + if (!direction) { + direction = 'Horizontal'; + } + + if (direction === 'Vertical' && index !== -1) { + change *= EmojisPerRow; + } + + const newIndex = index + change; + const length = currentEmojiList.length; + if (newIndex >= 0 && newIndex < length) { + setIndex(newIndex); + return newIndex; + } + + return -1; + }, + [index] + ); + + const normalizeSearchText = React.useCallback( + (text: string, colonIncluded: boolean): string => { + if (text == null) { + return ''; + } + + if (colonIncluded) { + text = text.substr(1); + } + return text.trim(); + }, + [] + ); + + const getEmojiElementIdByIndex = React.useCallback( + (index: number): string | null => { + const emoji = currentEmojiList[index]; + if (emoji) { + return getEmojiIconId(emoji); + } + + return null; + }, + [currentEmojiList] + ); + + const getSelectedEmoji = React.useCallback((): Emoji => { + return currentEmojiList[index]; + }, [currentEmojiList, index]); + + const showFullPicker = React.useCallback( + (fullSearchText: string): void => { + const normalizedSearchValue = normalizeSearchText(fullSearchText, true); + const newMode: EmojiPaneMode = normalizedSearchValue.length === 0 ? 'Full' : 'Partial'; + setIndex(newMode === 'Full' ? -1 : 0); + setMode(newMode); + setCurrentEmojiList(getSearchResult(normalizedSearchValue, newMode)); + setSearchString(fullSearchText); + setSearchInBox(normalizedSearchValue); + }, + [mode] + ); + + const setSearch = React.useCallback( + (value: string): void => { + const normalizedSearchValue = normalizeSearchText(value, false); + setIndex(0); + setCurrentEmojiList(getSearchResult(normalizedSearchValue, mode)); + setSearchString(value); + }, + [index, search, currentEmojiList] + ); + + const getSearchResult = React.useCallback( + (searchValue: string, mode: EmojiPaneMode): Emoji[] => { + const isQuickMode = mode === 'Quick'; + if (!searchValue) { + return isQuickMode ? currentEmojiList : EmojiList[currentFamily]; + } + + const emojiList = searchEmojis(searchValue, props.strings); + return isQuickMode ? emojiList.slice(0, 5).concat([MoreEmoji]) : emojiList; + }, + [mode, currentFamily, currentEmojiList] + ); + + const getEmojiIconId = React.useCallback( + (emoji: Emoji) => (emoji ? `${listId}-${emoji.key}` : ''), + [listId] + ); + + React.useImperativeHandle( + ref, + () => ({ + navigate, + getEmojiElementIdByIndex, + showFullPicker, + getSearchResult, + setSearch, + getSelectedEmoji, + normalizeSearchText, + getEmojiIconId, + }), + [ + navigate, + getEmojiElementIdByIndex, + showFullPicker, + getSearchResult, + setSearch, + getEmojiIconId, + getSelectedEmoji, + normalizeSearchText, + ] + ); + + const renderQuickPicker = ( + props: EmojiPaneProps, + index: number, + currentEmojiList: Emoji[] + ): JSX.Element => { + const { strings } = props; + const selectedEmoji = getSelectedEmoji(); + const target = selectedEmoji ? `#${getEmojiIconId(selectedEmoji)}` : undefined; + const content = selectedEmoji?.description ? strings[selectedEmoji.description] : undefined; + const emojiList = renderCurrentEmojiIcons(index, currentEmojiList); + // note: we're using a callout since TooltipHost does not support manual trigger, and we need to show the tooltip since quick picker is shown + // as an autocomplete menu (false focus based on transferring navigation keyboard event) + return ( +
+ {emojiList} +
+ +
+
+ ); + }; + + const renderFullPicker = ( + props: EmojiPaneProps, + index: number, + searchInBox: string, + currentFamily: EmojiFamilyKeys, + currentEmojiList: Emoji[] + ): JSX.Element => { + const { searchDisabled, searchBoxString } = props; + const emojiId = getEmojiIconId(getSelectedEmoji()); + const autoCompleteAttributes = { + [AriaAttributes.AutoComplete]: 'list', + [AriaAttributes.Expanded]: 'true', + [AriaAttributes.HasPopup]: 'listbox', + [AriaAttributes.Owns]: listId, + }; + if (emojiId) { + autoCompleteAttributes[AriaAttributes.ActiveDescendant] = emojiId; + } + + return ( +
+ {!searchDisabled && ( + searchRefCallback(ref)} + value={searchInBox} + onChange={onSearchChange} + inputClassName={classNames.emojiTextInput} + onKeyPress={onSearchKeyPress} + onKeyDown={onSearchKeyDown} + onFocus={onSearchFocus} + placeholder={getLocalizedString( + searchBoxString, + 'emojiSearchPlaceholder', + 'Search...' + )} + ariaLabel={getLocalizedString( + searchBoxString, + 'emojiSearchInputAriaLabel', + 'Search...' + )} + {...autoCompleteAttributes} + /> + )} + {mode === 'Full' + ? renderFullList(props, index, currentFamily, currentEmojiList) + : renderPartialList(props, index, currentEmojiList)} +
+ ); + }; + + const onSearchFocus = (e: React.FocusEvent): void => { + input = e.target as HTMLInputElement; + }; + + const onSearchKeyPress = (e: React.KeyboardEvent): void => { + if (!e || e.which !== KeyCodes.enter) { + return; + } + + if (index >= 0 && currentEmojiList && currentEmojiList.length > 0) { + onSelect(e, currentEmojiList[index]); + } + }; + + const onSearchKeyDown = (e: React.KeyboardEvent): void => { + if (!e || DirectionKeys.indexOf(e.which) < 0) { + return; + } + e.preventDefault(); + e.stopPropagation(); + if (e.which === KeyCodes.home) { + setIndex(0); + emojiBody.scrollTop = 0; + return; + } + if (e.which === KeyCodes.end) { + setIndex(currentEmojiList.length - 1); + emojiBody.scrollTop = emojiBody.scrollHeight; // scrollHeight will be larger than max + return; + } + + const direction: EmojiPaneNavigateDirection = + VerticalDirectionKeys.indexOf(e.which) < 0 ? 'Horizontal' : 'Vertical'; + const newIndex = navigate( + e.which === KeyCodes.left || e.which === KeyCodes.up ? -1 : 1, + direction + ); + if (newIndex > -1) { + const visibleRowCount = + mode === 'Full' ? EmojiVisibleRowCount : EmojiVisibleWithoutNavBarRowCount; + const currentRow = Math.floor(newIndex / EmojisPerRow); + const visibleTop = emojiBody.scrollTop; + const visibleBottom = visibleTop + visibleRowCount * EmojiHeightPx; + const currentRowTop = currentRow * EmojiHeightPx; + const currentRowBottom = currentRowTop + EmojiHeightPx; + if (visibleTop <= currentRowTop && visibleBottom >= currentRowBottom) { + return; // row is visible, so exit + } + + emojiBody.scrollTop = currentRow * EmojiHeightPx; + } + }; + + const renderCurrentEmojiIcons = (index: number, currentEmojiList: Emoji[]): JSX.Element[] => { + const { strings } = props; + return currentEmojiList.map((emoji, emojiIndex) => ( + setIndex(emojiIndex)} + onFocus={() => setIndex(emojiIndex)} + emoji={emoji} + classNames={classNames} + isSelected={index === emojiIndex} + onClick={e => onSelect(e, emoji)} + aria-posinset={index + 1} + aria-setsize={currentEmojiList.length} + /> + )); + }; + + const renderPartialList = ( + props: EmojiPaneProps, + index: number, + currentEmojiList: Emoji[] + ): JSX.Element => { + const { strings, hideStatusBar, statusBarProps } = props; + const hasResult = currentEmojiList && currentEmojiList.length > 0; + + return ( +
+
+ + {renderCurrentEmojiIcons(index, currentEmojiList)} + +
+ {!hideStatusBar && ( + + )} +
+ ); + }; + + const renderFullList = ( + props: EmojiPaneProps, + index: number, + currentFamily: EmojiFamilyKeys, + currentEmojiList: Emoji[] + ): JSX.Element => { + const { strings, hideStatusBar, navBarProps, statusBarProps } = props; + + const hasResult = currentEmojiList && currentEmojiList.length > 0; + + return ( +
+
+ +
+
+ + {renderCurrentEmojiIcons(index, currentEmojiList)} + +
+
+
+ + {!hideStatusBar && ( + + )} +
+ ); + }; + + const onEmojiBodyRef = (ref: HTMLDivElement) => { + emojiBody = ref; + }; + + const pivotClick = (selected: string): void => { + const currentFamily = selected as EmojiFamilyKeys; + setCurrentEmojiList(EmojiList[currentFamily]); + setCurrentFamily(currentFamily); + }; + + const getTabId = (itemKey: EmojiFamilyKeys): string => { + return `family_${itemKey}_${props.baseId}`; + }; + + const searchRefCallback = (ref: ITextField | null): void => { + if (ref) { + searchBox = ref; + if (searchBox?.value) { + searchBox.focus(); + searchBox.setSelectionStart(searchBox.value.length); + } + } + }; + + const focusZoneRefCallback = (ref: FocusZone): void => { + if (props.searchDisabled && ref) { + ref.focus(); + } + + if (input) { + // make sure to announce the active descending after the focus zone containing the emojis is ready + input.removeAttribute(AriaAttributes.ActiveDescendant); + const emojiId = getEmojiIconId(getSelectedEmoji()); + // we need to delay so NVDA will announce the first selection + if (emojiId) { + setTimeout(() => input.setAttribute(AriaAttributes.ActiveDescendant, emojiId), 0); + } + } + }; + + const onSearchChange = (_: any, newValue?: string): void => { + if (typeof newValue === 'string') { + const normalizedSearchValue = normalizeSearchText(newValue, false); + const newMode: EmojiPaneMode = normalizedSearchValue.length === 0 ? 'Full' : 'Partial'; + setIndex(newMode === 'Full' ? -1 : 0); + setCurrentEmojiList(getSearchResult(normalizedSearchValue, mode)); + setSearchInBox(newValue); + setMode(newMode); + } + }; + + const onSelect = ( + e: React.MouseEvent | React.KeyboardEvent, + emoji: Emoji + ): void => { + e.stopPropagation(); + e.preventDefault(); + if (props.onSelect) { + props.onSelect(emoji, search); + } + }; + + const renderPane = ( + props: EmojiPaneProps, + index: number, + searchInBox: string, + currentFamily: EmojiFamilyKeys, + currentEmojiList: Emoji[] + ) => { + return mode === 'Quick' + ? renderQuickPicker(props, index, currentEmojiList) + : renderFullPicker(props, index, searchInBox, currentFamily, currentEmojiList); + }; + + return <>{renderPane(props, index, searchInBox, currentFamily, currentEmojiList)}; +}); + +/** + * @internal + * Emoji pane component + */ +export function showEmojiPane( + onSelect: (emoji: Emoji, wordBeforeCursor: string) => void, + strings: Record, + paneRef: React.RefObject, + baseId: number, + searchBoxString?: LocalizedStrings +) { + return ( + + ); +} + +const calcMaxHeight = () => { + const buttonHeight = 40; + const rowsOfIcons = 6; // including family bar if shown + const bottomPaddingForContent = 5; + const maxHeightForContent = rowsOfIcons * buttonHeight + bottomPaddingForContent; + return maxHeightForContent.toString() + 'px'; +}; + +const calcPaneWidth = () => { + const buttonWidth = 40; + const pivotItemCount = 7; + const paneHorizontalPadding = 1; + const paneWidth = buttonWidth * pivotItemCount + 2 * paneHorizontalPadding; + return paneWidth.toString() + 'px'; +}; + +const getEmojiPaneClassName = memoizeFunction((theme: Theme) => { + const palette = theme.palette; + return mergeStyleSets({ + quickPicker: { + overflowY: 'hidden', + ':after': { + content: '', + position: 'absolute', + left: '0px', + top: '0px', + bottom: '0px', + right: '0px', + zIndex: 1, + borderWidth: '1px', + borderStyle: 'solid', + borderColor: 'rgb(255, 255, 255)', + borderImage: 'initial', + outline: 'rgb(102, 102, 102) solid 1px', + }, + }, + + tooltip: { + padding: '8px', + }, + + emojiTextInput: { + padding: '6px', + }, + + partialList: { + maxHeight: calcMaxHeight(), + overflow: 'hidden', + overflowY: 'scroll', + }, + + fullListContent: { + width: calcPaneWidth(), + }, + + fullListBody: { + maxHeight: calcMaxHeight(), + overflow: 'hidden', + overflowY: 'scroll', + height: calcMaxHeight(), + }, + + fullList: { + position: 'relative', + }, + + roosterEmojiPane: { + padding: '1px', + background: palette.white, + }, + + emoji: { + fontSize: '18px', + width: '40px', + height: '40px', + border: '0', + position: 'relative', + background: palette.white, + transition: 'backgorund 0.5s ease-in-out', + }, + + emojiSelected: { + background: palette.neutralLight, + }, + + navBar: { + top: '-1px', + zIndex: 10, + position: 'sticky', + }, + + navBarTooltip: { + display: 'inline-block', + }, + + navBarButton: { + height: '40px', + width: '40px', + border: '0', + borderBottom: 'solid 1px', + padding: 0, + marginBottom: 0, + display: 'inline-block', + color: palette.themeDark, + background: palette.white, + '&:hover': { + cursor: 'default', + }, + }, + + selected: { + borderBottom: '2px solid', + borderBottomColor: palette.themeDark, + }, + + statusBar: { + borderTop: 'solid 1px', + height: '50px', + overflow: 'hidden', + position: 'relative', + background: palette.white, + }, + + statusBarIcon: { + padding: '4px', + fontSize: '25px', + display: 'inline-block', + fontStyle: 'normal', + fontWeight: 'normal', + lineHeight: '40px', + }, + + statusBarDetailsContainer: { + padding: '0 4px', + lineHeight: '50px', + position: 'absolute', + display: 'inline-block', + left: '40px', + right: '0', + top: '0', + }, + + statusBarDetails: { + fontWeight: 'bold', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + + statusBarNoResultDetailsContainer: { + lineHeight: '50px', + position: 'absolute', + display: 'inline-block', + top: '0', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + left: '0', + padding: '0 8px', + }, + }); +}); diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiStatusBar.tsx b/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiStatusBar.tsx new file mode 100644 index 00000000000..22b95434c3a --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/components/EmojiStatusBar.tsx @@ -0,0 +1,62 @@ +import * as React from 'react'; +import { TooltipHost, TooltipOverflowMode } from '@fluentui/react/lib/Tooltip'; +import type { Emoji } from '../type/Emoji'; +import type { EmojiPaneStyle } from '../type/EmojiPaneStyles'; +import type { IProcessedStyleSet, IStyleSet } from '@fluentui/react/lib/Styling'; +/** + * @internal + * Emoji Status Bar data + */ +export interface EmojiStatusBarProps { + emoji: Emoji; + strings: Record; + hasResult: boolean; + classNames: IProcessedStyleSet>; +} + +const NO_SUGGESTIONS = 'emjDNoSuggetions'; + +/** + * @internal + * Emoji status bar component + */ +export function EmojiStatusBar(props: EmojiStatusBarProps) { + const { emoji, strings, hasResult, classNames } = props; + + if (!hasResult) { + const noResultDescription = strings[NO_SUGGESTIONS]; + return ( +
+
+ {noResultDescription} +
+
+ + {noResultDescription} + +
+
+ ); + } + + const icon = emoji ? emoji.codePoint : ''; + const description = emoji?.description ? strings[emoji.description] : ''; + + return ( +
+ + +
+
+ + {description} + +
+
+
+ ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/components/showEmojiCallout.tsx b/demo/scripts/controlsV2/roosterjsReact/emoji/components/showEmojiCallout.tsx new file mode 100644 index 00000000000..772d546d73d --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/components/showEmojiCallout.tsx @@ -0,0 +1,120 @@ +import * as React from 'react'; +import { Callout, DirectionalHint } from '@fluentui/react/lib/Callout'; +import { renderReactComponent } from '../../common/utils/renderReactComponent'; +import { showEmojiPane } from './EmojiPane'; +import type { Emoji } from '../type/Emoji'; +import type { EmojiPane } from './EmojiPane'; +import type { EmojiStringKeys } from '../type/EmojiStringKeys'; +import type { LocalizedStrings, UIUtilities } from '../../common/index'; + +/** + * @internal + * Emoji callout data + */ +interface EmojiICallOutProps { + cursorRect: DOMRect; + strings: Record; + onSelectFromPane: (emoji: Emoji, wordBeforeCursor: string) => void; + paneRef: React.RefObject; + onHideCallout: () => void; + searchBoxString?: LocalizedStrings; + dismiss: () => void; + baseId: number; +} + +const EmojiICallout = React.forwardRef(function EmojiCalloutFunc( + props: EmojiICallOutProps, + ref: React.Ref +) { + const { + cursorRect, + strings, + onSelectFromPane, + onHideCallout, + searchBoxString, + dismiss, + paneRef, + baseId, + } = props; + const [isCalloutVisible, toggleIsCalloutVisible] = React.useState(true); + + React.useImperativeHandle( + ref, + () => ({ + dismiss, + }), + [dismiss] + ); + + const point = { + x: cursorRect.left, + y: (cursorRect.top + cursorRect.bottom) / 2, + }; + const gap = (cursorRect.bottom - cursorRect.top) / 2 + 5; + if (!isCalloutVisible) { + onHideCallout(); + } + const toogleCallout = React.useCallback(() => { + toggleIsCalloutVisible(false); + dismiss(); + }, [dismiss]); + + return ( + <> + {isCalloutVisible && ( + + {showEmojiPane(onSelectFromPane, strings, paneRef, baseId, searchBoxString)} + + )} + + ); +}); + +/** + * @internal + */ +export interface EmojiICallout { + dismiss: () => void; +} + +/** + * @internal + * Enable emoji callout + */ +export function showEmojiCallout( + uiUtilities: UIUtilities, + cursorRect: DOMRect, + strings: Record, + onSelectFromPane: (emoji: Emoji, wordBeforeCursor: string) => void, + paneRef: React.RefObject, + emojiCalloutRef: React.RefObject, + onHideCallout: () => void, + baseId: number, + searchBoxString?: LocalizedStrings +) { + let disposer: (() => void) | null = null; + const onDismiss = () => { + disposer?.(); + disposer = null; + }; + + disposer = renderReactComponent( + uiUtilities, + + ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/index.ts b/demo/scripts/controlsV2/roosterjsReact/emoji/index.ts new file mode 100644 index 00000000000..ef0454d0c39 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/index.ts @@ -0,0 +1,2 @@ +export { createEmojiPlugin } from './plugin/createEmojiPlugin'; +export { EmojiStringKeys } from './type/EmojiStringKeys'; diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/plugin/createEmojiPlugin.ts b/demo/scripts/controlsV2/roosterjsReact/emoji/plugin/createEmojiPlugin.ts new file mode 100644 index 00000000000..2cfaad965b5 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/plugin/createEmojiPlugin.ts @@ -0,0 +1,328 @@ +import * as React from 'react'; +import { isModifierKey, iterateSelections, undo } from 'roosterjs-content-model-core'; +import { isNodeOfType } from 'roosterjs-content-model-dom'; +import { KeyCodes } from '@fluentui/react/lib/Utilities'; +import { MoreEmoji } from '../utils/emojiList'; +import { showEmojiCallout } from '../components/showEmojiCallout'; +import type { EmojiICallout } from '../components/showEmojiCallout'; +import type { Emoji } from '../type/Emoji'; +import type { EmojiPane } from '../components/EmojiPane'; +import type { EmojiStringKeys } from '../type/EmojiStringKeys'; +import type { LocalizedStrings, ReactEditorPlugin, UIUtilities } from '../../common/index'; +import { + EmojiDescriptionStrings, + EmojiFamilyStrings, + EmojiKeywordStrings, +} from '../type/EmojiStrings'; +import type { IEditor, KeyDownEvent, KeyUpEvent, PluginEvent } from 'roosterjs-content-model-types'; + +const KEYCODE_COLON = 186; +const KEYCODE_COLON_FIREFOX = 59; + +// Regex looks for an emoji right before the : to allow contextual search immediately following an emoji +// MATCHES: 0: 😃:r +// 1: 😃 +// 2: :r +const EMOJI_BEFORE_COLON_REGEX = /([\u0023-\u0039][\u20e3]|[\ud800-\udbff][\udc00-\udfff]|[\u00a9-\u00ae]|[\u2122-\u3299])*([:;][^:]*)/; + +// White space matching regex. It matches following chars: +// \s: white space +// \u00A0: no-breaking white space +// \u200B: zero width space +// \u3000: full width space (which can come from JPN IME) +const WHITESPACE_REGEX = /[\s\u00A0\u200B\u3000]+([^\s\u00A0\u200B\u3000]*)$/i; + +class EmojiPlugin implements ReactEditorPlugin { + private editor: IEditor | null = null; + private eventHandledOnKeyDown: boolean = false; + private canUndoEmoji: boolean = false; + private isSuggesting: boolean = false; + private paneRef = React.createRef(); + private timer: number | null = null; + private uiUtilities: UIUtilities | null = null; + private strings: Record; + private emojiCalloutRef = React.createRef(); + private baseId = 0; + + constructor(private searchBoxStrings?: LocalizedStrings) { + this.strings = { + ...EmojiDescriptionStrings, + ...EmojiKeywordStrings, + ...EmojiFamilyStrings, + }; + } + + setUIUtilities(uiUtilities: UIUtilities) { + this.uiUtilities = uiUtilities; + } + + public getName() { + return 'Emoji'; + } + + public dispose() { + this.setIsSuggesting(false); + this.emojiCalloutRef.current?.dismiss(); + this.editor = null; + this.baseId = 0; + } + + public initialize(editor: IEditor): void { + this.editor = editor; + } + + public onPluginEvent(event: PluginEvent): void { + if (event.eventType === 'keyDown') { + this.eventHandledOnKeyDown = false; + if (this.isSuggesting) { + this.onKeyDownSuggestingDomEvent(event); + } else if (event.rawEvent.key === 'Backspace' && this.canUndoEmoji) { + //TODO: 1051 + // If KeyDown is backspace and canUndoEmoji, call editor undo + undo(this.editor); + + this.handleEventOnKeyDown(event); + this.canUndoEmoji = false; + } + } else if (event.eventType === 'keyUp' && !isModifierKey(event.rawEvent)) { + if (this.isSuggesting) { + this.onKeyUpSuggestingDomEvent(event); + } else { + this.onKeyUpDomEvent(event); + } + } else if (event.eventType === 'mouseUp') { + //TODO: 1052 + // If MouseUp, the emoji cannot be undone + this.canUndoEmoji = false; + this.setIsSuggesting(false); + } + } + + /** + * On KeyDown suggesting DOM event + * Try to insert emoji is possible + * Intercept arrow keys to move selection if popup is shown + */ + private onKeyDownSuggestingDomEvent(event: KeyDownEvent): void { + // If key is enter, try insert emoji at selection + // If key is space and selection is shortcut, try insert emoji + + const wordBeforeCursor = this.getWordBeforeCursor(event); + switch (event.rawEvent.key) { + case 'Enter': + const selectedEmoji = this.paneRef.current?.getSelectedEmoji(); + // check if selection is on the "..." and show full picker if so, otherwise try to apply emoji + if ( + !selectedEmoji || + !wordBeforeCursor || + this.tryShowFullPicker(event, selectedEmoji, wordBeforeCursor) + ) { + break; + } else { + this.insertEmoji(selectedEmoji, wordBeforeCursor); + this.handleEventOnKeyDown(event); + } + + break; + case 'ArrowLeft': + case 'ArrowRight': + this.paneRef.current?.navigate(event.rawEvent.key === 'ArrowLeft' ? -1 : 1); + this.handleEventOnKeyDown(event); + break; + case 'Escape': + this.setIsSuggesting(false); + this.handleEventOnKeyDown(event); + } + } + + private tryShowFullPicker( + event: KeyDownEvent, + selectedEmoji: Emoji, + wordBeforeCursor: string + ): boolean { + if (selectedEmoji !== MoreEmoji) { + return false; + } + + this.handleEventOnKeyDown(event); + this.paneRef.current?.showFullPicker(wordBeforeCursor); + return true; + } + + /** + * On KeyUp suggesting DOM event + * If key is character, update search term + * Otherwise set isSuggesting to false + */ + private onKeyUpSuggestingDomEvent(event: KeyUpEvent): void { + if (this.eventHandledOnKeyDown) { + return; + } + // If this is a character key or backspace + // Clear the timer as we will either queue a new timer or stop suggesting + if ( + this.timer && + ((event.rawEvent.key.length === 1 && event.rawEvent.which !== KeyCodes.space) || + event.rawEvent.which === KeyCodes.backspace) + ) { + this.editor?.getDocument().defaultView?.clearTimeout(this.timer); + this.timer = null; + this.emojiCalloutRef.current?.dismiss(); + } + + const wordBeforeCursor = this.getWordBeforeCursor(event); + if (wordBeforeCursor) { + if (this.paneRef) { + this.paneRef.current?.setSearch(wordBeforeCursor); + } else { + this.setIsSuggesting(false); + } + } else { + this.setIsSuggesting(false); + } + } + + private onKeyUpDomEvent(event: KeyUpEvent): void { + if (this.eventHandledOnKeyDown) { + return; + } + + if ( + (event.rawEvent.which === KEYCODE_COLON || + event.rawEvent.which === KEYCODE_COLON_FIREFOX) && + this.getWordBeforeCursor(event) === ':' + ) { + this.setIsSuggesting(true); + } + } + + private getCallout() { + const selection = this.editor?.getDOMSelection(); + const rangeNode = selection?.type == 'range' ? selection.range.startContainer : null; + const rangeElement = isNodeOfType(rangeNode, 'ELEMENT_NODE') + ? rangeNode + : rangeNode.parentElement; + const rect = rangeElement?.getBoundingClientRect(); + + if (this.uiUtilities && rect) { + this.baseId++; + + showEmojiCallout( + this.uiUtilities, + rect, + this.strings, + this.onSelectFromPane, + this.paneRef, + this.emojiCalloutRef, + this.onHideCallout, + this.baseId, + this.searchBoxStrings + ); + } + } + + private onHideCallout = () => this.setIsSuggesting(false); + + private onSelectFromPane = (emoji: Emoji, wordBeforeCursor: string): void => { + if (emoji === MoreEmoji) { + this.paneRef.current?.showFullPicker(wordBeforeCursor); + return; + } + + this.insertEmoji(emoji, wordBeforeCursor); + }; + + private setIsSuggesting(isSuggesting: boolean): void { + if (this.isSuggesting === isSuggesting) { + return; + } + + this.isSuggesting = isSuggesting; + if (this.isSuggesting) { + this.getCallout(); + } else if (this.emojiCalloutRef) { + this.emojiCalloutRef.current?.dismiss(); + } + } + + private insertEmoji(emoji: Emoji, wordBeforeCursor: string) { + if (!wordBeforeCursor || !this.editor || !emoji.codePoint) { + return; + } + + this.editor.formatContentModel(model => { + iterateSelections(model, (_, __, block, segments) => { + if ( + block?.blockType == 'Paragraph' && + segments?.length == 1 && + segments[0].segmentType == 'SelectionMarker' + ) { + const index = block.segments.indexOf(segments[0]); + const previousSegment = block.segments[index - 1]; + + if ( + previousSegment?.segmentType == 'Text' && + previousSegment.text.endsWith(wordBeforeCursor) + ) { + previousSegment.text = + previousSegment.text.substring( + 0, + previousSegment.text.length - wordBeforeCursor.length + ) + emoji.codePoint; + } + } + + return true; + }); + + return true; + }); + + this.emojiCalloutRef.current?.dismiss(); + } + + private getWordBeforeCursor(event: PluginEvent): string | null { + let wordBeforeCursor: string | null = null; + + this.editor.formatContentModel(model => { + iterateSelections(model, (_, __, block, segments) => { + if ( + block?.blockType == 'Paragraph' && + segments?.length == 1 && + segments[0].segmentType == 'SelectionMarker' + ) { + const index = block.segments.indexOf(segments[0]); + const prevSegment = block.segments[index - 1]; + + if (prevSegment?.segmentType == 'Text') { + // Match on the white space, the portion after space is on the index of 1 of the matched result + // (index at 0 is whole match result, index at 1 is the word) + const matches = WHITESPACE_REGEX.exec(prevSegment.text); + wordBeforeCursor = matches?.length == 2 ? matches[1] : prevSegment.text; + } + } + + return true; + }); + return false; + }); + + const matches = wordBeforeCursor ? EMOJI_BEFORE_COLON_REGEX.exec(wordBeforeCursor) : null; + return matches && matches.length > 2 && matches[0] === wordBeforeCursor ? matches[2] : null; + } + + private handleEventOnKeyDown(event: KeyDownEvent): void { + this.eventHandledOnKeyDown = true; + event.rawEvent.preventDefault(); + event.rawEvent.stopImmediatePropagation(); + } +} + +/** + * Create a new instance of Emoji plugin with FluentUI components. + */ +export function createEmojiPlugin( + searchBoxStrings?: LocalizedStrings +): ReactEditorPlugin { + return new EmojiPlugin(searchBoxStrings); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/type/Emoji.ts b/demo/scripts/controlsV2/roosterjsReact/emoji/type/Emoji.ts new file mode 100644 index 00000000000..e07678b5bb2 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/type/Emoji.ts @@ -0,0 +1,19 @@ +/** + * @internal + */ +export interface Emoji { + /** + * Uniquely identifies an emoji. It is stored in hex string in lower case. + * Examples: + * single code point emoji: 1f60d + * double code point emoji: 1f1fa_1f1f8 + */ + key: string; + description?: string; + /** + * Unicode representation of the emoji, computable from the key + */ + codePoint?: string; + keywords?: string; + shortcut?: string; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiPaneStyles.ts b/demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiPaneStyles.ts new file mode 100644 index 00000000000..0b4c32f9606 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiPaneStyles.ts @@ -0,0 +1,26 @@ +import type { IStyleSet } from '@fluentui/react/lib/Styling'; +/** + * @internal + * EmojiPane Style classes + */ +export interface EmojiPaneStyle { + quickPicker: IStyleSet; + tooltip: IStyleSet; + emojiTextInput: IStyleSet; + partialList: IStyleSet; + fullListContent: IStyleSet; + fullListBody: IStyleSet; + fullList: IStyleSet; + roosterEmojiPane: IStyleSet; + emoji: IStyleSet; + navBarButton: IStyleSet; + navBarTooltip: IStyleSet; + selected: IStyleSet; + navBar: IStyleSet; + statusBarDetails: IStyleSet; + statusBarDetailsContainer: IStyleSet; + statusBar: IStyleSet; + statusBarIcon: IStyleSet; + statusBarNoResultDetailsContainer: IStyleSet; + emojiSelected: IStyleSet; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiStringKeys.ts b/demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiStringKeys.ts new file mode 100644 index 00000000000..9450d256f01 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiStringKeys.ts @@ -0,0 +1,4 @@ +/** + * Localized string keys for Emoji UI component + */ +export type EmojiStringKeys = 'emojiSearchPlaceholder' | 'emojiSearchInputAriaLabel'; diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiStrings.ts b/demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiStrings.ts new file mode 100644 index 00000000000..0b62a704e6e --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/type/EmojiStrings.ts @@ -0,0 +1,1231 @@ +/** + * @internal + * Emoji Description + */ +export const EmojiDescriptionStrings = { + emjDMore: 'More', + emjDNoSuggestions: 'No suggestions found', + emjD0270a: 'Raised fist', + emjD0270b: 'Raised hand', + emjD0270c: 'Victory hand', + emjD02764: 'Red heart', + emjD1f440: 'Eyes', + emjD1f442: 'Ear', + emjD1f443: 'Nose', + emjD1f444: 'Mouth', + emjD1f445: 'Tongue', + emjD1f446: 'Up-pointing backhand index finger', + emjD1f447: 'Down-pointing backhand index finger', + emjD1f448: 'Left-pointing backhand index finger', + emjD1f449: 'Right-pointing backhand index finger', + emjD1f44a: 'Fist bump', + emjD1f44b: 'Waving hand', + emjD1f44c: 'OK hand', + emjD1f44d: 'Thumbs up', + emjD1f44e: 'Thumbs down', + emjD1f44f: 'Clapping hands', + emjD1f450: 'Open hands', + emjD1f590: 'Raised hand with fingers splayed', + emjD1f595: 'Middle finger', + emjD1f596: 'Raised hand with part between middle and ring fingers', + emjD1f464: 'Bust in silhouette', + emjD1f466: 'Boy', + emjD1f467: 'Girl', + emjD1f468: 'Man', + emjD1f469: 'Woman', + emjD1f46a: 'Family', + emjD1f46b: 'Man and woman holding hands', + emjD1f46e: 'Police officer', + emjD1f46f: 'Woman with bunny ears', + emjD1f470: 'Bride with veil', + emjD1f471: 'Person with blond hair', + emjD1f472: 'Man with gua pi mao', + emjD1f473: 'Man with turban', + emjD1f474: 'Older man', + emjD1f475: 'Older woman', + emjD1f476: 'Baby', + emjD1f477: 'Construction worker', + emjD1f481: 'Information desk person', + emjD1f482: 'Guardsman', + emjD1f48b: 'Kiss mark', + emjD1f493: 'Beating heart', + emjD1f494: 'Broken heart', + emjD1f495: 'Two hearts', + emjD1f496: 'Sparkling heart', + emjD1f497: 'Growing heart', + emjD1f498: 'Heart with arrow', + emjD1f499: 'Blue heart', + emjD1f49a: 'Green heart', + emjD1f49b: 'Yellow heart', + emjD1f49c: 'Purple heart', + emjD1f49d: 'Heart with ribbon', + emjD1f49e: 'Revolving hearts', + emjD1f49f: 'Heart decoration', + emjD1f601: 'Grinning face with smiling eyes', + emjD1f602: 'Face with tears of joy', + emjD1f603: 'Smiling face with open mouth', + emjD1f604: 'Smiling face with open mouth and smiling eyes', + emjD1f605: 'Smiling face with open mouth and cold sweat', + emjD1f606: 'Smiling face with open mouth and tightly closed eyes', + emjD1f607: 'Smiling face with halo', + emjD1f608: 'Smiling face with horns', + emjD1f609: 'Winking face', + emjD1f60a: 'Smiling face with smiling eyes', + emjD1f60b: 'Face savoring delicious food', + emjD1f60c: 'Relieved face', + emjD1f60d: 'Smiling face with heart-shaped eyes', + emjD1f60e: 'Smiling face with sunglasses', + emjD1f60f: 'Smirking face', + emjD1f610: 'Neutral face', + emjD1f612: 'Unamused face', + emjD1f613: 'Face with cold sweat', + emjD1f614: 'Pensive face', + emjD1f616: 'Confounded face', + emjD1f618: 'Face throwing a kiss', + emjD1f61a: 'Kissing face with closed eyes', + emjD1f61c: 'Face with stuck-out tongue and winking eye', + emjD1f61d: 'Face with stuck-out tongue and tightly closed eyes', + emjD1f61e: 'Disappointed face', + emjD1f620: 'Angry face', + emjD1f621: 'Pouting face', + emjD1f622: 'Crying face', + emjD1f623: 'Persevering face', + emjD1f624: 'Face with look of triumph', + emjD1f625: 'Disappointed but relieved face', + emjD1f628: 'Fearful face', + emjD1f629: 'Weary face', + emjD1f62a: 'Sleepy face', + emjD1f62b: 'Tired face', + emjD1f62d: 'Loudly crying face', + emjD1f630: 'Face with open mouth and cold sweat', + emjD1f631: 'Face screaming in fear', + emjD1f632: 'Astonished face', + emjD1f633: 'Flushed face', + emjD1f635: 'Dizzy face', + emjD1f636: 'Face without mouth', + emjD1f637: 'Face with medical mask', + emjD1f645: 'Face with No Good gesture', + emjD1f646: 'Face with OK gesture', + emjD1f647: 'Person bowing deeply', + emjD1f648: 'See-no-evil monkey', + emjD1f649: 'Hear-no-evil monkey', + emjD1f641: 'Slightly frowning face', + emjD1f642: 'Slightly smiling face', + emjD1f64a: 'Speak-no-evil monkey', + emjD1f64b: 'Happy person raising one hand', + emjD1f64c: 'Person raising both hands in celebration', + emjD1f64d: 'Person frowning', + emjD1f64e: 'Person with pouting face', + emjD1f64f: 'Person with folded hands', + emjD02600: 'Sun with rays', + emjD02601: 'Cloud', + emjD02614: 'Umbrella with rain drops', + emjD0267b: 'Recycling symbol', + emjD026c4: 'Snowman without snow', + emjD026c5: 'Sun behind cloud', + emjD02728: 'Sparkles', + emjD02733: 'Eight-spoked asterisk', + emjD02734: 'Eight-pointed star', + emjD02744: 'Snowflake', + emjD02747: 'Sparkle', + emjD02b50: 'White medium star', + emjD1f300: 'Cyclone', + emjD1f301: 'Foggy', + emjD1f302: 'Closed umbrella', + emjD1f303: 'Night with stars', + emjD1f304: 'Sunrise over mountains', + emjD1f305: 'Sunrise', + emjD1f306: 'Cityscape at dusk', + emjD1f307: 'Sunset over buildings', + emjD1f308: 'Rainbow', + emjD1f309: 'Bridge at night', + emjD1f30a: 'Water wave', + emjD1f30b: 'Volcano', + emjD1f30c: 'Milky Way', + emjD1f311: 'New moon', + emjD1f313: 'First quarter moon', + emjD1f314: 'Waxing gibbous moon', + emjD1f315: 'Full moon', + emjD1f319: 'Crescent moon', + emjD1f31b: 'First quarter moon with face', + emjD1f31f: 'Glowing star', + emjD1f320: 'Shooting star', + emjD1f330: 'Chestnut', + emjD1f331: 'Seedling', + emjD1f334: 'Palm tree', + emjD1f335: 'Cactus', + emjD1f337: 'Tulip', + emjD1f338: 'Cherry blossom', + emjD1f339: 'Rose', + emjD1f33a: 'Hibiscus', + emjD1f33b: 'Sunflower', + emjD1f33c: 'Blossom', + emjD1f33d: 'Ear of corn', + emjD1f33e: 'Ear of rice', + emjD1f33f: 'Herb', + emjD1f340: 'Four leaf clover', + emjD1f341: 'Maple leaf', + emjD1f342: 'Fallen leaf', + emjD1f343: 'Leaf fluttering in wind', + emjD1f344: 'Mushroom', + emjD1f40c: 'Snail', + emjD1f40d: 'Snake', + emjD1f40e: 'Horse', + emjD1f411: 'Sheep', + emjD1f412: 'Monkey', + emjD1f414: 'Chicken', + emjD1f417: 'Boar', + emjD1f418: 'Elephant', + emjD1f419: 'Octopus', + emjD1f41a: 'Spiral shell', + emjD1f41b: 'Bug', + emjD1f41c: 'Ant', + emjD1f41d: 'Honeybee', + emjD1f41e: 'Ladybug', + emjD1f41f: 'Fish', + emjD1f420: 'Tropical fish', + emjD1f421: 'Blowfish', + emjD1f422: 'Turtle', + emjD1f423: 'Hatching chick', + emjD1f424: 'Baby chick', + emjD1f425: 'Front-facing baby chick', + emjD1f426: 'Bird', + emjD1f427: 'Penguin', + emjD1f428: 'Koala', + emjD1f429: 'Poodle', + emjD1f42b: 'Bactrian camel', + emjD1f42c: 'Dolphin', + emjD1f42d: 'Mouse face', + emjD1f42e: 'Cow face', + emjD1f42f: 'Tiger face', + emjD1f430: 'Rabbit face', + emjD1f431: 'Cat face', + emjD1f432: 'Dragon face', + emjD1f433: 'Spouting whale', + emjD1f434: 'Horse face', + emjD1f435: 'Monkey face', + emjD1f436: 'Dog face', + emjD1f437: 'Pig face', + emjD1f438: 'Frog face', + emjD1f439: 'Hamster face', + emjD1f43a: 'Wolf face', + emjD1f43b: 'Bear face', + emjD1f43c: 'Panda face', + emjD1f43d: 'Pig nose', + emjD1f43e: 'Paw prints', + emjD1f638: 'Grinning cat face with smiling eyes', + emjD1f639: 'Cat face with tears of joy', + emjD1f63a: 'Smiling cat face with open mouth', + emjD1f63b: 'Smiling cat face with heart-shaped eyes', + emjD1f63c: 'Cat face with wry smile', + emjD1f63d: 'Kissing cat face with closed eyes', + emjD1f63e: 'Pouting cat face', + emjD1f63f: 'Crying cat face', + emjD1f640: 'Weary cat face', + emjD0260e: 'Telephone', + emjD026bd: 'Soccer ball', + emjD026be: 'Baseball', + emjD1f004: 'Mahjong tile red dragon', + emjD1f380: 'Ribbon', + emjD1f381: 'Wrapped present', + emjD1f382: 'Birthday cake', + emjD1f383: 'Jack-o-lantern', + emjD1f384: 'Christmas tree', + emjD1f385: 'Father Christmas', + emjD1f386: 'Fireworks', + emjD1f387: 'Firework sparkler', + emjD1f388: 'Balloon', + emjD1f389: 'Party popper', + emjD1f38a: 'Confetti ball', + emjD1f38b: 'Tanabata tree', + emjD1f38c: 'Crossed flags', + emjD1f38d: 'Pine decoration', + emjD1f38e: 'Japanese dolls', + emjD1f38f: 'Carp streamer', + emjD1f390: 'Wind chime', + emjD1f391: 'Moon-viewing ceremony', + emjD1f392: 'School backpack', + emjD1f393: 'Graduation cap', + emjD1f3a0: 'Carousel horse', + emjD1f3a1: 'Ferris wheel', + emjD1f3a2: 'Roller coaster', + emjD1f3a3: 'Fishing pole and fish', + emjD1f3a4: 'Microphone', + emjD1f3a5: 'Movie camera', + emjD1f3a6: 'Cinema', + emjD1f3a7: 'Headphones', + emjD1f3a8: 'Artist palette', + emjD1f3a9: 'Top hat', + emjD1f3aa: 'Circus tent', + emjD1f3ab: 'Ticket', + emjD1f3ac: 'Clapper board', + emjD1f3ad: 'Performing arts', + emjD1f3ae: 'Video game', + emjD1f3af: 'Direct hit', + emjD1f3b0: 'Slot machine', + emjD1f3b1: 'Billiards', + emjD1f3b2: 'Game die', + emjD1f3b3: 'Bowling', + emjD1f3b4: 'Flower playing cards', + emjD1f3b5: 'Musical note', + emjD1f3b6: 'Multiple musical notes', + emjD1f3b7: 'Saxophone', + emjD1f3b8: 'Guitar', + emjD1f3b9: 'Musical keyboard', + emjD1f3ba: 'Trumpet', + emjD1f3bb: 'Violin', + emjD1f3bc: 'Musical score', + emjD1f3bd: 'Running shirt with sash', + emjD1f3be: 'Tennis racquet and ball', + emjD1f3bf: 'Ski and ski boot', + emjD1f3c0: 'Basketball and hoop', + emjD1f3c1: 'Checkered flag', + emjD1f3c2: 'Snowboarder', + emjD1f3c3: 'Runner', + emjD1f3c4: 'Surfer', + emjD1f3c6: 'Trophy', + emjD1f3c8: 'American football', + emjD1f3ca: 'Swimmer', + emjD1f478: 'Princess', + emjD1f479: 'Japanese ogre', + emjD1f47a: 'Japanese goblin', + emjD1f47b: 'Ghost', + emjD1f47c: 'Baby angel', + emjD1f47d: 'Extraterrestrial alien', + emjD1f47e: 'Alien monster', + emjD1f47f: 'Imp', + emjD1f480: 'Skull', + emjD1f483: 'Dancer', + emjD1f484: 'Lipstick', + emjD1f485: 'Nail polish', + emjD1f486: 'Face massage', + emjD1f487: 'Haircut', + emjD1f488: 'Barber pole', + emjD1f489: 'Syringe', + emjD1f48a: 'Pill', + emjD1f48c: 'Love letter', + emjD1f48d: 'Ring', + emjD1f48e: 'Gemstone', + emjD1f48f: 'Kiss', + emjD1f490: 'Bouquet', + emjD1f491: 'Couple with heart', + emjD1f492: 'Wedding', + emjD1f4f7: 'Camera', + emjD1f4f9: 'Video camera', + emjD1f4fa: 'Television', + emjD1f4fb: 'Radio', + emjD1f4fc: 'Videocassette', + emjD02615: 'Hot beverage', + emjD02702: 'Scissors', + emjD02709: 'Envelope', + emjD0270f: 'Pencil', + emjD02712: 'Nib', + emjD1f345: 'Tomato', + emjD1f346: 'Eggplant', + emjD1f347: 'Grapes', + emjD1f348: 'Melon', + emjD1f349: 'Watermelon', + emjD1f34a: 'Tangerine', + emjD1f34c: 'Banana', + emjD1f34d: 'Pineapple', + emjD1f34e: 'Red apple', + emjD1f34f: 'Green apple', + emjD1f351: 'Peach', + emjD1f352: 'Cherries', + emjD1f353: 'Strawberry', + emjD1f354: 'Hamburger', + emjD1f355: 'Slice of pizza', + emjD1f356: 'Meat on bone', + emjD1f357: 'Poultry leg', + emjD1f358: 'Rice cracker', + emjD1f359: 'Rice ball', + emjD1f35a: 'Cooked rice', + emjD1f35b: 'Curry and rice', + emjD1f35c: 'Steaming bowl', + emjD1f35d: 'Spaghetti', + emjD1f35e: 'Bread', + emjD1f35f: 'French fries', + emjD1f360: 'Roasted sweet potato', + emjD1f361: 'Dango', + emjD1f362: 'Oden', + emjD1f363: 'Sushi', + emjD1f364: 'Fried shrimp', + emjD1f365: 'Fish cake with swirl design', + emjD1f366: 'Soft ice cream', + emjD1f367: 'Shaved ice', + emjD1f368: 'Ice cream', + emjD1f369: 'Doughnut', + emjD1f36a: 'Cookie', + emjD1f36b: 'Chocolate bar', + emjD1f36c: 'Candy', + emjD1f36d: 'Lollipop', + emjD1f36e: 'Custard', + emjD1f36f: 'Honey pot', + emjD1f370: 'Shortcake', + emjD1f371: 'Bento box', + emjD1f372: 'Pot of food', + emjD1f373: 'Cooking', + emjD1f374: 'Fork and knife', + emjD1f375: 'Teacup without handle', + emjD1f376: 'Sake bottle and cup', + emjD1f377: 'Wine glass', + emjD1f378: 'Cocktail glass', + emjD1f379: 'Tropical drink', + emjD1f37a: 'Beer mug', + emjD1f37b: 'Clinking beer mugs', + emjD1f451: 'Crown', + emjD1f452: "Woman's hat", + emjD1f453: 'Eyeglasses', + emjD1f454: 'Necktie', + emjD1f455: 'T-shirt', + emjD1f456: 'Jeans', + emjD1f457: 'Dress', + emjD1f458: 'Kimono', + emjD1f459: 'Bikini', + emjD1f45a: "Woman's clothes", + emjD1f45b: 'Purse', + emjD1f45c: 'Handbag', + emjD1f45d: 'Pouch', + emjD1f45e: "Man's shoe", + emjD1f45f: 'Athletic shoe', + emjD1f460: 'High-heeled shoe', + emjD1f461: "Woman's sandal", + emjD1f462: "Woman's boots", + emjD1f463: 'Footprints', + emjD1f4ba: 'Seat', + emjD1f4bb: 'Personal computer', + emjD1f4bc: 'Briefcase', + emjD1f4bd: 'Minidisc', + emjD1f4be: 'Floppy disk', + emjD1f4bf: 'Optical disc', + emjD1f4c0: 'DVD', + emjD1f4c1: 'File folder', + emjD1f4c2: 'Open file folder', + emjD1f4c3: 'Page with curl', + emjD1f4c4: 'Page facing up', + emjD1f4c5: 'Calendar', + emjD1f4c6: 'Tear-off calendar', + emjD1f4c7: 'Card index', + emjD1f4c8: 'Chart with upward trend', + emjD1f4c9: 'Chart with downward trend', + emjD1f4ca: 'Bar chart', + emjD1f4cb: 'Clipboard', + emjD1f4cc: 'Pushpin', + emjD1f4cd: 'Round pushpin', + emjD1f4ce: 'Paper clip', + emjD1f4cf: 'Straight ruler', + emjD1f4d0: 'Triangular ruler', + emjD1f4d1: 'Bookmark tabs', + emjD1f4d2: 'Ledger', + emjD1f4d3: 'Notebook', + emjD1f4d4: 'Notebook with decorative cover', + emjD1f4d5: 'Closed book', + emjD1f4d6: 'Open book', + emjD1f4d7: 'Green book', + emjD1f4d8: 'Blue book', + emjD1f4d9: 'Orange book', + emjD1f4da: 'Books', + emjD1f4db: 'Name badge', + emjD1f4dc: 'Scroll', + emjD1f4dd: 'Memo', + emjD1f4de: 'Telephone receiver', + emjD1f4df: 'Pager', + emjD1f4e0: 'Fax machine', + emjD1f4e1: 'Satellite antenna', + emjD1f4e2: 'Public address loudspeaker', + emjD1f4e3: 'Cheering megaphone', + emjD1f4e4: 'Outbox tray', + emjD1f4e5: 'Inbox tray', + emjD1f4e6: 'Package', + emjD1f4e7: 'Email', + emjD1f4e8: 'Incoming envelope', + emjD1f4e9: 'Envelope with downward-facing arrow above', + emjD1f4ea: 'Closed mailbox with lowered flag', + emjD1f4eb: 'Closed mailbox with raised flag', + emjD1f4ee: 'Postbox', + emjD1f4f0: 'Newspaper', + emjD1f4f1: 'Mobile phone', + emjD1f4f2: 'Mobile phone with right-facing arrow at left', + emjD1f4f3: 'Vibration mode', + emjD1f4f4: 'Mobile phone off', + emjD1f4f6: 'Antenna with bars', + emjD1f525: 'Fire', + emjD1f526: 'Flashlight', + emjD1f527: 'Wrench', + emjD1f528: 'Hammer', + emjD1f529: 'Nut and bolt', + emjD1f52a: 'Hocho', + emjD1f52b: 'Pistol', + emjD1f52e: 'Crystal ball', + emjD1f52f: 'Six-pointed star with middle dot', + emjD1f531: 'Trident emblem', + emjD1f550: "Clock face one o'clock", + emjD1f551: "Clock face two o'clock", + emjD1f552: "Clock face three o'clock", + emjD1f553: "Clock face four o'clock", + emjD1f554: "Clock face five o'clock", + emjD1f555: "Clock face six o'clock", + emjD1f556: "Clock face seven o'clock", + emjD1f557: "Clock face eight o'clock", + emjD1f558: "Clock face nine o'clock", + emjD1f559: "Clock face ten o'clock", + emjD1f55a: "Clock face eleven o'clock", + emjD1f55b: "Clock face twelve o'clock", + emjD02668: 'Hot springs', + emjD0267f: 'Wheelchair', + emjD02693: 'Anchor', + emjD026a0: 'Warning', + emjD026a1: 'High Voltage', + emjD026d4: 'No Entry', + emjD026ea: 'Church', + emjD026f2: 'Fountain', + emjD026f3: 'Flag in hole', + emjD026f5: 'Sailboat', + emjD026fa: 'Tent', + emjD026fd: 'Fuel pump', + emjD02708: 'Airplane', + emjD1f17f: 'Squared Latin capital letter P', + emjD1f3e0: 'House building', + emjD1f3e1: 'House with garden', + emjD1f3e2: 'Office building', + emjD1f3e3: 'Japanese post office', + emjD1f3e5: 'Hospital', + emjD1f3e6: 'Bank', + emjD1f3e7: 'Automated teller machine', + emjD1f3e8: 'Hotel', + emjD1f3e9: 'Love hotel', + emjD1f3ea: 'Convenience store', + emjD1f3eb: 'School', + emjD1f3ec: 'Department store', + emjD1f3ed: 'Factory', + emjD1f3ee: 'Izakaya lantern', + emjD1f3ef: 'Japanese castle', + emjD1f3f0: 'European castle', + emjD1f530: 'Japanese symbol for beginner', + emjD1f680: 'Rocket', + emjD1f683: 'Railway car', + emjD1f684: 'High-speed train', + emjD1f685: 'High-speed train with bullet nose', + emjD1f687: 'Metro', + emjD1f689: 'Station', + emjD1f68c: 'Bus', + emjD1f68f: 'Bus stop', + emjD1f691: 'Ambulance', + emjD1f692: 'Fire engine', + emjD1f693: 'Police car', + emjD1f695: 'Taxi', + emjD1f697: 'Automobile', + emjD1f699: 'Recreational vehicle', + emjD1f69a: 'Delivery truck', + emjD1f6a2: 'Ship', + emjD1f6a4: 'Speedboat', + emjD1f6a5: 'Horizontal traffic light', + emjD1f6a7: 'Construction', + emjD1f6a8: "Police car's revolving light", + emjD1f6a9: 'Triangular flag on post', + emjD1f6aa: 'Door', + emjD1f6ab: 'No entry', + emjD1f6ac: 'Smoking symbol', + emjD1f6ad: 'No smoking symbol', + emjD1f6b2: 'Bicycle', + emjD1f6b6: 'Pedestrian', + emjD1f6b9: "Men's symbol", + emjD1f6ba: "Women's symbol", + emjD1f6bb: 'Restroom', + emjD1f6bc: 'Baby symbol', + emjD1f6bd: 'Toilet', + emjD1f6be: 'Water closet', + emjD1f6c0: 'Bath', + emjD02049: 'Exclamation question mark', + emjD02139: 'Information source', + emjD021a9: 'Left-facing arrow with hook', + emjD021aa: 'Right-facing arrow with hook', + emjD0231a: 'Watch', + emjD0231b: 'Hourglass', + emjD023e9: 'Right-pointing double triangle', + emjD023ea: 'Left-pointing double triangle', + emjD023eb: 'Up-pointing double triangle', + emjD023ec: 'Down-pointing double triangle', + emjD023f0: 'Alarm clock', + emjD023f3: 'Hourglass with flowing sand', + emjD024c2: 'Circled Latin capital letter M', + emjD025b6: 'Black right-pointing triangle', + emjD025c0: 'Black left-pointing triangle', + emjD025fb: 'White medium square', + emjD025fc: 'Black medium square', + emjD026ce: 'Ophiuchus', + emjD02611: 'Ballot box with check', + emjD0261d: 'White up-pointing index', + emjD02648: 'Aries', + emjD02649: 'Taurus', + emjD0264a: 'Gemini', + emjD0264b: 'Cancer', + emjD0264c: 'Leo', + emjD0264d: 'Virgo', + emjD0264e: 'Libra', + emjD0264f: 'Scorpio', + emjD02650: 'Sagittarius', + emjD02651: 'Capricorn', + emjD02652: 'Aquarius', + emjD02653: 'Pisces', + emjD026aa: 'Medium white circle', + emjD026ab: 'Medium black circle', + emjD02705: 'White heavy check mark', + emjD02714: 'Heavy check mark', + emjD02716: 'Heavy multiplication x', + emjD0274c: 'Cross mark', + emjD0274e: 'Squared cross mark', + emjD02753: 'question mark ornament', + emjD02754: 'White question mark ornament', + emjD02755: 'White exclamation mark ornament', + emjD02757: 'Heavy exclamation mark', + emjD02795: 'Heavy plus sign', + emjD02796: 'Heavy minus sign', + emjD02797: 'Heavy division sign', + emjD027a1: 'Black right-facing arrow', + emjD027b0: 'Curly loop', + emjD027bf: 'Double curly loop', + emjD02934: 'Arrow pointing right then curving upward', + emjD02935: 'Arrow pointing right then curving downward', + emjD02b05: 'Black arrow pointing left', + emjD02b06: 'Black arrow pointing right', + emjD02b07: 'Downwards black arrow', + emjD02b1b: 'Black large square', + emjD02b1c: 'White large square', + emjD02b55: 'Heavy large circle', + emjD03030: 'Wavy dash', + emjD0303d: 'Part alternation mark', + emjD03297: 'Circled Ideograph congratulation', + emjD03299: 'Circled Ideograph secret', + emjD1f0cf: 'Playing card Joker', + emjD1f170: 'Squared Latin capital letter A', + emjD1f171: 'Squared Latin capital letter B', + emjD1f17e: 'Squared Latin capital letter O', + emjD1f18e: 'Squared AB', + emjD1f191: 'Squared CL', + emjD1f192: 'Squared COOL', + emjD1f193: 'Squared FREE', + emjD1f194: 'Squared ID', + emjD1f195: 'Squared NEW', + emjD1f196: 'Squared NG', + emjD1f197: 'Squared OK', + emjD1f198: 'Squared SOS', + emjD1f199: 'Squared UP!', + emjD1f19a: 'Squared VS', + emjD1f201: 'Squared Katakana Koko', + emjD1f202: 'Squared Katakana Sa', + emjD1f21a: 'Squared CJK Unified Ideograph-7121', + emjD1f22f: 'Squared CJK Unified Ideograph-6307', + emjD1f232: 'Squared CJK Unified Ideograph-7981', + emjD1f233: 'Squared CJK Unified Ideograph-7a7a', + emjD1f234: 'Squared CJK Unified Ideograph-5408', + emjD1f235: 'Squared CJK Unified Ideograph-6e80', + emjD1f236: 'Squared CJK Unified Ideograph-6709', + emjD1f237: 'Squared CJK Unified Ideograph-6708', + emjD1f238: 'Squared CJK Unified Ideograph-7533', + emjD1f239: 'Squared CJK Unified Ideograph-5272', + emjD1f23a: 'Squared CJK Unified Ideograph-55b6', + emjD1f250: 'Circled Ideograph advantage', + emjD1f251: 'Circled Ideograph accept', + emjD1f30f: 'Earth globe Asia-Australia', + emjD1f4a0: 'Diamond shape with a dot inside', + emjD1f4a1: 'Electric light bulb', + emjD1f4a2: 'Anger', + emjD1f4a3: 'Bomb', + emjD1f4a4: 'Sleeping', + emjD1f4a5: 'Collision', + emjD1f4a6: 'Splashing sweat', + emjD1f4a7: 'Droplet', + emjD1f4a8: 'Dash', + emjD1f4a9: 'Pile of poo', + emjD1f4aa: 'Flexed biceps', + emjD1f4ab: 'Dizzy', + emjD1f4ac: 'Speech balloon', + emjD1f4ae: 'White flower', + emjD1f4af: 'Hundred points', + emjD1f4b0: 'Money bag', + emjD1f4b1: 'Currency exchange', + emjD1f4b2: 'Heavy dollar sign', + emjD1f4b3: 'Credit card', + emjD1f4b4: 'Banknote with yen sign', + emjD1f4b5: 'Banknote with dollar sign', + emjD1f4b8: 'Money with wings', + emjD1f4b9: 'Chart with upwards trend and yen sign', + emjD1f503: 'Clockwise downward and upward open circle arrows', + emjD1f50a: 'Speaker with three sound waves', + emjD1f50b: 'Battery', + emjD1f50c: 'Electric plug', + emjD1f50d: 'Left-leaning magnifying glass', + emjD1f50e: 'Right-leaning magnifying glass', + emjD1f50f: 'Lock with ink pen', + emjD1f510: 'Closed lock with key', + emjD1f511: 'Key', + emjD1f512: 'Lock', + emjD1f513: 'Open lock', + emjD1f514: 'Bell', + emjD1f516: 'Bookmark', + emjD1f517: 'Link', + emjD1f518: 'Radio button', + emjD1f519: 'Back with left-facing arrow above', + emjD1f51a: 'End with left-facing arrow above', + emjD1f51b: 'On with exclamation mark with left right arrow above', + emjD1f51c: 'Soon with right-facing arrow above', + emjD1f51d: 'Top with upward arrow above', + emjD1f51e: 'No one under eighteen', + emjD1f51f: 'Keycap ten', + emjD1f520: 'Input Latin uppercase', + emjD1f521: 'Input Latin lowercase', + emjD1f522: 'Input numbers', + emjD1f523: 'Input symbols', + emjD1f524: 'Input Latin letters', + emjD1f532: 'Black square button', + emjD1f533: 'White square button', + emjD1f534: 'Large red circle', + emjD1f535: 'Large blue circle', + emjD1f536: 'Large orange diamond', + emjD1f537: 'Large blue diamond', + emjD1f538: 'Small orange diamond', + emjD1f539: 'Small blue diamond', + emjD1f53a: 'Up-pointing red triangle', + emjD1f53b: 'Down-pointing red triangle', + emjD1f53c: 'Up-pointing small red triangle', + emjD1f53d: 'Down-pointing small red triangle', + emjD1f5fb: 'Mount Fuji', + emjD1f5fc: 'Tokyo Tower', + emjD1f5fd: 'Statue of Liberty', + emjD1f5fe: 'Silhouette of Japan', + emjD1f5ff: 'Moyai', +}; + +/** + * @internal + * Emoji Keywords + */ +export const EmojiKeywordStrings = { + emjK1f607: 'saint angel innocent', + emjK1f47c: 'cherub angel', + emjK1f34e: 'apple', + emjK1f34f: 'apple', + emjK1f477: 'construction', + emjK1f6a7: 'construction detour', + emjK1f491: 'couple engaged married marry marriage', + emjK1f46b: 'couple engaged', + emjK1f622: 'crying sad', + emjK1f62d: 'crying sad', + emjK1f525: 'fire', + emjK1f692: 'fire truck fire engine', + emjK1f386: 'fireworks sparkler july 4th', + emjK1f387: 'fireworks sparkler july 4th', + emjK1f44a: 'punch fist pump chuck norris bam', + emjK0270a: 'fist pump punch', + emjK1f498: 'heart love cupid', + emjK1f496: 'heart love', + emjK1f497: 'love heart', + emjK1f493: 'heart love heartbeat', + emjK1f368: 'ice cream dessert treat sundae sweets', + emjK1f366: 'ice cream dessert treat sweets', + emjK1f48b: 'kiss xoxo love kisses kissing mwah', + emjK1f444: 'kiss mouth', + emjK1f618: 'kiss love kisses kissing', + emjK1f61a: 'kiss love kisses kissing smooch', + emjK1f48f: 'kiss love', + emjK1f435: 'monkey', + emjK1f64a: 'monkey speak no evil', + emjK1f649: 'monkey hear no evil', + emjK1f648: 'monkey see no evil', + emjK1f3b6: 'music melody song singing tune jingle', + emjK1f3b5: 'music musical note melody musical', + emjK1f44c: 'ok okay perfect', + emjK1f646: 'ok awesome', + emjK1f621: 'pouting sad pout', + emjK1f64e: 'pouting sad depressed', + emjK1f60c: 'relieved phew whew relief', + emjK1f630: 'relieved phew whew', + emjK1f605: 'relieved phew whew sheesh', + emjK1f380: 'ribbon', + emjK1f381: 'gift present presents ribbon', + emjK1f613: 'scared yikes scary uh oh', + emjK1f631: 'scared yikes fear whoa', + emjK1f629: 'scared anxious uncertain unsure', + emjK1f628: 'scared fearful', + emjK1f45e: 'shoe shoes', + emjK1f45f: 'shoe shoes', + emjK1f60a: 'smile happy smiling yay', + emjK1f642: 'smile happy smiling', + emjK1f603: 'smiling happy excited woo woohoo woot', + emjK1f604: 'smiling happy grin excited', + emjK1f6bd: 'toilet bathroom potty restroom washroom', + emjK1f6ba: 'toilet bathroom', + emjK1f6b9: 'toilet bathroom', + emjK1f684: 'train light rail monorail', + emjK1f683: 'train caboose', + emjK1f446: 'up click', + emjK0261d: 'up', + emjK1f64b: 'wave hi bye hey aloha', + emjK1f44b: 'wave hi bye waving high five', + emjK1f601: 'happy grin lol funny grinning hehe', + emjK1f602: 'happy lol funny joy lmao rofl', + emjK1f606: 'lol haha laughing lmao', + emjK1f609: 'haha winking wink winky', + emjK1f60f: 'haha winking wink smirking smirk heh', + emjK1f645: 'bad', + emjK1f44e: 'bad thumbs down wrong boo', + emjK1f60d: 'love love you loving love u', + emjK1f495: 'love hearts', + emjK1f44d: 'ok thumbs up', + emjK1f610: 'ok umm', + emjK1f620: 'mad angry grrr', + emjK1f612: 'mad angry unamused not funny amused bleh blah', + emjK1f47b: 'scared halloween ghost boo', + emjK1f480: 'scared skull danger die death poison', + emjK1f47e: 'scared monster', + emjK1f365: 'rice ball fish cake', + emjK1f61e: 'disappointed sad', + emjK1f64d: 'disappointed sad frown', + emjK1f494: 'heartbroken sorrow sad broken heart brokenhearted', + emjK1f625: 'relieved phew', + emjK1f62b: 'sleepy tired yawn', + emjK1f4a4: 'sleepy tired sleep zzz', + emjK1f62a: 'sleepy tired', + emjK1f632: 'wow astonished', + emjK1f633: 'wow flushed', + emjK1f3c4: 'wave surf surfer surfers surfs surfing', + emjK1f44f: 'yay clapping applause clap', + emjK1f64c: 'yay celebrate', + emjK1f48d: 'ring marry fiance engaged engage engagement ring engagement diamond ring bling', + emjK1f483: 'party dancer dance dancing', + emjK1f389: 'party fun congratulations celebrate congrats', + emjK1f388: 'party fun balloon', + emjK1f38a: 'party confetti surprise', + emjK1f383: 'happy halloween trick treat halloween pumpkin', + emjK1f385: 'merry christmas santa claus father xmas', + emjK1f384: 'merry christmas tree happy holidays', + emjK1f3eb: 'school college study teacher learn studying', + emjK1f392: 'backpack school bag back pack', + emjK1f41f: 'fish goldfish', + emjK1f3a3: 'fish fishing fishing pole', + emjK1f3a5: 'video camera film movie movie camera', + emjK1f4f9: 'video camera', + emjK1f3a6: 'theatre theater cinema', + emjK1f3ad: 'performing arts drama shakespeare theatre theater play actor actress', + emjK1f3bc: 'treble clef sheet music music musical score', + emjK1f3b9: 'music piano', + emjK1f3a7: 'music headphones headphone', + emjK1f3b7: 'music saxophone sax', + emjK1f3ba: 'music trumpet', + emjK1f3b8: 'music guitar', + emjK1f3bb: 'music violin', + emjK1f3ac: 'film action movie', + emjK1f48e: 'diamond stone gem', + emjK1f3be: 'wimbledon tennis raquet tennis sports racquet', + emjK1f3c8: 'football superbowl sports nfl', + emjK1f3bf: 'sports winter ski skiing skis', + emjK1f3c2: 'sports winter snowboard snowboarder snowboarding snowboards', + emjK026c4: 'winter snow snowman', + emjK02744: 'winter snow snowflake snowing snowed christmas xmas blizzard', + emjK1f3c1: 'race finish nascar', + emjK1f3c3: 'race run marathon runner sprinting running late', + emjK1f375: 'tea coffee', + emjK02615: 'coffee tea', + emjK1f377: 'alcohol wine', + emjK1f378: 'alcohol cocktail drinks martini happy hour', + emjK1f379: 'alcohol tropical drink', + emjK1f68c: 'bus transit', + emjK1f687: 'bus transit metro', + emjK1f43b: 'bear teddy', + emjK1f43c: 'bear panda', + emjK1f340: "luck lucky st. patrick's day shamrock", + emjK1f320: 'luck shooting star wish comet asteroid meteor meteroid', + emjK1f303: 'skyline starry stars night', + emjK1f307: 'skyline sunset', + emjK1f31f: 'star twinkle', + emjK02747: 'star sparkle glitter', + emjK1f4b0: 'cash loot', + emjK1f4b5: 'cash money dollar', + emjK1f4b4: 'yen money', + emjK1f4b3: 'money credit debt debit credit card', + emjK1f60b: 'dinner hungry lunch food yummy yum delicious tasty mmmmm', + emjK1f60e: 'cool', + emjK1f614: 'alas thinking sigh', + emjK1f616: 'confused confounded huh', + emjK1f61c: 'goofy wassup nyah kidding', + emjK1f61d: 'gross yuck eww blech', + emjK1f623: 'persevering', + emjK1f624: 'triumph congratulations grats congrats yahoo ftw woot wahoo', + emjK1f635: 'dizzy drunk confused', + emjK1f636: 'unsure', + emjK1f637: 'sick flu', + emjK1f440: 'eyes snoop', + emjK1f442: 'ear listen', + emjK1f443: 'nose smell', + emjK1f445: 'tongue lick taste drool', + emjK1f447: 'down', + emjK1f448: 'left', + emjK1f449: 'right', + emjK1f64f: 'pray praying prayer', + emjK0270b: 'hand', + emjK0270c: 'peace victory', + emjK1f466: 'boy', + emjK1f467: 'girl', + emjK1f468: 'man', + emjK1f469: 'woman', + emjK1f46a: 'family', + emjK1f46e: 'police officer cop', + emjK1f46f: 'bunny ears costume', + emjK1f470: 'bride marriage bridezilla', + emjK1f471: 'blond', + emjK1f474: 'grandpa old man gramps grandfather', + emjK1f475: 'grandma old lady old woman grandmother', + emjK1f476: 'baby kid toddler newborn infant', + emjK1f382: 'birthday cake happy cake', + emjK1f390: 'chime', + emjK1f393: 'graduation college high school graduated graduating graduate grad', + emjK1f0cf: 'cards card joker blackjack poker', + emjK1f3a0: 'carousel carnival', + emjK1f3a1: 'ferris wheel amusement park', + emjK1f3a2: 'roller coaster', + emjK1f3a4: 'microphone sing karaoke singing', + emjK1f3a8: 'paint artist palette artist', + emjK1f3a9: 'top hat black tie', + emjK1f3aa: 'circus', + emjK1f3ab: 'ticket stub ticket stub admission', + emjK1f3ae: 'xbox video game video games controller', + emjK1f3af: 'bullseye archery darts arrow', + emjK1f3b0: 'slot slots machine casino gamble gambling', + emjK1f3b1: 'pool billiards', + emjK1f3b2: 'die dice game craps gambling casino gamble', + emjK1f3b3: 'bowling bowl', + emjK1f4f7: 'camera picture photo', + emjK1f4fa: 'tv television', + emjK1f4fb: 'radio', + emjK1f4fc: 'videotape videocassette cassette vcr', + emjK1f478: 'princess', + emjK1f47d: 'alien ufo', + emjK1f47f: 'imp pixie loki leprechaun', + emjK1f484: 'lipstick makeup', + emjK1f485: 'nail polish manicure', + emjK1f486: 'massage', + emjK1f487: 'haircut', + emjK1f488: 'barber barbershop', + emjK1f489: 'syringe shot needle', + emjK1f48a: 'pill pills drug drugs', + emjK1f48c: 'love letter', + emjK1f490: 'flowers bouquet love', + emjK1f492: 'wedding marry church', + emjK1f3c0: 'basketball bball hoops', + emjK1f3c6: 'win trophy champions champion', + emjK1f3ca: 'swim swimmer swimming swims', + emjK026bd: 'soccer ball', + emjK026be: 'baseball', + emjK1f451: 'crown king queen', + emjK1f453: 'glasses hipster', + emjK1f454: 'tie', + emjK1f455: 'tshirt tshirts', + emjK1f456: 'jeans pants', + emjK1f457: 'dress', + emjK1f459: 'bikini bathing suit swimsuit swim', + emjK1f45b: 'purse', + emjK1f45c: 'handbag purse', + emjK1f45d: 'pouch clutch', + emjK1f461: 'sandal heels heel shoe shoes sandals pumps', + emjK1f462: 'boots boot', + emjK1f463: 'footprints barefoot', + emjK1f4dd: 'memo note', + emjK1f4de: 'telephone call phone', + emjK1f4df: 'pager', + emjK1f4e0: 'fax machine', + emjK1f4e1: 'satellite antenna', + emjK1f4e3: 'megaphone cheering', + emjK1f4e6: 'present package parcel', + emjK1f4e7: 'email mail', + emjK1f4ee: 'mail post', + emjK1f4f0: 'newspaper news', + emjK1f4f1: 'phone cell call', + emjK1f4f6: 'wifi signal', + emjK1f526: 'flashlight', + emjK1f527: 'wrench', + emjK1f528: 'hammer hammers', + emjK1f529: 'nuts bolts', + emjK1f52a: 'knife', + emjK1f52e: 'crystal ball clairvoyance clairvoyant psychic mystic', + emjK1f531: 'trident', + emjK1f354: 'hamburger fast burger hamburgers burgers food hungry', + emjK1f355: 'pizza fast food hungry dinner lunch', + emjK1f356: 'meat dinner lunch hungry food', + emjK1f357: 'chicken leg turkey leg meat chicken turkey hungry dinner lunch food drumstick', + emjK1f35a: 'rice dinner hungry lunch food', + emjK1f35c: 'noodles ramen food dinner lunch', + emjK1f35d: 'spaghetti dinner noodles food hungry', + emjK1f35e: 'bread food', + emjK1f35f: 'fries fast food french fries food', + emjK1f360: 'potato potatoes vegetable food', + emjK1f363: 'sushi food maki', + emjK1f364: 'fried shrimp shrimp seafood food tempura', + emjK1f369: 'doughnut doughnuts donut donuts food sweets', + emjK1f36a: 'cookie cookies food sweets', + emjK1f36b: 'chocolate chocolates candy bar chocolate bar sweets', + emjK1f36c: 'candy sweet sweets treat', + emjK1f36d: 'lollipop candy treat sucker', + emjK1f36e: 'custard', + emjK1f36f: 'honey pot', + emjK1f370: 'pie food hungry cake dessert cheesecake shortcake sweets', + emjK1f371: 'bento box', + emjK1f372: 'soup pot of food stew', + emjK1f374: 'food dinner fork knife eat hungry meal restaurant', + emjK1f376: 'sake alcohol', + emjK1f37a: 'beer alcohol', + emjK1f37b: 'cheers beers alcohol', + emjK1f345: 'food vegetable fruit tomato', + emjK1f346: 'eggplant food vegetable', + emjK1f347: 'grapes fruit food', + emjK1f348: 'melon honeydew food fruit', + emjK1f349: 'watermelon melon food fruit', + emjK1f34a: 'tangerine fruit food orange mandarin clementine slice slices', + emjK1f34c: 'banana fruit food', + emjK1f34d: 'pineapple fruit food', + emjK1f351: 'peach peaches fruit food', + emjK1f352: 'cherry cherries fruit food', + emjK1f353: 'strawberries stawberry fruit food berry berries', + emjK1f4ba: 'seat', + emjK1f4bb: 'pc computer laptop tablet', + emjK1f4bc: 'briefcase brief case', + emjK1f4be: 'floppy disk diskette', + emjK1f4bf: 'cd compact disc', + emjK1f4c0: 'dvd', + emjK1f4c1: 'file folder', + emjK1f4c5: 'calendar', + emjK1f4cb: 'clipboard', + emjK1f4cc: 'pushpin', + emjK1f4cd: 'drawing pin', + emjK1f4ce: 'paperclip clippy', + emjK1f4cf: 'ruler measure', + emjK1f4d2: 'ledger', + emjK1f4d3: 'notebook', + emjK1f4da: 'book books', + emjK1f4db: 'name badge', + emjK02702: 'scissors cut', + emjK02709: 'envelope mail', + emjK0270f: 'pencil', + emjK1f3e0: 'house home', + emjK1f3e1: 'garden greenhouse', + emjK1f3e2: 'office work', + emjK1f3e6: 'bank', + emjK1f3e7: 'atm', + emjK1f3e8: 'hotel motel bed sleep', + emjK1f3ea: 'convenience store', + emjK1f3ec: 'department store shopping', + emjK1f3ed: 'factory', + emjK1f3f0: 'castle', + emjK02668: 'hot springs', + emjK02693: 'anchor', + emjK026ea: 'church temple', + emjK026f2: 'fountain', + emjK026f3: 'golf', + emjK026f5: 'sailboat sailing sail', + emjK026fd: 'gas fuel gasoline pump', + emjK1f6aa: 'door', + emjK1f6ac: 'smoke smoking', + emjK1f6ad: 'no smoking', + emjK1f6b2: 'bike bicycle biking', + emjK1f6b6: 'walking pedestrian', + emjK1f6c0: 'bath bathing bathtub tub', + emjK0267f: 'accessible accessibility wheelchair', + emjK026a0: 'warning attention caution hazzard', + emjK026a1: 'high voltage zap lightning', + emjK1f680: 'rocket spaceship space', + emjK1f68f: 'bus stop', + emjK1f693: 'cop police', + emjK1f695: 'taxi cab', + emjK1f697: 'car vehicle automobile', + emjK1f6a2: 'ship cruise ferry yacht cruise travel vacation', + emjK02708: 'airplane flight airline plane travel vacation', + emjK1f42d: 'mouse eek mice squeak', + emjK1f42e: 'cow moo', + emjK1f42f: 'tiger roar rawr', + emjK1f430: 'rabbit bunny easter', + emjK1f431: 'cat kitty kitten meow', + emjK1f432: 'dragon roar smaug', + emjK1f433: 'whale moby dick', + emjK1f434: 'horse', + emjK1f436: 'dog woof puppy bark doggy', + emjK1f437: 'pig oink piggy piglet', + emjK1f438: 'frog croak ribbit', + emjK1f439: 'hamster guinea pig', + emjK1f43a: 'wolf howl', + emjK1f341: 'maple leaf', + emjK1f344: 'mushroom shroom', + emjK1f308: 'rainbow love', + emjK02601: 'cloud gloomy cloudy', + emjK02614: 'drops raining rain rainy raindrops umbrella', + emjK02728: 'sparkles twinkle twinkling glitter shiny sparkly glittery', + emjK1f4a9: 'poop turd shit', + emjK1f4aa: 'strong work out muscles biceps', + emjK02648: 'aries zodiac', + emjK02649: 'taurus zodiac', + emjK0264a: 'gemini zodiac', + emjK0264b: 'cancer zodiac', + emjK0264c: 'leo zodiac', + emjK0264d: 'virgo zodiac', + emjK0264e: 'libra zodiac', + emjK0264f: 'scorpio zodiac', + emjK02650: 'sagittarius zodiac', + emjK02651: 'capricorn zodiac', + emjK02652: 'aquarius zodiac', + emjK02653: 'pisces zodiac', + emjK1f411: 'bah sheep', + emjK1f300: 'cyclone typhoon hurricane', + emjK1f301: 'foggy', + emjK1f302: 'umbrella', + emjK1f304: 'sunrise', + emjK1f305: 'sunrise', + emjK1f306: 'dusk', + emjK1f309: 'bridge night', + emjK1f30a: 'wave tsunami', + emjK1f30c: 'milky way night sky galaxy universe', + emjK1f311: 'moon', + emjK1f313: 'moon', + emjK1f314: 'moon', + emjK1f315: 'moon full', + emjK1f319: 'moon crescent', + emjK1f31b: 'moon', + emjK1f330: 'chestnut', + emjK1f331: 'seed seedling planting', + emjK1f334: 'palm tree vacation', + emjK1f335: 'cactus hot desert', + emjK1f337: 'tulip flower', + emjK1f338: 'cherry blossom sakura flower', + emjK1f339: 'rose love romance flower', + emjK1f33a: 'hibiscus flower', + emjK1f33b: 'sunflower', + emjK1f33c: 'blossom daisy flower', + emjK1f33d: 'maize corn', + emjK1f33e: 'rice', + emjK1f33f: 'herb herbs', + emjK1f342: 'leaf autumn', + emjK1f343: 'leaf windy', + emjK1f358: 'onigiri', + emjK1f359: 'onigiri', + emjK1f35b: 'curry katsu', + emjK1f361: 'dango', + emjK1f362: 'oden', + emjK1f373: 'frying cooking', + emjK1f3bd: 'exercise exercising', + emjK1f417: 'boar', + emjK1f418: 'elephant', + emjK1f419: 'octopus', + emjK1f41a: 'seashell shell', + emjK1f41b: 'bug insect centipede millipede', + emjK1f41c: 'ant ants insect bug', + emjK1f41d: 'bee bees honeybee honeybees', + emjK1f41e: 'ladybug ladybugs', + emjK1f420: 'fish', + emjK1f421: 'blowfish fish', + emjK1f422: 'turtle', + emjK1f423: 'chick easter', + emjK1f425: 'chick easter', + emjK1f40c: 'snail slow', + emjK1f40d: 'snake', + emjK1f40e: 'horse horsey pony', + emjK1f412: 'monkey', + emjK1f414: 'chicken bawk rooster', + emjK1f429: 'poodle dog', + emjK1f42b: 'camel desert', + emjK1f426: 'bird', + emjK1f427: 'penguin', + emjK1f42c: 'dolphin', + emjK1f43d: 'pig pig noise smelly', + emjK1f43e: 'paw prints paws', + emjK1f452: 'hat sunday best', + emjK1f458: 'kimono', + emjK1f45a: 'clothes', + emjK1f481: 'information desk support desk', + emjK1f482: 'guardsman', + emjK1f499: 'heart', + emjK1f49a: 'heart', + emjK1f49b: 'heart', + emjK1f49c: 'heart', + emjK1f49d: 'heart love', + emjK1f49e: 'heart', + emjK1f49f: 'heart', + emjK1f4a5: 'collision bang traffic accident crash', + emjK1f4a3: 'bomb', + emjK1f4a1: 'light bulb idea', + emjK1f4a8: 'dash gotta go gotta run', + emjK1f503: 'reload refresh loading', + emjK1f50c: 'plug plugged in', + emjK1f50b: 'battery charged', + emjK1f50d: 'search searching', + emjK1f510: 'secure secret', + emjK1f50f: 'privacy', + emjK1f511: 'key', + emjK1f512: 'lock locked', + emjK1f513: 'unlock unlocked', + emjK1f516: 'bookmark', + emjK1f514: 'bell alarm', + emjK1f550: '1pm 1am early afternoon', + emjK1f551: '2pm 2am afternoon', + emjK1f552: '3pm 3am', + emjK1f553: '4pm 4am', + emjK1f554: '5pm 5am late afternoon', + emjK1f555: '6pm 6am', + emjK1f556: '7pm 7am dinnertime', + emjK1f557: '8pm 8am morning', + emjK1f558: '9pm 9am', + emjK1f559: '10pm 10am bedtime', + emjK1f55a: '11pm 11am nighttime', + emjK1f55b: '12pm 12am midnight noon lunchtime', + emjK1f5fd: 'statue liberty nyc new york city', + emjK1f638: 'cat grin happy', + emjK1f639: 'cat rofl lol tears laughter tears joy funny', + emjK1f63a: 'cat smile', + emjK1f63b: 'cat love', + emjK1f63c: 'cat heh', + emjK1f63d: 'cat kiss kisses', + emjK1f63e: 'cat pout pouting sad', + emjK1f63f: 'cat cry cries sad', + emjK1f4e8: 'incoming message mail email letter', + emjK1f4e9: 'mail email letter sending send', + emjK1f4eb: 'mail', + emjK1f4ea: 'empty mailbox', + emjK1f6bb: 'restroom toilet washroom bathroom', + emjK1f6a5: 'traffic traffic light', + emjK1f6ab: 'no entry no admittance', + emjK1f689: 'station', + emjK1f69a: 'delivery truck', + emjK026fa: 'tent camp camping', + emjK1f6a8: 'police siren emergency', + emjK1f17f: 'parking parking spot', + emjK02733: 'star asterisk', + emjK02734: 'star', + emjK02b50: 'star', + emjK1f4c8: 'chart graph record profits trending up upwards skyrocketed', + emjK1f4c9: 'chart graph record losses trending down downwards', + emjK1f4ca: 'chart graph', + emjK02764: 'love heart', +}; + +/** + * @internal + * Emoji Family + */ +export const EmojiFamilyStrings = { + People: 'People', + Nature: 'Nature', + Activities: 'Activities', + Food: 'Food', + Travel: 'Travel', + Symbols: 'Symbols', + Objects: 'Objects', +}; + +/** + * @internal + * Emoji Description Key + */ +export type EmojiDescriptionStringKey = keyof typeof EmojiDescriptionStrings; + +/** + * @internal + * Emoji Keyword Key + */ +export type EmojiKeywordStringKey = keyof typeof EmojiKeywordStrings; diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/utils/emojiList.ts b/demo/scripts/controlsV2/roosterjsReact/emoji/utils/emojiList.ts new file mode 100644 index 00000000000..fb19ea49c81 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/utils/emojiList.ts @@ -0,0 +1,795 @@ +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import type { Emoji } from '../type/Emoji'; + +const Common1 = createEmoji('1f60a', ':) :-)'); +const common2 = createEmoji('1f609', ';) ;-)'); +const common3 = createEmoji('02764', '<3'); +const common4 = createEmoji('1f61e', ':( :-('); +const common5 = createEmoji('1f603', ':D :-D'); + +/** + * @internal + */ +export const MoreEmoji: Emoji = { + key: 'more', + description: 'emjDMore', + codePoint: '', +}; + +/** + * @internal + */ +export const CommonEmojis = [Common1, common2, common3, common4, common5, MoreEmoji]; + +/** + * @internal + */ +export type EmojiFamilyKeys = + | 'People' + | 'Nature' + | 'Activities' + | 'Food' + | 'Travel' + | 'Symbols' + | 'Objects'; + +/** + * @internal + * NOTE: name for an emoji should be in lower case + */ +export const EmojiList: Record = { + People: [ + createEmoji('1f601'), + createEmoji('1f602'), + common5, + createEmoji('1f604'), + createEmoji('1f605'), + createEmoji('1f606'), + createEmoji('1f607'), + common2, + Common1, + createEmoji('1f642'), + createEmoji('1f60b'), + createEmoji('1f60c'), + createEmoji('1f60d'), + createEmoji('1f618'), + createEmoji('1f61a'), + createEmoji('1f61c'), + createEmoji('1f61d', ':p :P :-p :-P'), + createEmoji('1f60e', 'B) B-)'), + createEmoji('1f60f'), + createEmoji('1f636'), + createEmoji('1f610', ':| :-|'), + createEmoji('1f612'), + createEmoji('1f633'), + common4, + createEmoji('1f620'), + createEmoji('1f621'), + createEmoji('1f614'), + createEmoji('1f623'), + createEmoji('1f616'), + createEmoji('1f62b'), + createEmoji('1f629'), + createEmoji('1f624'), + createEmoji('1f631'), + createEmoji('1f628'), + createEmoji('1f630'), + createEmoji('1f625'), + createEmoji('1f622'), + createEmoji('1f62a'), + createEmoji('1f613'), + createEmoji('1f62d'), + createEmoji('1f635'), + createEmoji('1f632', ':o :O :-o :-O'), + createEmoji('1f637'), + createEmoji('1f4a4'), + createEmojiWithNoKeyword('1f608'), + createEmoji('1f47f'), + createEmojiWithNoKeyword('1f479'), + createEmojiWithNoKeyword('1f47a'), + createEmoji('1f480'), + createEmoji('1f47b'), + createEmoji('1f47d'), + createEmoji('1f63a'), + createEmoji('1f638'), + createEmoji('1f639'), + createEmoji('1f63b'), + createEmoji('1f63c'), + createEmoji('1f63d'), + createEmojiWithNoKeyword('1f640'), + createEmoji('1f63f'), + createEmoji('1f63e'), + createEmoji('1f64c'), + createEmoji('1f44f'), + createEmoji('1f44b'), + createEmoji('1f44d'), + createEmoji('1f44e'), + createEmoji('1f44a'), + createEmoji('0270a'), + createEmoji('0270b'), + createEmoji('0270c'), + createEmoji('1f44c'), + createEmojiWithNoKeyword('1f450'), + createEmojiWithNoKeyword('1f596'), + createEmoji('1f4aa'), + createEmoji('1f64f'), + createEmoji('1f446'), + createEmoji('1f447'), + createEmoji('1f448'), + createEmoji('1f449'), + createEmoji('1f485'), + createEmoji('1f444'), + createEmoji('1f445'), + createEmoji('1f442'), + createEmoji('1f443'), + createEmoji('1f440'), + createEmojiWithNoKeyword('1f464'), + createEmoji('1f476'), + createEmoji('1f466'), + createEmoji('1f467'), + createEmoji('1f468'), + createEmoji('1f469'), + createEmoji('1f471'), + createEmoji('1f474'), + createEmoji('1f475'), + createEmojiWithNoKeyword('1f472'), + createEmojiWithNoKeyword('1f473'), + createEmoji('1f46e'), + createEmoji('1f477'), + createEmoji('1f482'), + createEmoji('1f385'), + createEmoji('1f47c'), + createEmoji('1f478'), + createEmoji('1f470'), + createEmoji('1f6b6'), + createEmoji('1f3c3'), + createEmoji('1f483'), + createEmoji('1f46f'), + createEmoji('1f46b'), + createEmojiWithNoKeyword('1f647'), + createEmoji('1f481'), + createEmoji('1f645'), + createEmoji('1f646'), + createEmoji('1f64b'), + createEmoji('1f64e'), + createEmoji('1f64d'), + createEmoji('1f487'), + createEmoji('1f486'), + createEmoji('1f491'), + createEmoji('1f48f'), + createEmoji('1f46a'), + createEmoji('1f45a'), + createEmoji('1f455'), + createEmoji('1f456'), + createEmoji('1f454'), + createEmoji('1f457'), + createEmoji('1f459'), + createEmoji('1f458'), + createEmoji('1f484'), + createEmoji('1f48b'), + createEmoji('1f463'), + createEmojiWithNoKeyword('1f460'), + createEmoji('1f461'), + createEmoji('1f462'), + createEmoji('1f45e'), + createEmoji('1f45f'), + createEmoji('1f452'), + createEmoji('1f3a9'), + createEmoji('1f393'), + createEmoji('1f451'), + createEmoji('1f392'), + createEmoji('1f45d'), + createEmoji('1f45b'), + createEmoji('1f45c'), + createEmoji('1f4bc'), + createEmoji('1f453'), + createEmoji('1f48d'), + createEmoji('1f302'), + ], + Nature: [ + createEmoji('1f436'), + createEmoji('1f431'), + createEmoji('1f42d'), + createEmoji('1f439'), + createEmoji('1f430'), + createEmoji('1f43b'), + createEmoji('1f43c'), + createEmojiWithNoKeyword('1f428'), + createEmoji('1f42f'), + createEmoji('1f42e'), + createEmoji('1f437'), + createEmoji('1f43d'), + createEmoji('1f438'), + createEmoji('1f419'), + createEmoji('1f435'), + createEmoji('1f648'), + createEmoji('1f649'), + createEmoji('1f64a'), + createEmoji('1f412'), + createEmoji('1f427'), + createEmoji('1f426'), + createEmojiWithNoKeyword('1f424'), + createEmoji('1f423'), + createEmoji('1f425'), + createEmoji('1f43a'), + createEmoji('1f417'), + createEmoji('1f434'), + createEmoji('1f41d'), + createEmoji('1f41b'), + createEmoji('1f40c'), + createEmoji('1f41e'), + createEmoji('1f41c'), + createEmoji('1f40d'), + createEmoji('1f422'), + createEmoji('1f420'), + createEmoji('1f41f'), + createEmoji('1f421'), + createEmoji('1f42c'), + createEmoji('1f433'), + createEmoji('1f414'), + createEmoji('1f42b'), + createEmoji('1f418'), + createEmoji('1f411'), + createEmoji('1f40e'), + createEmoji('1f429'), + createEmoji('1f43e'), + createEmoji('1f432'), + createEmoji('1f335'), + createEmoji('1f384'), + createEmoji('1f334'), + createEmoji('1f331'), + createEmoji('1f33f'), + createEmoji('1f340'), + createEmojiWithNoKeyword('1f38d'), + createEmojiWithNoKeyword('1f38b'), + createEmoji('1f343'), + createEmoji('1f342'), + createEmoji('1f341'), + createEmoji('1f33e'), + createEmoji('1f33a'), + createEmoji('1f33b'), + createEmoji('1f339'), + createEmoji('1f33c'), + createEmoji('1f337'), + createEmoji('1f338'), + createEmoji('1f344'), + createEmoji('1f490'), + createEmoji('1f330'), + createEmoji('1f383'), + createEmoji('1f41a'), + createEmojiWithNoKeyword('1f30f'), + createEmoji('1f315'), + createEmoji('1f311'), + createEmoji('1f313'), + createEmoji('1f314'), + createEmoji('1f31b'), + createEmoji('1f319'), + createEmoji('02b50'), + createEmoji('1f31f'), + createEmojiWithNoKeyword('1f4ab'), + createEmoji('02728'), + createEmojiWithNoKeyword('02600'), + createEmojiWithNoKeyword('026c5'), + createEmoji('02601'), + createEmoji('026a1'), + createEmoji('1f525'), + createEmoji('1f4a5'), + createEmoji('02744'), + createEmoji('026c4'), + createEmoji('1f4a8'), + createEmoji('02614'), + createEmojiWithNoKeyword('1f4a7'), + createEmojiWithNoKeyword('1f4a6'), + createEmoji('1f30a'), + ], + Activities: [ + createEmoji('026bd'), + createEmoji('1f3c0'), + createEmoji('1f3c8'), + createEmoji('026be'), + createEmoji('1f3be'), + createEmoji('1f3b1'), + createEmoji('026f3'), + createEmoji('1f3bf'), + createEmoji('1f3c2'), + createEmoji('1f3a3'), + createEmoji('1f3ca'), + createEmoji('1f3c4'), + createEmoji('1f3c6'), + createEmoji('1f3bd'), + createEmoji('1f3ab'), + createEmoji('1f3ad'), + createEmoji('1f3a8'), + createEmoji('1f3aa'), + createEmoji('1f3a4'), + createEmoji('1f3a7'), + createEmoji('1f3bc'), + createEmoji('1f3b9'), + createEmoji('1f3b7'), + createEmoji('1f3ba'), + createEmoji('1f3b8'), + createEmoji('1f3bb'), + createEmoji('1f3ac'), + createEmoji('1f3ae'), + createEmoji('1f47e'), + createEmoji('1f3af'), + createEmoji('1f3b2'), + createEmoji('1f3b0'), + createEmoji('1f3b3'), + ], + Food: [ + createEmoji('1f34f'), + createEmoji('1f34e'), + createEmoji('1f34a'), + createEmoji('1f34c'), + createEmoji('1f349'), + createEmoji('1f347'), + createEmoji('1f353'), + createEmoji('1f348'), + createEmoji('1f352'), + createEmoji('1f351'), + createEmoji('1f34d'), + createEmoji('1f345'), + createEmoji('1f346'), + createEmoji('1f33d'), + createEmoji('1f360'), + createEmoji('1f35e'), + createEmoji('1f357'), + createEmoji('1f356'), + createEmoji('1f364'), + createEmoji('1f373'), + createEmoji('1f354'), + createEmoji('1f35f'), + createEmoji('1f355'), + createEmoji('1f35d'), + createEmoji('1f35c'), + createEmoji('1f372'), + createEmoji('1f365'), + createEmoji('1f363'), + createEmoji('1f371'), + createEmoji('1f35b'), + createEmoji('1f359'), + createEmoji('1f35a'), + createEmoji('1f358'), + createEmoji('1f362'), + createEmoji('1f361'), + createEmojiWithNoKeyword('1f367'), + createEmoji('1f368'), + createEmoji('1f366'), + createEmoji('1f370'), + createEmoji('1f36f'), + createEmoji('1f382'), + createEmoji('1f36e'), + createEmoji('1f36c'), + createEmoji('1f36d'), + createEmoji('1f36b'), + createEmoji('1f369'), + createEmoji('1f36a'), + createEmoji('1f37a'), + createEmoji('1f37b'), + createEmoji('1f377'), + createEmoji('1f378'), + createEmoji('1f379'), + createEmoji('1f376'), + createEmoji('1f375'), + createEmoji('02615'), + createEmoji('1f374'), + ], + Travel: [ + createEmoji('1f697'), + createEmoji('1f695'), + createEmoji('1f687'), + createEmojiWithNoKeyword('1f699'), + createEmoji('1f68c'), + createEmoji('1f693'), + createEmoji('1f69a'), + createEmojiWithNoKeyword('1f691'), + createEmoji('1f692'), + createEmoji('1f6b2'), + createEmoji('1f6a8'), + createEmoji('1f683'), + createEmoji('1f684'), + createEmojiWithNoKeyword('1f685'), + createEmoji('1f689'), + createEmoji('02708'), + createEmoji('026f5'), + createEmojiWithNoKeyword('1f6a4'), + createEmoji('1f680'), + createEmoji('1f4ba'), + createEmoji('02693'), + createEmoji('1f6a7'), + createEmoji('026fd'), + createEmoji('1f68f'), + createEmoji('1f6a5'), + createEmoji('1f3c1'), + createEmoji('1f6a2'), + createEmoji('1f3a1'), + createEmoji('1f3a2'), + createEmoji('1f3a0'), + createEmoji('1f301'), + createEmojiWithNoKeyword('1f5fc'), + createEmoji('1f3ed'), + createEmoji('026f2'), + createEmojiWithNoKeyword('1f391'), + createEmojiWithNoKeyword('1f5fb'), + createEmojiWithNoKeyword('1f30b'), + createEmojiWithNoKeyword('1f5fe'), + createEmoji('1f305'), + createEmoji('1f304'), + createEmoji('1f307'), + createEmoji('1f306'), + createEmoji('1f303'), + createEmoji('1f309'), + createEmoji('1f30c'), + createEmoji('1f387'), + createEmoji('1f386'), + createEmoji('1f308'), + createEmoji('1f3f0'), + createEmojiWithNoKeyword('1f3ef'), + createEmoji('1f5fd'), + createEmoji('1f3e0'), + createEmoji('1f3e1'), + createEmoji('1f3e2'), + createEmoji('1f3ec'), + createEmoji('026fa'), + createEmojiWithNoKeyword('1f3e3'), + createEmojiWithNoKeyword('1f3e5'), + createEmoji('1f3e6'), + createEmoji('1f3e8'), + createEmoji('1f3ea'), + createEmoji('1f3eb'), + createEmojiWithNoKeyword('1f3e9'), + createEmoji('1f492'), + createEmoji('026ea'), + createEmoji('1f320'), + ], + Symbols: [ + common3, + createEmoji('1f49b'), + createEmoji('1f49a'), + createEmoji('1f499'), + createEmoji('1f494'), + createEmoji('1f49c'), + createEmoji('1f495'), + createEmoji('1f493'), + createEmoji('1f49e'), + createEmoji('1f497'), + createEmoji('1f498'), + createEmoji('1f496'), + createEmoji('1f49d'), + createEmoji('1f49f'), + createEmojiWithNoKeyword('1f52f'), + createEmojiWithNoKeyword('026ce'), + createEmoji('02648'), + createEmoji('02649'), + createEmoji('0264a'), + createEmoji('0264b'), + createEmoji('0264c'), + createEmoji('0264d'), + createEmoji('0264e'), + createEmoji('0264f'), + createEmoji('02650'), + createEmoji('02651'), + createEmoji('02652'), + createEmoji('02653'), + createEmojiWithNoKeyword('1f194'), + createEmojiWithNoKeyword('1f4f4'), + createEmojiWithNoKeyword('1f4f3'), + createEmojiWithNoKeyword('1f19a'), + createEmojiWithNoKeyword('1f4ae'), + createEmojiWithNoKeyword('1f18e'), + createEmojiWithNoKeyword('1f191'), + createEmojiWithNoKeyword('1f198'), + createEmojiWithNoKeyword('026d4'), + createEmoji('1f4db'), + createEmoji('1f6ab'), + createEmojiWithNoKeyword('0274c'), + createEmojiWithNoKeyword('02b55'), + createEmojiWithNoKeyword('1f4a2'), + createEmoji('02668'), + createEmojiWithNoKeyword('1f51e'), + createEmojiWithNoKeyword('02757'), + createEmojiWithNoKeyword('02755'), + createEmojiWithNoKeyword('02753'), + createEmojiWithNoKeyword('02754'), + createEmojiWithNoKeyword('02049'), + createEmojiWithNoKeyword('1f4af'), + createEmoji('1f531'), + createEmojiWithNoKeyword('0303d'), + createEmoji('026a0'), + createEmojiWithNoKeyword('1f530'), + createEmojiWithNoKeyword('1f22f'), + createEmojiWithNoKeyword('1f4b9'), + createEmoji('02733'), + createEmojiWithNoKeyword('0274e'), + createEmojiWithNoKeyword('02705'), + createEmojiWithNoKeyword('1f4a0'), + createEmoji('1f300'), + createEmoji('1f3e7'), + createEmoji('0267f'), + createEmoji('1f6ad'), + createEmojiWithNoKeyword('1f6be'), + createEmoji('02734'), + createEmoji('1f17f'), + createEmoji('1f6b9'), + createEmoji('1f6ba'), + createEmojiWithNoKeyword('1f6bc'), + createEmoji('1f6bb'), + createEmoji('1f3a6'), + createEmoji('1f4f6'), + createEmojiWithNoKeyword('1f201'), + createEmojiWithNoKeyword('1f196'), + createEmojiWithNoKeyword('0267b'), + createEmojiWithNoKeyword('1f197'), + createEmojiWithNoKeyword('1f192'), + createEmoji('02747'), + createEmojiWithNoKeyword('1f195'), + createEmojiWithNoKeyword('1f193'), + createEmojiWithNoKeyword('1f51f'), + createEmojiWithNoKeyword('1f522'), + createEmojiWithNoKeyword('023ea'), + createEmojiWithNoKeyword('023e9'), + createEmojiWithNoKeyword('1f53c'), + createEmojiWithNoKeyword('1f53d'), + createEmojiWithNoKeyword('023eb'), + createEmojiWithNoKeyword('023ec'), + createEmojiWithNoKeyword('1f199'), + createEmojiWithNoKeyword('02139'), + createEmojiWithNoKeyword('1f524'), + createEmojiWithNoKeyword('1f521'), + createEmojiWithNoKeyword('1f520'), + createEmojiWithNoKeyword('1f523'), + createEmoji('1f3b5'), + createEmoji('1f3b6'), + createEmojiWithNoKeyword('03030'), + createEmojiWithNoKeyword('027bf'), + createEmojiWithNoKeyword('02714'), + createEmoji('1f503'), + createEmojiWithNoKeyword('02795'), + createEmojiWithNoKeyword('02796'), + createEmojiWithNoKeyword('02797'), + createEmojiWithNoKeyword('02716'), + createEmojiWithNoKeyword('027b0'), + createEmojiWithNoKeyword('1f4b2'), + createEmojiWithNoKeyword('1f4b1'), + createEmojiWithNoKeyword('1f51a'), + createEmojiWithNoKeyword('1f519'), + createEmojiWithNoKeyword('1f51b'), + createEmojiWithNoKeyword('1f51d'), + createEmojiWithNoKeyword('1f51c'), + createEmojiWithNoKeyword('02611'), + createEmojiWithNoKeyword('1f518'), + createEmojiWithNoKeyword('026ab'), + createEmojiWithNoKeyword('1f534'), + createEmojiWithNoKeyword('1f535'), + createEmojiWithNoKeyword('1f539'), + createEmojiWithNoKeyword('1f538'), + createEmojiWithNoKeyword('1f536'), + createEmojiWithNoKeyword('1f537'), + createEmojiWithNoKeyword('1f53a'), + createEmojiWithNoKeyword('1f53b'), + createEmojiWithNoKeyword('02b1b'), + createEmojiWithNoKeyword('02b1c'), + createEmojiWithNoKeyword('1f532'), + createEmojiWithNoKeyword('1f533'), + createEmojiWithNoKeyword('1f50a'), + createEmoji('1f4e3'), + createEmojiWithNoKeyword('1f4e2'), + createEmoji('1f514'), + createEmojiWithNoKeyword('1f004'), + createEmoji('1f0cf'), + createEmojiWithNoKeyword('1f3b4'), + createEmojiWithNoKeyword('1f4ac'), + createEmoji('1f550'), + createEmoji('1f551'), + createEmoji('1f552'), + createEmoji('1f553'), + createEmoji('1f554'), + createEmoji('1f555'), + createEmoji('1f556'), + createEmoji('1f557'), + createEmoji('1f558'), + createEmoji('1f559'), + createEmoji('1f55a'), + createEmoji('1f55b'), + createEmojiWithNoKeyword('1f236'), + createEmojiWithNoKeyword('1f250'), + createEmojiWithNoKeyword('1f239'), + createEmojiWithNoKeyword('1f21a'), + createEmojiWithNoKeyword('1f232'), + createEmojiWithNoKeyword('1f251'), + createEmojiWithNoKeyword('1f234'), + createEmojiWithNoKeyword('1f233'), + createEmojiWithNoKeyword('1f23a'), + createEmojiWithNoKeyword('1f235'), + ], + Objects: [ + createEmojiWithNoKeyword('0231a'), + createEmoji('1f4f1'), + createEmojiWithNoKeyword('1f4f2'), + createEmoji('1f4bb'), + createEmojiWithNoKeyword('1f4bd'), + createEmoji('1f4be'), + createEmoji('1f4bf'), + createEmoji('1f4c0'), + createEmoji('1f4fc'), + createEmoji('1f4f7'), + createEmoji('1f4f9'), + createEmoji('1f3a5'), + createEmoji('1f4de'), + createEmojiWithNoKeyword('0260e'), + createEmoji('1f4df'), + createEmoji('1f4e0'), + createEmoji('1f4fa'), + createEmoji('1f4fb'), + createEmojiWithNoKeyword('023f0'), + createEmojiWithNoKeyword('0231b'), + createEmojiWithNoKeyword('023f3'), + createEmoji('1f4e1'), + createEmoji('1f50b'), + createEmoji('1f50c'), + createEmoji('1f4a1'), + createEmoji('1f526'), + createEmojiWithNoKeyword('1f4b8'), + createEmoji('1f4b5'), + createEmoji('1f4b4'), + createEmoji('1f4b0'), + createEmoji('1f4b3'), + createEmoji('1f48e'), + createEmoji('1f527'), + createEmoji('1f528'), + createEmoji('1f529'), + createEmojiWithNoKeyword('1f52b'), + createEmoji('1f4a3'), + createEmoji('1f52a'), + createEmoji('1f6ac'), + createEmoji('1f52e'), + createEmoji('1f488'), + createEmoji('1f48a'), + createEmoji('1f489'), + createEmoji('1f516'), + createEmoji('1f6bd'), + createEmoji('1f6c0'), + createEmoji('1f511'), + createEmoji('1f6aa'), + createEmojiWithNoKeyword('1f5ff'), + createEmoji('1f388'), + createEmojiWithNoKeyword('1f38f'), + createEmoji('1f380'), + createEmoji('1f381'), + createEmoji('1f38a'), + createEmoji('1f389'), + createEmojiWithNoKeyword('1f38e'), + createEmoji('1f390'), + createEmojiWithNoKeyword('1f38c'), + createEmojiWithNoKeyword('1f3ee'), + createEmoji('02709'), + createEmoji('1f4e9'), + createEmoji('1f4e8'), + createEmoji('1f48c'), + createEmoji('1f4e7'), + createEmoji('1f4ee'), + createEmoji('1f4ea'), + createEmoji('1f4eb'), + createEmoji('1f4e6'), + createEmojiWithNoKeyword('1f4e5'), + createEmojiWithNoKeyword('1f4e4'), + createEmojiWithNoKeyword('1f4dc'), + createEmojiWithNoKeyword('1f4c3'), + createEmojiWithNoKeyword('1f4d1'), + createEmoji('1f4ca'), + createEmoji('1f4c8'), + createEmoji('1f4c9'), + createEmojiWithNoKeyword('1f4c4'), + createEmoji('1f4c5'), + createEmojiWithNoKeyword('1f4c6'), + createEmojiWithNoKeyword('1f4c7'), + createEmoji('1f4cb'), + createEmoji('1f4c1'), + createEmojiWithNoKeyword('1f4c2'), + createEmoji('1f4f0'), + createEmoji('1f4d3'), + createEmojiWithNoKeyword('1f4d5'), + createEmojiWithNoKeyword('1f4d7'), + createEmojiWithNoKeyword('1f4d8'), + createEmojiWithNoKeyword('1f4d9'), + createEmojiWithNoKeyword('1f4d4'), + createEmoji('1f4d2'), + createEmoji('1f4da'), + createEmojiWithNoKeyword('1f4d6'), + createEmojiWithNoKeyword('1f517'), + createEmoji('1f4ce'), + createEmoji('02702'), + createEmojiWithNoKeyword('1f4d0'), + createEmoji('1f4cf'), + createEmoji('1f4cc'), + createEmoji('1f4cd'), + createEmojiWithNoKeyword('1f6a9'), + createEmoji('1f510'), + createEmoji('1f512'), + createEmoji('1f513'), + createEmoji('1f50f'), + createEmojiWithNoKeyword('02712'), + createEmoji('1f4dd'), + createEmoji('0270f'), + createEmoji('1f50d'), + createEmojiWithNoKeyword('1f50e'), + ], +}; + +/** + * @internal + */ +export const EmojiFabricIconCharacterMap: Record = { + Activities: 'Soccer', + Food: 'EatDrink', + Nature: 'FangBody', + Objects: 'Lightbulb', + People: 'Emoji2', + Symbols: 'Heart', + Travel: 'Car', +}; + +/** + * @internal + */ +export function forEachEmojiFamily( + callback: (emojis: Emoji[], family: EmojiFamilyKeys) => boolean +): void { + const families = getObjectKeys(EmojiList); + for (const family of families) { + if (!callback(EmojiList[family], family)) { + break; + } + } +} + +/** + * @internal + */ +export function forEachEmoji(callback: (emoji: Emoji) => boolean): void { + forEachEmojiFamily(emojis => { + for (const emoji of emojis) { + if (!callback(emoji)) { + return false; + } + } + return true; + }); +} + +// get emoji code point from an emoji key +function getEmojiCodePoint(key: string): string | null { + const unicode = parseInt(key, 16); + if (isNaN(unicode)) { + return null; + } + + // All emojis have codepoints between the following ranges: + // 0x1F7000 - 0x1F700 -> has surrogate pairs + // 0x00023 - 0x04000 -> does not have surrogate pairs + let surrogatePairs: number[]; + if (unicode >= 0x1f000 && unicode <= 0x1f700) { + const hi = Math.floor((unicode - 0x10000) / 0x400) + 0xd800; + const lo = ((unicode - 0x10000) % 0x400) + 0xdc00; + surrogatePairs = [hi, lo]; + } else if (unicode >= 0x00023 && unicode <= 0x04000) { + surrogatePairs = [unicode]; + } + + return String.fromCharCode(...surrogatePairs!); +} + +function createEmoji(key: string, shortcut?: string): Emoji { + return { + key, + description: `emjD${key}`, + keywords: `emjK${key}`, + shortcut, + codePoint: getEmojiCodePoint(key) ?? undefined, + }; +} + +function createEmojiWithNoKeyword(key: string): Emoji { + return { key, description: `emjD${key}`, codePoint: getEmojiCodePoint(key) ?? undefined }; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/utils/searchEmojis.ts b/demo/scripts/controlsV2/roosterjsReact/emoji/utils/searchEmojis.ts new file mode 100644 index 00000000000..804bcb12032 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/utils/searchEmojis.ts @@ -0,0 +1,40 @@ +import { forEachEmoji } from './emojiList'; +import type { Emoji } from '../type/Emoji'; + +/** + * @internal + */ +export function searchEmojis(search: string, strings: Record): Emoji[] { + const shortcutMatch = matchShortcut(search); + search = search.toLowerCase(); + const fullMatch: Emoji[] = shortcutMatch ? [shortcutMatch] : []; + const partialMatch: Emoji[] = []; + const partialSearch = ' ' + (search[0] == ':' ? search.substr(1) : search); + forEachEmoji(emoji => { + const keywords = (emoji.keywords && strings[emoji.keywords]) || ''; + const searchableKeywords = emoji.keywords ? ' ' + keywords.toLowerCase() + ' ' : ''; + const index = searchableKeywords.indexOf(partialSearch); + if (index >= 0) { + (searchableKeywords[index + partialSearch.length] == ' ' + ? fullMatch + : partialMatch + ).push(emoji); + } + return true; + }); + + return fullMatch.concat(partialMatch); +} + +function matchShortcut(search: string): Emoji | null { + let result: Emoji | null = null; + search = ' ' + search + ' '; + forEachEmoji((emoji: Emoji) => { + if (emoji.shortcut && (' ' + emoji.shortcut + ' ').indexOf(search) >= 0) { + result = emoji; + return false; + } + return true; + }); + return result; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialog.tsx b/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialog.tsx new file mode 100644 index 00000000000..2307e35c966 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialog.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import InputDialogItem from './InputDialogItem'; +import { DefaultButton, PrimaryButton } from '@fluentui/react/lib/Button'; +import { Dialog, DialogFooter, DialogType } from '@fluentui/react/lib/Dialog'; +import { getLocalizedString } from '../../common/index'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import type { DialogItem } from '../type/DialogItem'; +import type { + CancelButtonStringKey, + LocalizedStrings, + OkButtonStringKey, +} from '../../common/index'; + +/** + * @internal + */ +export interface InputDialogProps { + dialogTitleKey: Strings; + unlocalizedTitle: string; + items: Record>; + strings?: LocalizedStrings; + onChange?: ( + changedItemName: ItemNames, + newValue: string, + currentValues: Record + ) => Record | null; + onOk: (values: Record) => void; + onCancel: () => void; +} + +/** + * @internal + */ +export default function InputDialog( + props: InputDialogProps +) { + const { items, strings, dialogTitleKey, unlocalizedTitle, onOk, onCancel, onChange } = props; + const dialogContentProps = React.useMemo( + () => ({ + type: DialogType.normal, + title: getLocalizedString(strings, dialogTitleKey, unlocalizedTitle), + }), + [strings, dialogTitleKey, unlocalizedTitle] + ); + const [currentValues, setCurrentValues] = React.useState>( + getObjectKeys(items).reduce((result: Record, key) => { + result[key] = items[key].initValue; + return result; + }, {} as Record) + ); + + const onSubmit = React.useCallback(() => { + onOk?.(currentValues); + }, [onOk, currentValues]); + const onItemChanged = React.useCallback( + (itemName: ItemNames, newValue: string) => { + if (itemName in items) { + const newValues = onChange?.(itemName, newValue, { + ...currentValues, + }) || { + ...currentValues, + [itemName]: newValue, + }; + + setCurrentValues(newValues); + } + }, + [setCurrentValues, currentValues, items] + ); + + return ( + + ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialogItem.tsx b/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialogItem.tsx new file mode 100644 index 00000000000..4c642972037 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialogItem.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { getLocalizedString } from '../../common/index'; +import { mergeStyleSets } from '@fluentui/react/lib/Styling'; +import { TextField } from '@fluentui/react/lib/TextField'; +import type { DialogItem } from '../type/DialogItem'; +import type { LocalizedStrings } from '../../common/index'; + +/** + * @internal + */ +export interface InputDialogItemProps { + itemName: ItemNames; + strings: LocalizedStrings | undefined; + items: Record>; + currentValues: Record; + onEnterKey: () => void; + onChanged: (itemName: ItemNames, newValue: string) => void; +} + +const classNames = mergeStyleSets({ + inputBox: { + width: '100%', + minWidth: '250px', + height: '32px', + margin: '5px 0 16px', + borderRadius: '2px', + }, +}); + +/** + * @internal + */ +export default function InputDialogItem( + props: InputDialogItemProps +) { + const { itemName, strings, items, currentValues, onChanged, onEnterKey } = props; + const { labelKey, unlocalizedLabel, autoFocus } = items[itemName]; + const value = currentValues[itemName]; + const onValueChange = React.useCallback( + (_, newValue) => { + onChanged(itemName, newValue); + }, + [itemName, onChanged] + ); + + const onKeyPress = React.useCallback( + (e: React.KeyboardEvent) => { + if (e.key == 'Enter') { + onEnterKey(); + } + }, + [onEnterKey] + ); + + return ( +
+ {labelKey ?
{getLocalizedString(strings, labelKey, unlocalizedLabel)}
: null} +
+ +
+
+ ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/inputDialog/index.ts b/demo/scripts/controlsV2/roosterjsReact/inputDialog/index.ts new file mode 100644 index 00000000000..23164125a88 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/inputDialog/index.ts @@ -0,0 +1,2 @@ +export { showInputDialog } from './utils/showInputDialog'; +export { DialogItem } from './type/DialogItem'; diff --git a/demo/scripts/controlsV2/roosterjsReact/inputDialog/type/DialogItem.ts b/demo/scripts/controlsV2/roosterjsReact/inputDialog/type/DialogItem.ts new file mode 100644 index 00000000000..ac2e2428167 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/inputDialog/type/DialogItem.ts @@ -0,0 +1,24 @@ +/** + * Item of input dialog + */ +export interface DialogItem { + /** + * Localized string key of the input item name + */ + labelKey: Strings | null; + + /** + * Unlocalized string for the label text. This will be used when a valid localized string is not found using the given string key + */ + unlocalizedLabel: string | null; + + /** + * Initial value of this item + */ + initValue: string; + + /** + * Whether focus should be put into this item automatically + */ + autoFocus?: boolean; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/inputDialog/utils/showInputDialog.tsx b/demo/scripts/controlsV2/roosterjsReact/inputDialog/utils/showInputDialog.tsx new file mode 100644 index 00000000000..4a69eb5d869 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/inputDialog/utils/showInputDialog.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import InputDialog from '../component/InputDialog'; +import { renderReactComponent } from '../../common/utils/renderReactComponent'; +import type { DialogItem } from '../type/DialogItem'; +import type { + CancelButtonStringKey, + LocalizedStrings, + OkButtonStringKey, + UIUtilities, +} from '../../common/index'; + +/** + * Show a dialog with input items + * @param uiUtilities UI utilities to help render the dialog + * @param dialogTitleKey Localized string key for title of this dialog + * @param unlocalizedTitle Unlocalized title string of this dialog. It will be used if a valid localized string is not found using dialogTitleKey + * @param items Input items in this dialog + * @param strings Localized strings + * @param onChange An optional callback that will be invoked when input item value is changed + */ +export function showInputDialog( + uiUtilities: UIUtilities, + dialogTitleKey: Strings, + unlocalizedTitle: string, + items: Record>, + strings?: LocalizedStrings, + onChange?: ( + changedItemName: ItemNames, + newValue: string, + currentValues: Record + ) => Record | null +): Promise | null> { + return new Promise | null>(resolve => { + let disposer: null | (() => void) = null; + const onOk = (result: Record) => { + disposer?.(); + resolve(result); + }; + const onCancel = () => { + disposer?.(); + resolve(null); + }; + const component = ( + + ); + + disposer = renderReactComponent(uiUtilities, component); + }); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/component/showPasteOptionPane.tsx b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/component/showPasteOptionPane.tsx new file mode 100644 index 00000000000..9208f185e57 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/component/showPasteOptionPane.tsx @@ -0,0 +1,221 @@ +import * as React from 'react'; +import { ButtonKeys, Buttons } from '../utils/buttons'; +import { Callout, DirectionalHint } from '@fluentui/react/lib/Callout'; +import { getLocalizedString } from '../../common/index'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import { getPositionRect } from '../utils/getPositionRect'; +import { Icon } from '@fluentui/react/lib/Icon'; +import { IconButton } from '@fluentui/react/lib/Button'; +import { memoizeFunction } from '@fluentui/react/lib/Utilities'; +import { mergeStyleSets } from '@fluentui/react/lib/Styling'; +import { renderReactComponent } from '../../common/utils/renderReactComponent'; +import { useTheme } from '@fluentui/react/lib/Theme'; +import type { LocalizedStrings, UIUtilities } from '../../common/index'; +import type { Theme } from '@fluentui/react/lib/Theme'; +import type { PasteOptionButtonKeys, PasteOptionStringKeys } from '../type/PasteOptionStringKeys'; + +const getPasteOptionClassNames = memoizeFunction((theme: Theme) => { + const palette = theme.palette; + + return mergeStyleSets({ + pastePane: { + paddingLeft: '4px', + minWidth: '72px', + }, + optionPane: { + textAlign: 'center', + padding: '4px', + }, + icon: { + fontSize: '14px', + }, + buttonsContainer: { + justifyContent: 'center', + display: 'flex', + }, + button: { + width: '32px', + height: '32px', + margin: '0 4px 4px 0', + borderRadius: '2px', + flex: '0 0 auto', + '&:hover': { + backgroundColor: palette.themeLighter, + }, + }, + isChecked: { + backgroundColor: palette.themeLight, + '&:hover': { + backgroundColor: palette.themeLighter, + }, + }, + }); +}); + +interface PasteOptionButtonProps { + buttonName: PasteOptionButtonKeys; + className: string; + paste: (key: PasteOptionButtonKeys) => void; + strings?: LocalizedStrings; +} + +function PasteOptionButton(props: PasteOptionButtonProps) { + const { buttonName, paste, strings, className } = props; + const button = Buttons[buttonName]; + const onClick = React.useCallback(() => { + paste(buttonName); + }, [paste, buttonName]); + + return ( + + ); +} + +interface PasteOptionProps { + strings?: LocalizedStrings; + container: Node; + offset: number; + isRtl: boolean; + paste: (key: PasteOptionButtonKeys) => void; + dismiss: () => void; +} + +/** + * @internal + */ +export interface PasteOptionPane { + getSelectedKey: () => PasteOptionButtonKeys | null; + setSelectedKey: (index: PasteOptionButtonKeys) => void; + dismiss: () => void; +} + +const PasteOptionComponent = React.forwardRef(function PasteOptionFunc( + props: PasteOptionProps, + ref: React.Ref +) { + const { strings, container, offset, paste, dismiss, isRtl } = props; + const theme = useTheme(); + const classNames = getPasteOptionClassNames(theme); + const [selectedKey, setSelectedKey] = React.useState(null); + + const rect = getPositionRect(container, offset); + const target = rect && { x: props.isRtl ? rect.left : rect.right, y: rect.bottom }; + + React.useImperativeHandle( + ref, + () => ({ + dismiss, + setSelectedKey, + getSelectedKey: () => selectedKey, + }), + [dismiss, paste, isRtl, selectedKey, setSelectedKey] + ); + + const buttonPane = React.useRef(null); + const onDismiss = React.useCallback( + (evt?: Event | React.MouseEvent | React.KeyboardEvent) => { + const target = + evt instanceof FocusEvent && evt.relatedTarget instanceof Node + ? evt.relatedTarget + : null; + const clickOnButtonPane = + target && + buttonPane.current && + (buttonPane.current == target || buttonPane.current.contains(target)); + if (!clickOnButtonPane) { + dismiss(); + } + }, + [dismiss] + ); + + const onClickShowSubMenu = React.useCallback( + (event: React.MouseEvent) => { + setSelectedKey(ButtonKeys[0]); + event.preventDefault(); + event.stopPropagation(); + }, + [setSelectedKey] + ); + + return ( + +
+
+ + {getLocalizedString(strings, 'pasteOptionPaneText', '(Ctrl)')} +
+ {selectedKey && ( +
+ {getObjectKeys(Buttons).map(key => ( + + ))} +
+ )} +
+
+ ); +}); + +/** + * @internal Show paste open pane component + * @param uiUtilities The UI utilities object to help render component + * @param position Target position + * @param strings Localize string for this component + * @param onPaste A callback to be called when user click on a paste button + * @param ref Reference object for this component + */ +export function showPasteOptionPane( + uiUtilities: UIUtilities, + container: Node, + offset: number, + onPaste: (key: PasteOptionButtonKeys) => void, + ref: React.RefObject, + strings?: LocalizedStrings +) { + let disposer: (() => void) | null = null; + const onDismiss = () => { + disposer?.(); + disposer = null; + }; + + disposer = renderReactComponent( + uiUtilities, + + ); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/index.ts b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/index.ts new file mode 100644 index 00000000000..e46c9d92cd5 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/index.ts @@ -0,0 +1,2 @@ +export { PasteOptionButtonKeys, PasteOptionStringKeys } from './type/PasteOptionStringKeys'; +export { createPasteOptionPlugin } from './plugin/createPasteOptionPlugin'; diff --git a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts new file mode 100644 index 00000000000..cbdf9bd300f --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts @@ -0,0 +1,187 @@ +import * as React from 'react'; +import { ButtonKeys, Buttons } from '../utils/buttons'; +import { ChangeSource } from 'roosterjs-content-model-core'; +import { ClipboardData, IEditor, PluginEvent } from 'roosterjs-content-model-types'; +import { showPasteOptionPane } from '../component/showPasteOptionPane'; +import type { PasteOptionPane } from '../component/showPasteOptionPane'; +import type { LocalizedStrings, ReactEditorPlugin, UIUtilities } from '../../common/index'; +import type { PasteOptionButtonKeys, PasteOptionStringKeys } from '../type/PasteOptionStringKeys'; + +class PasteOptionPlugin implements ReactEditorPlugin { + private clipboardData: ClipboardData | null = null; + private editor: IEditor | null = null; + private uiUtilities: UIUtilities | null = null; + private pasteOptionRef = React.createRef(); + + constructor(private strings?: LocalizedStrings) {} + + getName() { + return 'PasteOption'; + } + + initialize(editor: IEditor) { + this.editor = editor; + } + + dispose() { + this.pasteOptionRef.current?.dismiss(); + this.editor = null; + } + + onPluginEvent(event: PluginEvent) { + if (event.eventType == 'scroll') { + if (this.pasteOptionRef.current) { + this.showPasteOptionPane(); + } + } else if (this.pasteOptionRef.current) { + this.handlePasteOptionPaneEvent(event); + } else if (event.eventType == 'contentChanged') { + if (event.source == ChangeSource.Paste) { + const clipboardData = event.data as ClipboardData; + + // Only show paste option when we pasted HTML with some format + if (clipboardData?.text && clipboardData.types?.indexOf('text/html') >= 0) { + this.clipboardData = clipboardData; + this.showPasteOptionPane(); + } + } + } + } + + setUIUtilities(uiUtilities: UIUtilities) { + this.uiUtilities = uiUtilities; + } + + private handlePasteOptionPaneEvent(event: PluginEvent) { + if (event.eventType == 'keyDown' && this.pasteOptionRef.current) { + const selectedKey = this.pasteOptionRef.current.getSelectedKey(); + + if (!selectedKey) { + switch (event.rawEvent.key) { + case 'Control': + this.pasteOptionRef.current.setSelectedKey(ButtonKeys[0]); + cancelEvent(event.rawEvent); + break; + + case 'Escape': + this.pasteOptionRef.current.dismiss(); + cancelEvent(event.rawEvent); + break; + + default: + this.pasteOptionRef.current.dismiss(); + break; + } + } else { + const keyboardEvent = event.rawEvent; + + if (keyboardEvent.key != 'Control' && keyboardEvent.ctrlKey) { + // Dismiss the paste option when pressing hotkey CTRL+ + this.pasteOptionRef.current.dismiss(); + return; + } + + for (let i = 0; i < ButtonKeys.length; i++) { + const key = ButtonKeys[i]; + const button = Buttons[key]; + if (button.shortcut == String.fromCharCode(keyboardEvent.which)) { + this.onPaste(key); + cancelEvent(keyboardEvent); + return; + } + } + + switch (keyboardEvent.key) { + case 'Escape': + this.pasteOptionRef.current.dismiss(); + break; + case 'ArrowLeft': + case 'ArrowRight': + const buttonCount = ButtonKeys.length; + const diff = + (keyboardEvent.key == 'ArrowRight') == this.uiUtilities?.isRightToLeft() + ? -1 + : 1; + this.pasteOptionRef.current.setSelectedKey( + ButtonKeys[ + (ButtonKeys.indexOf(selectedKey) + diff + buttonCount) % buttonCount + ] + ); + break; + case 'Enter': + this.onPaste(selectedKey); + break; + case 'Control': + // Noop + break; + default: + this.pasteOptionRef.current.dismiss(); + return; + } + + cancelEvent(keyboardEvent); + } + } + } + + private onPaste = (key: PasteOptionButtonKeys) => { + if (this.clipboardData && this.editor) { + this.editor.focus(); + + switch (key) { + case 'pasteOptionPasteAsIs': + this.editor.pasteFromClipboard(this.clipboardData); + break; + + case 'pasteOptionPasteText': + this.editor.pasteFromClipboard(this.clipboardData, 'asPlainText'); + break; + + case 'pasteOptionMergeFormat': + this.editor.pasteFromClipboard(this.clipboardData, 'mergeFormat'); + break; + case 'pasteOptionPasteAsImage': + this.editor.pasteFromClipboard(this.clipboardData, 'asImage'); + } + + this.pasteOptionRef.current?.setSelectedKey(key); + } + }; + + private showPasteOptionPane() { + this.pasteOptionRef.current?.dismiss(); + + const selection = this.editor.getDOMSelection(); + + if (selection?.type == 'range' && this.uiUtilities) { + showPasteOptionPane( + this.uiUtilities, + selection.range.startContainer, + selection.range.startOffset, + this.onPaste, + this.pasteOptionRef, + this.strings + ); + } + } +} + +function cancelEvent(event: UIEvent) { + event.preventDefault(); + event.stopPropagation(); +} + +/** + * Create a new instance of PasteOption plugin to show an option pane when paste, so that user can choose + * an option to change the paste result, including: + * - Paste as is + * - Paste as text + * - Paste and merge format + * @param strings Localized string for this plugin + * @returns A paste option plugin + */ +export function createPasteOptionPlugin( + strings?: LocalizedStrings +): ReactEditorPlugin { + return new PasteOptionPlugin(strings); +} diff --git a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/type/PasteOptionStringKeys.ts b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/type/PasteOptionStringKeys.ts new file mode 100644 index 00000000000..92167b8fa49 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/type/PasteOptionStringKeys.ts @@ -0,0 +1,13 @@ +/** + * keys for Paste Option buttons + */ +export type PasteOptionButtonKeys = + | 'pasteOptionPasteAsIs' + | 'pasteOptionPasteText' + | 'pasteOptionMergeFormat' + | 'pasteOptionPasteAsImage'; + +/** + * Localized string keys for Paste Option buttons and its UI component + */ +export type PasteOptionStringKeys = PasteOptionButtonKeys | 'pasteOptionPaneText'; diff --git a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/buttons.ts b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/buttons.ts new file mode 100644 index 00000000000..8549bdd6d7d --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/buttons.ts @@ -0,0 +1,41 @@ +import type { PasteOptionButtonKeys } from '../type/PasteOptionStringKeys'; + +/** + * @internal + */ +export interface PasteOptionButtonType { + unlocalizedText: string; + shortcut: string; + icon: string; +} + +/** + * @internal + */ +export const Buttons: Record = { + pasteOptionPasteAsIs: { + unlocalizedText: 'Paste as is', + shortcut: 'P', + icon: 'Paste', + }, + pasteOptionPasteText: { + unlocalizedText: 'Paste text', + shortcut: 'T', + icon: 'PasteAsText', + }, + pasteOptionMergeFormat: { + unlocalizedText: 'Paste text and merge format', + shortcut: 'M', + icon: 'ClipboardList', + }, + pasteOptionPasteAsImage: { + unlocalizedText: 'Paste as image', + shortcut: 'I', + icon: 'PictureFill', + }, +}; + +/** + * @internal + */ +export const ButtonKeys: PasteOptionButtonKeys[] = Object.keys(Buttons) as PasteOptionButtonKeys[]; diff --git a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/getPositionRect.ts b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/getPositionRect.ts new file mode 100644 index 00000000000..88b077e1cf9 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/getPositionRect.ts @@ -0,0 +1,74 @@ +import { isNodeOfType } from 'roosterjs-content-model-dom'; +import { Rect } from 'roosterjs-content-model-types'; + +/** + * Get bounding rect of this position + * @param position The position to get rect from + */ +export function getPositionRect(container: Node, offset: number): Rect | null { + let range = container.ownerDocument.createRange(); + + range.setStart(container, offset); + + // 1) try to get rect using range.getBoundingClientRect() + let rect = normalizeRect(range.getBoundingClientRect()); + + if (rect) { + return rect; + } + + // 2) try to get rect using range.getClientRects + while (container.lastChild) { + if (offset == container.childNodes.length) { + container = container.lastChild; + offset = container.childNodes.length; + } else { + container = container.childNodes[offset]; + offset = 0; + } + } + + const rects = range.getClientRects && range.getClientRects(); + rect = rects && rects.length == 1 ? normalizeRect(rects[0]) : null; + if (rect) { + return rect; + } + + // 3) if node is text node, try inserting a SPAN and get the rect of SPAN for others + if (isNodeOfType(container, 'TEXT_NODE')) { + const span = container.ownerDocument.createElement('span'); + + span.textContent = '\u200b'; + range.insertNode(span); + rect = normalizeRect(span.getBoundingClientRect()); + span.parentNode.removeChild(span); + + if (rect) { + return rect; + } + } + + // 4) try getBoundingClientRect on element + if (isNodeOfType(container, 'ELEMENT_NODE') && container.getBoundingClientRect) { + rect = normalizeRect(container.getBoundingClientRect()); + + if (rect) { + return rect; + } + } + + return null; +} + +function normalizeRect(clientRect: DOMRect): Rect | null { + const { left, right, top, bottom } = + clientRect || { left: 0, right: 0, top: 0, bottom: 0 }; + return left === 0 && right === 0 && top === 0 && bottom === 0 + ? null + : { + left: Math.round(left), + right: Math.round(right), + top: Math.round(top), + bottom: Math.round(bottom), + }; +} diff --git a/demo/scripts/controlsV2/roosterjsReact/rooster/index.ts b/demo/scripts/controlsV2/roosterjsReact/rooster/index.ts new file mode 100644 index 00000000000..cff86a2e34a --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/rooster/index.ts @@ -0,0 +1 @@ +export { Rooster } from './component/Rooster'; From cd4695d568e4c0452b5f75502701f179569fc4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 5 Mar 2024 09:25:36 -0300 Subject: [PATCH 45/80] add check --- .../lib/autoFormat/utils/getListTypeStyle.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts index 89c76020365..7c704fde709 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts @@ -31,6 +31,9 @@ export function getListTypeStyle( shouldSearchForNumbering: boolean = true ): ListTypeStyle | undefined { const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs(model, true); + if (!selectedSegmentsAndParagraphs[0]) { + return undefined; + } const marker = selectedSegmentsAndParagraphs[0][0]; const paragraph = selectedSegmentsAndParagraphs[0][1]; const listMarkerSegment = paragraph?.segments[0]; From 12ce84ec182c31c27f4629403e91c79d5291ea14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 5 Mar 2024 11:04:28 -0300 Subject: [PATCH 46/80] fix test --- .../lib/shortcut/shortcuts.ts | 20 +- .../utils/setShortcutIndentationCommand.ts | 22 ++ .../test/shortcut/ShortcutPluginTest.ts | 75 +----- .../setShortcutIndentationCommandTest.ts | 224 ++++++++++++++++++ 4 files changed, 254 insertions(+), 87 deletions(-) create mode 100644 packages/roosterjs-content-model-plugins/lib/shortcut/utils/setShortcutIndentationCommand.ts create mode 100644 packages/roosterjs-content-model-plugins/test/shortcut/utils/setShortcutIndentationCommandTest.ts diff --git a/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts b/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts index 101936b58a4..853e582d90f 100644 --- a/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts +++ b/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts @@ -1,8 +1,8 @@ -import { getFirstSelectedListItem, redo, undo } from 'roosterjs-content-model-core'; +import { redo, undo } from 'roosterjs-content-model-core'; +import { setShortcutIndentationCommand } from './utils/setShortcutIndentationCommand'; import { changeFontSize, clearFormat, - setModelIndentation, toggleBold, toggleBullet, toggleItalic, @@ -10,7 +10,6 @@ import { toggleUnderline, } from 'roosterjs-content-model-api'; import type { ShortcutCommand } from './ShortcutCommand'; -import type { IEditor } from 'roosterjs-content-model-types'; const enum Keys { BACKSPACE = 8, @@ -225,18 +224,3 @@ export const ShortcutOutdentList: ShortcutCommand = { setShortcutIndentationCommand(editor, 'outdent'); }, }; - -function setShortcutIndentationCommand(editor: IEditor, operation: 'indent' | 'outdent') { - editor.formatContentModel(model => { - const listItem = getFirstSelectedListItem(model); - if ( - listItem && - listItem.blocks[0].blockType == 'Paragraph' && - listItem.blocks[0].segments[0].segmentType == 'SelectionMarker' - ) { - setModelIndentation(model, operation); - return true; - } - return false; - }); -} diff --git a/packages/roosterjs-content-model-plugins/lib/shortcut/utils/setShortcutIndentationCommand.ts b/packages/roosterjs-content-model-plugins/lib/shortcut/utils/setShortcutIndentationCommand.ts new file mode 100644 index 00000000000..40a4d74b993 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/lib/shortcut/utils/setShortcutIndentationCommand.ts @@ -0,0 +1,22 @@ +import { getFirstSelectedListItem } from 'roosterjs-content-model-core'; +import { setModelIndentation } from 'roosterjs-content-model-api'; +import type { IEditor } from 'roosterjs-content-model-types'; + +/** + * @internal + */ +export function setShortcutIndentationCommand(editor: IEditor, operation: 'indent' | 'outdent') { + editor.formatContentModel(model => { + const listItem = getFirstSelectedListItem(model); + console.log('listItem:', listItem); + if ( + listItem && + listItem.blocks[0].blockType == 'Paragraph' && + listItem.blocks[0].segments[0].segmentType == 'SelectionMarker' + ) { + setModelIndentation(model, operation); + return true; + } + return false; + }); +} diff --git a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts index 220893bc20f..20c96db03e1 100644 --- a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts @@ -1,22 +1,15 @@ import * as changeFontSize from 'roosterjs-content-model-api/lib/publicApi/segment/changeFontSize'; import * as clearFormat from 'roosterjs-content-model-api/lib/publicApi/format/clearFormat'; import * as redo from 'roosterjs-content-model-core/lib/publicApi/undo/redo'; -import * as setModelIndentation from 'roosterjs-content-model-api/lib/modelApi/block/setModelIndentation'; +import * as setShortcutIndentationCommand from '../../lib/shortcut/utils/setShortcutIndentationCommand'; import * as toggleBold from 'roosterjs-content-model-api/lib/publicApi/segment/toggleBold'; import * as toggleBullet from 'roosterjs-content-model-api/lib/publicApi/list/toggleBullet'; import * as toggleItalic from 'roosterjs-content-model-api/lib/publicApi/segment/toggleItalic'; import * as toggleNumbering from 'roosterjs-content-model-api/lib/publicApi/list/toggleNumbering'; import * as toggleUnderline from 'roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline'; import * as undo from 'roosterjs-content-model-core/lib/publicApi/undo/undo'; +import { EditorEnvironment, IEditor, PluginEvent } from 'roosterjs-content-model-types'; import { ShortcutPlugin } from '../../lib/shortcut/ShortcutPlugin'; -import { - ContentModelDocument, - ContentModelFormatter, - EditorEnvironment, - FormatContentModelOptions, - IEditor, - PluginEvent, -} from 'roosterjs-content-model-types'; const enum Keys { BACKSPACE = 8, @@ -38,66 +31,11 @@ describe('ShortcutPlugin', () => { let preventDefaultSpy: jasmine.Spy; let mockedEditor: IEditor; let mockedEnvironment: EditorEnvironment; - const model: ContentModelDocument = { - blockGroupType: 'Document', - blocks: [ - { - blockType: 'BlockGroup', - blockGroupType: 'ListItem', - blocks: [ - { - blockType: 'Paragraph', - segments: [ - { - segmentType: 'SelectionMarker', - isSelected: true, - format: {}, - }, - { - segmentType: 'Br', - format: {}, - }, - ], - format: {}, - }, - ], - levels: [ - { - listType: 'OL', - format: { - listStyleType: 'decimal', - }, - dataset: { - editingInfo: '{"orderedStyleType":1}', - }, - }, - ], - formatHolder: { - segmentType: 'SelectionMarker', - isSelected: true, - format: {}, - }, - format: {}, - }, - ], - format: {}, - }; - const formatContentModelSpy = jasmine - .createSpy('formatContentModel') - .and.callFake((callback: ContentModelFormatter, options: FormatContentModelOptions) => { - callback(model, { - newEntities: [], - deletedEntities: [], - newImages: [], - rawEvent: options.rawEvent, - }); - }); beforeEach(() => { preventDefaultSpy = jasmine.createSpy('preventDefault'); mockedEnvironment = {}; mockedEditor = { - formatContentModel: formatContentModelSpy, getEnvironment: () => mockedEnvironment, } as any; }); @@ -672,7 +610,7 @@ describe('ShortcutPlugin', () => { }); it('indent list', () => { - const apiSpy = spyOn(setModelIndentation, 'setModelIndentation'); + const apiSpy = spyOn(setShortcutIndentationCommand, 'setShortcutIndentationCommand'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -689,12 +627,11 @@ describe('ShortcutPlugin', () => { plugin.onPluginEvent(event); expect(apiSpy).toHaveBeenCalledTimes(1); - expect(apiSpy).toHaveBeenCalledWith(model, 'indent'); + expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'indent'); }); it('outdent list', () => { - const apiSpy = spyOn(setModelIndentation, 'setModelIndentation'); - + const apiSpy = spyOn(setShortcutIndentationCommand, 'setShortcutIndentationCommand'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -711,7 +648,7 @@ describe('ShortcutPlugin', () => { plugin.onPluginEvent(event); expect(apiSpy).toHaveBeenCalledTimes(1); - expect(apiSpy).toHaveBeenCalledWith(model, 'outdent'); + expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'outdent'); }); }); }); diff --git a/packages/roosterjs-content-model-plugins/test/shortcut/utils/setShortcutIndentationCommandTest.ts b/packages/roosterjs-content-model-plugins/test/shortcut/utils/setShortcutIndentationCommandTest.ts new file mode 100644 index 00000000000..f683e28415e --- /dev/null +++ b/packages/roosterjs-content-model-plugins/test/shortcut/utils/setShortcutIndentationCommandTest.ts @@ -0,0 +1,224 @@ +import * as getFirstSelectedListItem from 'roosterjs-content-model-core/lib/publicApi/selection/collectSelections'; +import * as setModelIndentation from 'roosterjs-content-model-api/lib/modelApi/block/setModelIndentation'; +import { setShortcutIndentationCommand } from '../../../lib/shortcut/utils/setShortcutIndentationCommand'; +import type { + ContentModelDocument, + ContentModelFormatter, + ContentModelListItem, + FormatContentModelContext, + IEditor, +} from 'roosterjs-content-model-types'; + +describe('setShortcutIndentationCommand', () => { + let editor: IEditor; + let formatContentModelSpy: jasmine.Spy; + let context: FormatContentModelContext; + let setModelIndentationSpy: jasmine.Spy; + + beforeEach(() => { + setModelIndentationSpy = spyOn(setModelIndentation, 'setModelIndentation'); + }); + + function runTest( + model: ContentModelDocument, + listItem: ContentModelListItem, + shouldIndent: boolean, + operation: 'indent' | 'outdent' + ) { + context = undefined!; + formatContentModelSpy = jasmine + .createSpy('formatContentModel') + .and.callFake((callback: ContentModelFormatter) => { + context = { + newEntities: [], + newImages: [], + deletedEntities: [], + }; + callback(model, context); + }); + + spyOn(getFirstSelectedListItem, 'getFirstSelectedListItem').and.returnValue(listItem); + + editor = ({ + formatContentModel: formatContentModelSpy, + focus: jasmine.createSpy('focus'), + getPendingFormat: () => null as any, + } as any) as IEditor; + + setShortcutIndentationCommand(editor, operation); + expect(formatContentModelSpy).toHaveBeenCalledTimes(1); + if (shouldIndent) { + expect(setModelIndentationSpy).toHaveBeenCalledTimes(1); + expect(setModelIndentationSpy).toHaveBeenCalledWith(model, operation); + } else { + expect(setModelIndentationSpy).not.toHaveBeenCalled(); + } + } + + it('indent', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + ], + format: {}, + }; + + runTest(model, model.blocks[0] as ContentModelListItem, true, 'indent'); + }); + + it('should not indent', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + ], + format: {}, + }; + runTest(model, model.blocks[1] as ContentModelListItem, false, 'indent'); + }); +}); From 5f9eb4fe4cc91ef51962d2b445b17691c996ddb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 5 Mar 2024 11:08:10 -0300 Subject: [PATCH 47/80] fix comments --- .../lib/shortcut/shortcuts.ts | 6 +++++- .../test/shortcut/ShortcutPluginTest.ts | 20 ------------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts b/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts index 853e582d90f..93b7dd5e3ac 100644 --- a/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts +++ b/packages/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts @@ -199,6 +199,8 @@ export const ShortcutDecreaseFont: ShortcutCommand = { /** * Shortcut command for Intent list + * Windows: Alt + Shift + Arrow Right + * MacOS: Option + Shift+ Arrow Right */ export const ShortcutIndentList: ShortcutCommand = { shortcutKey: { @@ -212,7 +214,9 @@ export const ShortcutIndentList: ShortcutCommand = { }; /** - * Shortcut command for Intent list + * Shortcut command for Outdent list + * Windows: Alt + Shift + Arrow Left + * MacOS: Option + Shift+ Arrow Left */ export const ShortcutOutdentList: ShortcutCommand = { shortcutKey: { diff --git a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts index 20c96db03e1..a75682a2f81 100644 --- a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts @@ -589,26 +589,6 @@ describe('ShortcutPlugin', () => { expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'decrease'); }); - it('decrease font', () => { - const apiSpy = spyOn(changeFontSize, 'default'); - const plugin = new ShortcutPlugin(); - const event: PluginEvent = { - eventType: 'keyDown', - rawEvent: createMockedEvent(Keys.COMMA, false, false, true, true), - }; - - plugin.initialize(mockedEditor); - - const exclusively = plugin.willHandleEventExclusively(event); - - expect(exclusively).toBeTrue(); - expect(event.eventDataCache!.__ShortcutCommandCache).toBeDefined(); - - plugin.onPluginEvent(event); - - expect(apiSpy).toHaveBeenCalledWith(mockedEditor, 'decrease'); - }); - it('indent list', () => { const apiSpy = spyOn(setShortcutIndentationCommand, 'setShortcutIndentationCommand'); const plugin = new ShortcutPlugin(); From 0b43c463063ca2c90dd78dc2ebdb08268930d417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 5 Mar 2024 11:11:39 -0300 Subject: [PATCH 48/80] remove console --- .../lib/shortcut/utils/setShortcutIndentationCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/roosterjs-content-model-plugins/lib/shortcut/utils/setShortcutIndentationCommand.ts b/packages/roosterjs-content-model-plugins/lib/shortcut/utils/setShortcutIndentationCommand.ts index 40a4d74b993..28a0b1fa7b4 100644 --- a/packages/roosterjs-content-model-plugins/lib/shortcut/utils/setShortcutIndentationCommand.ts +++ b/packages/roosterjs-content-model-plugins/lib/shortcut/utils/setShortcutIndentationCommand.ts @@ -8,7 +8,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; export function setShortcutIndentationCommand(editor: IEditor, operation: 'indent' | 'outdent') { editor.formatContentModel(model => { const listItem = getFirstSelectedListItem(model); - console.log('listItem:', listItem); + if ( listItem && listItem.blocks[0].blockType == 'Paragraph' && From d509420e1c238e58ea36e8d6308a44e664a7b31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 5 Mar 2024 13:58:27 -0300 Subject: [PATCH 49/80] fixes --- .../lib/edit/deleteSteps/deleteEmptyQuote.ts | 47 ++- .../edit/deleteSteps/deleteEmptyQuoteTest.ts | 279 +++++++++++++++++- 2 files changed, 314 insertions(+), 12 deletions(-) diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteEmptyQuote.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteEmptyQuote.ts index c1650806a7d..628bedf3242 100644 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteEmptyQuote.ts +++ b/packages-content-model/roosterjs-content-model-plugins/lib/edit/deleteSteps/deleteEmptyQuote.ts @@ -1,9 +1,10 @@ -import { unwrapBlock } from 'roosterjs-content-model-dom'; +import { createParagraph, createSelectionMarker, unwrapBlock } from 'roosterjs-content-model-dom'; import { getClosestAncestorBlockGroupIndex, isBlockGroupOfType, } from 'roosterjs-content-model-core'; import type { + ContentModelBlockGroup, ContentModelFormatContainer, DeleteSelectionStep, } from 'roosterjs-content-model-types'; @@ -20,7 +21,7 @@ export const deleteEmptyQuote: DeleteSelectionStep = context => { ) { const { insertPoint, formatContext } = context; const { path } = insertPoint; - const rawEvent = formatContext?.rawEvent; + const rawEvent = formatContext?.rawEvent as KeyboardEvent; const index = getClosestAncestorBlockGroupIndex( path, ['FormatContainer', 'ListItem'], @@ -28,12 +29,7 @@ export const deleteEmptyQuote: DeleteSelectionStep = context => { ); const quote = path[index]; - if ( - quote && - quote.blockGroupType === 'FormatContainer' && - quote.tagName == 'blockquote' && - isEmptyQuote(quote) - ) { + if (quote && quote.blockGroupType === 'FormatContainer' && quote.tagName == 'blockquote') { const parent = path[index + 1]; const quoteBlockIndex = parent.blocks.indexOf(quote); const blockQuote = parent.blocks[quoteBlockIndex]; @@ -41,9 +37,15 @@ export const deleteEmptyQuote: DeleteSelectionStep = context => { isBlockGroupOfType(blockQuote, 'FormatContainer') && blockQuote.tagName === 'blockquote' ) { - unwrapBlock(parent, blockQuote); - rawEvent?.preventDefault(); - context.deleteResult = 'range'; + if (isEmptyQuote(blockQuote)) { + unwrapBlock(parent, blockQuote); + rawEvent?.preventDefault(); + context.deleteResult = 'range'; + } else if (isSelectionOnEmptyLine(blockQuote) && rawEvent?.key === 'Enter') { + insertNewLine(blockQuote, parent, quoteBlockIndex); + rawEvent?.preventDefault(); + context.deleteResult = 'range'; + } } } } @@ -58,3 +60,26 @@ const isEmptyQuote = (quote: ContentModelFormatContainer) => { ) ); }; + +const isSelectionOnEmptyLine = (quote: ContentModelFormatContainer) => { + const quoteLength = quote.blocks.length; + const lastParagraph = quote.blocks[quoteLength - 1]; + if (lastParagraph && lastParagraph.blockType === 'Paragraph') { + return lastParagraph.segments.every( + s => s.segmentType === 'SelectionMarker' || s.segmentType === 'Br' + ); + } +}; + +const insertNewLine = ( + quote: ContentModelFormatContainer, + parent: ContentModelBlockGroup, + index: number +) => { + const quoteLength = quote.blocks.length; + quote.blocks.splice(quoteLength - 1, 1); + const marker = createSelectionMarker(); + const newParagraph = createParagraph(false /* isImplicit */); + newParagraph.segments.push(marker); + parent.blocks.splice(index + 1, 0, newParagraph); +}; diff --git a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts b/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts index 4eb894006bc..5f5e5f9956b 100644 --- a/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts +++ b/packages-content-model/roosterjs-content-model-plugins/test/edit/deleteSteps/deleteEmptyQuoteTest.ts @@ -1,9 +1,11 @@ import { ContentModelDocument } from 'roosterjs-content-model-types'; import { deleteEmptyQuote } from '../../../lib/edit/deleteSteps/deleteEmptyQuote'; import { deleteSelection } from 'roosterjs-content-model-core'; +import { editingTestCommon } from '../editingTestCommon'; +import { keyboardInput } from '../../../lib/edit/keyboardInput'; import { normalizeContentModel } from 'roosterjs-content-model-dom'; -describe('deleteEmptyQuoteTest', () => { +describe('deleteEmptyQuote', () => { function runTest( model: ContentModelDocument, expectedModel: ContentModelDocument, @@ -130,3 +132,278 @@ describe('deleteEmptyQuoteTest', () => { runTest(model, model, 'notDeleted'); }); }); + +describe('deleteEmptyQuote - keyboardInput', () => { + function runTest( + input: ContentModelDocument, + key: string, + expectedResult: ContentModelDocument, + doNotCallDefaultFormat: boolean = false, + calledTimes: number = 1 + ) { + const preventDefault = jasmine.createSpy('preventDefault'); + const mockedEvent = ({ + key: key, + shiftKey: false, + preventDefault, + } as any) as KeyboardEvent; + + let editor: any; + + editingTestCommon( + undefined, + newEditor => { + editor = newEditor; + + editor.getDOMSelection = () => ({ + type: 'range', + range: { + collapsed: true, + }, + }); + + keyboardInput(editor, mockedEvent); + }, + input, + expectedResult, + calledTimes, + doNotCallDefaultFormat + ); + } + + it('should delete empty quote when press Enter', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'FormatContainer', + tagName: 'blockquote', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + { + segmentType: 'Br', + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: {}, + }, + ], + format: { + marginTop: '1em', + marginRight: '40px', + marginBottom: '1em', + marginLeft: '40px', + paddingLeft: '10px', + borderLeft: '3px solid rgb(200, 200, 200)', + }, + }, + ], + format: {}, + }; + const expectedTestModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + { + segmentType: 'Br', + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + segmentFormat: { textColor: 'rgb(102, 102, 102)' }, + format: {}, + }, + ], + format: {}, + }; + + runTest(model, 'Enter', expectedTestModel); + }); + + it('should exit quote when press Enter', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'FormatContainer', + tagName: 'blockquote', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: {}, + segmentFormat: { + textColor: 'rgb(102, 102, 102)', + }, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + { + segmentType: 'Br', + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: {}, + segmentFormat: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: { + marginTop: '1em', + marginRight: '40px', + marginBottom: '1em', + marginLeft: '40px', + paddingLeft: '10px', + borderLeft: '3px solid rgb(200, 200, 200)', + }, + }, + ], + format: {}, + }; + const expectedTestModel: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'FormatContainer', + tagName: 'blockquote', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: {}, + segmentFormat: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: { + marginTop: '1em', + marginRight: '40px', + marginBottom: '1em', + marginLeft: '40px', + paddingLeft: '10px', + borderLeft: '3px solid rgb(200, 200, 200)', + }, + }, + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Br', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(model, 'Enter', expectedTestModel); + }); + + it('should not exit quote when press Enter', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'FormatContainer', + tagName: 'blockquote', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: {}, + segmentFormat: { + textColor: 'rgb(102, 102, 102)', + }, + }, + ], + format: { + marginTop: '1em', + marginRight: '40px', + marginBottom: '1em', + marginLeft: '40px', + paddingLeft: '10px', + borderLeft: '3px solid rgb(200, 200, 200)', + }, + }, + ], + format: {}, + }; + + runTest(model, 'Enter', model, false, 0); + }); +}); From a119ea5035fbd366379742a67b55ea2f397dd30c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 5 Mar 2024 14:17:14 -0300 Subject: [PATCH 50/80] no selection --- .../autoFormat/utils/getListTypeStyleTest.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts index 63c1d036247..05e7c0d4a7f 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts @@ -984,4 +984,25 @@ describe('getListTypeStyle', () => { }; runTest(model, undefined); }); + + it('No selection', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: '1)', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(model, undefined); + }); }); From 24204913747ca65921f5e851cfc424aa0bd4d77c Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Tue, 5 Mar 2024 09:40:32 -0800 Subject: [PATCH 51/80] Stop using default export (#2471) * Stop using default export * fix test * fix build --- .../menus/createImageEditMenuProvider.tsx | 2 +- .../roosterjs-content-model-api/lib/index.ts | 88 ++++++++++--------- .../modelApi/image/applyImageBorderFormat.ts | 2 +- .../lib/publicApi/block/setAlignment.ts | 5 +- .../lib/publicApi/block/setDirection.ts | 2 +- .../lib/publicApi/block/setHeadingLevel.ts | 2 +- .../lib/publicApi/block/setIndentation.ts | 2 +- .../lib/publicApi/block/setParagraphMargin.ts | 2 +- .../lib/publicApi/block/setSpacing.ts | 2 +- .../lib/publicApi/block/toggleBlockQuote.ts | 2 +- .../lib/publicApi/entity/insertEntity.ts | 6 +- .../lib/publicApi/format/clearFormat.ts | 2 +- .../lib/publicApi/format/getFormatState.ts | 2 +- .../publicApi/image/adjustImageSelection.ts | 2 +- .../lib/publicApi/image/changeImage.ts | 4 +- .../lib/publicApi/image/insertImage.ts | 2 +- .../lib/publicApi/image/setImageAltText.ts | 4 +- .../lib/publicApi/image/setImageBorder.ts | 10 +-- .../lib/publicApi/image/setImageBoxShadow.ts | 8 +- .../lib/publicApi/link/adjustLinkSelection.ts | 2 +- .../lib/publicApi/link/insertLink.ts | 2 +- .../lib/publicApi/link/removeLink.ts | 2 +- .../lib/publicApi/list/setListStartNumber.ts | 2 +- .../lib/publicApi/list/setListStyle.ts | 2 +- .../lib/publicApi/list/toggleBullet.ts | 2 +- .../lib/publicApi/list/toggleNumbering.ts | 2 +- .../publicApi/segment/applySegmentFormat.ts | 2 +- .../publicApi/segment/changeCapitalization.ts | 2 +- .../lib/publicApi/segment/changeFontSize.ts | 2 +- .../publicApi/segment/setBackgroundColor.ts | 2 +- .../lib/publicApi/segment/setFontName.ts | 2 +- .../lib/publicApi/segment/setFontSize.ts | 2 +- .../lib/publicApi/segment/setTextColor.ts | 2 +- .../lib/publicApi/segment/toggleBold.ts | 2 +- .../lib/publicApi/segment/toggleCode.ts | 2 +- .../lib/publicApi/segment/toggleItalic.ts | 2 +- .../publicApi/segment/toggleStrikethrough.ts | 2 +- .../lib/publicApi/segment/toggleSubscript.ts | 2 +- .../publicApi/segment/toggleSuperscript.ts | 2 +- .../lib/publicApi/segment/toggleUnderline.ts | 2 +- .../publicApi/table/applyTableBorderFormat.ts | 2 +- .../lib/publicApi/table/editTable.ts | 2 +- .../lib/publicApi/table/formatTable.ts | 6 +- .../lib/publicApi/table/insertTable.ts | 2 +- .../lib/publicApi/table/setTableCellShade.ts | 2 +- .../utils/formatImageWithContentModel.ts | 7 +- .../utils/formatParagraphWithContentModel.ts | 5 +- .../utils/formatSegmentWithContentModel.ts | 8 +- .../image/applyImageBorderFormatTest.ts | 2 +- .../test/publicApi/block/setAlignmentTest.ts | 2 +- .../test/publicApi/block/setDirectionTest.ts | 2 +- .../publicApi/block/setHeadingLevelTest.ts | 2 +- .../publicApi/block/setIndentationTest.ts | 2 +- .../publicApi/block/setParagraphMarginTest.ts | 2 +- .../test/publicApi/block/setSpacingTest.ts | 2 +- .../publicApi/block/toggleBlockQuoteTest.ts | 2 +- .../test/publicApi/entity/insertEntityTest.ts | 2 +- .../test/publicApi/format/clearFormatTest.ts | 2 +- .../publicApi/format/getFormatStateTest.ts | 2 +- .../image/adjustImageSelectionTest.ts | 2 +- .../test/publicApi/image/changeImageTest.ts | 2 +- .../test/publicApi/image/insertImageTest.ts | 2 +- .../publicApi/image/setImageAltTextTest.ts | 2 +- .../publicApi/image/setImageBorderTest.ts | 2 +- .../publicApi/image/setImageBoxShadowTest.ts | 2 +- .../publicApi/link/adjustLinkSelectionTest.ts | 2 +- .../test/publicApi/link/insertLinkTest.ts | 2 +- .../test/publicApi/link/removeLinkTest.ts | 2 +- .../publicApi/list/setListStartNumberTest.ts | 2 +- .../test/publicApi/list/setListStyleTest.ts | 2 +- .../test/publicApi/list/toggleBulletTest.ts | 2 +- .../publicApi/list/toggleNumberingTest.ts | 2 +- .../segment/applySegmentFormatTest.ts | 2 +- .../segment/changeCapitalizationTest.ts | 2 +- .../publicApi/segment/changeFontSizeTest.ts | 2 +- .../segment/setBackgroundColorTest.ts | 2 +- .../test/publicApi/segment/setFontNameTest.ts | 2 +- .../test/publicApi/segment/setFontSizeTest.ts | 2 +- .../publicApi/segment/setTextColorTest.ts | 2 +- .../test/publicApi/segment/toggleBoldTest.ts | 2 +- .../test/publicApi/segment/toggleCodeTest.ts | 2 +- .../publicApi/segment/toggleItalicTest.ts | 2 +- .../segment/toggleStrikethroughTest.ts | 2 +- .../publicApi/segment/toggleSubscriptTest.ts | 2 +- .../segment/toggleSuperscriptTest.ts | 2 +- .../publicApi/segment/toggleUnderlineTest.ts | 2 +- .../table/applyTableBorderFormatTest.ts | 2 +- .../test/publicApi/table/editTableTest.ts | 2 +- .../publicApi/table/setTableCellShadeTest.ts | 2 +- .../utils/formatImageWithContentModelTest.ts | 2 +- .../utils/formatTableWithContentModelTest.ts | 8 +- .../lib/corePlugin/utils/deleteEmptyList.ts | 4 +- .../roosterjs-content-model-core/lib/index.ts | 6 +- .../selection/hasSelectionInBlock.ts | 6 +- .../selection/hasSelectionInBlockGroup.ts | 4 +- .../selection/hasSelectionInSegment.ts | 4 +- .../lib/publicApi/table/getSelectedCells.ts | 2 +- .../test/coreApi/pasteTest.ts | 22 ++--- .../selection/hasSelectionInBlockTest.ts | 4 +- .../selection/hasSelectionInSegmentTest.ts | 2 +- .../lib/domUtils/entityUtils.ts | 2 +- .../lib/domUtils/toArray.ts | 12 +-- .../roosterjs-content-model-dom/lib/index.ts | 2 +- .../lib/modelToDom/contentModelToDom.ts | 2 +- .../Excel/processPastedContentFromExcel.ts | 2 +- .../lib/paste/PastePlugin.ts | 2 +- .../processPastedContentWacComponents.ts | 2 +- .../lib/paste/WordDesktop/getStyleMetadata.ts | 2 +- .../processPastedContentFromWordDesktop.ts | 4 +- .../lib/paste/utils/addParser.ts | 2 +- .../CreateElement/CreateElementData.ts | 2 +- .../CreateElement/createElement.ts | 7 +- .../lib/pluginUtils/Disposable.ts | 2 +- .../DragAndDrop/DragAndDropHandler.ts | 2 +- .../DragAndDrop/DragAndDropHelper.ts | 6 +- .../pluginUtils/Rect/getIntersectedRect.ts | 4 +- .../lib/pluginUtils/Rect/normalizeRect.ts | 2 +- .../lib/tableEdit/TableEditPlugin.ts | 4 +- .../lib/tableEdit/editors/TableEditor.ts | 16 ++-- .../tableEdit/editors/features/CellResizer.ts | 12 +-- ...leEditorFeature.ts => TableEditFeature.ts} | 4 +- .../editors/features/TableInserter.ts | 14 +-- .../tableEdit/editors/features/TableMover.ts | 14 +-- .../editors/features/TableResizer.ts | 10 +-- .../test/paste/ContentModelPastePluginTest.ts | 20 ++--- .../test/paste/getStyleMetadataTest.ts | 2 +- ...processPastedContentFromWordDesktopTest.ts | 12 +-- .../test/pluginUtils/DragAndDropHelperTest.ts | 2 +- .../test/pluginUtils/createElementTest.ts | 4 +- .../test/shortcut/ShortcutPluginTest.ts | 36 ++++---- .../test/tableEdit/tableEditPluginTest.ts | 2 +- .../test/tableEdit/tableMoverTest.ts | 4 +- tools/buildTools/dts.js | 23 ++--- 133 files changed, 303 insertions(+), 304 deletions(-) rename packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/{TableEditorFeature.ts => TableEditFeature.ts} (79%) diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx index 8fc3189fd51..9968b807e42 100644 --- a/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx @@ -1,6 +1,6 @@ -import formatImageWithContentModel from 'roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel'; import { createContextMenuProvider } from '../utils/createContextMenuProvider'; import { EditorPlugin, IEditor, ImageEditor } from 'roosterjs-content-model-types'; +import { formatImageWithContentModel } from 'roosterjs-content-model-api'; import { iterateSelections, updateImageMetadata } from 'roosterjs-content-model-core'; import { setImageAltText } from 'roosterjs-content-model-api'; import { showInputDialog } from '../../inputDialog/utils/showInputDialog'; diff --git a/packages/roosterjs-content-model-api/lib/index.ts b/packages/roosterjs-content-model-api/lib/index.ts index f27263ca785..be6c409bbc3 100644 --- a/packages/roosterjs-content-model-api/lib/index.ts +++ b/packages/roosterjs-content-model-api/lib/index.ts @@ -1,49 +1,53 @@ -export { default as insertTable } from './publicApi/table/insertTable'; -export { default as formatTable } from './publicApi/table/formatTable'; -export { default as setTableCellShade } from './publicApi/table/setTableCellShade'; -export { default as editTable } from './publicApi/table/editTable'; -export { default as applyTableBorderFormat } from './publicApi/table/applyTableBorderFormat'; -export { default as toggleBullet } from './publicApi/list/toggleBullet'; -export { default as toggleNumbering } from './publicApi/list/toggleNumbering'; -export { default as toggleBold } from './publicApi/segment/toggleBold'; -export { default as toggleItalic } from './publicApi/segment/toggleItalic'; -export { default as toggleUnderline } from './publicApi/segment/toggleUnderline'; -export { default as toggleStrikethrough } from './publicApi/segment/toggleStrikethrough'; -export { default as toggleSubscript } from './publicApi/segment/toggleSubscript'; -export { default as toggleSuperscript } from './publicApi/segment/toggleSuperscript'; -export { default as setBackgroundColor } from './publicApi/segment/setBackgroundColor'; -export { default as setFontName } from './publicApi/segment/setFontName'; -export { default as setFontSize } from './publicApi/segment/setFontSize'; -export { default as setTextColor } from './publicApi/segment/setTextColor'; -export { default as changeFontSize } from './publicApi/segment/changeFontSize'; -export { default as applySegmentFormat } from './publicApi/segment/applySegmentFormat'; -export { default as changeCapitalization } from './publicApi/segment/changeCapitalization'; -export { default as insertImage } from './publicApi/image/insertImage'; -export { default as setListStyle } from './publicApi/list/setListStyle'; -export { default as setListStartNumber } from './publicApi/list/setListStartNumber'; -export { default as setIndentation } from './publicApi/block/setIndentation'; -export { default as setAlignment } from './publicApi/block/setAlignment'; -export { default as setDirection } from './publicApi/block/setDirection'; -export { default as setHeadingLevel } from './publicApi/block/setHeadingLevel'; -export { default as toggleBlockQuote } from './publicApi/block/toggleBlockQuote'; -export { default as setSpacing } from './publicApi/block/setSpacing'; -export { default as setImageBorder } from './publicApi/image/setImageBorder'; -export { default as setImageBoxShadow } from './publicApi/image/setImageBoxShadow'; -export { default as changeImage } from './publicApi/image/changeImage'; -export { default as getFormatState } from './publicApi/format/getFormatState'; -export { default as clearFormat } from './publicApi/format/clearFormat'; -export { default as insertLink } from './publicApi/link/insertLink'; -export { default as removeLink } from './publicApi/link/removeLink'; -export { default as adjustLinkSelection } from './publicApi/link/adjustLinkSelection'; -export { default as setImageAltText } from './publicApi/image/setImageAltText'; -export { default as adjustImageSelection } from './publicApi/image/adjustImageSelection'; -export { default as setParagraphMargin } from './publicApi/block/setParagraphMargin'; -export { default as toggleCode } from './publicApi/segment/toggleCode'; -export { default as insertEntity } from './publicApi/entity/insertEntity'; +export { insertTable } from './publicApi/table/insertTable'; +export { formatTable } from './publicApi/table/formatTable'; +export { setTableCellShade } from './publicApi/table/setTableCellShade'; +export { editTable } from './publicApi/table/editTable'; +export { applyTableBorderFormat } from './publicApi/table/applyTableBorderFormat'; +export { toggleBullet } from './publicApi/list/toggleBullet'; +export { toggleNumbering } from './publicApi/list/toggleNumbering'; +export { toggleBold } from './publicApi/segment/toggleBold'; +export { toggleItalic } from './publicApi/segment/toggleItalic'; +export { toggleUnderline } from './publicApi/segment/toggleUnderline'; +export { toggleStrikethrough } from './publicApi/segment/toggleStrikethrough'; +export { toggleSubscript } from './publicApi/segment/toggleSubscript'; +export { toggleSuperscript } from './publicApi/segment/toggleSuperscript'; +export { setBackgroundColor } from './publicApi/segment/setBackgroundColor'; +export { setFontName } from './publicApi/segment/setFontName'; +export { setFontSize } from './publicApi/segment/setFontSize'; +export { setTextColor } from './publicApi/segment/setTextColor'; +export { changeFontSize } from './publicApi/segment/changeFontSize'; +export { applySegmentFormat } from './publicApi/segment/applySegmentFormat'; +export { changeCapitalization } from './publicApi/segment/changeCapitalization'; +export { insertImage } from './publicApi/image/insertImage'; +export { setListStyle } from './publicApi/list/setListStyle'; +export { setListStartNumber } from './publicApi/list/setListStartNumber'; +export { setIndentation } from './publicApi/block/setIndentation'; +export { setAlignment } from './publicApi/block/setAlignment'; +export { setDirection } from './publicApi/block/setDirection'; +export { setHeadingLevel } from './publicApi/block/setHeadingLevel'; +export { toggleBlockQuote } from './publicApi/block/toggleBlockQuote'; +export { setSpacing } from './publicApi/block/setSpacing'; +export { setImageBorder } from './publicApi/image/setImageBorder'; +export { setImageBoxShadow } from './publicApi/image/setImageBoxShadow'; +export { changeImage } from './publicApi/image/changeImage'; +export { getFormatState } from './publicApi/format/getFormatState'; +export { clearFormat } from './publicApi/format/clearFormat'; +export { insertLink } from './publicApi/link/insertLink'; +export { removeLink } from './publicApi/link/removeLink'; +export { adjustLinkSelection } from './publicApi/link/adjustLinkSelection'; +export { setImageAltText } from './publicApi/image/setImageAltText'; +export { adjustImageSelection } from './publicApi/image/adjustImageSelection'; +export { setParagraphMargin } from './publicApi/block/setParagraphMargin'; +export { toggleCode } from './publicApi/segment/toggleCode'; +export { insertEntity } from './publicApi/entity/insertEntity'; export { insertTableRow } from './modelApi/table/insertTableRow'; export { insertTableColumn } from './modelApi/table/insertTableColumn'; export { formatTableWithContentModel } from './publicApi/utils/formatTableWithContentModel'; +export { formatImageWithContentModel } from './publicApi/utils/formatImageWithContentModel'; +export { formatParagraphWithContentModel } from './publicApi/utils/formatParagraphWithContentModel'; +export { formatSegmentWithContentModel } from './publicApi/utils/formatSegmentWithContentModel'; + export { setListType } from './modelApi/list/setListType'; export { findListItemsInSameThread } from './modelApi/list/findListItemsInSameThread'; export { setModelIndentation } from './modelApi/block/setModelIndentation'; diff --git a/packages/roosterjs-content-model-api/lib/modelApi/image/applyImageBorderFormat.ts b/packages/roosterjs-content-model-api/lib/modelApi/image/applyImageBorderFormat.ts index 6d7ef1a5898..6f94d6f6123 100644 --- a/packages/roosterjs-content-model-api/lib/modelApi/image/applyImageBorderFormat.ts +++ b/packages/roosterjs-content-model-api/lib/modelApi/image/applyImageBorderFormat.ts @@ -5,7 +5,7 @@ import type { Border, ContentModelImage } from 'roosterjs-content-model-types'; /** * @internal */ -export default function applyImageBorderFormat( +export function applyImageBorderFormat( image: ContentModelImage, border: Border | null, borderRadius?: string diff --git a/packages/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts index 8eff386e60c..bc82e25378e 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts @@ -6,10 +6,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to set alignment * @param alignment Alignment value: left, center or right */ -export default function setAlignment( - editor: IEditor, - alignment: 'left' | 'center' | 'right' | 'justify' -) { +export function setAlignment(editor: IEditor, alignment: 'left' | 'center' | 'right' | 'justify') { editor.focus(); editor.formatContentModel(model => setModelAlignment(model, alignment), { diff --git a/packages/roosterjs-content-model-api/lib/publicApi/block/setDirection.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setDirection.ts index 520ec5cd98f..e31b1cd24a2 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/block/setDirection.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/block/setDirection.ts @@ -6,7 +6,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to set alignment * @param direction Direction value: ltr (Left to right) or rtl (Right to left) */ -export default function setDirection(editor: IEditor, direction: 'ltr' | 'rtl') { +export function setDirection(editor: IEditor, direction: 'ltr' | 'rtl') { editor.focus(); editor.formatContentModel(model => setModelDirection(model, direction), { diff --git a/packages/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts index 53e3536ad41..7547ac9ef78 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/block/setHeadingLevel.ts @@ -17,7 +17,7 @@ const HeaderFontSizes: Record = { * @param editor The editor to set heading level to * @param headingLevel Level of heading, from 1 to 6. Set to 0 means set it back to a regular paragraph */ -export default function setHeadingLevel(editor: IEditor, headingLevel: 0 | 1 | 2 | 3 | 4 | 5 | 6) { +export function setHeadingLevel(editor: IEditor, headingLevel: 0 | 1 | 2 | 3 | 4 | 5 | 6) { editor.focus(); formatParagraphWithContentModel(editor, 'setHeadingLevel', para => { diff --git a/packages/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts index 8e82effe88d..32ab89bd65b 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/block/setIndentation.ts @@ -8,7 +8,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param indentation Whether indent or outdent * @param length The length of pixel to indent/outdent @default 40 */ -export default function setIndentation( +export function setIndentation( editor: IEditor, indentation: 'indent' | 'outdent', length?: number diff --git a/packages/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts index 3f0b05c0aff..446b28308e6 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/block/setParagraphMargin.ts @@ -9,7 +9,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param marginTop value for top margin * @param marginBottom value for bottom margin */ -export default function setParagraphMargin( +export function setParagraphMargin( editor: IEditor, marginTop?: string | null, marginBottom?: string | null diff --git a/packages/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts index befed4e3fe0..fd4847a4e91 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/block/setSpacing.ts @@ -6,7 +6,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to operate on * @param spacing Unitless/px value to set line height */ -export default function setSpacing(editor: IEditor, spacing: number | string) { +export function setSpacing(editor: IEditor, spacing: number | string) { editor.focus(); formatParagraphWithContentModel(editor, 'setSpacing', paragraph => { diff --git a/packages/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts b/packages/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts index f1a4bdddeed..2c19230230f 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/block/toggleBlockQuote.ts @@ -23,7 +23,7 @@ const BuildInQuoteFormat: ContentModelFormatContainerFormat = { * @param editor The editor object to toggle BLOCKQUOTE onto * @param quoteFormat @optional Block format for the new quote object */ -export default function toggleBlockQuote( +export function toggleBlockQuote( editor: IEditor, quoteFormat?: ContentModelFormatContainerFormat, quoteFormatRtl?: ContentModelFormatContainerFormat diff --git a/packages/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts b/packages/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts index c6615a0089c..19ff0d1cacd 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/entity/insertEntity.ts @@ -28,7 +28,7 @@ const InlineEntityTag = 'span'; * the beginning of this range (when focusAfterEntity is not set to true) or after the new entity (when focusAfterEntity is set to true) * @param options Move options to insert. See InsertEntityOptions */ -export default function insertEntity( +export function insertEntity( editor: IEditor, type: string, isBlock: boolean, @@ -47,7 +47,7 @@ export default function insertEntity( * the beginning of this range (when focusAfterEntity is not set to true) or after the new entity (when focusAfterEntity is set to true) * @param options Move options to insert. See InsertEntityOptions */ -export default function insertEntity( +export function insertEntity( editor: IEditor, type: string, isBlock: true, @@ -55,7 +55,7 @@ export default function insertEntity( options?: InsertEntityOptions ): ContentModelEntity | null; -export default function insertEntity( +export function insertEntity( editor: IEditor, type: string, isBlock: boolean, diff --git a/packages/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts b/packages/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts index ed1a172d678..5e8fa8a0a1a 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/format/clearFormat.ts @@ -12,7 +12,7 @@ import type { * Clear format of selection * @param editor The editor to clear format from */ -export default function clearFormat(editor: IEditor) { +export function clearFormat(editor: IEditor) { editor.focus(); editor.formatContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts b/packages/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts index 9c3af3ddcc8..b08bb8badbb 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/format/getFormatState.ts @@ -5,7 +5,7 @@ import type { IEditor, ContentModelFormatState } from 'roosterjs-content-model-t * Get current format state * @param editor The editor to get format from */ -export default function getFormatState(editor: IEditor): ContentModelFormatState { +export function getFormatState(editor: IEditor): ContentModelFormatState { const pendingFormat = editor.getPendingFormat(); const model = editor.getContentModelCopy('reduced'); const manager = editor.getSnapshotsManager(); diff --git a/packages/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts index 622ca208ce1..09e66661169 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/image/adjustImageSelection.ts @@ -5,7 +5,7 @@ import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; * Adjust selection to make sure select an image if any * @return Content Model Image object if an image is select, or null */ -export default function adjustImageSelection(editor: IEditor): ContentModelImage | null { +export function adjustImageSelection(editor: IEditor): ContentModelImage | null { let image: ContentModelImage | null = null; editor.formatContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts index 08872385a14..03290f690d0 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/image/changeImage.ts @@ -1,4 +1,4 @@ -import formatImageWithContentModel from '../utils/formatImageWithContentModel'; +import { formatImageWithContentModel } from '../utils/formatImageWithContentModel'; import { readFile, updateImageMetadata } from 'roosterjs-content-model-core'; import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; @@ -7,7 +7,7 @@ import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; * @param editor The editor instance * @param file The image file */ -export default function changeImage(editor: IEditor, file: File) { +export function changeImage(editor: IEditor, file: File) { editor.focus(); const selection = editor.getDOMSelection(); diff --git a/packages/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts index b63614347c0..32d734b5f26 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/image/insertImage.ts @@ -7,7 +7,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to operate on * @param file Image Blob file or source string */ -export default function insertImage(editor: IEditor, imageFileOrSrc: File | string) { +export function insertImage(editor: IEditor, imageFileOrSrc: File | string) { editor.focus(); if (typeof imageFileOrSrc == 'string') { diff --git a/packages/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts index aa3779db680..f5a39dba7ea 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/image/setImageAltText.ts @@ -1,4 +1,4 @@ -import formatImageWithContentModel from '../utils/formatImageWithContentModel'; +import { formatImageWithContentModel } from '../utils/formatImageWithContentModel'; import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** @@ -7,7 +7,7 @@ import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; * @param editor The editor instance * @param altText The image alt text */ -export default function setImageAltText(editor: IEditor, altText: string) { +export function setImageAltText(editor: IEditor, altText: string) { editor.focus(); formatImageWithContentModel(editor, 'setImageAltText', (image: ContentModelImage) => { diff --git a/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts index 8fea90f6f86..8b5873c2e20 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBorder.ts @@ -1,5 +1,5 @@ -import applyImageBorderFormat from '../../modelApi/image/applyImageBorderFormat'; -import formatImageWithContentModel from '../utils/formatImageWithContentModel'; +import { applyImageBorderFormat } from '../../modelApi/image/applyImageBorderFormat'; +import { formatImageWithContentModel } from '../utils/formatImageWithContentModel'; import type { Border, ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** @@ -9,11 +9,7 @@ import type { Border, ContentModelImage, IEditor } from 'roosterjs-content-model * its value will not be changed. Passing null instead of an object will remove the border * @param borderRadius the border radius value, if undefined, the border radius will keep the actual value */ -export default function setImageBorder( - editor: IEditor, - border: Border | null, - borderRadius?: string -) { +export function setImageBorder(editor: IEditor, border: Border | null, borderRadius?: string) { editor.focus(); formatImageWithContentModel(editor, 'setImageBorder', (image: ContentModelImage) => { diff --git a/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts b/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts index 4da659632fa..b93e41a74ac 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/image/setImageBoxShadow.ts @@ -1,4 +1,4 @@ -import formatImageWithContentModel from '../utils/formatImageWithContentModel'; +import { formatImageWithContentModel } from '../utils/formatImageWithContentModel'; import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** @@ -7,11 +7,7 @@ import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; * @param boxShadow The image box boxShadow * @param margin The image margin for all sides (eg. "4px"), null to remove margin */ -export default function setImageBoxShadow( - editor: IEditor, - boxShadow: string, - margin?: string | null -) { +export function setImageBoxShadow(editor: IEditor, boxShadow: string, margin?: string | null) { editor.focus(); formatImageWithContentModel(editor, 'setImageBoxShadow', (image: ContentModelImage) => { diff --git a/packages/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts b/packages/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts index c2b192dbc06..10d562e3481 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/link/adjustLinkSelection.ts @@ -7,7 +7,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * Adjust selection to make sure select a hyperlink if any, or a word if original selection is collapsed * @return A combination of existing link display text and url if any. If there is no existing link, return selected text and null */ -export default function adjustLinkSelection(editor: IEditor): [string, string | null] { +export function adjustLinkSelection(editor: IEditor): [string, string | null] { let text = ''; let url: string | null = null; diff --git a/packages/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts b/packages/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts index edf36977355..70d44b85931 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts @@ -29,7 +29,7 @@ const FTP_REGEX = /^ftp\./i; * If specified, the display text of link will be replaced with this text. * If not specified and there wasn't a link, the link url will be used as display text. */ -export default function insertLink( +export function insertLink( editor: IEditor, link: string, anchorTitle?: string, diff --git a/packages/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts b/packages/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts index 8b90fa1d68b..8bbf2f82dc0 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/link/removeLink.ts @@ -8,7 +8,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * If only part of a link is selected, the whole link style will be removed. * @param editor The editor instance */ -export default function removeLink(editor: IEditor) { +export function removeLink(editor: IEditor) { editor.focus(); editor.formatContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts b/packages/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts index 37e1091894a..ec44760e741 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/list/setListStartNumber.ts @@ -6,7 +6,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to operate on * @param value The number to set to, must be equal or greater than 1 */ -export default function setListStartNumber(editor: IEditor, value: number) { +export function setListStartNumber(editor: IEditor, value: number) { editor.focus(); editor.formatContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts b/packages/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts index 98b4507d841..db103a90b05 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/list/setListStyle.ts @@ -7,7 +7,7 @@ import type { IEditor, ListMetadataFormat } from 'roosterjs-content-model-types' * @param editor The editor to operate on * @param style The target list item style to set */ -export default function setListStyle(editor: IEditor, style: ListMetadataFormat) { +export function setListStyle(editor: IEditor, style: ListMetadataFormat) { editor.focus(); editor.formatContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts b/packages/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts index cbf8a36ca65..99b429e5991 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/list/toggleBullet.ts @@ -8,7 +8,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to operate on * @param removeMargins true to remove margins, false to keep margins @default false */ -export default function toggleBullet(editor: IEditor, removeMargins: boolean = false) { +export function toggleBullet(editor: IEditor, removeMargins: boolean = false) { editor.focus(); editor.formatContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts b/packages/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts index 2e754b6fad6..ec03653ab0b 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/list/toggleNumbering.ts @@ -8,7 +8,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to operate on * @param removeMargins true to remove margins, false to keep margins @default false */ -export default function toggleNumbering(editor: IEditor, removeMargins: boolean = false) { +export function toggleNumbering(editor: IEditor, removeMargins: boolean = false) { editor.focus(); editor.formatContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts index 65aaec07a00..15293f507bb 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/applySegmentFormat.ts @@ -6,7 +6,7 @@ import type { ContentModelSegmentFormat, IEditor } from 'roosterjs-content-model * @param editor The editor to operate on * @param newFormat The segment format to apply */ -export default function applySegmentFormat(editor: IEditor, newFormat: ContentModelSegmentFormat) { +export function applySegmentFormat(editor: IEditor, newFormat: ContentModelSegmentFormat) { formatSegmentWithContentModel( editor, 'applySegmentFormat', diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts index 4bbe56f8fd8..111dceafdc1 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/changeCapitalization.ts @@ -9,7 +9,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * Identifying Languages". For example: 'en' or 'en-US' for English, 'tr' for Turkish. * Default is the host environment’s current locale. */ -export default function changeCapitalization( +export function changeCapitalization( editor: IEditor, capitalization: 'sentence' | 'lowerCase' | 'upperCase' | 'capitalize', language?: string diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts index b1b23c401be..1bc3ede6e5a 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/changeFontSize.ts @@ -21,7 +21,7 @@ const MAX_FONT_SIZE = 1000; * @param change Whether increase or decrease font size * @param fontSizes A sorted font size array, in pt. Default value is FONT_SIZES */ -export default function changeFontSize(editor: IEditor, change: 'increase' | 'decrease') { +export function changeFontSize(editor: IEditor, change: 'increase' | 'decrease') { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts index 13ba5da55db..cd248f92d94 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/setBackgroundColor.ts @@ -8,7 +8,7 @@ import type { ContentModelParagraph, IEditor } from 'roosterjs-content-model-typ * @param editor The editor to operate on * @param backgroundColor The color to set. Pass null to remove existing color. */ -export default function setBackgroundColor(editor: IEditor, backgroundColor: string | null) { +export function setBackgroundColor(editor: IEditor, backgroundColor: string | null) { editor.focus(); let lastParagraph: ContentModelParagraph | null = null; diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts index 3acaeeaa37c..d0131065654 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontName.ts @@ -6,7 +6,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to operate on * @param fontName The font name to set */ -export default function setFontName(editor: IEditor, fontName: string) { +export function setFontName(editor: IEditor, fontName: string) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts index ccfa9194f32..d888e89752a 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/setFontSize.ts @@ -10,7 +10,7 @@ import type { * @param editor The editor to operate on * @param fontSize The font size to set */ -export default function setFontSize(editor: IEditor, fontSize: string) { +export function setFontSize(editor: IEditor, fontSize: string) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts index 0c6c4c03e0f..eddf653232a 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/setTextColor.ts @@ -6,7 +6,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor to operate on * @param textColor The text color to set. Pass null to remove existing color. */ -export default function setTextColor(editor: IEditor, textColor: string | null) { +export function setTextColor(editor: IEditor, textColor: string | null) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts index 5fc881ce9d1..e6983acbf3d 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleBold.ts @@ -6,7 +6,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * Toggle bold style * @param editor The editor to operate on */ -export default function toggleBold(editor: IEditor) { +export function toggleBold(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts index 06526e8aee2..bbd90ba335c 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleCode.ts @@ -12,7 +12,7 @@ const DefaultCode: ContentModelCode = { * Toggle italic style * @param editor The editor to operate on */ -export default function toggleCode(editor: IEditor) { +export function toggleCode(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts index 2be06d9b899..fbfa51699d1 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleItalic.ts @@ -5,7 +5,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * Toggle italic style * @param editor The editor to operate on */ -export default function toggleItalic(editor: IEditor) { +export function toggleItalic(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts index 3794ea99d27..f6a9eb924b2 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleStrikethrough.ts @@ -5,7 +5,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * Toggle strikethrough style * @param editor The editor to operate on */ -export default function toggleStrikethrough(editor: IEditor) { +export function toggleStrikethrough(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts index 4396b9e1d47..7e104476205 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSubscript.ts @@ -5,7 +5,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * Toggle subscript style * @param editor The editor to operate on */ -export default function toggleSubscript(editor: IEditor) { +export function toggleSubscript(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts index 74d37665778..94d559599b1 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleSuperscript.ts @@ -5,7 +5,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * Toggle superscript style * @param editor The editor to operate on */ -export default function toggleSuperscript(editor: IEditor) { +export function toggleSuperscript(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts index f1aa57e86c9..0cb6e7fd5a4 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts @@ -6,7 +6,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * Toggle underline style * @param editor The editor to operate on */ -export default function toggleUnderline(editor: IEditor) { +export function toggleUnderline(editor: IEditor) { editor.focus(); formatSegmentWithContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts index ffc0d4026a4..c58df59cfd2 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/table/applyTableBorderFormat.ts @@ -38,7 +38,7 @@ type Perimeter = { * @param border The border to apply * @param operation The operation to apply */ -export default function applyTableBorderFormat( +export function applyTableBorderFormat( editor: IEditor, border: Border, operation: BorderOperations diff --git a/packages/roosterjs-content-model-api/lib/publicApi/table/editTable.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/editTable.ts index 543110c135f..f34f8c42750 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/table/editTable.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/table/editTable.ts @@ -21,7 +21,7 @@ import { * @param editor The editor instance * @param operation The table operation to apply */ -export default function editTable(editor: IEditor, operation: TableOperation) { +export function editTable(editor: IEditor, operation: TableOperation) { editor.focus(); formatTableWithContentModel(editor, 'editTable', tableModel => { diff --git a/packages/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts index 8fd4d6235a4..7b5017e1f74 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/table/formatTable.ts @@ -11,11 +11,7 @@ import type { IEditor, TableMetadataFormat } from 'roosterjs-content-model-types * @param format The table format to apply * @param keepCellShade Whether keep existing shade color when apply format if there is a manually set shade color */ -export default function formatTable( - editor: IEditor, - format: TableMetadataFormat, - keepCellShade?: boolean -) { +export function formatTable(editor: IEditor, format: TableMetadataFormat, keepCellShade?: boolean) { editor.focus(); editor.formatContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts index 8c1072c94ae..bfdf81c89e2 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/table/insertTable.ts @@ -18,7 +18,7 @@ import type { IEditor, TableMetadataFormat } from 'roosterjs-content-model-types * @param format (Optional) The table format. If not passed, the default format will be applied: * background color: #FFF; border color: #ABABAB */ -export default function insertTable( +export function insertTable( editor: IEditor, columns: number, rows: number, diff --git a/packages/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts index 8c54f2c595e..a32b14d6e5f 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/table/setTableCellShade.ts @@ -11,7 +11,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; * @param editor The editor instance * @param color The color to set. Pass null to remove existing shade color */ -export default function setTableCellShade(editor: IEditor, color: string | null) { +export function setTableCellShade(editor: IEditor, color: string | null) { editor.focus(); editor.formatContentModel( diff --git a/packages/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts index 1bda429eeed..dee10498e7c 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatImageWithContentModel.ts @@ -2,9 +2,12 @@ import { formatSegmentWithContentModel } from './formatSegmentWithContentModel'; import type { ContentModelImage, IEditor } from 'roosterjs-content-model-types'; /** - * @internal + * Invoke a callback to format the selected image using Content Model + * @param editor The editor object + * @param apiName Name of API this calling this function. This is mostly for logging. + * @param callback The callback to format the image. It will be called with current selected table. If no table is selected, it will not be called. */ -export default function formatImageWithContentModel( +export function formatImageWithContentModel( editor: IEditor, apiName: string, callback: (segment: ContentModelImage) => void diff --git a/packages/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts index 951e26f7748..00a2e26b364 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatParagraphWithContentModel.ts @@ -2,7 +2,10 @@ import { getSelectedParagraphs } from 'roosterjs-content-model-core'; import type { ContentModelParagraph, IEditor } from 'roosterjs-content-model-types'; /** - * @internal + * Invoke a callback to format the selected paragraph using Content Model + * @param editor The editor object + * @param apiName Name of API this calling this function. This is mostly for logging. + * @param setStyleCallback The callback to format the paragraph. It will be called with current selected table. If no table is selected, it will not be called. */ export function formatParagraphWithContentModel( editor: IEditor, diff --git a/packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts index 727615732e5..c5fea44b67e 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts @@ -9,7 +9,13 @@ import type { } from 'roosterjs-content-model-types'; /** - * @internal + * Invoke a callback to format the selected segment using Content Model + * @param editor The editor object + * @param apiName Name of API this calling this function. This is mostly for logging. + * @param toggleStyleCallback The callback to format the segment. It will be called with current selected table. If no table is selected, it will not be called. + * @param segmentHasStyleCallback The callback used for checking if the given segment already has required format + * @param includingFormatHolder True to also include format holder of list item when search selected segments + * @param afterFormatCallback A callback to invoke after format is applied to all selected segments and before the change is applied to DOM tree */ export function formatSegmentWithContentModel( editor: IEditor, diff --git a/packages/roosterjs-content-model-api/test/modelApi/image/applyImageBorderFormatTest.ts b/packages/roosterjs-content-model-api/test/modelApi/image/applyImageBorderFormatTest.ts index acf4bd0038a..516ebd8c82c 100644 --- a/packages/roosterjs-content-model-api/test/modelApi/image/applyImageBorderFormatTest.ts +++ b/packages/roosterjs-content-model-api/test/modelApi/image/applyImageBorderFormatTest.ts @@ -1,4 +1,4 @@ -import applyImageBorderFormat from '../../../lib/modelApi/image/applyImageBorderFormat'; +import { applyImageBorderFormat } from '../../../lib/modelApi/image/applyImageBorderFormat'; import { Border, ContentModelImage } from 'roosterjs-content-model-types'; describe('applyImageBorderFormat', () => { diff --git a/packages/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts index 011584e2754..dece6427cad 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/block/setAlignmentTest.ts @@ -1,8 +1,8 @@ import * as normalizeTable from 'roosterjs-content-model-core/lib/publicApi/table/normalizeTable'; -import setAlignment from '../../../lib/publicApi/block/setAlignment'; import { createContentModelDocument } from 'roosterjs-content-model-dom'; import { IEditor } from 'roosterjs-content-model-types'; import { paragraphTestCommon } from './paragraphTestCommon'; +import { setAlignment } from '../../../lib/publicApi/block/setAlignment'; import { ContentModelDocument, ContentModelListItem, diff --git a/packages/roosterjs-content-model-api/test/publicApi/block/setDirectionTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setDirectionTest.ts index 118c7591d55..7e57b256e9e 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/block/setDirectionTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/block/setDirectionTest.ts @@ -1,6 +1,6 @@ -import setDirection from '../../../lib/publicApi/block/setDirection'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { paragraphTestCommon } from './paragraphTestCommon'; +import { setDirection } from '../../../lib/publicApi/block/setDirection'; describe('setDirection', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/block/setHeadingLevelTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setHeadingLevelTest.ts index 2d818e50f9f..db042db28aa 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/block/setHeadingLevelTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/block/setHeadingLevelTest.ts @@ -1,6 +1,6 @@ -import setHeadingLevel from '../../../lib/publicApi/block/setHeadingLevel'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { paragraphTestCommon } from './paragraphTestCommon'; +import { setHeadingLevel } from '../../../lib/publicApi/block/setHeadingLevel'; describe('setHeadingLevel to 1', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts index a72c0e02f21..ef7da8471be 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/block/setIndentationTest.ts @@ -1,7 +1,7 @@ import * as setModelIndentation from '../../../lib/modelApi/block/setModelIndentation'; -import setIndentation from '../../../lib/publicApi/block/setIndentation'; import { ContentModelFormatter, FormatContentModelContext } from 'roosterjs-content-model-types'; import { IEditor } from 'roosterjs-content-model-types'; +import { setIndentation } from '../../../lib/publicApi/block/setIndentation'; describe('setIndentation', () => { const fakeModel: any = { a: 'b' }; diff --git a/packages/roosterjs-content-model-api/test/publicApi/block/setParagraphMarginTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setParagraphMarginTest.ts index 84ce9324e5d..97c171677c1 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/block/setParagraphMarginTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/block/setParagraphMarginTest.ts @@ -1,6 +1,6 @@ -import setParagraphMargin from '../../../lib/publicApi/block/setParagraphMargin'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { paragraphTestCommon } from './paragraphTestCommon'; +import { setParagraphMargin } from '../../../lib/publicApi/block/setParagraphMargin'; describe('setParagraphMargin', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/block/setSpacingTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/setSpacingTest.ts index 8f18a38e060..af4c37ede50 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/block/setSpacingTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/block/setSpacingTest.ts @@ -1,6 +1,6 @@ -import setSpacing from '../../../lib/publicApi/block/setSpacing'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { paragraphTestCommon } from './paragraphTestCommon'; +import { setSpacing } from '../../../lib/publicApi/block/setSpacing'; describe('setSpacing', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts b/packages/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts index 76ebbef70e5..17baafe34ad 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/block/toggleBlockQuoteTest.ts @@ -1,7 +1,7 @@ import * as toggleModelBlockQuote from '../../../lib/modelApi/block/toggleModelBlockQuote'; -import toggleBlockQuote from '../../../lib/publicApi/block/toggleBlockQuote'; import { ContentModelFormatter, FormatContentModelContext } from 'roosterjs-content-model-types'; import { IEditor } from 'roosterjs-content-model-types'; +import { toggleBlockQuote } from '../../../lib/publicApi/block/toggleBlockQuote'; describe('toggleBlockQuote', () => { const fakeModel: any = { a: 'b' }; diff --git a/packages/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts b/packages/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts index 475163b52e5..3d55e5fa8cf 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/entity/insertEntityTest.ts @@ -1,9 +1,9 @@ import * as entityUtils from 'roosterjs-content-model-dom/lib/domUtils/entityUtils'; import * as insertEntityModel from '../../../lib/modelApi/entity/insertEntityModel'; import * as normalizeContentModel from 'roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel'; -import insertEntity from '../../../lib/publicApi/entity/insertEntity'; import { ChangeSource } from 'roosterjs-content-model-core'; import { IEditor } from 'roosterjs-content-model-types'; +import { insertEntity } from '../../../lib/publicApi/entity/insertEntity'; import { FormatContentModelContext, FormatContentModelOptions, diff --git a/packages/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts b/packages/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts index f98777a04e8..80958b94380 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/format/clearFormatTest.ts @@ -1,6 +1,6 @@ import * as clearModelFormat from '../../../lib/modelApi/common/clearModelFormat'; import * as normalizeContentModel from 'roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel'; -import clearFormat from '../../../lib/publicApi/format/clearFormat'; +import { clearFormat } from '../../../lib/publicApi/format/clearFormat'; import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, diff --git a/packages/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts b/packages/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts index a9e90949ed8..ccd7c681b45 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/format/getFormatStateTest.ts @@ -1,7 +1,7 @@ import * as retrieveModelFormatState from 'roosterjs-content-model-core/lib/publicApi/format/retrieveModelFormatState'; -import getFormatState from '../../../lib/publicApi/format/getFormatState'; import { ContentModelDocument, ContentModelSegmentFormat } from 'roosterjs-content-model-types'; import { ContentModelFormatState } from 'roosterjs-content-model-types'; +import { getFormatState } from '../../../lib/publicApi/format/getFormatState'; import { IEditor } from 'roosterjs-content-model-types'; import { reducedModelChildProcessor } from 'roosterjs-content-model-core/lib/override/reducedModelChildProcessor'; import { diff --git a/packages/roosterjs-content-model-api/test/publicApi/image/adjustImageSelectionTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/adjustImageSelectionTest.ts index 59ae5b25ae3..5e60019d87a 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/image/adjustImageSelectionTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/image/adjustImageSelectionTest.ts @@ -1,4 +1,4 @@ -import adjustImageSelection from '../../../lib/publicApi/image/adjustImageSelection'; +import { adjustImageSelection } from '../../../lib/publicApi/image/adjustImageSelection'; import { ContentModelDocument, ContentModelImage } from 'roosterjs-content-model-types'; import { segmentTestCommon } from '../segment/segmentTestCommon'; import { diff --git a/packages/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts index 4b4593d7513..dcbd9db2405 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/image/changeImageTest.ts @@ -1,5 +1,5 @@ import * as readFile from 'roosterjs-content-model-core/lib/publicApi/domUtils/readFile'; -import changeImage from '../../../lib/publicApi/image/changeImage'; +import { changeImage } from '../../../lib/publicApi/image/changeImage'; import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, diff --git a/packages/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts index ab9cbd3212b..f11f34786a4 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/image/insertImageTest.ts @@ -1,6 +1,6 @@ import * as readFile from 'roosterjs-content-model-core/lib/publicApi/domUtils/readFile'; -import insertImage from '../../../lib/publicApi/image/insertImage'; import { IEditor } from 'roosterjs-content-model-types'; +import { insertImage } from '../../../lib/publicApi/image/insertImage'; import { ContentModelDocument, ContentModelFormatter, diff --git a/packages/roosterjs-content-model-api/test/publicApi/image/setImageAltTextTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/setImageAltTextTest.ts index 21ce069df7c..6f7b2e9fd5a 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/image/setImageAltTextTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/image/setImageAltTextTest.ts @@ -1,6 +1,6 @@ -import setImageAltText from '../../../lib/publicApi/image/setImageAltText'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from '../segment/segmentTestCommon'; +import { setImageAltText } from '../../../lib/publicApi/image/setImageAltText'; import { addSegment, createContentModelDocument, diff --git a/packages/roosterjs-content-model-api/test/publicApi/image/setImageBorderTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/setImageBorderTest.ts index 2110db87731..46da280fb64 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/image/setImageBorderTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/image/setImageBorderTest.ts @@ -1,6 +1,6 @@ -import setImageBorder from '../../../lib/publicApi/image/setImageBorder'; import { Border, ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from '../segment/segmentTestCommon'; +import { setImageBorder } from '../../../lib/publicApi/image/setImageBorder'; import { addSegment, createContentModelDocument, diff --git a/packages/roosterjs-content-model-api/test/publicApi/image/setImageBoxShadowTest.ts b/packages/roosterjs-content-model-api/test/publicApi/image/setImageBoxShadowTest.ts index 4ce9da63dc1..b287f8a7cf1 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/image/setImageBoxShadowTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/image/setImageBoxShadowTest.ts @@ -1,6 +1,6 @@ -import setImageBoxShadow from '../../../lib/publicApi/image/setImageBoxShadow'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from '../segment/segmentTestCommon'; +import { setImageBoxShadow } from '../../../lib/publicApi/image/setImageBoxShadow'; import { addSegment, createContentModelDocument, diff --git a/packages/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts b/packages/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts index ae1d7127f80..fdac2d121dc 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/link/adjustLinkSelectionTest.ts @@ -1,4 +1,4 @@ -import adjustLinkSelection from '../../../lib/publicApi/link/adjustLinkSelection'; +import { adjustLinkSelection } from '../../../lib/publicApi/link/adjustLinkSelection'; import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, diff --git a/packages/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts b/packages/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts index 18924cb5ae9..33ab5c5bfde 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/link/insertLinkTest.ts @@ -1,6 +1,6 @@ -import insertLink from '../../../lib/publicApi/link/insertLink'; import { ChangeSource, Editor } from 'roosterjs-content-model-core'; import { IEditor } from 'roosterjs-content-model-types'; +import { insertLink } from '../../../lib/publicApi/link/insertLink'; import { ContentModelDocument, ContentModelLink, diff --git a/packages/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts b/packages/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts index 3495cdb9f05..0d0f1f081bf 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/link/removeLinkTest.ts @@ -1,5 +1,5 @@ -import removeLink from '../../../lib/publicApi/link/removeLink'; import { IEditor } from 'roosterjs-content-model-types'; +import { removeLink } from '../../../lib/publicApi/link/removeLink'; import { ContentModelDocument, ContentModelLink, diff --git a/packages/roosterjs-content-model-api/test/publicApi/list/setListStartNumberTest.ts b/packages/roosterjs-content-model-api/test/publicApi/list/setListStartNumberTest.ts index 3e2dddecb53..52aa776c4e0 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/list/setListStartNumberTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/list/setListStartNumberTest.ts @@ -1,4 +1,4 @@ -import setListStartNumber from '../../../lib/publicApi/list/setListStartNumber'; +import { setListStartNumber } from '../../../lib/publicApi/list/setListStartNumber'; import { ContentModelDocument, ContentModelFormatter, diff --git a/packages/roosterjs-content-model-api/test/publicApi/list/setListStyleTest.ts b/packages/roosterjs-content-model-api/test/publicApi/list/setListStyleTest.ts index 811b7ad8972..e54c995400f 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/list/setListStyleTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/list/setListStyleTest.ts @@ -1,5 +1,5 @@ -import setListStyle from '../../../lib/publicApi/list/setListStyle'; import { ContentModelDocument, ListMetadataFormat } from 'roosterjs-content-model-types'; +import { setListStyle } from '../../../lib/publicApi/list/setListStyle'; describe('setListStyle', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts b/packages/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts index 00f4866eb81..f780767c190 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/list/toggleBulletTest.ts @@ -1,6 +1,6 @@ import * as setListType from '../../../lib/modelApi/list/setListType'; -import toggleBullet from '../../../lib/publicApi/list/toggleBullet'; import { IEditor } from 'roosterjs-content-model-types'; +import { toggleBullet } from '../../../lib/publicApi/list/toggleBullet'; import { ContentModelDocument, ContentModelFormatter, diff --git a/packages/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts b/packages/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts index bbde934f83f..a2056062992 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/list/toggleNumberingTest.ts @@ -1,6 +1,6 @@ import * as setListType from '../../../lib/modelApi/list/setListType'; -import toggleNumbering from '../../../lib/publicApi/list/toggleNumbering'; import { IEditor } from 'roosterjs-content-model-types'; +import { toggleNumbering } from '../../../lib/publicApi/list/toggleNumbering'; import { ContentModelDocument, ContentModelFormatter, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts index 1197cc9f388..673e07f7340 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts @@ -1,4 +1,4 @@ -import applySegmentFormat from '../../../lib/publicApi/segment/applySegmentFormat'; +import { applySegmentFormat } from '../../../lib/publicApi/segment/applySegmentFormat'; import { ContentModelDocument, ContentModelSegmentFormat } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/changeCapitalizationTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/changeCapitalizationTest.ts index 5b8aa627bbb..7cf4ddf4c4c 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/changeCapitalizationTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/changeCapitalizationTest.ts @@ -1,4 +1,4 @@ -import changeCapitalization from '../../../lib/publicApi/segment/changeCapitalization'; +import { changeCapitalization } from '../../../lib/publicApi/segment/changeCapitalization'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; import { diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts index 268d8cfc6b1..6241c1007b5 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/changeFontSizeTest.ts @@ -1,4 +1,4 @@ -import changeFontSize from '../../../lib/publicApi/segment/changeFontSize'; +import { changeFontSize } from '../../../lib/publicApi/segment/changeFontSize'; import { createDomToModelContext, domToContentModel } from 'roosterjs-content-model-dom'; import { createRange } from 'roosterjs-content-model-dom/test/testUtils'; import { IEditor } from 'roosterjs-content-model-types'; diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts index 6462bc162ae..bfc9e208a91 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts @@ -1,6 +1,6 @@ -import setBackgroundColor from '../../../lib/publicApi/segment/setBackgroundColor'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { setBackgroundColor } from '../../../lib/publicApi/segment/setBackgroundColor'; describe('setBackgroundColor', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts index 54dd78ec113..be71d490150 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts @@ -1,6 +1,6 @@ -import setFontName from '../../../lib/publicApi/segment/setFontName'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { setFontName } from '../../../lib/publicApi/segment/setFontName'; describe('setFontName', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts index 78ee1274cb3..81725bd874a 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts @@ -1,6 +1,6 @@ -import setFontSize from '../../../lib/publicApi/segment/setFontSize'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { setFontSize } from '../../../lib/publicApi/segment/setFontSize'; describe('setFontSize', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts index d39c1e7233f..0dae57bd630 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts @@ -1,6 +1,6 @@ -import setTextColor from '../../../lib/publicApi/segment/setTextColor'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { setTextColor } from '../../../lib/publicApi/segment/setTextColor'; describe('setTextColor', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts index d536aa12ef0..7ff3e947bab 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts @@ -1,6 +1,6 @@ -import toggleBold from '../../../lib/publicApi/segment/toggleBold'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { toggleBold } from '../../../lib/publicApi/segment/toggleBold'; describe('toggleBold', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts index 6d33a29c01e..bd032376893 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts @@ -1,6 +1,6 @@ -import toggleCode from '../../../lib/publicApi/segment/toggleCode'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { toggleCode } from '../../../lib/publicApi/segment/toggleCode'; describe('toggleCode', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts index ca709af52e5..402f873de61 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts @@ -1,6 +1,6 @@ -import toggleItalic from '../../../lib/publicApi/segment/toggleItalic'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { toggleItalic } from '../../../lib/publicApi/segment/toggleItalic'; describe('toggleItalic', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts index b8946ea3bda..b758010f93d 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts @@ -1,6 +1,6 @@ -import toggleStrikethrough from '../../../lib/publicApi/segment/toggleStrikethrough'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { toggleStrikethrough } from '../../../lib/publicApi/segment/toggleStrikethrough'; describe('toggleStrikethrough', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts index 39f616c883f..04a710b652d 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts @@ -1,6 +1,6 @@ -import toggleSubscript from '../../../lib/publicApi/segment/toggleSubscript'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { toggleSubscript } from '../../../lib/publicApi/segment/toggleSubscript'; describe('toggleSubscript', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts index 7b0f6a51451..d554ad27168 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts @@ -1,6 +1,6 @@ -import toggleSuperscript from '../../../lib/publicApi/segment/toggleSuperscript'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { toggleSuperscript } from '../../../lib/publicApi/segment/toggleSuperscript'; describe('toggleSuperscript', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts index 210971c9eb3..3356aee5a0b 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts @@ -1,6 +1,6 @@ -import toggleUnderline from '../../../lib/publicApi/segment/toggleUnderline'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { segmentTestCommon } from './segmentTestCommon'; +import { toggleUnderline } from '../../../lib/publicApi/segment/toggleUnderline'; describe('toggleUnderline', () => { function runTest( diff --git a/packages/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts b/packages/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts index 2e69d1b483e..5fc6b579a13 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/table/applyTableBorderFormatTest.ts @@ -1,5 +1,5 @@ import * as normalizeTable from 'roosterjs-content-model-core/lib/publicApi/table/normalizeTable'; -import applyTableBorderFormat from '../../../lib/publicApi/table/applyTableBorderFormat'; +import { applyTableBorderFormat } from '../../../lib/publicApi/table/applyTableBorderFormat'; import { createContentModelDocument } from 'roosterjs-content-model-dom'; import { createTable, createTableCell } from 'roosterjs-content-model-dom'; import { IEditor } from 'roosterjs-content-model-types'; diff --git a/packages/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts b/packages/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts index 841b50cc468..f78b80cbde1 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/table/editTableTest.ts @@ -11,7 +11,7 @@ import * as mergeTableColumn from '../../../lib/modelApi/table/mergeTableColumn' import * as mergeTableRow from '../../../lib/modelApi/table/mergeTableRow'; import * as splitTableCellHorizontally from '../../../lib/modelApi/table/splitTableCellHorizontally'; import * as splitTableCellVertically from '../../../lib/modelApi/table/splitTableCellVertically'; -import editTable from '../../../lib/publicApi/table/editTable'; +import { editTable } from '../../../lib/publicApi/table/editTable'; import { IEditor, TableOperation } from 'roosterjs-content-model-types'; describe('editTable', () => { diff --git a/packages/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts index 9869155565a..0575020c6bd 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/table/setTableCellShadeTest.ts @@ -1,7 +1,7 @@ import * as normalizeTable from 'roosterjs-content-model-core/lib/publicApi/table/normalizeTable'; -import setTableCellShade from '../../../lib/publicApi/table/setTableCellShade'; import { createContentModelDocument } from 'roosterjs-content-model-dom'; import { IEditor } from 'roosterjs-content-model-types'; +import { setTableCellShade } from '../../../lib/publicApi/table/setTableCellShade'; import { ContentModelTable, ContentModelFormatter, diff --git a/packages/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts b/packages/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts index f15a296691b..f300f6078b3 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/utils/formatImageWithContentModelTest.ts @@ -1,4 +1,4 @@ -import formatImageWithContentModel from '../../../lib/publicApi/utils/formatImageWithContentModel'; +import { formatImageWithContentModel } from '../../../lib/publicApi/utils/formatImageWithContentModel'; import { IEditor } from 'roosterjs-content-model-types'; import { ContentModelDocument, diff --git a/packages/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts b/packages/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts index ef3b022d22c..ec967bb542f 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/utils/formatTableWithContentModelTest.ts @@ -72,7 +72,7 @@ describe('formatTableWithContentModel', () => { table.rows[0].cells.push(tableCell); model.blocks.push(table); - spyOn(hasSelectionInBlock, 'default').and.returnValue(true); + spyOn(hasSelectionInBlock, 'hasSelectionInBlock').and.returnValue(true); spyOn(ensureFocusableParagraphForTable, 'ensureFocusableParagraphForTable'); spyOn(normalizeTable, 'normalizeTable'); spyOn(applyTableFormat, 'applyTableFormat'); @@ -104,7 +104,7 @@ describe('formatTableWithContentModel', () => { table.rows[0].cells.push(tableCell); model.blocks.push(table); - spyOn(hasSelectionInBlock, 'default').and.returnValue(false); + spyOn(hasSelectionInBlock, 'hasSelectionInBlock').and.returnValue(false); spyOn( ensureFocusableParagraphForTable, 'ensureFocusableParagraphForTable' @@ -165,7 +165,7 @@ describe('formatTableWithContentModel', () => { table.rows[0].cells.push(tableCell); model.blocks.push(table); - spyOn(hasSelectionInBlock, 'default').and.returnValue(false); + spyOn(hasSelectionInBlock, 'hasSelectionInBlock').and.returnValue(false); spyOn( ensureFocusableParagraphForTable, 'ensureFocusableParagraphForTable' @@ -228,7 +228,7 @@ describe('formatTableWithContentModel', () => { table.rows[0].cells.push(tableCell); model.blocks.push(table); - spyOn(hasSelectionInBlock, 'default').and.returnValue(false); + spyOn(hasSelectionInBlock, 'hasSelectionInBlock').and.returnValue(false); spyOn( ensureFocusableParagraphForTable, 'ensureFocusableParagraphForTable' diff --git a/packages/roosterjs-content-model-core/lib/corePlugin/utils/deleteEmptyList.ts b/packages/roosterjs-content-model-core/lib/corePlugin/utils/deleteEmptyList.ts index 2d82acbd8af..c5f6729376e 100644 --- a/packages/roosterjs-content-model-core/lib/corePlugin/utils/deleteEmptyList.ts +++ b/packages/roosterjs-content-model-core/lib/corePlugin/utils/deleteEmptyList.ts @@ -1,6 +1,6 @@ -import hasSelectionInBlock from '../../publicApi/selection/hasSelectionInBlock'; -import hasSelectionInBlockGroup from '../../publicApi/selection/hasSelectionInBlockGroup'; import { getClosestAncestorBlockGroupIndex } from '../../publicApi/model/getClosestAncestorBlockGroupIndex'; +import { hasSelectionInBlock } from '../../publicApi/selection/hasSelectionInBlock'; +import { hasSelectionInBlockGroup } from '../../publicApi/selection/hasSelectionInBlockGroup'; import type { ContentModelBlock, DeleteSelectionContext, diff --git a/packages/roosterjs-content-model-core/lib/index.ts b/packages/roosterjs-content-model-core/lib/index.ts index ca10b6cde89..13250cc598a 100644 --- a/packages/roosterjs-content-model-core/lib/index.ts +++ b/packages/roosterjs-content-model-core/lib/index.ts @@ -18,9 +18,9 @@ export { getSelectionRootNode } from './publicApi/selection/getSelectionRootNode export { deleteSelection } from './publicApi/selection/deleteSelection'; export { deleteSegment } from './publicApi/selection/deleteSegment'; export { deleteBlock } from './publicApi/selection/deleteBlock'; -export { default as hasSelectionInBlock } from './publicApi/selection/hasSelectionInBlock'; -export { default as hasSelectionInSegment } from './publicApi/selection/hasSelectionInSegment'; -export { default as hasSelectionInBlockGroup } from './publicApi/selection/hasSelectionInBlockGroup'; +export { hasSelectionInBlock } from './publicApi/selection/hasSelectionInBlock'; +export { hasSelectionInSegment } from './publicApi/selection/hasSelectionInSegment'; +export { hasSelectionInBlockGroup } from './publicApi/selection/hasSelectionInBlockGroup'; export { OperationalBlocks, getFirstSelectedListItem, diff --git a/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock.ts index a0144bcb1e9..8d721e4f048 100644 --- a/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock.ts +++ b/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlock.ts @@ -1,12 +1,12 @@ -import hasSelectionInBlockGroup from './hasSelectionInBlockGroup'; -import hasSelectionInSegment from './hasSelectionInSegment'; +import { hasSelectionInBlockGroup } from './hasSelectionInBlockGroup'; +import { hasSelectionInSegment } from './hasSelectionInSegment'; import type { ContentModelBlock } from 'roosterjs-content-model-types'; /** * Check if there is selection within the given block * @param block The block to check */ -export default function hasSelectionInBlock(block: ContentModelBlock): boolean { +export function hasSelectionInBlock(block: ContentModelBlock): boolean { switch (block.blockType) { case 'Paragraph': return block.segments.some(hasSelectionInSegment); diff --git a/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlockGroup.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlockGroup.ts index a6bc83ede70..eb9b2199e61 100644 --- a/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlockGroup.ts +++ b/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInBlockGroup.ts @@ -1,11 +1,11 @@ -import hasSelectionInBlock from './hasSelectionInBlock'; +import { hasSelectionInBlock } from './hasSelectionInBlock'; import type { ContentModelBlockGroup } from 'roosterjs-content-model-types'; /** * Check if there is selection within the given block * @param block The block to check */ -export default function hasSelectionInBlockGroup(group: ContentModelBlockGroup): boolean { +export function hasSelectionInBlockGroup(group: ContentModelBlockGroup): boolean { if (group.blockGroupType == 'TableCell' && group.isSelected) { return true; } diff --git a/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInSegment.ts b/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInSegment.ts index 8d059ab6196..8bc58860ef8 100644 --- a/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInSegment.ts +++ b/packages/roosterjs-content-model-core/lib/publicApi/selection/hasSelectionInSegment.ts @@ -1,11 +1,11 @@ -import hasSelectionInBlock from './hasSelectionInBlock'; +import { hasSelectionInBlock } from './hasSelectionInBlock'; import type { ContentModelSegment } from 'roosterjs-content-model-types'; /** * Check if there is selection within the given segment * @param segment The segment to check */ -export default function hasSelectionInSegment(segment: ContentModelSegment): boolean { +export function hasSelectionInSegment(segment: ContentModelSegment): boolean { return ( segment.isSelected || (segment.segmentType == 'General' && segment.blocks.some(hasSelectionInBlock)) diff --git a/packages/roosterjs-content-model-core/lib/publicApi/table/getSelectedCells.ts b/packages/roosterjs-content-model-core/lib/publicApi/table/getSelectedCells.ts index 1b035df0ac1..beb8b46fe86 100644 --- a/packages/roosterjs-content-model-core/lib/publicApi/table/getSelectedCells.ts +++ b/packages/roosterjs-content-model-core/lib/publicApi/table/getSelectedCells.ts @@ -1,4 +1,4 @@ -import hasSelectionInBlockGroup from '../selection/hasSelectionInBlockGroup'; +import { hasSelectionInBlockGroup } from '../selection/hasSelectionInBlockGroup'; import type { ContentModelTable, TableSelectionCoordinates } from 'roosterjs-content-model-types'; /** diff --git a/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts b/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts index fcb8f1e13d4..b291d2132d5 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts @@ -114,7 +114,7 @@ describe('paste with content model & paste plugin', () => { editor = new Editor(div, { plugins: [new PastePlugin()], }); - spyOn(addParserF, 'default').and.callThrough(); + spyOn(addParserF, 'addParser').and.callThrough(); spyOn(setProcessorF, 'setProcessor').and.callThrough(); clipboardData = { types: ['image/png', 'text/html'], @@ -140,7 +140,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(1); - expect(addParserF.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 5); + expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 5); expect(WordDesktopFile.processPastedContentFromWordDesktop).toHaveBeenCalledTimes(1); }); @@ -151,7 +151,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(2); - expect(addParserF.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 6); + expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 6); expect(WacComponents.processPastedContentWacComponents).toHaveBeenCalledTimes(1); }); @@ -162,7 +162,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(1); - expect(addParserF.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); + expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); expect(ExcelF.processPastedContentFromExcel).toHaveBeenCalledTimes(1); }); @@ -173,7 +173,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(1); - expect(addParserF.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); + expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); expect(ExcelF.processPastedContentFromExcel).toHaveBeenCalledTimes(1); }); @@ -184,7 +184,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); - expect(addParserF.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); + expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); expect(PPT.processPastedContentFromPowerPoint).toHaveBeenCalledTimes(1); }); @@ -196,7 +196,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); - expect(addParserF.default).toHaveBeenCalledTimes(0); + expect(addParserF.addParser).toHaveBeenCalledTimes(0); expect(WordDesktopFile.processPastedContentFromWordDesktop).toHaveBeenCalledTimes(0); }); @@ -207,7 +207,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); - expect(addParserF.default).toHaveBeenCalledTimes(0); + expect(addParserF.addParser).toHaveBeenCalledTimes(0); expect(WacComponents.processPastedContentWacComponents).toHaveBeenCalledTimes(0); }); @@ -218,7 +218,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); - expect(addParserF.default).toHaveBeenCalledTimes(0); + expect(addParserF.addParser).toHaveBeenCalledTimes(0); expect(ExcelF.processPastedContentFromExcel).toHaveBeenCalledTimes(0); }); @@ -229,7 +229,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); - expect(addParserF.default).toHaveBeenCalledTimes(0); + expect(addParserF.addParser).toHaveBeenCalledTimes(0); expect(ExcelF.processPastedContentFromExcel).toHaveBeenCalledTimes(0); }); @@ -240,7 +240,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); - expect(addParserF.default).toHaveBeenCalledTimes(0); + expect(addParserF.addParser).toHaveBeenCalledTimes(0); expect(PPT.processPastedContentFromPowerPoint).toHaveBeenCalledTimes(0); }); diff --git a/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInBlockTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInBlockTest.ts index 788e9e3efea..ab45f1b9557 100644 --- a/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInBlockTest.ts +++ b/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInBlockTest.ts @@ -1,5 +1,5 @@ -import hasSelectionInBlock from '../../../lib/publicApi/selection/hasSelectionInBlock'; -import hasSelectionInBlockGroup from '../../../lib/publicApi/selection/hasSelectionInBlockGroup'; +import { hasSelectionInBlock } from '../../../lib/publicApi/selection/hasSelectionInBlock'; +import { hasSelectionInBlockGroup } from '../../../lib/publicApi/selection/hasSelectionInBlockGroup'; import { ContentModelBlock, ContentModelDivider, diff --git a/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInSegmentTest.ts b/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInSegmentTest.ts index 94f76418310..0c859e64d66 100644 --- a/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInSegmentTest.ts +++ b/packages/roosterjs-content-model-core/test/publicApi/selection/hasSelectionInSegmentTest.ts @@ -1,5 +1,5 @@ -import hasSelectionInSegment from '../../../lib/publicApi/selection/hasSelectionInSegment'; import { ContentModelSegment } from 'roosterjs-content-model-types'; +import { hasSelectionInSegment } from '../../../lib/publicApi/selection/hasSelectionInSegment'; describe('hasSelectionInSegment', () => { it('Simple text segment', () => { diff --git a/packages/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts b/packages/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts index 2579f769c37..0e3368f439d 100644 --- a/packages/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts +++ b/packages/roosterjs-content-model-dom/lib/domUtils/entityUtils.ts @@ -1,7 +1,7 @@ -import toArray from './toArray'; import { applyFormat } from '../modelToDom/utils/applyFormat'; import { isElementOfType } from './isElementOfType'; import { isNodeOfType } from './isNodeOfType'; +import { toArray } from './toArray'; import type { ContentModelEntityFormat, ContentModelSegmentFormat, diff --git a/packages/roosterjs-content-model-dom/lib/domUtils/toArray.ts b/packages/roosterjs-content-model-dom/lib/domUtils/toArray.ts index f14f2924149..1a7846c34f7 100644 --- a/packages/roosterjs-content-model-dom/lib/domUtils/toArray.ts +++ b/packages/roosterjs-content-model-dom/lib/domUtils/toArray.ts @@ -2,25 +2,25 @@ * Convert a named node map to an array * @param collection The map to convert */ -export default function toArray(collection: NamedNodeMap): Attr[]; +export function toArray(collection: NamedNodeMap): Attr[]; /** * Convert a named node map to an array * @param collection The map to convert */ -export default function toArray(collection: DataTransferItemList): DataTransferItem[]; +export function toArray(collection: DataTransferItemList): DataTransferItem[]; /** * Convert a collection to an array * @param collection The collection to convert */ -export default function toArray(collection: NodeListOf): T[]; +export function toArray(collection: NodeListOf): T[]; /** * Convert a collection to an array * @param collection The collection to convert */ -export default function toArray(collection: HTMLCollectionOf): T[]; +export function toArray(collection: HTMLCollectionOf): T[]; /** * Convert an array to an array. @@ -28,8 +28,8 @@ export default function toArray(collection: HTMLCollectionOf< * but the declaration is an array. e.g. ClipboardData.types * @param array The array to convert */ -export default function toArray(array: readonly T[]): T[]; +export function toArray(array: readonly T[]): T[]; -export default function toArray(collection: any): any[] { +export function toArray(collection: any): any[] { return [].slice.call(collection); } diff --git a/packages/roosterjs-content-model-dom/lib/index.ts b/packages/roosterjs-content-model-dom/lib/index.ts index b8f909736da..e46cc69b0cc 100644 --- a/packages/roosterjs-content-model-dom/lib/index.ts +++ b/packages/roosterjs-content-model-dom/lib/index.ts @@ -18,7 +18,7 @@ export { updateMetadata, hasMetadata } from './domUtils/metadata/updateMetadata' export { isNodeOfType, NodeTypeMap } from './domUtils/isNodeOfType'; export { isElementOfType } from './domUtils/isElementOfType'; export { getObjectKeys } from './domUtils/getObjectKeys'; -export { default as toArray } from './domUtils/toArray'; +export { toArray } from './domUtils/toArray'; export { moveChildNodes, wrapAllChildNodes } from './domUtils/moveChildNodes'; export { wrap } from './domUtils/wrap'; export { diff --git a/packages/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts index 7b90a3654d6..2a5e836f6bb 100644 --- a/packages/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts +++ b/packages/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts @@ -1,5 +1,5 @@ -import toArray from '../domUtils/toArray'; import { isNodeOfType } from '../domUtils/isNodeOfType'; +import { toArray } from '../domUtils/toArray'; import type { ContentModelDocument, DOMSelection, diff --git a/packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts b/packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts index 47339f76d24..6fc0762590f 100644 --- a/packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts +++ b/packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts @@ -1,4 +1,4 @@ -import addParser from '../utils/addParser'; +import { addParser } from '../utils/addParser'; import { isNodeOfType, moveChildNodes } from 'roosterjs-content-model-dom'; import { setProcessor } from '../utils/setProcessor'; import type { BeforePasteEvent, TrustedHTMLHandler } from 'roosterjs-content-model-types'; diff --git a/packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts b/packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts index 4da8cf902b4..985d531e0f3 100644 --- a/packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/paste/PastePlugin.ts @@ -1,4 +1,4 @@ -import addParser from './utils/addParser'; +import { addParser } from './utils/addParser'; import { BorderKeys } from 'roosterjs-content-model-dom'; import { deprecatedBorderColorParser } from './utils/deprecatedColorParser'; import { getPasteSource } from './pasteSourceValidations/getPasteSource'; diff --git a/packages/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts b/packages/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts index c0402df790a..9c51fb47e96 100644 --- a/packages/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts +++ b/packages/roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents.ts @@ -1,4 +1,4 @@ -import addParser from '../utils/addParser'; +import { addParser } from '../utils/addParser'; import { createListLevel, parseFormat } from 'roosterjs-content-model-dom'; import { setProcessor } from '../utils/setProcessor'; import { diff --git a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts index 32a797e9450..72709b51b40 100644 --- a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts +++ b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts @@ -24,7 +24,7 @@ const FORMATING_REGEX = /[\n\t'{}"]+/g; * 5. Save data in record and only use the required information. * */ -export default function getStyleMetadata( +export function getStyleMetadata( ev: BeforePasteEvent, trustedHTMLHandler: (val: string) => string ) { diff --git a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts index daf6497990e..972b1be8ec1 100644 --- a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts +++ b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts @@ -1,5 +1,5 @@ -import addParser from '../utils/addParser'; -import getStyleMetadata from './getStyleMetadata'; +import { addParser } from '../utils/addParser'; +import { getStyleMetadata } from './getStyleMetadata'; import { getStyles } from '../utils/getStyles'; import { processWordComments } from './processWordComments'; import { processWordList } from './processWordLists'; diff --git a/packages/roosterjs-content-model-plugins/lib/paste/utils/addParser.ts b/packages/roosterjs-content-model-plugins/lib/paste/utils/addParser.ts index 034b6461fb1..0b7b3aa932b 100644 --- a/packages/roosterjs-content-model-plugins/lib/paste/utils/addParser.ts +++ b/packages/roosterjs-content-model-plugins/lib/paste/utils/addParser.ts @@ -8,7 +8,7 @@ import type { /** * @internal */ -export default function addParser( +export function addParser( domToModelOption: DomToModelOption, entry: TKey, additionalFormatParsers: FormatParser diff --git a/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/CreateElementData.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/CreateElementData.ts index 80fb8b9d425..01dcb6b8e0a 100644 --- a/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/CreateElementData.ts +++ b/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/CreateElementData.ts @@ -2,7 +2,7 @@ * @internal * An interface represents the data for creating element used by createElement() */ -export default interface CreateElementData { +export interface CreateElementData { /** * Tag name of this element. * It can be just a tag, or in format "namespace:tag" diff --git a/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/createElement.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/createElement.ts index 1e331473ad6..671cdea84b1 100644 --- a/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/createElement.ts +++ b/packages/roosterjs-content-model-plugins/lib/pluginUtils/CreateElement/createElement.ts @@ -1,5 +1,5 @@ import { getObjectKeys, isNodeOfType } from 'roosterjs-content-model-dom'; -import type CreateElementData from './CreateElementData'; +import type { CreateElementData } from './CreateElementData'; /** * @internal @@ -8,10 +8,7 @@ import type CreateElementData from './CreateElementData'; * @param document The document to create the element from * @returns The root DOM element just created */ -export default function createElement( - elementData: CreateElementData, - document: Document -): Element | null { +export function createElement(elementData: CreateElementData, document: Document): Element | null { if (!elementData || !elementData.tag) { return null; } diff --git a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Disposable.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Disposable.ts index 06a344e0fb3..c760542df80 100644 --- a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Disposable.ts +++ b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Disposable.ts @@ -2,7 +2,7 @@ * @internal * Represents a disposable object */ -export default interface Disposable { +export interface Disposable { /** * Dispose this object */ diff --git a/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHandler.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHandler.ts index ff1eed6756e..c87d26b9191 100644 --- a/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHandler.ts +++ b/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHandler.ts @@ -2,7 +2,7 @@ * @internal * Drag and drop handler interface, used for implementing a handler object and pass into DragAndDropHelper class */ -export default interface DragAndDropHandler { +export interface DragAndDropHandler { /** * A callback that will be called when user starts to drag (mouse down event from the trigger element) * @param context The context object that was passed into DragAndDropHelper from its constructor. We can use diff --git a/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHelper.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHelper.ts index e15f30000ed..c88b6572913 100644 --- a/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHelper.ts +++ b/packages/roosterjs-content-model-plugins/lib/pluginUtils/DragAndDrop/DragAndDropHelper.ts @@ -1,5 +1,5 @@ -import type Disposable from '../Disposable'; -import type DragAndDropHandler from './DragAndDropHandler'; +import type { Disposable } from '../Disposable'; +import type { DragAndDropHandler } from './DragAndDropHandler'; /** * @internal @@ -65,7 +65,7 @@ function getTouchEventPageXY(e: TouchEvent): [number, number] { * @internal * A helper class to help manage drag and drop to an HTML element */ -export default class DragAndDropHelper implements Disposable { +export class DragAndDropHelper implements Disposable { private initX: number = 0; private initY: number = 0; private initValue: TInitValue | undefined = undefined; diff --git a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts index 4845fcaef6b..3f087cee05e 100644 --- a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts +++ b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts @@ -1,4 +1,4 @@ -import normalizeRect from './normalizeRect'; +import { normalizeRect } from './normalizeRect'; import type { Rect } from 'roosterjs-content-model-types'; /** @@ -26,7 +26,7 @@ import type { Rect } from 'roosterjs-content-model-types'; * @param additionalRects additional rects to use * @returns If the Rect is valid return the rect, if not, return null. */ -export default function getIntersectedRect( +export function getIntersectedRect( elements: HTMLElement[], additionalRects: Rect[] = [] ): Rect | null { diff --git a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts index dca5dcf9af0..f4ff23e8e30 100644 --- a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts +++ b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts @@ -5,7 +5,7 @@ import type { Rect } from 'roosterjs-content-model-types'; * A ClientRect of all 0 is possible. i.e. chrome returns a ClientRect of 0 when the cursor is on an empty p * We validate that and only return a rect when the passed in ClientRect is valid */ -export default function normalizeRect(clientRect: DOMRect): Rect | null { +export function normalizeRect(clientRect: DOMRect): Rect | null { const { left, right, top, bottom } = clientRect || { left: 0, right: 0, top: 0, bottom: 0 }; return left === 0 && right === 0 && top === 0 && bottom === 0 diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts index f2b0c1b9649..2634b9b13d4 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts @@ -1,6 +1,6 @@ -import normalizeRect from '../pluginUtils/Rect/normalizeRect'; -import TableEditor from './editors/TableEditor'; import { isNodeOfType } from 'roosterjs-content-model-dom'; +import { normalizeRect } from '../pluginUtils/Rect/normalizeRect'; +import { TableEditor } from './editors/TableEditor'; import type { EditorPlugin, IEditor, PluginEvent, Rect } from 'roosterjs-content-model-types'; const TABLE_RESIZER_LENGTH = 12; diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts index 96cedfe6d22..752cb15f98a 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts @@ -1,11 +1,11 @@ -import createCellResizer from './features/CellResizer'; -import createTableInserter from './features/TableInserter'; -import createTableMover from './features/TableMover'; -import createTableResizer from './features/TableResizer'; -import normalizeRect from '../../pluginUtils/Rect/normalizeRect'; -import { disposeTableEditFeature } from './features/TableEditorFeature'; +import { createCellResizer } from './features/CellResizer'; +import { createTableInserter } from './features/TableInserter'; +import { createTableMover } from './features/TableMover'; +import { createTableResizer } from './features/TableResizer'; +import { disposeTableEditFeature } from './features/TableEditFeature'; import { isNodeOfType } from 'roosterjs-content-model-dom'; -import type TableEditFeature from './features/TableEditorFeature'; +import { normalizeRect } from '../../pluginUtils/Rect/normalizeRect'; +import type { TableEditFeature } from './features/TableEditFeature'; import type { IEditor, TableSelection } from 'roosterjs-content-model-types'; const INSERTER_HOVER_OFFSET = 6; @@ -42,7 +42,7 @@ const enum TOP_OR_SIDE { * * When set a different current table or change current TD, we need to update these areas */ -export default class TableEditor { +export class TableEditor { // 1, 2 - Insert a column or a row private horizontalInserter: TableEditFeature | null = null; private verticalInserter: TableEditFeature | null = null; diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts index 799879858be..16d298cd96b 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts @@ -1,22 +1,22 @@ -import createElement from '../../../pluginUtils/CreateElement/createElement'; -import DragAndDropHelper from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; -import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; +import { createElement } from '../../../pluginUtils/CreateElement/createElement'; +import { DragAndDropHelper } from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; import { isElementOfType } from 'roosterjs-content-model-dom'; +import { normalizeRect } from '../../../pluginUtils/Rect/normalizeRect'; import { getFirstSelectedTable, MIN_ALLOWED_TABLE_CELL_WIDTH, normalizeTable, } from 'roosterjs-content-model-core'; -import type DragAndDropHandler from '../../../pluginUtils/DragAndDrop/DragAndDropHandler'; +import type { DragAndDropHandler } from '../../../pluginUtils/DragAndDrop/DragAndDropHandler'; import type { ContentModelTable, IEditor } from 'roosterjs-content-model-types'; -import type TableEditFeature from './TableEditorFeature'; +import type { TableEditFeature } from './TableEditFeature'; const CELL_RESIZER_WIDTH = 4; /** * @internal */ -export default function createCellResizer( +export function createCellResizer( editor: IEditor, td: HTMLTableCellElement, table: HTMLTableElement, diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditFeature.ts similarity index 79% rename from packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts rename to packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditFeature.ts index f244e2bd39d..769b8dd54f3 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditFeature.ts @@ -1,9 +1,9 @@ -import type Disposable from '../../../pluginUtils/Disposable'; +import type { Disposable } from '../../../pluginUtils/Disposable'; /** * @internal */ -export default interface TableEditFeature { +export interface TableEditFeature { node: Node; div: HTMLDivElement | null; featureHandler: Disposable | null; diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts index 9a6d45eec5d..b1d4a9870db 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts @@ -1,15 +1,15 @@ -import createElement from '../../../pluginUtils/CreateElement/createElement'; -import getIntersectedRect from '../../../pluginUtils/Rect/getIntersectedRect'; -import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; +import { createElement } from '../../../pluginUtils/CreateElement/createElement'; +import { getIntersectedRect } from '../../../pluginUtils/Rect/getIntersectedRect'; import { isElementOfType } from 'roosterjs-content-model-dom'; +import { normalizeRect } from '../../../pluginUtils/Rect/normalizeRect'; import { formatTableWithContentModel, insertTableColumn, insertTableRow, } from 'roosterjs-content-model-api'; -import type CreateElementData from '../../../pluginUtils/CreateElement/CreateElementData'; -import type Disposable from '../../../pluginUtils/Disposable'; -import type TableEditFeature from './TableEditorFeature'; +import type { CreateElementData } from '../../../pluginUtils/CreateElement/CreateElementData'; +import type { Disposable } from '../../../pluginUtils/Disposable'; +import type { TableEditFeature } from './TableEditFeature'; import type { IEditor } from 'roosterjs-content-model-types'; const INSERTER_COLOR = '#4A4A4A'; @@ -20,7 +20,7 @@ const INSERTER_BORDER_SIZE = 1; /** * @internal */ -export default function createTableInserter( +export function createTableInserter( editor: IEditor, td: HTMLTableCellElement, table: HTMLTableElement, diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts index d4dd46a94c0..8b366a771c1 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts @@ -1,10 +1,10 @@ -import createElement from '../../../pluginUtils/CreateElement/createElement'; -import DragAndDropHelper from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; -import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; +import { createElement } from '../../../pluginUtils/CreateElement/createElement'; +import { DragAndDropHelper } from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; import { isNodeOfType } from 'roosterjs-content-model-dom'; -import type DragAndDropHandler from '../../../pluginUtils/DragAndDrop/DragAndDropHandler'; +import { normalizeRect } from '../../../pluginUtils/Rect/normalizeRect'; +import type { DragAndDropHandler } from '../../../pluginUtils/DragAndDrop/DragAndDropHandler'; import type { IEditor, Rect } from 'roosterjs-content-model-types'; -import type TableEditorFeature from './TableEditorFeature'; +import type { TableEditFeature } from './TableEditFeature'; const TABLE_MOVER_LENGTH = 12; const TABLE_MOVER_ID = '_Table_Mover'; @@ -14,7 +14,7 @@ const TABLE_MOVER_ID = '_Table_Mover'; * Contains the function to select whole table * Moving behavior not implemented yet */ -export default function createTableMover( +export function createTableMover( table: HTMLTableElement, editor: IEditor, isRTL: boolean, @@ -22,7 +22,7 @@ export default function createTableMover( getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void, contentDiv?: EventTarget | null, anchorContainer?: HTMLElement -): TableEditorFeature | null { +): TableEditFeature | null { const rect = normalizeRect(table.getBoundingClientRect()); if (!isTableTopVisible(editor, rect, contentDiv as Node)) { diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts index f455c9f83a3..998f9557865 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts @@ -1,10 +1,10 @@ -import createElement from '../../../pluginUtils/CreateElement/createElement'; -import DragAndDropHelper from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; -import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; +import { createElement } from '../../../pluginUtils/CreateElement/createElement'; +import { DragAndDropHelper } from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; import { getFirstSelectedTable, normalizeTable } from 'roosterjs-content-model-core'; import { isNodeOfType } from 'roosterjs-content-model-dom'; +import { normalizeRect } from '../../../pluginUtils/Rect/normalizeRect'; import type { ContentModelTable, IEditor, Rect } from 'roosterjs-content-model-types'; -import type TableEditFeature from './TableEditorFeature'; +import type { TableEditFeature } from './TableEditFeature'; const TABLE_RESIZER_LENGTH = 12; const TABLE_RESIZER_ID = '_Table_Resizer'; @@ -12,7 +12,7 @@ const TABLE_RESIZER_ID = '_Table_Resizer'; /** * @internal */ -export default function createTableResizer( +export function createTableResizer( table: HTMLTableElement, editor: IEditor, isRTL: boolean, diff --git a/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts b/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts index f016f1a0e15..3ed5d3196f7 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts @@ -18,7 +18,7 @@ describe('Content Model Paste Plugin Test', () => { editor = ({ getTrustedHTMLHandler: () => trustedHTMLHandler, } as any) as IEditor; - spyOn(addParser, 'default').and.callThrough(); + spyOn(addParser, 'addParser').and.callThrough(); spyOn(setProcessor, 'setProcessor').and.callThrough(); }); @@ -53,7 +53,7 @@ describe('Content Model Paste Plugin Test', () => { plugin.initialize(editor); plugin.onPluginEvent(event); - expect(addParser.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 5); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 5); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(1); }); @@ -70,7 +70,7 @@ describe('Content Model Paste Plugin Test', () => { trustedHTMLHandler, undefined /*allowExcelNoBorderTable*/ ); - expect(addParser.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 3); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 3); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(1); }); @@ -87,7 +87,7 @@ describe('Content Model Paste Plugin Test', () => { trustedHTMLHandler, undefined /*allowExcelNoBorderTable*/ ); - expect(addParser.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(0); }); @@ -103,7 +103,7 @@ describe('Content Model Paste Plugin Test', () => { trustedHTMLHandler, undefined /*allowExcelNoBorderTable*/ ); - expect(addParser.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(1); }); @@ -119,7 +119,7 @@ describe('Content Model Paste Plugin Test', () => { trustedHTMLHandler, undefined /*allowExcelNoBorderTable*/ ); - expect(addParser.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(1); }); @@ -134,7 +134,7 @@ describe('Content Model Paste Plugin Test', () => { event, trustedHTMLHandler ); - expect(addParser.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(0); }); @@ -146,7 +146,7 @@ describe('Content Model Paste Plugin Test', () => { plugin.onPluginEvent(event); expect(WacFile.processPastedContentWacComponents).toHaveBeenCalledWith(event); - expect(addParser.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 6); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 6); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(2); }); @@ -156,7 +156,7 @@ describe('Content Model Paste Plugin Test', () => { plugin.initialize(editor); plugin.onPluginEvent(event); - expect(addParser.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(0); }); @@ -166,7 +166,7 @@ describe('Content Model Paste Plugin Test', () => { plugin.initialize(editor); plugin.onPluginEvent(event); - expect(addParser.default).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(0); expect(event.domToModelOption.additionalAllowedTags).toEqual([ PastePropertyNames.GOOGLE_SHEET_NODE_NAME as Lowercase, diff --git a/packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts b/packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts index 4067fbc000d..21e7ac1e9ae 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/getStyleMetadataTest.ts @@ -1,5 +1,5 @@ -import getStyleMetadata from '../../lib/paste/WordDesktop/getStyleMetadata'; import { BeforePasteEvent } from 'roosterjs-content-model-types'; +import { getStyleMetadata } from '../../lib/paste/WordDesktop/getStyleMetadata'; describe('getStyleMetadata', () => { it('Extract metadata from style element', () => { diff --git a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts index 2fa1514ea94..7b186403d55 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts @@ -290,7 +290,7 @@ describe('processPastedContentFromWordDesktopTest', () => { const dta = { 'mso-level-number-format': 'bullet', }; - spyOn(getStyleMetadata, 'default').and.returnValue( + spyOn(getStyleMetadata, 'getStyleMetadata').and.returnValue( new Map().set('l0:level1', dta) ); @@ -382,7 +382,7 @@ describe('processPastedContentFromWordDesktopTest', () => { const dta = { 'mso-level-number-format': 'bullet', }; - spyOn(getStyleMetadata, 'default').and.returnValue( + spyOn(getStyleMetadata, 'getStyleMetadata').and.returnValue( new Map().set('l0:level1', dta).set('l0:level2', dta) ); @@ -483,7 +483,7 @@ describe('processPastedContentFromWordDesktopTest', () => { const dta = { 'mso-level-number-format': 'bullet', }; - spyOn(getStyleMetadata, 'default').and.returnValue( + spyOn(getStyleMetadata, 'getStyleMetadata').and.returnValue( new Map().set('l0:level1', dta).set('l0:level3', dta) ); @@ -592,7 +592,7 @@ describe('processPastedContentFromWordDesktopTest', () => { const dta = { 'mso-level-number-format': 'bullet', }; - spyOn(getStyleMetadata, 'default').and.returnValue( + spyOn(getStyleMetadata, 'getStyleMetadata').and.returnValue( new Map().set('l0:level1', dta).set('l1:level3', dta) ); runTest(html, { @@ -704,7 +704,7 @@ describe('processPastedContentFromWordDesktopTest', () => { const dta = { 'mso-level-number-format': 'bullet', }; - spyOn(getStyleMetadata, 'default').and.returnValue( + spyOn(getStyleMetadata, 'getStyleMetadata').and.returnValue( new Map() .set('l1:level2', dta) .set('l1:level3', dta) @@ -1230,7 +1230,7 @@ describe('processPastedContentFromWordDesktopTest', () => { const source = '

Test

Test

·      \nTEST

'; - spyOn(getStyleMetadata, 'default').and.returnValue( + spyOn(getStyleMetadata, 'getStyleMetadata').and.returnValue( new Map().set('l0:level1', { 'mso-level-number-format': 'bullet', 'mso-level-start-at': '1', diff --git a/packages/roosterjs-content-model-plugins/test/pluginUtils/DragAndDropHelperTest.ts b/packages/roosterjs-content-model-plugins/test/pluginUtils/DragAndDropHelperTest.ts index b2222c8374e..77afe20a3d5 100644 --- a/packages/roosterjs-content-model-plugins/test/pluginUtils/DragAndDropHelperTest.ts +++ b/packages/roosterjs-content-model-plugins/test/pluginUtils/DragAndDropHelperTest.ts @@ -1,4 +1,4 @@ -import DragAndDropHelper from '../../lib/pluginUtils/DragAndDrop/DragAndDropHelper'; +import { DragAndDropHelper } from '../../lib/pluginUtils/DragAndDrop/DragAndDropHelper'; interface DragAndDropContext { node: HTMLElement; diff --git a/packages/roosterjs-content-model-plugins/test/pluginUtils/createElementTest.ts b/packages/roosterjs-content-model-plugins/test/pluginUtils/createElementTest.ts index 488770f69d8..17d273615dd 100644 --- a/packages/roosterjs-content-model-plugins/test/pluginUtils/createElementTest.ts +++ b/packages/roosterjs-content-model-plugins/test/pluginUtils/createElementTest.ts @@ -1,5 +1,5 @@ -import createElement from '../../lib/pluginUtils/CreateElement/createElement'; -import CreateElementData from '../../lib/pluginUtils/CreateElement/CreateElementData'; +import { createElement } from '../../lib/pluginUtils/CreateElement/createElement'; +import { CreateElementData } from '../../lib/pluginUtils/CreateElement/CreateElementData'; describe('createElement', () => { function runTest(input: CreateElementData, output: string) { diff --git a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts index 0c4a69a47d0..6b87a40168c 100644 --- a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts @@ -56,7 +56,7 @@ describe('ShortcutPlugin', () => { describe('Windows', () => { it('not a shortcut', () => { - const apiSpy = spyOn(toggleBold, 'default'); + const apiSpy = spyOn(toggleBold, 'toggleBold'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -76,7 +76,7 @@ describe('ShortcutPlugin', () => { }); it('bold', () => { - const apiSpy = spyOn(toggleBold, 'default'); + const apiSpy = spyOn(toggleBold, 'toggleBold'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -96,7 +96,7 @@ describe('ShortcutPlugin', () => { }); it('italic', () => { - const apiSpy = spyOn(toggleItalic, 'default'); + const apiSpy = spyOn(toggleItalic, 'toggleItalic'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { @@ -117,7 +117,7 @@ describe('ShortcutPlugin', () => { }); it('underline', () => { - const apiSpy = spyOn(toggleUnderline, 'default'); + const apiSpy = spyOn(toggleUnderline, 'toggleUnderline'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -137,7 +137,7 @@ describe('ShortcutPlugin', () => { }); it('clear format', () => { - const apiSpy = spyOn(clearFormat, 'default'); + const apiSpy = spyOn(clearFormat, 'clearFormat'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -237,7 +237,7 @@ describe('ShortcutPlugin', () => { }); it('bullet list', () => { - const apiSpy = spyOn(toggleBullet, 'default'); + const apiSpy = spyOn(toggleBullet, 'toggleBullet'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -257,7 +257,7 @@ describe('ShortcutPlugin', () => { }); it('numbering list', () => { - const apiSpy = spyOn(toggleNumbering, 'default'); + const apiSpy = spyOn(toggleNumbering, 'toggleNumbering'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -277,7 +277,7 @@ describe('ShortcutPlugin', () => { }); it('increase font', () => { - const apiSpy = spyOn(changeFontSize, 'default'); + const apiSpy = spyOn(changeFontSize, 'changeFontSize'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -297,7 +297,7 @@ describe('ShortcutPlugin', () => { }); it('decrease font', () => { - const apiSpy = spyOn(changeFontSize, 'default'); + const apiSpy = spyOn(changeFontSize, 'changeFontSize'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -323,7 +323,7 @@ describe('ShortcutPlugin', () => { }); it('not a shortcut', () => { - const apiSpy = spyOn(toggleBold, 'default'); + const apiSpy = spyOn(toggleBold, 'toggleBold'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -343,7 +343,7 @@ describe('ShortcutPlugin', () => { }); it('bold', () => { - const apiSpy = spyOn(toggleBold, 'default'); + const apiSpy = spyOn(toggleBold, 'toggleBold'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -363,7 +363,7 @@ describe('ShortcutPlugin', () => { }); it('italic', () => { - const apiSpy = spyOn(toggleItalic, 'default'); + const apiSpy = spyOn(toggleItalic, 'toggleItalic'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { @@ -384,7 +384,7 @@ describe('ShortcutPlugin', () => { }); it('underline', () => { - const apiSpy = spyOn(toggleUnderline, 'default'); + const apiSpy = spyOn(toggleUnderline, 'toggleUnderline'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -404,7 +404,7 @@ describe('ShortcutPlugin', () => { }); it('clear format', () => { - const apiSpy = spyOn(clearFormat, 'default'); + const apiSpy = spyOn(clearFormat, 'clearFormat'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -507,7 +507,7 @@ describe('ShortcutPlugin', () => { }); it('bullet list', () => { - const apiSpy = spyOn(toggleBullet, 'default'); + const apiSpy = spyOn(toggleBullet, 'toggleBullet'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -527,7 +527,7 @@ describe('ShortcutPlugin', () => { }); it('numbering list', () => { - const apiSpy = spyOn(toggleNumbering, 'default'); + const apiSpy = spyOn(toggleNumbering, 'toggleNumbering'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -547,7 +547,7 @@ describe('ShortcutPlugin', () => { }); it('increase font', () => { - const apiSpy = spyOn(changeFontSize, 'default'); + const apiSpy = spyOn(changeFontSize, 'changeFontSize'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', @@ -567,7 +567,7 @@ describe('ShortcutPlugin', () => { }); it('decrease font', () => { - const apiSpy = spyOn(changeFontSize, 'default'); + const apiSpy = spyOn(changeFontSize, 'changeFontSize'); const plugin = new ShortcutPlugin(); const event: PluginEvent = { eventType: 'keyDown', diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts index 664ea407aed..f9981981a22 100644 --- a/packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts @@ -1,5 +1,5 @@ import * as TestHelper from '../TestHelper'; -import createElement from '../../lib/pluginUtils/CreateElement/createElement'; +import { createElement } from '../../lib/pluginUtils/CreateElement/createElement'; import { getModelTable } from './tableData'; import { IEditor } from 'roosterjs-content-model-types'; import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts index 2a9a49448a6..3c16dc0284d 100644 --- a/packages/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/tableMoverTest.ts @@ -1,7 +1,7 @@ -import createTableMover from '../../lib/tableEdit/editors/features/TableMover'; -import TableEditor from '../../lib/tableEdit/editors/TableEditor'; +import { createTableMover } from '../../lib/tableEdit/editors/features/TableMover'; import { Editor } from 'roosterjs-content-model-core'; import { EditorOptions, IEditor } from 'roosterjs-content-model-types'; +import { TableEditor } from '../../lib/tableEdit/editors/TableEditor'; import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; describe('Table Mover Tests', () => { diff --git a/tools/buildTools/dts.js b/tools/buildTools/dts.js index 35d96ba0096..56a3ba49be2 100644 --- a/tools/buildTools/dts.js +++ b/tools/buildTools/dts.js @@ -335,21 +335,22 @@ function generateDts(library, isAmd, queue, version) { if (exports) { for (var name in exports) { var alias = exports[name]; - var texts = null; + var texts = []; + if (elements[name]) { - texts = publicElement(elements[name]); - } else { - for (var n in elements) { - if (n.indexOf(alias + '<') == 0) { - texts = publicElement(elements[n]); - break; - } - } - if (!texts) { - err(`Name not found: ${name}; alias: ${alias}; file: ${filename}`); + texts = texts.concat(publicElement(elements[name])); + } + + for (var n in elements) { + if (n.indexOf(alias + '<') == 0) { + texts = texts.concat(publicElement(elements[n])); } } + if (texts.length == 0) { + err(`Name not found: ${name}; alias: ${alias}; file: ${filename}`); + } + for (var text of texts) { text = text.replace(namePlaceholder, alias); From 7ad2599907f35bf7d835ca67b1301021f917ff36 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Tue, 5 Mar 2024 09:48:54 -0800 Subject: [PATCH 52/80] Temporarily disable unstable test suite (#2475) --- .../tableEdit/{teableResizerTest.ts => tableResizerTest.ts} | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) rename packages/roosterjs-content-model-plugins/test/tableEdit/{teableResizerTest.ts => tableResizerTest.ts} (97%) diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts similarity index 97% rename from packages/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts rename to packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts index d4bba0575ab..beebeb06f42 100644 --- a/packages/roosterjs-content-model-plugins/test/tableEdit/teableResizerTest.ts +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts @@ -20,7 +20,9 @@ import { const TABLE_RESIZER_ID = '_Table_Resizer'; -describe('Table Resizer tests', () => { +// TODO: Reenable this test +// https://github.com/microsoft/roosterjs/issues/2474 +xdescribe('Table Resizer tests', () => { let editor: IEditor; let plugin: TableEditPlugin; const TEST_ID = 'resizerTest'; @@ -37,7 +39,7 @@ describe('Table Resizer tests', () => { afterTableTest(editor, plugin, TEST_ID); }); - /************************** Resizier removing tests **************************/ + /************************** Resizer removing tests **************************/ function removeResizerTest(pluginEvent: PluginEvent) { let resizer: HTMLElement | null = null; From 7cbe1505218c8d5aefda19c5e928d85c75fdf0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 5 Mar 2024 18:48:55 -0300 Subject: [PATCH 53/80] add link features --- .../ContentModelEditorOptionsPlugin.ts | 11 +- .../roosterjs-content-model-api/lib/index.ts | 1 + .../lib/modelApi/link/matchLink.ts | 1 - .../lib/autoFormat/AutoFormatPlugin.ts | 35 ++- .../lib/autoFormat/link/createLink.ts | 36 +++ .../lib/autoFormat/link/getLinkSegment.ts | 29 +++ .../lib/autoFormat/link/unlink.ts | 25 ++ .../{utils => list}/convertAlphaToDecimals.ts | 0 .../autoFormat/{utils => list}/getIndex.ts | 0 .../{utils => list}/getListTypeStyle.ts | 0 .../{utils => list}/getNumberingListStyle.ts | 0 .../{ => list}/keyboardListTrigger.ts | 2 +- .../test/autoFormat/AutoFormatPluginTest.ts | 128 +++++++++- .../test/autoFormat/link/createLinkTest.ts | 166 +++++++++++++ .../autoFormat/link/getLinkSegmentTest.ts | 174 ++++++++++++++ .../test/autoFormat/link/unlinkTest.ts | 225 ++++++++++++++++++ .../convertAlphaToDecimalsTest.ts | 2 +- .../{utils => list}/getIndexTest.ts | 2 +- .../{utils => list}/getListTypeStyleTest.ts | 2 +- .../getNumberingListStyleTest.ts | 2 +- .../{ => list}/keyboardListTriggerTest.ts | 2 +- 21 files changed, 825 insertions(+), 18 deletions(-) create mode 100644 packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts create mode 100644 packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts create mode 100644 packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts rename packages/roosterjs-content-model-plugins/lib/autoFormat/{utils => list}/convertAlphaToDecimals.ts (100%) rename packages/roosterjs-content-model-plugins/lib/autoFormat/{utils => list}/getIndex.ts (100%) rename packages/roosterjs-content-model-plugins/lib/autoFormat/{utils => list}/getListTypeStyle.ts (100%) rename packages/roosterjs-content-model-plugins/lib/autoFormat/{utils => list}/getNumberingListStyle.ts (100%) rename packages/roosterjs-content-model-plugins/lib/autoFormat/{ => list}/keyboardListTrigger.ts (96%) create mode 100644 packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts create mode 100644 packages/roosterjs-content-model-plugins/test/autoFormat/link/getLinkSegmentTest.ts create mode 100644 packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts rename packages/roosterjs-content-model-plugins/test/autoFormat/{utils => list}/convertAlphaToDecimalsTest.ts (94%) rename packages/roosterjs-content-model-plugins/test/autoFormat/{utils => list}/getIndexTest.ts (90%) rename packages/roosterjs-content-model-plugins/test/autoFormat/{utils => list}/getListTypeStyleTest.ts (99%) rename packages/roosterjs-content-model-plugins/test/autoFormat/{utils => list}/getNumberingListStyleTest.ts (96%) rename packages/roosterjs-content-model-plugins/test/autoFormat/{ => list}/keyboardListTriggerTest.ts (99%) diff --git a/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts b/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts index 7b4befdfa18..e4e85038390 100644 --- a/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts +++ b/demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts @@ -20,6 +20,11 @@ const listFeatures = { indentWhenAltShiftRight: false, }; +const autoLink = { + autoLink: false, + unlinkWhenBackspaceAfterLink: false, +}; + const initialState: BuildInPluginState = { pluginList: { contentEdit: true, @@ -37,7 +42,11 @@ const initialState: BuildInPluginState = { autoFormat: true, announce: true, }, - contentEditFeatures: { ...getDefaultContentEditFeatureSettings(), ...listFeatures }, + contentEditFeatures: { + ...getDefaultContentEditFeatureSettings(), + ...listFeatures, + ...autoLink, + }, defaultFormat: {}, linkTitle: 'Ctrl+Click to follow the link:' + UrlPlaceholder, watermarkText: 'Type content here ...', diff --git a/packages/roosterjs-content-model-api/lib/index.ts b/packages/roosterjs-content-model-api/lib/index.ts index f27263ca785..9bc1574c205 100644 --- a/packages/roosterjs-content-model-api/lib/index.ts +++ b/packages/roosterjs-content-model-api/lib/index.ts @@ -47,3 +47,4 @@ export { formatTableWithContentModel } from './publicApi/utils/formatTableWithCo export { setListType } from './modelApi/list/setListType'; export { findListItemsInSameThread } from './modelApi/list/findListItemsInSameThread'; export { setModelIndentation } from './modelApi/block/setModelIndentation'; +export { matchLink } from './modelApi/link/matchLink'; diff --git a/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts b/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts index b2238fe08e9..0d56c66ac9b 100644 --- a/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts +++ b/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts @@ -85,7 +85,6 @@ const linkMatchRules: Record = { }; /** - * @internal * Try to match a given string with link match rules, return matched link * @param url Input url to match * @param option Link match option, exact or partial. If it is exact match, we need diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts index 5308866f429..09e2a119787 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts @@ -1,5 +1,8 @@ -import { keyboardListTrigger } from './keyboardListTrigger'; +import { createLink } from './link/createLink'; +import { keyboardListTrigger } from './list/keyboardListTrigger'; +import { unlink } from './link/unlink'; import type { + ContentChangedEvent, EditorPlugin, IEditor, KeyDownEvent, @@ -19,6 +22,16 @@ export type AutoFormatOptions = { * When true, after type 1, A, a, i, I followed by ., ), - or between () and space key a type of numbering list will be triggered. @default true */ autoNumbering: boolean; + + /** + * When press backspace before a link, remove the hyperlink + */ + autoUnlink: boolean; + + /** + * When paste content, create hyperlink for the pasted link + */ + autoLink: boolean; }; /** @@ -27,6 +40,8 @@ export type AutoFormatOptions = { const DefaultOptions: Required = { autoBullet: true, autoNumbering: true, + autoUnlink: true, + autoLink: true, }; /** @@ -81,6 +96,9 @@ export class AutoFormatPlugin implements EditorPlugin { case 'keyDown': this.handleKeyDownEvent(this.editor, event); break; + case 'contentChanged': + this.handleContentChangedEvent(this.editor, event); + break; } } } @@ -88,14 +106,27 @@ export class AutoFormatPlugin implements EditorPlugin { private handleKeyDownEvent(editor: IEditor, event: KeyDownEvent) { const rawEvent = event.rawEvent; if (!rawEvent.defaultPrevented && !event.handledByEditFeature) { + const { autoBullet, autoNumbering, autoUnlink } = this.options; switch (rawEvent.key) { case ' ': - const { autoBullet, autoNumbering } = this.options; if (autoBullet || autoNumbering) { keyboardListTrigger(editor, rawEvent, autoBullet, autoNumbering); } + break; + case 'Backspace': + if (autoUnlink) { + unlink(editor, rawEvent); + } + break; } } } + + private handleContentChangedEvent(editor: IEditor, event: ContentChangedEvent) { + const { autoLink } = this.options; + if (event.source == 'Paste' && autoLink) { + createLink(editor); + } + } } diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts new file mode 100644 index 00000000000..0a9173ea631 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts @@ -0,0 +1,36 @@ +import getLinkSegment from './getLinkSegment'; +import { addLink } from 'roosterjs-content-model-dom/lib'; +import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core/lib'; +import { IEditor } from 'roosterjs-content-model-types'; + +/** + * @internal + */ +export function createLink(editor: IEditor) { + editor.formatContentModel(model => { + const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( + model, + false /* includingFormatHolder */ + ); + const link = getLinkSegment(model); + if ( + link && + !link.link && + selectedSegmentsAndParagraphs[0] && + selectedSegmentsAndParagraphs[0][1] + ) { + addLink(link, { + format: { + href: link.text, + underline: true, + }, + dataset: {}, + }); + const selectedParagraph = selectedSegmentsAndParagraphs[0][1]; + selectedParagraph.segments.splice(selectedParagraph.segments.length - 2, 1, link); + return true; + } + + return false; + }); +} diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts new file mode 100644 index 00000000000..3b94346e1f3 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts @@ -0,0 +1,29 @@ +import { ContentModelDocument } from 'roosterjs-content-model-types'; +import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; +import { matchLink } from 'roosterjs-editor-dom'; + +/** + * @internal + */ +export default function getLinkSegment(model: ContentModelDocument) { + const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( + model, + false /* includingFormatHolder */ + ); + if (selectedSegmentsAndParagraphs.length == 1 && selectedSegmentsAndParagraphs[0][1]) { + const selectedParagraph = selectedSegmentsAndParagraphs[0][1]; + const marker = selectedParagraph.segments[selectedParagraph.segments.length - 1]; + const link = selectedParagraph.segments[selectedParagraph.segments.length - 2]; + if ( + marker && + link && + marker.segmentType === 'SelectionMarker' && + marker.isSelected && + link.segmentType === 'Text' && + (matchLink(link.text) || link.link) + ) { + return link; + } + } + return undefined; +} diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts new file mode 100644 index 00000000000..ff018f3ed25 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts @@ -0,0 +1,25 @@ +import getLinkSegment from './getLinkSegment'; +import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core/lib'; +import { IEditor } from 'roosterjs-content-model-types'; + +/** + * @internal + */ +export function unlink(editor: IEditor, rawEvent: KeyboardEvent) { + editor.formatContentModel(model => { + const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( + model, + false /* includingFormatHolder */ + ); + const link = getLinkSegment(model); + if (link?.link && selectedSegmentsAndParagraphs[0] && selectedSegmentsAndParagraphs[0][1]) { + link.link = undefined; + const selectedParagraph = selectedSegmentsAndParagraphs[0][1]; + selectedParagraph.segments.splice(selectedParagraph.segments.length - 2, 1, link); + rawEvent.preventDefault(); + return true; + } + + return false; + }); +} diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/convertAlphaToDecimals.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/convertAlphaToDecimals.ts similarity index 100% rename from packages/roosterjs-content-model-plugins/lib/autoFormat/utils/convertAlphaToDecimals.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/list/convertAlphaToDecimals.ts diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getIndex.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/getIndex.ts similarity index 100% rename from packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getIndex.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/list/getIndex.ts diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/getListTypeStyle.ts similarity index 100% rename from packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getListTypeStyle.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/list/getListTypeStyle.ts diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getNumberingListStyle.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/getNumberingListStyle.ts similarity index 100% rename from packages/roosterjs-content-model-plugins/lib/autoFormat/utils/getNumberingListStyle.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/list/getNumberingListStyle.ts diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts similarity index 96% rename from packages/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts rename to packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts index 78fea6df5db..36ce2ec95cc 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/keyboardListTrigger.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts @@ -1,4 +1,4 @@ -import { getListTypeStyle } from './utils/getListTypeStyle'; +import { getListTypeStyle } from './getListTypeStyle'; import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import { normalizeContentModel } from 'roosterjs-content-model-dom'; import { setListStartNumber, setListStyle, setListType } from 'roosterjs-content-model-api'; diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts index 573b3411804..a2bb6501d79 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts @@ -1,6 +1,8 @@ -import * as keyboardTrigger from '../../lib/autoFormat/keyboardListTrigger'; -import { AutoFormatPlugin } from '../../lib/autoFormat/AutoFormatPlugin'; -import { IEditor, KeyDownEvent } from 'roosterjs-content-model-types'; +import * as createLink from '../../lib/autoFormat/link/createLink'; +import * as keyboardTrigger from '../../lib/autoFormat/list/keyboardListTrigger'; +import * as unlink from '../../lib/autoFormat/link/unlink'; +import { AutoFormatOptions, AutoFormatPlugin } from '../../lib/autoFormat/AutoFormatPlugin'; +import { ContentChangedEvent, IEditor, KeyDownEvent } from 'roosterjs-content-model-types'; describe('Content Model Auto Format Plugin Test', () => { let editor: IEditor; @@ -12,10 +14,11 @@ describe('Content Model Auto Format Plugin Test', () => { ({ type: -1, } as any), // Force return invalid range to go through content model code + formatContentModel: () => {}, } as any) as IEditor; }); - describe('onPluginEvent', () => { + describe('onPluginEvent - keyboardListTrigger', () => { let keyboardListTriggerSpy: jasmine.Spy; beforeEach(() => { @@ -25,7 +28,12 @@ describe('Content Model Auto Format Plugin Test', () => { function runTest( event: KeyDownEvent, shouldCallTrigger: boolean, - options?: { autoBullet: boolean; autoNumbering: boolean } + options?: { + autoBullet: boolean; + autoNumbering: boolean; + autoUnlink: boolean; + autoLink: boolean; + } ) { const plugin = new AutoFormatPlugin(options); plugin.initialize(editor); @@ -70,7 +78,7 @@ describe('Content Model Auto Format Plugin Test', () => { handledByEditFeature: false, }; - runTest(event, false, { autoBullet: false, autoNumbering: false }); + runTest(event, false, { autoBullet: false, autoNumbering: false } as AutoFormatOptions); }); it('should trigger keyboardListTrigger with auto bullet only', () => { @@ -79,7 +87,7 @@ describe('Content Model Auto Format Plugin Test', () => { rawEvent: { key: ' ', defaultPrevented: false, preventDefault: () => {} } as any, handledByEditFeature: false, }; - runTest(event, true, { autoBullet: true, autoNumbering: false }); + runTest(event, true, { autoBullet: true, autoNumbering: false } as AutoFormatOptions); }); it('should trigger keyboardListTrigger with auto numbering only', () => { @@ -88,7 +96,7 @@ describe('Content Model Auto Format Plugin Test', () => { rawEvent: { key: ' ', defaultPrevented: false, preventDefault: () => {} } as any, handledByEditFeature: false, }; - runTest(event, true, { autoBullet: false, autoNumbering: true }); + runTest(event, true, { autoBullet: false, autoNumbering: true } as AutoFormatOptions); }); it('should not trigger keyboardListTrigger, because handledByEditFeature', () => { @@ -111,4 +119,108 @@ describe('Content Model Auto Format Plugin Test', () => { runTest(event, false); }); }); + + describe('onPluginEvent - createLink', () => { + let createLinkSpy: jasmine.Spy; + + beforeEach(() => { + createLinkSpy = spyOn(createLink, 'createLink'); + }); + + function runTest( + event: ContentChangedEvent, + shouldCallTrigger: boolean, + options?: { + autoLink: boolean; + } + ) { + const plugin = new AutoFormatPlugin(options as AutoFormatOptions); + plugin.initialize(editor); + + plugin.onPluginEvent(event); + + if (shouldCallTrigger) { + expect(createLinkSpy).toHaveBeenCalledWith(editor); + } else { + expect(createLinkSpy).not.toHaveBeenCalled(); + } + } + + it('should call createLink', () => { + const event: ContentChangedEvent = { + eventType: 'contentChanged', + source: 'Paste', + }; + runTest(event, true); + }); + + it('should not call createLink - autolink disabled', () => { + const event: ContentChangedEvent = { + eventType: 'contentChanged', + source: 'Paste', + }; + runTest(event, false, { autoLink: false }); + }); + + it('should not call createLink - not paste', () => { + const event: ContentChangedEvent = { + eventType: 'contentChanged', + source: 'Format', + }; + runTest(event, false); + }); + }); + + describe('onPluginEvent - unlink', () => { + let unlinkSpy: jasmine.Spy; + + beforeEach(() => { + unlinkSpy = spyOn(unlink, 'unlink'); + }); + + function runTest( + event: KeyDownEvent, + shouldCallTrigger: boolean, + options?: { + autoUnlink: boolean; + } + ) { + const plugin = new AutoFormatPlugin(options as AutoFormatOptions); + plugin.initialize(editor); + + plugin.onPluginEvent(event); + + if (shouldCallTrigger) { + expect(unlinkSpy).toHaveBeenCalledWith(editor, event.rawEvent); + } else { + expect(unlinkSpy).not.toHaveBeenCalled(); + } + } + + it('should call unlink', () => { + const event: KeyDownEvent = { + eventType: 'keyDown', + rawEvent: { key: 'Backspace', preventDefault: () => {} } as any, + }; + runTest(event, true); + }); + + it('should not call unlink - disable options', () => { + const event: KeyDownEvent = { + eventType: 'keyDown', + rawEvent: { key: 'Backspace', preventDefault: () => {} } as any, + }; + runTest(event, false, { + autoUnlink: false, + }); + }); + + it('should not call unlink - not backspace', () => { + const event: KeyDownEvent = { + eventType: 'keyDown', + rawEvent: { key: ' ', preventDefault: () => {} } as any, + }; + runTest(event, false); + }); + }); }); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts new file mode 100644 index 00000000000..d87b4f9c562 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts @@ -0,0 +1,166 @@ +import { ContentModelDocument } from 'roosterjs-content-model-types'; +import { createLink } from '../../../lib/autoFormat/link/createLink'; + +describe('createLink', () => { + function runTest( + input: ContentModelDocument, + expectedModel: ContentModelDocument, + expectedResult: boolean + ) { + const formatWithContentModelSpy = jasmine + .createSpy('formatWithContentModel') + .and.callFake((callback, options) => { + const result = callback(input, { + newEntities: [], + deletedEntities: [], + newImages: [], + }); + expect(result).toBe(expectedResult); + }); + + createLink({ + focus: () => {}, + formatContentModel: formatWithContentModelSpy, + } as any); + + expect(formatWithContentModelSpy).toHaveBeenCalled(); + expect(input).toEqual(expectedModel); + } + + it('no selected segments', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, input, false); + }); + + it('no link segment', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, input, false); + }); + + it('link segment with WWW', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expected: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, expected, true); + }); + + it('link segment with hyperlink', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(input, input, false); + }); +}); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/getLinkSegmentTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/getLinkSegmentTest.ts new file mode 100644 index 00000000000..a21123f8c43 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/getLinkSegmentTest.ts @@ -0,0 +1,174 @@ +import getLinkSegment from '../../../lib/autoFormat/link/getLinkSegment'; +import { ContentModelDocument, ContentModelText } from 'roosterjs-content-model-types'; + +describe('getLinkSegment', () => { + function runTest(model: ContentModelDocument, link: ContentModelText | undefined) { + const result = getLinkSegment(model); + expect(result).toEqual(link); + } + + it('no selected segments', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(model, undefined); + }); + + it('no link segment', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(model, undefined); + }); + + it('link segment starting with WWW', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(model, { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + }); + }); + + it('link segment starting with hyperlink', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(model, { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }); + }); + + it('link segment starting with text and hyperlink', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'bing', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(model, { + segmentType: 'Text', + text: 'bing', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }); + }); +}); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts new file mode 100644 index 00000000000..caa0b734a93 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts @@ -0,0 +1,225 @@ +import { ContentModelDocument } from 'roosterjs-content-model-types'; +import { unlink } from '../../../lib/autoFormat/link/unlink'; + +describe('unlink', () => { + function runTest( + input: ContentModelDocument, + expectedModel: ContentModelDocument, + expectedResult: boolean + ) { + const formatWithContentModelSpy = jasmine + .createSpy('formatWithContentModel') + .and.callFake((callback, options) => { + const result = callback(input, { + newEntities: [], + deletedEntities: [], + newImages: [], + }); + expect(result).toBe(expectedResult); + }); + const rawEvent = { + preventDefault: () => {}, + } as KeyboardEvent; + + unlink( + { + focus: () => {}, + formatContentModel: formatWithContentModelSpy, + } as any, + rawEvent + ); + + expect(formatWithContentModelSpy).toHaveBeenCalled(); + expect(input).toEqual(expectedModel); + } + + it('no selected segments', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, input, false); + }); + + it('no link segment', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, input, false); + }); + + it('link segment with WWW but no link', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(input, input, false); + }); + + it('link segment with hyperlink', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expected: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + link: undefined, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(input, expected, true); + }); + + it('link segment with text and hyperlink', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'Bing', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expected: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'Bing', + format: {}, + link: undefined, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(input, expected, true); + }); +}); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/convertAlphaToDecimalsTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/list/convertAlphaToDecimalsTest.ts similarity index 94% rename from packages/roosterjs-content-model-plugins/test/autoFormat/utils/convertAlphaToDecimalsTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/list/convertAlphaToDecimalsTest.ts index 223e7349689..b77707d2c28 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/convertAlphaToDecimalsTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/list/convertAlphaToDecimalsTest.ts @@ -1,4 +1,4 @@ -import { convertAlphaToDecimals } from '../../../lib/autoFormat/utils/convertAlphaToDecimals'; +import { convertAlphaToDecimals } from '../../../lib/autoFormat/list/convertAlphaToDecimals'; describe('convertAlphaToDecimals', () => { function runTest(alpha: string, expectedResult: number) { diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getIndexTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/list/getIndexTest.ts similarity index 90% rename from packages/roosterjs-content-model-plugins/test/autoFormat/utils/getIndexTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/list/getIndexTest.ts index 9a5e006100d..acf6517b9db 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getIndexTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/list/getIndexTest.ts @@ -1,4 +1,4 @@ -import { getIndex } from '../../../lib/autoFormat/utils/getIndex'; +import { getIndex } from '../../../lib/autoFormat/list/getIndex'; describe('getIndex', () => { function runTest(listMarker: string, expectedResult: number) { diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/list/getListTypeStyleTest.ts similarity index 99% rename from packages/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/list/getListTypeStyleTest.ts index 63c1d036247..d542bb0a998 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getListTypeStyleTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/list/getListTypeStyleTest.ts @@ -1,6 +1,6 @@ import { BulletListType, NumberingListType } from 'roosterjs-content-model-core'; import { ContentModelDocument } from 'roosterjs-content-model-types'; -import { getListTypeStyle } from '../../../lib/autoFormat/utils/getListTypeStyle'; +import { getListTypeStyle } from '../../../lib/autoFormat/list/getListTypeStyle'; describe('getListTypeStyle', () => { function runTest( diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getNumberingListStyleTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/list/getNumberingListStyleTest.ts similarity index 96% rename from packages/roosterjs-content-model-plugins/test/autoFormat/utils/getNumberingListStyleTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/list/getNumberingListStyleTest.ts index 5877b37a805..31568025c35 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/utils/getNumberingListStyleTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/list/getNumberingListStyleTest.ts @@ -1,4 +1,4 @@ -import { getNumberingListStyle } from '../../../lib/autoFormat/utils/getNumberingListStyle'; +import { getNumberingListStyle } from '../../../lib/autoFormat/list/getNumberingListStyle'; import { NumberingListType } from 'roosterjs-content-model-core'; describe('getNumberingListStyle', () => { diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/list/keyboardListTriggerTest.ts similarity index 99% rename from packages/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts rename to packages/roosterjs-content-model-plugins/test/autoFormat/list/keyboardListTriggerTest.ts index a9fe10ec934..ee58bc46ef1 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/keyboardListTriggerTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/list/keyboardListTriggerTest.ts @@ -1,6 +1,6 @@ import * as normalizeContentModel from 'roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel'; import { ContentModelDocument } from 'roosterjs-content-model-types'; -import { keyboardListTrigger } from '../../lib/autoFormat/keyboardListTrigger'; +import { keyboardListTrigger } from '../../../lib/autoFormat/list/keyboardListTrigger'; describe('keyboardListTrigger', () => { let normalizeContentModelSpy: jasmine.Spy; From 4ae705affd17122b0454c163d8a9696d72449074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Tue, 5 Mar 2024 18:56:34 -0300 Subject: [PATCH 54/80] fix build --- .../lib/autoFormat/link/createLink.ts | 6 +++--- .../lib/autoFormat/link/getLinkSegment.ts | 2 +- .../lib/autoFormat/link/unlink.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts index 0a9173ea631..a51b94c73d3 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts @@ -1,7 +1,7 @@ import getLinkSegment from './getLinkSegment'; -import { addLink } from 'roosterjs-content-model-dom/lib'; -import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core/lib'; -import { IEditor } from 'roosterjs-content-model-types'; +import { addLink } from 'roosterjs-content-model-dom'; +import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * @internal diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts index 3b94346e1f3..d31d211df68 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts @@ -1,6 +1,6 @@ -import { ContentModelDocument } from 'roosterjs-content-model-types'; import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import { matchLink } from 'roosterjs-editor-dom'; +import type { ContentModelDocument } from 'roosterjs-content-model-types'; /** * @internal diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts index ff018f3ed25..3be931723ca 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts @@ -1,6 +1,6 @@ import getLinkSegment from './getLinkSegment'; -import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core/lib'; -import { IEditor } from 'roosterjs-content-model-types'; +import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; +import type { IEditor } from 'roosterjs-content-model-types'; /** * @internal From 5162e0bd5d5a8945aacaba4daedafab5b18a3cdf Mon Sep 17 00:00:00 2001 From: Gani <107857762+gm-al@users.noreply.github.com> Date: Wed, 6 Mar 2024 03:14:43 +0300 Subject: [PATCH 55/80] Support '(' + trigger character in Picker plugin (#2478) Co-authored-by: Jiuqing Song --- .../lib/plugins/Picker/PickerPlugin.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/roosterjs-editor-plugins/lib/plugins/Picker/PickerPlugin.ts b/packages/roosterjs-editor-plugins/lib/plugins/Picker/PickerPlugin.ts index 0650005599b..52132d6e792 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/Picker/PickerPlugin.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/Picker/PickerPlugin.ts @@ -319,7 +319,7 @@ export default class PickerPlugin Date: Tue, 5 Mar 2024 16:43:34 -0800 Subject: [PATCH 56/80] Port demo site step 4: Port ribbon and ribbon buttons (#2467) * Port demo site step 2 * Port demo site step 3 * fix build * remove ribbon * Port demo site step 4 --------- Co-authored-by: Bryan Valverde U --- .../controls/ContentModelEditorMainPane.tsx | 148 +++++------ .../controls/StandaloneEditorMainPane.tsx | 158 +++++------ .../contentModel/backgroundColorButton.ts | 27 -- .../contentModel/formatPainterButton.ts | 16 -- .../contentModel/imageBorderColorButton.ts | 24 -- .../contentModel/insertTableButton.ts | 24 -- .../contentModel/setHeadingLevelButton.ts | 35 --- .../contentModel/setTableCellShadeButton.ts | 34 --- .../contentModel/tableBorderColorButton.ts | 25 -- .../contentModel/textColorButton.ts | 27 -- .../contentModel/ContentModelPane.tsx | 13 +- .../contentModel/ContentModelPanePlugin.ts | 6 +- .../contentModel/buttons/exportButton.ts | 4 +- .../contentModel/buttons/refreshButton.ts | 4 +- .../demoButtons}/changeImageButton.ts | 25 +- .../demoButtons/darkModeButton.ts} | 6 +- .../demoButtons/exportContentButton.ts} | 8 +- .../demoButtons/formatPainterButton.ts | 15 ++ .../demoButtons}/formatTableButton.ts | 4 +- .../demoButtons/imageBorderColorButton.ts | 38 +++ .../demoButtons}/imageBorderRemoveButton.ts | 4 +- .../demoButtons}/imageBorderStyleButton.ts | 5 +- .../demoButtons}/imageBorderWidthButton.ts | 6 +- .../demoButtons}/imageBoxShadowButton.ts | 5 +- .../demoButtons}/listStartNumberButton.ts | 10 +- .../demoButtons}/pasteButton.ts | 8 +- .../demoButtons/popoutButton.ts} | 6 +- .../setBulletedListStyleButton.ts | 5 +- .../setNumberedListStyleButton.ts | 4 +- .../demoButtons/setTableCellShadeButton.ts | 39 +++ .../demoButtons}/setTableHeaderButton.ts | 4 +- .../demoButtons}/spaceBeforeAfterButtons.ts | 10 +- .../demoButtons}/spacingButton.ts | 4 +- .../demoButtons}/tableBorderApplyButton.ts | 6 +- .../demoButtons/tableBorderColorButton.ts | 39 +++ .../demoButtons}/tableBorderStyleButton.ts | 6 +- .../demoButtons}/tableBorderWidthButton.ts | 6 +- .../demoButtons}/tableEditButtons.ts | 14 +- .../demoButtons/zoomButton.ts} | 13 +- .../ribbon/buttons}/alignCenterButton.ts | 7 +- .../ribbon/buttons}/alignJustifyButton.ts | 5 +- .../ribbon/buttons}/alignLeftButton.ts | 7 +- .../ribbon/buttons}/alignRightButton.ts | 7 +- .../ribbon/buttons/backgroundColorButton.ts | 40 +++ .../ribbon/buttons}/blockQuoteButton.ts | 7 +- .../ribbon/buttons}/boldButton.ts | 7 +- .../ribbon/buttons}/bulletedListButton.ts | 7 +- .../ribbon/buttons}/clearFormatButton.ts | 6 +- .../ribbon/buttons}/codeButton.ts | 6 +- .../ribbon/buttons}/decreaseFontSizeButton.ts | 6 +- .../ribbon/buttons}/decreaseIndentButton.ts | 6 +- .../ribbon/buttons}/fontButton.ts | 6 +- .../ribbon/buttons}/fontSizeButton.ts | 6 +- .../ribbon/buttons}/increaseFontSizeButton.ts | 6 +- .../ribbon/buttons}/increaseIndentButton.ts | 6 +- .../ribbon/buttons}/insertImageButton.ts | 27 +- .../ribbon/buttons}/insertLinkButton.ts | 7 +- .../ribbon/buttons/insertTableButton.tsx | 172 ++++++++++++ .../ribbon/buttons}/italicButton.ts | 7 +- .../ribbon/buttons}/ltrButton.ts | 8 +- .../ribbon/buttons}/moreCommands.ts | 6 +- .../ribbon/buttons}/numberedListButton.ts | 8 +- .../ribbon/buttons}/redoButton.ts | 8 +- .../ribbon/buttons}/removeLinkButton.ts | 6 +- .../ribbon/buttons}/rtlButton.ts | 8 +- .../ribbon/buttons/setHeadingLevelButton.ts | 42 +++ .../ribbon/buttons}/strikethroughButton.ts | 8 +- .../ribbon/buttons}/subscriptButton.ts | 8 +- .../ribbon/buttons}/superscriptButton.ts | 8 +- .../ribbon/buttons/textColorButton.ts | 40 +++ .../ribbon/buttons}/underlineButton.ts | 8 +- .../ribbon/buttons}/undoButton.ts | 8 +- .../ribbon/component/Ribbon.tsx} | 18 +- .../controlsV2/roosterjsReact/ribbon/index.ts | 45 ++++ .../ribbon/plugin/createRibbonPlugin.ts} | 25 +- .../ribbon/type/RibbonButton.ts} | 12 +- .../ribbon/type/RibbonButtonDropDown.ts | 49 ++++ .../ribbon/type/RibbonButtonStringKeys.ts | 246 ++++++++++++++++++ .../ribbon/type}/RibbonPlugin.ts | 23 +- .../roosterjsReact/ribbon/type/RibbonProps.ts | 25 ++ 80 files changed, 1162 insertions(+), 630 deletions(-) delete mode 100644 demo/scripts/controls/ribbonButtons/contentModel/backgroundColorButton.ts delete mode 100644 demo/scripts/controls/ribbonButtons/contentModel/formatPainterButton.ts delete mode 100644 demo/scripts/controls/ribbonButtons/contentModel/imageBorderColorButton.ts delete mode 100644 demo/scripts/controls/ribbonButtons/contentModel/insertTableButton.ts delete mode 100644 demo/scripts/controls/ribbonButtons/contentModel/setHeadingLevelButton.ts delete mode 100644 demo/scripts/controls/ribbonButtons/contentModel/setTableCellShadeButton.ts delete mode 100644 demo/scripts/controls/ribbonButtons/contentModel/tableBorderColorButton.ts delete mode 100644 demo/scripts/controls/ribbonButtons/contentModel/textColorButton.ts rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/changeImageButton.ts (61%) rename demo/scripts/{controls/ribbonButtons/contentModel/darkMode.ts => controlsV2/demoButtons/darkModeButton.ts} (74%) rename demo/scripts/{controls/ribbonButtons/contentModel/export.ts => controlsV2/demoButtons/exportContentButton.ts} (61%) create mode 100644 demo/scripts/controlsV2/demoButtons/formatPainterButton.ts rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/formatTableButton.ts (98%) create mode 100644 demo/scripts/controlsV2/demoButtons/imageBorderColorButton.ts rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/imageBorderRemoveButton.ts (69%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/imageBorderStyleButton.ts (79%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/imageBorderWidthButton.ts (80%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/imageBoxShadowButton.ts (87%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/listStartNumberButton.ts (83%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/pasteButton.ts (89%) rename demo/scripts/{controls/ribbonButtons/contentModel/popout.ts => controlsV2/demoButtons/popoutButton.ts} (66%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/setBulletedListStyleButton.ts (84%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/setNumberedListStyleButton.ts (91%) create mode 100644 demo/scripts/controlsV2/demoButtons/setTableCellShadeButton.ts rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/setTableHeaderButton.ts (72%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/spaceBeforeAfterButtons.ts (80%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/spacingButton.ts (86%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/tableBorderApplyButton.ts (87%) create mode 100644 demo/scripts/controlsV2/demoButtons/tableBorderColorButton.ts rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/tableBorderStyleButton.ts (76%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/tableBorderWidthButton.ts (76%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/demoButtons}/tableEditButtons.ts (92%) rename demo/scripts/{controls/ribbonButtons/contentModel/zoom.ts => controlsV2/demoButtons/zoomButton.ts} (69%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/alignCenterButton.ts (55%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/alignJustifyButton.ts (63%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/alignLeftButton.ts (55%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/alignRightButton.ts (55%) create mode 100644 demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/backgroundColorButton.ts rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/blockQuoteButton.ts (60%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/boldButton.ts (58%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/bulletedListButton.ts (59%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/clearFormatButton.ts (56%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/codeButton.ts (61%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/decreaseFontSizeButton.ts (58%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/decreaseIndentButton.ts (60%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/fontButton.ts (97%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/fontSizeButton.ts (76%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/increaseFontSizeButton.ts (58%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/increaseIndentButton.ts (60%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/insertImageButton.ts (56%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/insertLinkButton.ts (85%) create mode 100644 demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertTableButton.tsx rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/italicButton.ts (59%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/ltrButton.ts (57%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/moreCommands.ts (52%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/numberedListButton.ts (59%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/redoButton.ts (57%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/removeLinkButton.ts (61%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/rtlButton.ts (57%) create mode 100644 demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/setHeadingLevelButton.ts rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/strikethroughButton.ts (60%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/subscriptButton.ts (59%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/superscriptButton.ts (59%) create mode 100644 demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/textColorButton.ts rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/underlineButton.ts (59%) rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/buttons}/undoButton.ts (57%) rename demo/scripts/{controls/ribbonButtons/contentModel/ContentModelRibbon.tsx => controlsV2/roosterjsReact/ribbon/component/Ribbon.tsx} (91%) create mode 100644 demo/scripts/controlsV2/roosterjsReact/ribbon/index.ts rename demo/scripts/{controls/ribbonButtons/contentModel/ContentModelRibbonPlugin.ts => controlsV2/roosterjsReact/ribbon/plugin/createRibbonPlugin.ts} (86%) rename demo/scripts/{controls/ribbonButtons/contentModel/ContentModelRibbonButton.ts => controlsV2/roosterjsReact/ribbon/type/RibbonButton.ts} (85%) create mode 100644 demo/scripts/controlsV2/roosterjsReact/ribbon/type/RibbonButtonDropDown.ts create mode 100644 demo/scripts/controlsV2/roosterjsReact/ribbon/type/RibbonButtonStringKeys.ts rename demo/scripts/{controls/ribbonButtons/contentModel => controlsV2/roosterjsReact/ribbon/type}/RibbonPlugin.ts (60%) create mode 100644 demo/scripts/controlsV2/roosterjsReact/ribbon/type/RibbonProps.ts diff --git a/demo/scripts/controls/ContentModelEditorMainPane.tsx b/demo/scripts/controls/ContentModelEditorMainPane.tsx index 4dd6f479db1..d4a473ef169 100644 --- a/demo/scripts/controls/ContentModelEditorMainPane.tsx +++ b/demo/scripts/controls/ContentModelEditorMainPane.tsx @@ -6,90 +6,86 @@ import ContentModelEventViewPlugin from './sidePane/eventViewer/ContentModelEven import ContentModelFormatPainterPlugin from './contentModel/plugins/ContentModelFormatPainterPlugin'; import ContentModelFormatStatePlugin from './sidePane/formatState/ContentModelFormatStatePlugin'; import ContentModelPanePlugin from './sidePane/contentModel/ContentModelPanePlugin'; -import ContentModelRibbonButton from './ribbonButtons/contentModel/ContentModelRibbonButton'; import ContentModelSnapshotPlugin from './sidePane/snapshot/ContentModelSnapshotPlugin'; import getToggleablePlugins from './getToggleablePlugins'; import MainPaneBase, { MainPaneBaseState } from './MainPaneBase'; -import RibbonPlugin from './ribbonButtons/contentModel/RibbonPlugin'; import SampleEntityPlugin from './sampleEntity/SampleEntityPlugin'; import SidePane from './sidePane/SidePane'; import TitleBar from './titleBar/TitleBar'; -import { alignCenterButton } from './ribbonButtons/contentModel/alignCenterButton'; -import { alignJustifyButton } from './ribbonButtons/contentModel/alignJustifyButton'; -import { alignLeftButton } from './ribbonButtons/contentModel/alignLeftButton'; -import { alignRightButton } from './ribbonButtons/contentModel/alignRightButton'; +import { alignCenterButton } from '../controlsV2/roosterjsReact/ribbon/buttons/alignCenterButton'; +import { alignJustifyButton } from '../controlsV2/roosterjsReact/ribbon/buttons/alignJustifyButton'; +import { alignLeftButton } from '../controlsV2/roosterjsReact/ribbon/buttons/alignLeftButton'; +import { alignRightButton } from '../controlsV2/roosterjsReact/ribbon/buttons/alignRightButton'; import { arrayPush } from 'roosterjs-editor-dom'; -import { backgroundColorButton } from './ribbonButtons/contentModel/backgroundColorButton'; -import { blockQuoteButton } from './ribbonButtons/contentModel/blockQuoteButton'; -import { boldButton } from './ribbonButtons/contentModel/boldButton'; -import { bulletedListButton } from './ribbonButtons/contentModel/bulletedListButton'; -import { changeImageButton } from './ribbonButtons/contentModel/changeImageButton'; -import { clearFormatButton } from './ribbonButtons/contentModel/clearFormatButton'; -import { codeButton } from './ribbonButtons/contentModel/codeButton'; -import { ContentModelRibbon } from './ribbonButtons/contentModel/ContentModelRibbon'; -import { ContentModelRibbonPlugin } from './ribbonButtons/contentModel/ContentModelRibbonPlugin'; +import { backgroundColorButton } from '../controlsV2/roosterjsReact/ribbon/buttons/backgroundColorButton'; +import { blockQuoteButton } from '../controlsV2/roosterjsReact/ribbon/buttons/blockQuoteButton'; +import { boldButton } from '../controlsV2/roosterjsReact/ribbon/buttons/boldButton'; +import { bulletedListButton } from '../controlsV2/roosterjsReact/ribbon/buttons/bulletedListButton'; +import { changeImageButton } from '../controlsV2/demoButtons/changeImageButton'; +import { clearFormatButton } from '../controlsV2/roosterjsReact/ribbon/buttons/clearFormatButton'; +import { codeButton } from '../controlsV2/roosterjsReact/ribbon/buttons/codeButton'; import { ContentModelSegmentFormat, IEditor, Snapshots } from 'roosterjs-content-model-types'; import { createEmojiPlugin, createPasteOptionPlugin } from 'roosterjs-react'; -import { darkMode } from './ribbonButtons/contentModel/darkMode'; -import { decreaseFontSizeButton } from './ribbonButtons/contentModel/decreaseFontSizeButton'; -import { decreaseIndentButton } from './ribbonButtons/contentModel/decreaseIndentButton'; +import { darkModeButton } from '../controlsV2/demoButtons/darkModeButton'; +import { decreaseFontSizeButton } from '../controlsV2/roosterjsReact/ribbon/buttons/decreaseFontSizeButton'; +import { decreaseIndentButton } from '../controlsV2/roosterjsReact/ribbon/buttons/decreaseIndentButton'; import { EditorAdapter, EditorAdapterOptions } from 'roosterjs-editor-adapter'; import { EditorPlugin } from 'roosterjs-editor-types'; -import { exportContent } from './ribbonButtons/contentModel/export'; -import { fontButton } from './ribbonButtons/contentModel/fontButton'; -import { fontSizeButton } from './ribbonButtons/contentModel/fontSizeButton'; -import { formatPainterButton } from './ribbonButtons/contentModel/formatPainterButton'; -import { formatTableButton } from './ribbonButtons/contentModel/formatTableButton'; +import { exportContentButton } from '../controlsV2/demoButtons/exportContentButton'; +import { fontButton } from '../controlsV2/roosterjsReact/ribbon/buttons/fontButton'; +import { fontSizeButton } from '../controlsV2/roosterjsReact/ribbon/buttons/fontSizeButton'; +import { formatPainterButton } from '../controlsV2/demoButtons/formatPainterButton'; +import { formatTableButton } from '../controlsV2/demoButtons/formatTableButton'; import { getDarkColor } from 'roosterjs-color-utils'; -import { imageBorderColorButton } from './ribbonButtons/contentModel/imageBorderColorButton'; -import { imageBorderRemoveButton } from './ribbonButtons/contentModel/imageBorderRemoveButton'; -import { imageBorderStyleButton } from './ribbonButtons/contentModel/imageBorderStyleButton'; -import { imageBorderWidthButton } from './ribbonButtons/contentModel/imageBorderWidthButton'; -import { imageBoxShadowButton } from './ribbonButtons/contentModel/imageBoxShadowButton'; -import { increaseFontSizeButton } from './ribbonButtons/contentModel/increaseFontSizeButton'; -import { increaseIndentButton } from './ribbonButtons/contentModel/increaseIndentButton'; -import { insertImageButton } from './ribbonButtons/contentModel/insertImageButton'; -import { insertLinkButton } from './ribbonButtons/contentModel/insertLinkButton'; -import { insertTableButton } from './ribbonButtons/contentModel/insertTableButton'; -import { italicButton } from './ribbonButtons/contentModel/italicButton'; -import { listStartNumberButton } from './ribbonButtons/contentModel/listStartNumberButton'; -import { ltrButton } from './ribbonButtons/contentModel/ltrButton'; -import { numberedListButton } from './ribbonButtons/contentModel/numberedListButton'; +import { imageBorderColorButton } from '../controlsV2/demoButtons/imageBorderColorButton'; +import { imageBorderRemoveButton } from '../controlsV2/demoButtons/imageBorderRemoveButton'; +import { imageBorderStyleButton } from '../controlsV2/demoButtons/imageBorderStyleButton'; +import { imageBorderWidthButton } from '../controlsV2/demoButtons/imageBorderWidthButton'; +import { imageBoxShadowButton } from '../controlsV2/demoButtons/imageBoxShadowButton'; +import { increaseFontSizeButton } from '../controlsV2/roosterjsReact/ribbon/buttons/increaseFontSizeButton'; +import { increaseIndentButton } from '../controlsV2/roosterjsReact/ribbon/buttons/increaseIndentButton'; +import { insertImageButton } from '../controlsV2/roosterjsReact/ribbon/buttons/insertImageButton'; +import { insertLinkButton } from '../controlsV2/roosterjsReact/ribbon/buttons/insertLinkButton'; +import { insertTableButton } from '../controlsV2/roosterjsReact/ribbon/buttons/insertTableButton'; +import { italicButton } from '../controlsV2/roosterjsReact/ribbon/buttons/italicButton'; +import { listStartNumberButton } from '../controlsV2/demoButtons/listStartNumberButton'; +import { ltrButton } from '../controlsV2/roosterjsReact/ribbon/buttons/ltrButton'; +import { numberedListButton } from '../controlsV2/roosterjsReact/ribbon/buttons/numberedListButton'; import { PartialTheme } from '@fluentui/react/lib/Theme'; -import { pasteButton } from './ribbonButtons/contentModel/pasteButton'; -import { popout } from './ribbonButtons/contentModel/popout'; -import { redoButton } from './ribbonButtons/contentModel/redoButton'; -import { removeLinkButton } from './ribbonButtons/contentModel/removeLinkButton'; +import { pasteButton } from '../controlsV2/demoButtons/pasteButton'; +import { popoutButton } from '../controlsV2/demoButtons/popoutButton'; +import { redoButton } from '../controlsV2/roosterjsReact/ribbon/buttons/redoButton'; +import { removeLinkButton } from '../controlsV2/roosterjsReact/ribbon/buttons/removeLinkButton'; import { Rooster } from '../controlsV2/roosterjsReact/rooster/component/Rooster'; -import { rtlButton } from './ribbonButtons/contentModel/rtlButton'; -import { setBulletedListStyleButton } from './ribbonButtons/contentModel/setBulletedListStyleButton'; -import { setHeadingLevelButton } from './ribbonButtons/contentModel/setHeadingLevelButton'; -import { setNumberedListStyleButton } from './ribbonButtons/contentModel/setNumberedListStyleButton'; -import { setTableCellShadeButton } from './ribbonButtons/contentModel/setTableCellShadeButton'; -import { setTableHeaderButton } from './ribbonButtons/contentModel/setTableHeaderButton'; -import { spacingButton } from './ribbonButtons/contentModel/spacingButton'; -import { strikethroughButton } from './ribbonButtons/contentModel/strikethroughButton'; -import { subscriptButton } from './ribbonButtons/contentModel/subscriptButton'; -import { superscriptButton } from './ribbonButtons/contentModel/superscriptButton'; -import { tableBorderApplyButton } from './ribbonButtons/contentModel/tableBorderApplyButton'; -import { tableBorderColorButton } from './ribbonButtons/contentModel/tableBorderColorButton'; -import { tableBorderStyleButton } from './ribbonButtons/contentModel/tableBorderStyleButton'; -import { tableBorderWidthButton } from './ribbonButtons/contentModel/tableBorderWidthButton'; -import { textColorButton } from './ribbonButtons/contentModel/textColorButton'; +import { rtlButton } from '../controlsV2/roosterjsReact/ribbon/buttons/rtlButton'; +import { setBulletedListStyleButton } from '../controlsV2/demoButtons/setBulletedListStyleButton'; +import { setHeadingLevelButton } from '../controlsV2/roosterjsReact/ribbon/buttons/setHeadingLevelButton'; +import { setNumberedListStyleButton } from '../controlsV2/demoButtons/setNumberedListStyleButton'; +import { setTableCellShadeButton } from '../controlsV2/demoButtons/setTableCellShadeButton'; +import { setTableHeaderButton } from '../controlsV2/demoButtons/setTableHeaderButton'; +import { spacingButton } from '../controlsV2/demoButtons/spacingButton'; +import { strikethroughButton } from '../controlsV2/roosterjsReact/ribbon/buttons/strikethroughButton'; +import { subscriptButton } from '../controlsV2/roosterjsReact/ribbon/buttons/subscriptButton'; +import { superscriptButton } from '../controlsV2/roosterjsReact/ribbon/buttons/superscriptButton'; +import { tableBorderApplyButton } from '../controlsV2/demoButtons/tableBorderApplyButton'; +import { tableBorderColorButton } from '../controlsV2/demoButtons/tableBorderColorButton'; +import { tableBorderStyleButton } from '../controlsV2/demoButtons/tableBorderStyleButton'; +import { tableBorderWidthButton } from '../controlsV2/demoButtons/tableBorderWidthButton'; +import { textColorButton } from '../controlsV2/roosterjsReact/ribbon/buttons/textColorButton'; import { trustedHTMLHandler } from '../utils/trustedHTMLHandler'; -import { underlineButton } from './ribbonButtons/contentModel/underlineButton'; -import { undoButton } from './ribbonButtons/contentModel/undoButton'; -import { zoom } from './ribbonButtons/contentModel/zoom'; +import { underlineButton } from '../controlsV2/roosterjsReact/ribbon/buttons/underlineButton'; +import { undoButton } from '../controlsV2/roosterjsReact/ribbon/buttons/undoButton'; +import { zoomButton } from '../controlsV2/demoButtons/zoomButton'; import { - AutoFormatPlugin, - EditPlugin, - PastePlugin, - TableEditPlugin, -} from 'roosterjs-content-model-plugins'; + createRibbonPlugin, + Ribbon, + RibbonButton, + RibbonPlugin, +} from '../controlsV2/roosterjsReact/ribbon'; import { spaceAfterButton, spaceBeforeButton, -} from './ribbonButtons/contentModel/spaceBeforeAfterButtons'; +} from '../controlsV2/demoButtons/spaceBeforeAfterButtons'; import { tableAlignCellButton, tableAlignTableButton, @@ -97,7 +93,13 @@ import { tableInsertButton, tableMergeButton, tableSplitButton, -} from './ribbonButtons/contentModel/tableEditButtons'; +} from '../controlsV2/demoButtons/tableEditButtons'; +import { + AutoFormatPlugin, + EditPlugin, + PastePlugin, + TableEditPlugin, +} from 'roosterjs-content-model-plugins'; const styles = require('./ContentModelEditorMainPane.scss'); @@ -177,7 +179,7 @@ class ContentModelEditorMainPane extends MainPaneBase private sampleEntityPlugin: SampleEntityPlugin; private tableEditPlugin: TableEditPlugin; private snapshots: Snapshots; - private buttons: ContentModelRibbonButton[] = [ + private buttons: RibbonButton[] = [ formatPainterButton, boldButton, italicButton, @@ -237,9 +239,9 @@ class ContentModelEditorMainPane extends MainPaneBase spaceBeforeButton, spaceAfterButton, pasteButton, - darkMode, - zoom, - exportContent, + darkModeButton, + zoomButton, + exportContentButton, ]; constructor(props: {}) { @@ -261,7 +263,7 @@ class ContentModelEditorMainPane extends MainPaneBase this.contentModelPanePlugin = new ContentModelPanePlugin(); this.contentModelEditPlugin = new EditPlugin(); this.contentModelAutoFormatPlugin = new AutoFormatPlugin(); - this.contentModelRibbonPlugin = new ContentModelRibbonPlugin(); + this.contentModelRibbonPlugin = createRibbonPlugin(); this.pasteOptionPlugin = createPasteOptionPlugin(); this.emojiPlugin = createEmojiPlugin(); this.formatPainterPlugin = new ContentModelFormatPainterPlugin(); @@ -293,10 +295,10 @@ class ContentModelEditorMainPane extends MainPaneBase } renderRibbon(isPopout: boolean) { - const buttons = isPopout ? this.buttons : this.buttons.concat([popout]); + const buttons = isPopout ? this.buttons : this.buttons.concat([popoutButton]); return ( - private formatPainterPlugin: ContentModelFormatPainterPlugin; private tableEditPlugin: TableEditPlugin; private snapshots: Snapshots; - private buttons: ContentModelRibbonButton[] = [ + private buttons: RibbonButton[] = [ formatPainterButton, boldButton, italicButton, @@ -234,9 +236,9 @@ class ContentModelEditorMainPane extends MainPaneBase spaceBeforeButton, spaceAfterButton, pasteButton, - darkMode, - zoom, - exportContent, + darkModeButton, + zoomButton, + exportContentButton, ]; constructor(props: {}) { @@ -259,7 +261,7 @@ class ContentModelEditorMainPane extends MainPaneBase this.contentModelEditPlugin = new EditPlugin(); this.contentAutoFormatPlugin = new AutoFormatPlugin(); this.shortcutPlugin = new ShortcutPlugin(); - this.contentModelRibbonPlugin = new ContentModelRibbonPlugin(); + this.contentModelRibbonPlugin = createRibbonPlugin(); this.formatPainterPlugin = new ContentModelFormatPainterPlugin(); this.tableEditPlugin = new TableEditPlugin(); this.state = { @@ -287,9 +289,9 @@ class ContentModelEditorMainPane extends MainPaneBase } renderRibbon(isPopout: boolean) { - const buttons = isPopout ? this.buttons : this.buttons.concat([popout]); + const buttons = isPopout ? this.buttons : this.buttons.concat([popoutButton]); return ( - ; - -/** - * @internal - * "Background color" button on the format ribbon - */ -export const backgroundColorButton: ContentModelRibbonButton = { - ...originalButton, - onClick: (editor, key) => { - // This check will always be true, add it here just to satisfy compiler - if (key != 'buttonNameBackgroundColor') { - setBackgroundColor(editor, getBackgroundColorValue(key).lightModeColor); - } - }, -}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/formatPainterButton.ts b/demo/scripts/controls/ribbonButtons/contentModel/formatPainterButton.ts deleted file mode 100644 index df5585fd990..00000000000 --- a/demo/scripts/controls/ribbonButtons/contentModel/formatPainterButton.ts +++ /dev/null @@ -1,16 +0,0 @@ -import ContentModelFormatPainterPlugin from '../../contentModel/plugins/ContentModelFormatPainterPlugin'; -import ContentModelRibbonButton from './ContentModelRibbonButton'; - -/** - * @internal - * "Format Painter" button on the format ribbon - */ -export const formatPainterButton: ContentModelRibbonButton<'formatPainter'> = { - key: 'formatPainter', - unlocalizedText: 'Format painter', - iconName: 'Brush', - onClick: editor => { - ContentModelFormatPainterPlugin.startFormatPainter(); - return true; - }, -}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/imageBorderColorButton.ts b/demo/scripts/controls/ribbonButtons/contentModel/imageBorderColorButton.ts deleted file mode 100644 index 39d742f251d..00000000000 --- a/demo/scripts/controls/ribbonButtons/contentModel/imageBorderColorButton.ts +++ /dev/null @@ -1,24 +0,0 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { getButtons, getTextColorValue, KnownRibbonButtonKey, RibbonButton } from 'roosterjs-react'; -import { setImageBorder } from 'roosterjs-content-model-api'; - -const originalButton = getButtons([KnownRibbonButtonKey.TextColor])[0] as RibbonButton< - 'buttonNameImageBorderColor' ->; - -/** - * @internal - * "Image Border Color" button on the format ribbon - */ -export const imageBorderColorButton: ContentModelRibbonButton<'buttonNameImageBorderColor'> = { - ...originalButton, - unlocalizedText: 'Image Border Color', - iconName: 'Photo2', - isDisabled: formatState => !formatState.canAddImageAltText, - onClick: (editor, key) => { - // This check will always be true, add it here just to satisfy compiler - if (key != 'buttonNameImageBorderColor') { - setImageBorder(editor, { color: getTextColorValue(key).lightModeColor }, '5px'); - } - }, -}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/insertTableButton.ts b/demo/scripts/controls/ribbonButtons/contentModel/insertTableButton.ts deleted file mode 100644 index d337376696c..00000000000 --- a/demo/scripts/controls/ribbonButtons/contentModel/insertTableButton.ts +++ /dev/null @@ -1,24 +0,0 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { getButtons, KnownRibbonButtonKey } from 'roosterjs-react'; -import { insertTable } from 'roosterjs-content-model-api'; -import { InsertTableButtonStringKey, RibbonButton } from 'roosterjs-react'; - -const originalPasteButton: RibbonButton = getButtons([ - KnownRibbonButtonKey.InsertTable, -])[0] as RibbonButton; - -export const insertTableButton: ContentModelRibbonButton = { - ...originalPasteButton, - onClick: (editor, key) => { - const { row, col } = parseKey(key); - insertTable(editor, col, row); - }, -}; - -function parseKey(key: string): { row: number; col: number } { - const [row, col] = key.split(','); - return { - row: parseInt(row), - col: parseInt(col), - }; -} diff --git a/demo/scripts/controls/ribbonButtons/contentModel/setHeadingLevelButton.ts b/demo/scripts/controls/ribbonButtons/contentModel/setHeadingLevelButton.ts deleted file mode 100644 index d1934298d3a..00000000000 --- a/demo/scripts/controls/ribbonButtons/contentModel/setHeadingLevelButton.ts +++ /dev/null @@ -1,35 +0,0 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { setHeadingLevel } from 'roosterjs-content-model-api'; -import { - getButtons, - HeadingButtonStringKey, - KnownRibbonButtonKey, - RibbonButton, -} from 'roosterjs-react'; - -const originalHeadingButton: RibbonButton = getButtons([ - KnownRibbonButtonKey.Heading, -])[0] as RibbonButton; -const keys: HeadingButtonStringKey[] = [ - 'buttonNameNoHeading', - 'buttonNameHeading1', - 'buttonNameHeading2', - 'buttonNameHeading3', - 'buttonNameHeading4', - 'buttonNameHeading5', - 'buttonNameHeading6', -]; - -export const setHeadingLevelButton: ContentModelRibbonButton = { - dropDownMenu: { - ...originalHeadingButton.dropDownMenu, - }, - key: 'buttonNameHeading', - unlocalizedText: 'Heading', - iconName: 'Header1', - onClick: (editor, key) => { - const headingLevel = keys.indexOf(key); - - setHeadingLevel(editor, headingLevel as 0 | 1 | 2 | 3 | 4 | 5 | 6); - }, -}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/setTableCellShadeButton.ts b/demo/scripts/controls/ribbonButtons/contentModel/setTableCellShadeButton.ts deleted file mode 100644 index a8ebb0fb2d6..00000000000 --- a/demo/scripts/controls/ribbonButtons/contentModel/setTableCellShadeButton.ts +++ /dev/null @@ -1,34 +0,0 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { setTableCellShade } from 'roosterjs-content-model-api'; -import { - BackgroundColorKeys, - getBackgroundColorValue, - getButtons, - KnownRibbonButtonKey, - RibbonButton, -} from 'roosterjs-react'; - -const originalBackgroundColorButton: RibbonButton = getButtons([ - KnownRibbonButtonKey.BackgroundColor, -])[0] as RibbonButton; - -export const setTableCellShadeButton: ContentModelRibbonButton< - 'ribbonButtonSetTableCellShade' | BackgroundColorKeys -> = { - dropDownMenu: { - ...originalBackgroundColorButton.dropDownMenu, - allowLivePreview: true, - }, - key: 'ribbonButtonSetTableCellShade', - unlocalizedText: 'Set table shade color', - iconName: 'BackgroundColor', - isDisabled: formatState => !formatState.isInTable, - onClick: (editor, key) => { - if (key != 'ribbonButtonSetTableCellShade') { - const color = getBackgroundColorValue(key); - - // Content Model doesn't need dark mode color at this point, so always pass in light mode color - setTableCellShade(editor, color.lightModeColor); - } - }, -}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/tableBorderColorButton.ts b/demo/scripts/controls/ribbonButtons/contentModel/tableBorderColorButton.ts deleted file mode 100644 index 44e7f96a934..00000000000 --- a/demo/scripts/controls/ribbonButtons/contentModel/tableBorderColorButton.ts +++ /dev/null @@ -1,25 +0,0 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import MainPaneBase from '../../MainPaneBase'; -import { getButtons, getTextColorValue, KnownRibbonButtonKey, RibbonButton } from 'roosterjs-react'; - -const originalButton = getButtons([KnownRibbonButtonKey.TextColor])[0] as RibbonButton< - 'buttonNameTableBorderColor' ->; - -/** - * @internal - * "Table Border Color" button on the format ribbon - */ -export const tableBorderColorButton: ContentModelRibbonButton<'buttonNameTableBorderColor'> = { - ...originalButton, - unlocalizedText: 'Table Border Color', - iconName: 'ColorSolid', - isDisabled: formatState => !formatState.isInTable, - onClick: (editor, key) => { - // This check will always be true, add it here just to satisfy compiler - if (key != 'buttonNameTableBorderColor') { - MainPaneBase.getInstance().setTableBorderColor(getTextColorValue(key).lightModeColor); - editor.focus(); - } - }, -}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/textColorButton.ts b/demo/scripts/controls/ribbonButtons/contentModel/textColorButton.ts deleted file mode 100644 index 5b9ed653586..00000000000 --- a/demo/scripts/controls/ribbonButtons/contentModel/textColorButton.ts +++ /dev/null @@ -1,27 +0,0 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { setTextColor } from 'roosterjs-content-model-api'; -import { - getButtons, - getTextColorValue, - KnownRibbonButtonKey, - RibbonButton, - TextColorButtonStringKey, -} from 'roosterjs-react'; - -const originalButton = getButtons([KnownRibbonButtonKey.TextColor])[0] as RibbonButton< - TextColorButtonStringKey ->; - -/** - * @internal - * "Text color" button on the format ribbon - */ -export const textColorButton: ContentModelRibbonButton = { - ...originalButton, - onClick: (editor, key) => { - // This check will always be true, add it here just to satisfy compiler - if (key != 'buttonNameTextColor') { - setTextColor(editor, getTextColorValue(key).lightModeColor); - } - }, -}; diff --git a/demo/scripts/controls/sidePane/contentModel/ContentModelPane.tsx b/demo/scripts/controls/sidePane/contentModel/ContentModelPane.tsx index 903836d9e9e..746eae6c7d9 100644 --- a/demo/scripts/controls/sidePane/contentModel/ContentModelPane.tsx +++ b/demo/scripts/controls/sidePane/contentModel/ContentModelPane.tsx @@ -1,11 +1,9 @@ import * as React from 'react'; -import ContentModelRibbonButton from '../../ribbonButtons/contentModel/ContentModelRibbonButton'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { ContentModelDocumentView } from '../../../controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView'; -import { ContentModelRibbon } from '../../ribbonButtons/contentModel/ContentModelRibbon'; -import { ContentModelRibbonPlugin } from '../../ribbonButtons/contentModel/ContentModelRibbonPlugin'; import { exportButton } from './buttons/exportButton'; import { refreshButton } from './buttons/refreshButton'; +import { Ribbon, RibbonButton, RibbonPlugin } from '../../../controlsV2/roosterjsReact/ribbon'; import { SidePaneElementProps } from '../SidePaneElement'; const styles = require('./ContentModelPane.scss'); @@ -15,14 +13,14 @@ export interface ContentModelPaneState { } export interface ContentModelPaneProps extends ContentModelPaneState, SidePaneElementProps { - ribbonPlugin: ContentModelRibbonPlugin; + ribbonPlugin: RibbonPlugin; } export default class ContentModelPane extends React.Component< ContentModelPaneProps, ContentModelPaneState > { - private contentModelButtons: ContentModelRibbonButton[]; + private contentModelButtons: RibbonButton[]; constructor(props: ContentModelPaneProps) { super(props); @@ -43,10 +41,7 @@ export default class ContentModelPane extends React.Component< render() { return ( <> - +
{this.state.model ? : null}
diff --git a/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts b/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts index 1ceebff6902..5d68e4c3afd 100644 --- a/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts +++ b/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts @@ -1,6 +1,6 @@ import ContentModelPane, { ContentModelPaneProps } from './ContentModelPane'; import SidePanePluginImpl from '../SidePanePluginImpl'; -import { ContentModelRibbonPlugin } from '../../ribbonButtons/contentModel/ContentModelRibbonPlugin'; +import { createRibbonPlugin, RibbonPlugin } from '../../../controlsV2/roosterjsReact/ribbon'; import { IEditor as ILegacyEditor, PluginEvent, PluginEventType } from 'roosterjs-editor-types'; import { IEditor } from 'roosterjs-content-model-types'; import { setCurrentContentModel } from './currentModel'; @@ -10,11 +10,11 @@ export default class ContentModelPanePlugin extends SidePanePluginImpl< ContentModelPane, ContentModelPaneProps > { - private contentModelRibbon: ContentModelRibbonPlugin; + private contentModelRibbon: RibbonPlugin; constructor() { super(ContentModelPane, 'contentModel', 'Content Model (Under development)'); - this.contentModelRibbon = new ContentModelRibbonPlugin(); + this.contentModelRibbon = createRibbonPlugin(); } initialize(editor: ILegacyEditor): void { diff --git a/demo/scripts/controls/sidePane/contentModel/buttons/exportButton.ts b/demo/scripts/controls/sidePane/contentModel/buttons/exportButton.ts index 8d1b43fd834..58aa78e3d35 100644 --- a/demo/scripts/controls/sidePane/contentModel/buttons/exportButton.ts +++ b/demo/scripts/controls/sidePane/contentModel/buttons/exportButton.ts @@ -1,7 +1,7 @@ -import ContentModelRibbonButton from '../../../ribbonButtons/contentModel/ContentModelRibbonButton'; import { getCurrentContentModel } from '../currentModel'; +import { RibbonButton } from '../../../../controlsV2/roosterjsReact/ribbon'; -export const exportButton: ContentModelRibbonButton<'buttonNameExport'> = { +export const exportButton: RibbonButton<'buttonNameExport'> = { key: 'buttonNameExport', unlocalizedText: 'Create DOM tree', iconName: 'DOM', diff --git a/demo/scripts/controls/sidePane/contentModel/buttons/refreshButton.ts b/demo/scripts/controls/sidePane/contentModel/buttons/refreshButton.ts index d427036034f..0dc931d67f5 100644 --- a/demo/scripts/controls/sidePane/contentModel/buttons/refreshButton.ts +++ b/demo/scripts/controls/sidePane/contentModel/buttons/refreshButton.ts @@ -1,6 +1,6 @@ -import ContentModelRibbonButton from '../../../ribbonButtons/contentModel/ContentModelRibbonButton'; +import { RibbonButton } from '../../../../controlsV2/roosterjsReact/ribbon'; -export const refreshButton: ContentModelRibbonButton<'buttonNameRefresh'> = { +export const refreshButton: RibbonButton<'buttonNameRefresh'> = { key: 'buttonNameRefresh', unlocalizedText: 'Refresh', iconName: 'Refresh', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/changeImageButton.ts b/demo/scripts/controlsV2/demoButtons/changeImageButton.ts similarity index 61% rename from demo/scripts/controls/ribbonButtons/contentModel/changeImageButton.ts rename to demo/scripts/controlsV2/demoButtons/changeImageButton.ts index af76da20f48..98c000be494 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/changeImageButton.ts +++ b/demo/scripts/controlsV2/demoButtons/changeImageButton.ts @@ -1,29 +1,28 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { changeImage } from 'roosterjs-content-model-api'; -import { createElement } from 'roosterjs-editor-dom'; -import { CreateElementData } from 'roosterjs-editor-types'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; -const FileInput: CreateElementData = { - tag: 'input', - attributes: { - type: 'file', - accept: 'image/*', - display: 'none', - }, -}; +function createInput(doc: Document): HTMLInputElement { + const input = doc.createElement('input'); + + input.type = 'file'; + input.accept = 'image/*'; + input.setAttribute('display', 'none'); + + return input; +} /** * @internal * "Change Image" button on the format ribbon */ -export const changeImageButton: ContentModelRibbonButton<'buttonNameChangeImage'> = { +export const changeImageButton: RibbonButton<'buttonNameChangeImage'> = { key: 'buttonNameChangeImage', unlocalizedText: 'Change Image', iconName: 'ImageSearch', isDisabled: formatState => !formatState.canAddImageAltText, onClick: editor => { const document = editor.getDocument(); - const fileInput = createElement(FileInput, document) as HTMLInputElement; + const fileInput = createInput(document) as HTMLInputElement; document.body.appendChild(fileInput); fileInput.addEventListener('change', () => { diff --git a/demo/scripts/controls/ribbonButtons/contentModel/darkMode.ts b/demo/scripts/controlsV2/demoButtons/darkModeButton.ts similarity index 74% rename from demo/scripts/controls/ribbonButtons/contentModel/darkMode.ts rename to demo/scripts/controlsV2/demoButtons/darkModeButton.ts index f0a5b6a4b73..c8272e472eb 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/darkMode.ts +++ b/demo/scripts/controlsV2/demoButtons/darkModeButton.ts @@ -1,5 +1,5 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import MainPaneBase from '../../MainPaneBase'; +import MainPaneBase from '../../controls/MainPaneBase'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; /** * Key of localized strings of Dark mode button @@ -9,7 +9,7 @@ export type DarkModeButtonStringKey = 'buttonNameDarkMode'; /** * "Dark mode" button on the format ribbon */ -export const darkMode: ContentModelRibbonButton = { +export const darkModeButton: RibbonButton = { key: 'buttonNameDarkMode', unlocalizedText: 'Dark Mode', iconName: 'ClearNight', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/export.ts b/demo/scripts/controlsV2/demoButtons/exportContentButton.ts similarity index 61% rename from demo/scripts/controls/ribbonButtons/contentModel/export.ts rename to demo/scripts/controlsV2/demoButtons/exportContentButton.ts index 0c09ac7edea..44807386d04 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/export.ts +++ b/demo/scripts/controlsV2/demoButtons/exportContentButton.ts @@ -1,5 +1,5 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { exportContent as exportContentApi } from 'roosterjs-content-model-core'; +import { exportContent } from 'roosterjs-content-model-core'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; /** * Key of localized strings of Zoom button @@ -9,14 +9,14 @@ export type ExportButtonStringKey = 'buttonNameExport'; /** * "Export content" button on the format ribbon */ -export const exportContent: ContentModelRibbonButton = { +export const exportContentButton: RibbonButton = { key: 'buttonNameExport', unlocalizedText: 'Export', iconName: 'Export', flipWhenRtl: true, onClick: editor => { const win = editor.getDocument().defaultView.open(); - const html = exportContentApi(editor); + const html = exportContent(editor); win.document.write(editor.getTrustedHTMLHandler()(html)); }, }; diff --git a/demo/scripts/controlsV2/demoButtons/formatPainterButton.ts b/demo/scripts/controlsV2/demoButtons/formatPainterButton.ts new file mode 100644 index 00000000000..a73b3d7a6c2 --- /dev/null +++ b/demo/scripts/controlsV2/demoButtons/formatPainterButton.ts @@ -0,0 +1,15 @@ +import ContentModelFormatPainterPlugin from '../../controls/contentModel/plugins/ContentModelFormatPainterPlugin'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; + +/** + * @internal + * "Format Painter" button on the format ribbon + */ +export const formatPainterButton: RibbonButton<'formatPainter'> = { + key: 'formatPainter', + unlocalizedText: 'Format painter', + iconName: 'Brush', + onClick: () => { + ContentModelFormatPainterPlugin.startFormatPainter(); + }, +}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/formatTableButton.ts b/demo/scripts/controlsV2/demoButtons/formatTableButton.ts similarity index 98% rename from demo/scripts/controls/ribbonButtons/contentModel/formatTableButton.ts rename to demo/scripts/controlsV2/demoButtons/formatTableButton.ts index ea34aefa2d2..96edc49a96c 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/formatTableButton.ts +++ b/demo/scripts/controlsV2/demoButtons/formatTableButton.ts @@ -1,7 +1,7 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { formatTable } from 'roosterjs-content-model-api'; import { TableBorderFormat } from 'roosterjs-content-model-core'; import { TableMetadataFormat } from 'roosterjs-content-model-types'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; const PREDEFINED_STYLES: Record< string, @@ -191,7 +191,7 @@ export function createTableFormat( }; } -export const formatTableButton: ContentModelRibbonButton<'ribbonButtonTableFormat'> = { +export const formatTableButton: RibbonButton<'ribbonButtonTableFormat'> = { key: 'ribbonButtonTableFormat', iconName: 'TableComputed', unlocalizedText: 'Format Table', diff --git a/demo/scripts/controlsV2/demoButtons/imageBorderColorButton.ts b/demo/scripts/controlsV2/demoButtons/imageBorderColorButton.ts new file mode 100644 index 00000000000..4bf91649859 --- /dev/null +++ b/demo/scripts/controlsV2/demoButtons/imageBorderColorButton.ts @@ -0,0 +1,38 @@ +import { renderColorPicker } from '../roosterjsReact/colorPicker/component/renderColorPicker'; +import { setImageBorder } from 'roosterjs-content-model-api'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; +import { + getColorPickerContainerClassName, + getColorPickerItemClassName, +} from '../roosterjsReact/colorPicker/utils/getClassNamesForColorPicker'; +import { + getTextColorValue, + TextColorDropDownItems, + TextColors, +} from '../roosterjsReact/colorPicker/utils/textColors'; + +/** + * @internal + * "Image Border Color" button on the format ribbon + */ +export const imageBorderColorButton: RibbonButton<'buttonNameImageBorderColor'> = { + dropDownMenu: { + items: TextColorDropDownItems, + itemClassName: getColorPickerItemClassName(), + allowLivePreview: true, + itemRender: (item, onClick) => renderColorPicker(item, TextColors, onClick), + commandBarSubMenuProperties: { + className: getColorPickerContainerClassName(), + }, + }, + key: 'buttonNameImageBorderColor', + unlocalizedText: 'Image Border Color', + iconName: 'Photo2', + isDisabled: formatState => !formatState.canAddImageAltText, + onClick: (editor, key) => { + // This check will always be true, add it here just to satisfy compiler + if (key != 'buttonNameImageBorderColor') { + setImageBorder(editor, { color: getTextColorValue(key).lightModeColor }, '5px'); + } + }, +}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/imageBorderRemoveButton.ts b/demo/scripts/controlsV2/demoButtons/imageBorderRemoveButton.ts similarity index 69% rename from demo/scripts/controls/ribbonButtons/contentModel/imageBorderRemoveButton.ts rename to demo/scripts/controlsV2/demoButtons/imageBorderRemoveButton.ts index da91812e2dd..7444cab02ea 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/imageBorderRemoveButton.ts +++ b/demo/scripts/controlsV2/demoButtons/imageBorderRemoveButton.ts @@ -1,11 +1,11 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setImageBorder } from 'roosterjs-content-model-api'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; /** * @internal * "Remove Image Border" button on the format ribbon */ -export const imageBorderRemoveButton: ContentModelRibbonButton<'buttonNameImageBorderRemove'> = { +export const imageBorderRemoveButton: RibbonButton<'buttonNameImageBorderRemove'> = { key: 'buttonNameImageBorderRemove', unlocalizedText: 'Remove Image Border', iconName: 'Cancel', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/imageBorderStyleButton.ts b/demo/scripts/controlsV2/demoButtons/imageBorderStyleButton.ts similarity index 79% rename from demo/scripts/controls/ribbonButtons/contentModel/imageBorderStyleButton.ts rename to demo/scripts/controlsV2/demoButtons/imageBorderStyleButton.ts index 19e76fb71be..9a28e432849 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/imageBorderStyleButton.ts +++ b/demo/scripts/controlsV2/demoButtons/imageBorderStyleButton.ts @@ -1,5 +1,5 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setImageBorder } from 'roosterjs-content-model-api'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; const STYLES: Record = { dashed: 'dashed', @@ -16,7 +16,7 @@ const STYLES: Record = { * @internal * "Image Border Style" button on the format ribbon */ -export const imageBorderStyleButton: ContentModelRibbonButton<'buttonNameImageBorderStyle'> = { +export const imageBorderStyleButton: RibbonButton<'buttonNameImageBorderStyle'> = { key: 'buttonNameImageBorderStyle', unlocalizedText: 'Image Border Style', iconName: 'BorderDash', @@ -27,6 +27,5 @@ export const imageBorderStyleButton: ContentModelRibbonButton<'buttonNameImageBo }, onClick: (editor, style) => { setImageBorder(editor, { style: style }, '5px'); - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/imageBorderWidthButton.ts b/demo/scripts/controlsV2/demoButtons/imageBorderWidthButton.ts similarity index 80% rename from demo/scripts/controls/ribbonButtons/contentModel/imageBorderWidthButton.ts rename to demo/scripts/controlsV2/demoButtons/imageBorderWidthButton.ts index 29f7a6522bc..e148f088401 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/imageBorderWidthButton.ts +++ b/demo/scripts/controlsV2/demoButtons/imageBorderWidthButton.ts @@ -1,5 +1,5 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setImageBorder } from 'roosterjs-content-model-api'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; const WIDTH = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]; @@ -7,7 +7,7 @@ const WIDTH = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]; * @internal * "Image Border Width" button on the format ribbon */ -export const imageBorderWidthButton: ContentModelRibbonButton<'buttonNameImageBorderWidth'> = { +export const imageBorderWidthButton: RibbonButton<'buttonNameImageBorderWidth'> = { key: 'buttonNameImageBorderWidth', unlocalizedText: 'Image Border Width', iconName: 'Photo2', @@ -27,7 +27,5 @@ export const imageBorderWidthButton: ContentModelRibbonButton<'buttonNameImageBo }, '5px' ); - - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/imageBoxShadowButton.ts b/demo/scripts/controlsV2/demoButtons/imageBoxShadowButton.ts similarity index 87% rename from demo/scripts/controls/ribbonButtons/contentModel/imageBoxShadowButton.ts rename to demo/scripts/controlsV2/demoButtons/imageBoxShadowButton.ts index d1f936ab1d9..e5cca2a83c1 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/imageBoxShadowButton.ts +++ b/demo/scripts/controlsV2/demoButtons/imageBoxShadowButton.ts @@ -1,4 +1,4 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; +import { RibbonButton } from '../roosterjsReact/ribbon'; import { setImageBoxShadow } from 'roosterjs-content-model-api'; const STYLES_NAMES: Record = { @@ -31,7 +31,7 @@ const STYLES: Record = { * @internal * "Image Shadow" button on the format ribbon */ -export const imageBoxShadowButton: ContentModelRibbonButton<'buttonNameImageBoxSHadow'> = { +export const imageBoxShadowButton: RibbonButton<'buttonNameImageBoxSHadow'> = { key: 'buttonNameImageBoxSHadow', unlocalizedText: 'Image Shadow', iconName: 'Photo2', @@ -42,6 +42,5 @@ export const imageBoxShadowButton: ContentModelRibbonButton<'buttonNameImageBoxS }, onClick: (editor, size) => { setImageBoxShadow(editor, STYLES[size], STYLES[size].length ? '4px' : null); - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/listStartNumberButton.ts b/demo/scripts/controlsV2/demoButtons/listStartNumberButton.ts similarity index 83% rename from demo/scripts/controls/ribbonButtons/contentModel/listStartNumberButton.ts rename to demo/scripts/controlsV2/demoButtons/listStartNumberButton.ts index c53f3766556..4fa960e156c 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/listStartNumberButton.ts +++ b/demo/scripts/controlsV2/demoButtons/listStartNumberButton.ts @@ -1,13 +1,13 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { CancelButtonStringKey, OkButtonStringKey } from 'roosterjs-react'; +import { CancelButtonStringKey, OkButtonStringKey } from '../roosterjsReact/common'; +import { RibbonButton } from '../roosterjsReact/ribbon'; import { setListStartNumber } from 'roosterjs-content-model-api'; -import { showInputDialog } from 'roosterjs-react/lib/inputDialog'; +import { showInputDialog } from '../roosterjsReact/inputDialog'; /** * @internal * "Bulleted list" button on the format ribbon */ -export const listStartNumberButton: ContentModelRibbonButton< +export const listStartNumberButton: RibbonButton< | 'ribbonButtonSetStartNumber' | 'ribbonButtonSetStartNumberTo1' | 'ribbonButtonSetStartNumberCustomize' @@ -48,7 +48,5 @@ export const listStartNumberButton: ContentModelRibbonButton< } else { setListStartNumber(editor, 1); } - - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/pasteButton.ts b/demo/scripts/controlsV2/demoButtons/pasteButton.ts similarity index 89% rename from demo/scripts/controls/ribbonButtons/contentModel/pasteButton.ts rename to demo/scripts/controlsV2/demoButtons/pasteButton.ts index 8225b1a9e3f..99e51cac76a 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/pasteButton.ts +++ b/demo/scripts/controlsV2/demoButtons/pasteButton.ts @@ -1,11 +1,11 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { extractClipboardItems } from 'roosterjs-editor-dom'; +import { extractClipboardItems } from 'roosterjs-content-model-core'; +import { RibbonButton } from '../roosterjsReact/ribbon'; /** * @internal * "Paste" button on the format ribbon */ -export const pasteButton: ContentModelRibbonButton<'buttonNamePaste'> = { +export const pasteButton: RibbonButton<'buttonNamePaste'> = { key: 'buttonNamePaste', unlocalizedText: 'Paste', iconName: 'Paste', @@ -22,8 +22,6 @@ export const pasteButton: ContentModelRibbonButton<'buttonNamePaste'> = { editor.pasteFromClipboard(clipboardData); } catch {} } - - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/popout.ts b/demo/scripts/controlsV2/demoButtons/popoutButton.ts similarity index 66% rename from demo/scripts/controls/ribbonButtons/contentModel/popout.ts rename to demo/scripts/controlsV2/demoButtons/popoutButton.ts index a10f4dbdf48..8680e86e82d 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/popout.ts +++ b/demo/scripts/controlsV2/demoButtons/popoutButton.ts @@ -1,5 +1,5 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import MainPaneBase from '../../MainPaneBase'; +import MainPaneBase from '../../controls/MainPaneBase'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; /** * Key of localized strings of Popout button @@ -9,7 +9,7 @@ export type PopoutButtonStringKey = 'buttonNamePopout'; /** * "Popout" button on the format ribbon */ -export const popout: ContentModelRibbonButton = { +export const popoutButton: RibbonButton = { key: 'buttonNamePopout', unlocalizedText: 'Open in a separate window', iconName: 'OpenInNewWindow', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/setBulletedListStyleButton.ts b/demo/scripts/controlsV2/demoButtons/setBulletedListStyleButton.ts similarity index 84% rename from demo/scripts/controls/ribbonButtons/contentModel/setBulletedListStyleButton.ts rename to demo/scripts/controlsV2/demoButtons/setBulletedListStyleButton.ts index bbcfaf64369..c3f55c02674 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/setBulletedListStyleButton.ts +++ b/demo/scripts/controlsV2/demoButtons/setBulletedListStyleButton.ts @@ -1,6 +1,7 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { BulletListType } from 'roosterjs-content-model-core'; import { setListStyle } from 'roosterjs-content-model-api'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; + const dropDownMenuItems = { [BulletListType.Disc]: 'Disc', [BulletListType.Dash]: 'Dash', @@ -13,7 +14,7 @@ const dropDownMenuItems = { [BulletListType.Circle]: 'Circle', }; -export const setBulletedListStyleButton: ContentModelRibbonButton<'ribbonButtonBulletedListStyle'> = { +export const setBulletedListStyleButton: RibbonButton<'ribbonButtonBulletedListStyle'> = { key: 'ribbonButtonBulletedListStyle', dropDownMenu: { items: dropDownMenuItems }, unlocalizedText: 'Set unordered list style', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/setNumberedListStyleButton.ts b/demo/scripts/controlsV2/demoButtons/setNumberedListStyleButton.ts similarity index 91% rename from demo/scripts/controls/ribbonButtons/contentModel/setNumberedListStyleButton.ts rename to demo/scripts/controlsV2/demoButtons/setNumberedListStyleButton.ts index f1215267f30..ccc31cc604b 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/setNumberedListStyleButton.ts +++ b/demo/scripts/controlsV2/demoButtons/setNumberedListStyleButton.ts @@ -1,6 +1,6 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { NumberingListType } from 'roosterjs-content-model-core'; import { setListStyle } from 'roosterjs-content-model-api'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; const dropDownMenuItems = { [NumberingListType.Decimal]: 'Decimal', @@ -25,7 +25,7 @@ const dropDownMenuItems = { [NumberingListType.UpperRomanDash]: 'UpperRomanDash', }; -export const setNumberedListStyleButton: ContentModelRibbonButton<'ribbonButtonNumberedListStyle'> = { +export const setNumberedListStyleButton: RibbonButton<'ribbonButtonNumberedListStyle'> = { key: 'ribbonButtonNumberedListStyle', dropDownMenu: { items: dropDownMenuItems }, unlocalizedText: 'Set ordered list style', diff --git a/demo/scripts/controlsV2/demoButtons/setTableCellShadeButton.ts b/demo/scripts/controlsV2/demoButtons/setTableCellShadeButton.ts new file mode 100644 index 00000000000..8af31da39ff --- /dev/null +++ b/demo/scripts/controlsV2/demoButtons/setTableCellShadeButton.ts @@ -0,0 +1,39 @@ +import { BackgroundColorKeys } from '../roosterjsReact/colorPicker/types/stringKeys'; +import { renderColorPicker } from '../roosterjsReact/colorPicker/component/renderColorPicker'; +import { setTableCellShade } from 'roosterjs-content-model-api'; +import { + getColorPickerContainerClassName, + getColorPickerItemClassName, +} from '../roosterjsReact/colorPicker/utils/getClassNamesForColorPicker'; +import { + BackgroundColorDropDownItems, + BackgroundColors, + getBackgroundColorValue, +} from '../roosterjsReact/colorPicker/utils/backgroundColors'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; + +export const setTableCellShadeButton: RibbonButton< + 'ribbonButtonSetTableCellShade' | BackgroundColorKeys +> = { + dropDownMenu: { + items: BackgroundColorDropDownItems, + itemClassName: getColorPickerItemClassName(), + allowLivePreview: true, + itemRender: (item, onClick) => renderColorPicker(item, BackgroundColors, onClick), + commandBarSubMenuProperties: { + className: getColorPickerContainerClassName(), + }, + }, + key: 'ribbonButtonSetTableCellShade', + unlocalizedText: 'Set table shade color', + iconName: 'BackgroundColor', + isDisabled: formatState => !formatState.isInTable, + onClick: (editor, key) => { + if (key != 'ribbonButtonSetTableCellShade') { + const color = getBackgroundColorValue(key); + + // Content Model doesn't need dark mode color at this point, so always pass in light mode color + setTableCellShade(editor, color.lightModeColor); + } + }, +}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/setTableHeaderButton.ts b/demo/scripts/controlsV2/demoButtons/setTableHeaderButton.ts similarity index 72% rename from demo/scripts/controls/ribbonButtons/contentModel/setTableHeaderButton.ts rename to demo/scripts/controlsV2/demoButtons/setTableHeaderButton.ts index ff3b71555cc..c6ab1b058df 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/setTableHeaderButton.ts +++ b/demo/scripts/controlsV2/demoButtons/setTableHeaderButton.ts @@ -1,7 +1,7 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { formatTable, getFormatState } from 'roosterjs-content-model-api'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; -export const setTableHeaderButton: ContentModelRibbonButton<'ribbonButtonSetTableHeader'> = { +export const setTableHeaderButton: RibbonButton<'ribbonButtonSetTableHeader'> = { key: 'ribbonButtonSetTableHeader', unlocalizedText: 'Toggle table header', iconName: 'Header', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/spaceBeforeAfterButtons.ts b/demo/scripts/controlsV2/demoButtons/spaceBeforeAfterButtons.ts similarity index 80% rename from demo/scripts/controls/ribbonButtons/contentModel/spaceBeforeAfterButtons.ts rename to demo/scripts/controlsV2/demoButtons/spaceBeforeAfterButtons.ts index 2cf3d481e2e..8a7413ed482 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/spaceBeforeAfterButtons.ts +++ b/demo/scripts/controlsV2/demoButtons/spaceBeforeAfterButtons.ts @@ -1,5 +1,5 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { getFormatState, setParagraphMargin } from 'roosterjs-content-model-api'; +import { RibbonButton } from '../roosterjsReact/ribbon'; const spaceAfterButtonKey = 'buttonNameSpaceAfter'; const spaceBeforeButtonKey = 'buttonNameSpaceBefore'; @@ -8,7 +8,7 @@ const spaceBeforeButtonKey = 'buttonNameSpaceBefore'; * @internal * "Add space after" button on the format ribbon */ -export const spaceAfterButton: ContentModelRibbonButton = { +export const spaceAfterButton: RibbonButton = { key: spaceAfterButtonKey, unlocalizedText: 'Remove space after', iconName: 'CaretDown8', @@ -20,8 +20,6 @@ export const spaceAfterButton: ContentModelRibbonButton = { +export const spaceBeforeButton: RibbonButton = { key: spaceBeforeButtonKey, unlocalizedText: 'Add space before', iconName: 'CaretUp8', @@ -41,7 +39,5 @@ export const spaceBeforeButton: ContentModelRibbonButton = { +export const spacingButton: RibbonButton = { key: spacingButtonKey, unlocalizedText: 'Spacing', iconName: 'LineSpacing', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/tableBorderApplyButton.ts b/demo/scripts/controlsV2/demoButtons/tableBorderApplyButton.ts similarity index 87% rename from demo/scripts/controls/ribbonButtons/contentModel/tableBorderApplyButton.ts rename to demo/scripts/controlsV2/demoButtons/tableBorderApplyButton.ts index d28572ba532..1212417a118 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/tableBorderApplyButton.ts +++ b/demo/scripts/controlsV2/demoButtons/tableBorderApplyButton.ts @@ -1,7 +1,7 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import MainPaneBase from '../../MainPaneBase'; +import MainPaneBase from '../../controls/MainPaneBase'; import { applyTableBorderFormat } from 'roosterjs-content-model-api'; import { BorderOperations } from 'roosterjs-content-model-types'; +import { RibbonButton } from '../roosterjsReact/ribbon'; const TABLE_OPERATIONS: Record = { menuNameTableAllBorder: 'allBorders', @@ -14,7 +14,7 @@ const TABLE_OPERATIONS: Record = { menuNameTableOutsideBorder: 'outsideBorders', }; -export const tableBorderApplyButton: ContentModelRibbonButton<'ribbonButtonTableBorder'> = { +export const tableBorderApplyButton: RibbonButton<'ribbonButtonTableBorder'> = { key: 'ribbonButtonTableBorder', iconName: 'TableComputed', unlocalizedText: 'Table Border', diff --git a/demo/scripts/controlsV2/demoButtons/tableBorderColorButton.ts b/demo/scripts/controlsV2/demoButtons/tableBorderColorButton.ts new file mode 100644 index 00000000000..79194aa49b1 --- /dev/null +++ b/demo/scripts/controlsV2/demoButtons/tableBorderColorButton.ts @@ -0,0 +1,39 @@ +import MainPaneBase from '../../controls/MainPaneBase'; +import { renderColorPicker } from '../roosterjsReact/colorPicker/component/renderColorPicker'; +import { + getColorPickerContainerClassName, + getColorPickerItemClassName, +} from '../roosterjsReact/colorPicker/utils/getClassNamesForColorPicker'; +import { + getTextColorValue, + TextColorDropDownItems, + TextColors, +} from '../roosterjsReact/colorPicker/utils/textColors'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; + +/** + * @internal + * "Table Border Color" button on the format ribbon + */ +export const tableBorderColorButton: RibbonButton<'buttonNameTableBorderColor'> = { + dropDownMenu: { + items: TextColorDropDownItems, + itemClassName: getColorPickerItemClassName(), + allowLivePreview: true, + itemRender: (item, onClick) => renderColorPicker(item, TextColors, onClick), + commandBarSubMenuProperties: { + className: getColorPickerContainerClassName(), + }, + }, + key: 'buttonNameTableBorderColor', + unlocalizedText: 'Table Border Color', + iconName: 'ColorSolid', + isDisabled: formatState => !formatState.isInTable, + onClick: (editor, key) => { + // This check will always be true, add it here just to satisfy compiler + if (key != 'buttonNameTableBorderColor') { + MainPaneBase.getInstance().setTableBorderColor(getTextColorValue(key).lightModeColor); + editor.focus(); + } + }, +}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/tableBorderStyleButton.ts b/demo/scripts/controlsV2/demoButtons/tableBorderStyleButton.ts similarity index 76% rename from demo/scripts/controls/ribbonButtons/contentModel/tableBorderStyleButton.ts rename to demo/scripts/controlsV2/demoButtons/tableBorderStyleButton.ts index f9e7c410ab4..7476deca3e6 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/tableBorderStyleButton.ts +++ b/demo/scripts/controlsV2/demoButtons/tableBorderStyleButton.ts @@ -1,5 +1,5 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import MainPaneBase from '../../MainPaneBase'; +import MainPaneBase from '../../controls/MainPaneBase'; +import { RibbonButton } from '../roosterjsReact/ribbon'; const STYLES: Record = { dashed: 'dashed', @@ -16,7 +16,7 @@ const STYLES: Record = { * @internal * "Table Border Style" button on the format ribbon */ -export const tableBorderStyleButton: ContentModelRibbonButton<'buttonNameTableBorderStyle'> = { +export const tableBorderStyleButton: RibbonButton<'buttonNameTableBorderStyle'> = { key: 'buttonNameTableBorderStyle', unlocalizedText: 'Table Border Style', iconName: 'LineStyle', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/tableBorderWidthButton.ts b/demo/scripts/controlsV2/demoButtons/tableBorderWidthButton.ts similarity index 76% rename from demo/scripts/controls/ribbonButtons/contentModel/tableBorderWidthButton.ts rename to demo/scripts/controlsV2/demoButtons/tableBorderWidthButton.ts index d1fefea87be..1609a394e0b 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/tableBorderWidthButton.ts +++ b/demo/scripts/controlsV2/demoButtons/tableBorderWidthButton.ts @@ -1,5 +1,5 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import MainPaneBase from '../../MainPaneBase'; +import MainPaneBase from '../../controls/MainPaneBase'; +import { RibbonButton } from '../roosterjsReact/ribbon'; const WIDTH = [0.25, 0.5, 0.75, 1, 1.5, 2.25, 3, 4.5, 6]; @@ -7,7 +7,7 @@ const WIDTH = [0.25, 0.5, 0.75, 1, 1.5, 2.25, 3, 4.5, 6]; * @internal * "Table Border Width" button on the format ribbon */ -export const tableBorderWidthButton: ContentModelRibbonButton<'buttonNameTableBorderWidth'> = { +export const tableBorderWidthButton: RibbonButton<'buttonNameTableBorderWidth'> = { key: 'buttonNameTableBorderWidth', unlocalizedText: 'Table Border Width', iconName: 'LineThickness', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/tableEditButtons.ts b/demo/scripts/controlsV2/demoButtons/tableEditButtons.ts similarity index 92% rename from demo/scripts/controls/ribbonButtons/contentModel/tableEditButtons.ts rename to demo/scripts/controlsV2/demoButtons/tableEditButtons.ts index f3d2ed1095b..fe0bc42df27 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/tableEditButtons.ts +++ b/demo/scripts/controlsV2/demoButtons/tableEditButtons.ts @@ -1,4 +1,3 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { editTable } from 'roosterjs-content-model-api'; import { TableOperation } from 'roosterjs-content-model-types'; import { @@ -10,6 +9,7 @@ import { TableEditMergeMenuItemStringKey, TableEditSplitMenuItemStringKey, } from 'roosterjs-react'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; const TableEditOperationMap: Partial> = { menuNameTableInsertAbove: 'insertAbove', @@ -37,7 +37,7 @@ const TableEditOperationMap: Partial = { key: 'ribbonButtonTableInsert', @@ -59,7 +59,7 @@ export const tableInsertButton: ContentModelRibbonButton< }, }; -export const tableDeleteButton: ContentModelRibbonButton< +export const tableDeleteButton: RibbonButton< 'ribbonButtonTableDelete' | TableEditDeleteMenuItemStringKey > = { key: 'ribbonButtonTableDelete', @@ -80,7 +80,7 @@ export const tableDeleteButton: ContentModelRibbonButton< }, }; -export const tableMergeButton: ContentModelRibbonButton< +export const tableMergeButton: RibbonButton< 'ribbonButtonTableMerge' | TableEditMergeMenuItemStringKey > = { key: 'ribbonButtonTableMerge', @@ -104,7 +104,7 @@ export const tableMergeButton: ContentModelRibbonButton< }, }; -export const tableSplitButton: ContentModelRibbonButton< +export const tableSplitButton: RibbonButton< 'ribbonButtonTableSplit' | TableEditSplitMenuItemStringKey > = { key: 'ribbonButtonTableSplit', @@ -124,7 +124,7 @@ export const tableSplitButton: ContentModelRibbonButton< }, }; -export const tableAlignCellButton: ContentModelRibbonButton< +export const tableAlignCellButton: RibbonButton< 'ribbonButtonTableAlignCell' | TableEditAlignMenuItemStringKey > = { key: 'ribbonButtonTableAlignCell', @@ -149,7 +149,7 @@ export const tableAlignCellButton: ContentModelRibbonButton< }, }; -export const tableAlignTableButton: ContentModelRibbonButton< +export const tableAlignTableButton: RibbonButton< 'ribbonButtonTableAlignTable' | TableEditAlignTableMenuItemStringKey > = { key: 'ribbonButtonTableAlignTable', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/zoom.ts b/demo/scripts/controlsV2/demoButtons/zoomButton.ts similarity index 69% rename from demo/scripts/controls/ribbonButtons/contentModel/zoom.ts rename to demo/scripts/controlsV2/demoButtons/zoomButton.ts index b102d9c1c31..7c6c9c65e80 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/zoom.ts +++ b/demo/scripts/controlsV2/demoButtons/zoomButton.ts @@ -1,6 +1,5 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import MainPaneBase from '../../MainPaneBase'; -import { getObjectKeys } from 'roosterjs-editor-dom'; +import MainPaneBase from '../../controls/MainPaneBase'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; const DropDownItems = { 'zoom50%': '50%', @@ -26,16 +25,12 @@ export type ZoomButtonStringKey = 'buttonNameZoom'; /** * "Zoom" button on the format ribbon */ -export const zoom: ContentModelRibbonButton = { +export const zoomButton: RibbonButton = { key: 'buttonNameZoom', unlocalizedText: 'Zoom', iconName: 'ZoomIn', dropDownMenu: { items: DropDownItems, - getSelectedItemKey: formatState => - getObjectKeys(DropDownItems).filter( - key => DropDownValues[key] == formatState.zoomScale - )[0], }, onClick: (editor, key) => { const zoomScale = DropDownValues[key as keyof typeof DropDownItems]; @@ -45,7 +40,5 @@ export const zoom: ContentModelRibbonButton = { MainPaneBase.getInstance().setScale(zoomScale); editor.triggerEvent('zoomChanged', { newZoomScale: zoomScale }); - - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/alignCenterButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignCenterButton.ts similarity index 55% rename from demo/scripts/controls/ribbonButtons/contentModel/alignCenterButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignCenterButton.ts index a2306f0b8bd..fe77283fa25 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/alignCenterButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignCenterButton.ts @@ -1,17 +1,16 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setAlignment } from 'roosterjs-content-model-api'; -import { AlignCenterButtonStringKey } from 'roosterjs-react'; +import type { AlignCenterButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { RibbonButton } from '../type/RibbonButton'; /** * @internal * "Align center" button on the format ribbon */ -export const alignCenterButton: ContentModelRibbonButton = { +export const alignCenterButton: RibbonButton = { key: 'buttonNameAlignCenter', unlocalizedText: 'Align center', iconName: 'AlignCenter', onClick: editor => { setAlignment(editor, 'center'); - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/alignJustifyButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignJustifyButton.ts similarity index 63% rename from demo/scripts/controls/ribbonButtons/contentModel/alignJustifyButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignJustifyButton.ts index a93db909a0d..d565027111e 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/alignJustifyButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignJustifyButton.ts @@ -1,16 +1,15 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setAlignment } from 'roosterjs-content-model-api'; +import type { RibbonButton } from '../type/RibbonButton'; /** * @internal * "Align justify" button on the format ribbon */ -export const alignJustifyButton: ContentModelRibbonButton<'buttonNameAlignJustify'> = { +export const alignJustifyButton: RibbonButton<'buttonNameAlignJustify'> = { key: 'buttonNameAlignJustify', unlocalizedText: 'Align justify', iconName: 'AlignJustify', onClick: editor => { setAlignment(editor, 'justify'); - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/alignLeftButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignLeftButton.ts similarity index 55% rename from demo/scripts/controls/ribbonButtons/contentModel/alignLeftButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignLeftButton.ts index c1b0e014af8..ad2c1872ad7 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/alignLeftButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignLeftButton.ts @@ -1,17 +1,16 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setAlignment } from 'roosterjs-content-model-api'; -import { AlignLeftButtonStringKey } from 'roosterjs-react'; +import type { RibbonButton } from '../type/RibbonButton'; +import type { AlignLeftButtonStringKey } from '../type/RibbonButtonStringKeys'; /** * @internal * "Align left" button on the format ribbon */ -export const alignLeftButton: ContentModelRibbonButton = { +export const alignLeftButton: RibbonButton = { key: 'buttonNameAlignLeft', unlocalizedText: 'Align left', iconName: 'AlignLeft', onClick: editor => { setAlignment(editor, 'left'); - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/alignRightButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignRightButton.ts similarity index 55% rename from demo/scripts/controls/ribbonButtons/contentModel/alignRightButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignRightButton.ts index fa3ab23d40d..bc04dd564ea 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/alignRightButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/alignRightButton.ts @@ -1,17 +1,16 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setAlignment } from 'roosterjs-content-model-api'; -import { AlignRightButtonStringKey } from 'roosterjs-react'; +import type { RibbonButton } from '../type/RibbonButton'; +import type { AlignRightButtonStringKey } from '../type/RibbonButtonStringKeys'; /** * @internal * "Align right" button on the format ribbon */ -export const alignRightButton: ContentModelRibbonButton = { +export const alignRightButton: RibbonButton = { key: 'buttonNameAlignRight', unlocalizedText: 'Align right', iconName: 'AlignRight', onClick: editor => { setAlignment(editor, 'right'); - return true; }, }; diff --git a/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/backgroundColorButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/backgroundColorButton.ts new file mode 100644 index 00000000000..10332fdba47 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/backgroundColorButton.ts @@ -0,0 +1,40 @@ +import { renderColorPicker } from '../../colorPicker/component/renderColorPicker'; +import { setBackgroundColor } from 'roosterjs-content-model-api'; +import { + BackgroundColorDropDownItems, + BackgroundColors, + getBackgroundColorValue, +} from '../../colorPicker/utils/backgroundColors'; +import { + getColorPickerContainerClassName, + getColorPickerItemClassName, +} from '../../colorPicker/utils/getClassNamesForColorPicker'; +import type { RibbonButton } from '../type/RibbonButton'; +import type { BackgroundColorButtonStringKey } from '../type/RibbonButtonStringKeys'; + +const Key: 'buttonNameBackgroundColor' = 'buttonNameBackgroundColor'; + +/** + * @internal + * "Background color" button on the format ribbon + */ +export const backgroundColorButton: RibbonButton = { + dropDownMenu: { + items: BackgroundColorDropDownItems, + itemClassName: getColorPickerItemClassName(), + allowLivePreview: true, + itemRender: (item, onClick) => renderColorPicker(item, BackgroundColors, onClick), + commandBarSubMenuProperties: { + className: getColorPickerContainerClassName(), + }, + }, + key: Key, + unlocalizedText: 'Background color', + iconName: 'FabricTextHighlight', + onClick: (editor, key) => { + // This check will always be true, add it here just to satisfy compiler + if (key != 'buttonNameBackgroundColor') { + setBackgroundColor(editor, getBackgroundColorValue(key).lightModeColor); + } + }, +}; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/blockQuoteButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/blockQuoteButton.ts similarity index 60% rename from demo/scripts/controls/ribbonButtons/contentModel/blockQuoteButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/blockQuoteButton.ts index dfe8d88ca2e..4cfad41efea 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/blockQuoteButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/blockQuoteButton.ts @@ -1,18 +1,17 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { toggleBlockQuote } from 'roosterjs-content-model-api'; -import { QuoteButtonStringKey } from 'roosterjs-react'; +import type { QuoteButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { RibbonButton } from '../type/RibbonButton'; /** * @internal * "Block quote" button on the format ribbon */ -export const blockQuoteButton: ContentModelRibbonButton = { +export const blockQuoteButton: RibbonButton = { key: 'buttonNameQuote', unlocalizedText: 'Quote', iconName: 'RightDoubleQuote', isChecked: formatState => !!formatState.isBlockQuote, onClick: editor => { toggleBlockQuote(editor); - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/boldButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/boldButton.ts similarity index 58% rename from demo/scripts/controls/ribbonButtons/contentModel/boldButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/boldButton.ts index 9877b554f32..8f427c602f8 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/boldButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/boldButton.ts @@ -1,18 +1,17 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { BoldButtonStringKey } from 'roosterjs-react'; import { toggleBold } from 'roosterjs-content-model-api'; +import type { BoldButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { RibbonButton } from '../type/RibbonButton'; /** * @internal * "Bold" button on the format ribbon */ -export const boldButton: ContentModelRibbonButton = { +export const boldButton: RibbonButton = { key: 'buttonNameBold', unlocalizedText: 'Bold', iconName: 'Bold', isChecked: formatState => formatState.isBold, onClick: editor => { toggleBold(editor); - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/bulletedListButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/bulletedListButton.ts similarity index 59% rename from demo/scripts/controls/ribbonButtons/contentModel/bulletedListButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/bulletedListButton.ts index 543a187bff1..11a9a28a7d0 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/bulletedListButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/bulletedListButton.ts @@ -1,18 +1,17 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { toggleBullet } from 'roosterjs-content-model-api'; -import { BulletedListButtonStringKey } from 'roosterjs-react'; +import type { BulletedListButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { RibbonButton } from '../type/RibbonButton'; /** * @internal * "Bulleted list" button on the format ribbon */ -export const bulletedListButton: ContentModelRibbonButton = { +export const bulletedListButton: RibbonButton = { key: 'buttonNameBulletedList', unlocalizedText: 'Bulleted list', iconName: 'BulletedList', isChecked: formatState => formatState.isBullet, onClick: editor => { toggleBullet(editor); - return true; }, }; diff --git a/demo/scripts/controls/ribbonButtons/contentModel/clearFormatButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/clearFormatButton.ts similarity index 56% rename from demo/scripts/controls/ribbonButtons/contentModel/clearFormatButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/clearFormatButton.ts index 0ca5a9ccec9..954f09761e0 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/clearFormatButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/clearFormatButton.ts @@ -1,11 +1,11 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { clearFormat } from 'roosterjs-content-model-api'; -import { ClearFormatButtonStringKey } from 'roosterjs-react'; +import type { ClearFormatButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { RibbonButton } from '../type/RibbonButton'; /** * "Clear format" button on the format ribbon */ -export const clearFormatButton: ContentModelRibbonButton = { +export const clearFormatButton: RibbonButton = { key: 'buttonNameClearFormat', unlocalizedText: 'Clear format', iconName: 'ClearFormatting', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/codeButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/codeButton.ts similarity index 61% rename from demo/scripts/controls/ribbonButtons/contentModel/codeButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/codeButton.ts index 77c33176be6..373155efd48 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/codeButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/codeButton.ts @@ -1,12 +1,12 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { toggleCode } from 'roosterjs-content-model-api'; -import { CodeButtonStringKey } from 'roosterjs-react'; +import type { RibbonButton } from '../type/RibbonButton'; +import type { CodeButtonStringKey } from '../type/RibbonButtonStringKeys'; /** * @internal * "Code" button on the format ribbon */ -export const codeButton: ContentModelRibbonButton = { +export const codeButton: RibbonButton = { key: 'buttonNameCode', unlocalizedText: 'Code', iconName: 'Code', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/decreaseFontSizeButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/decreaseFontSizeButton.ts similarity index 58% rename from demo/scripts/controls/ribbonButtons/contentModel/decreaseFontSizeButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/decreaseFontSizeButton.ts index b4fa8213d40..f0b649721c4 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/decreaseFontSizeButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/decreaseFontSizeButton.ts @@ -1,12 +1,12 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { changeFontSize } from 'roosterjs-content-model-api'; -import { DecreaseFontSizeButtonStringKey } from 'roosterjs-react'; +import type { RibbonButton } from '../type/RibbonButton'; +import type { DecreaseFontSizeButtonStringKey } from '../type/RibbonButtonStringKeys'; /** * @internal * "Decrease font size" button on the format ribbon */ -export const decreaseFontSizeButton: ContentModelRibbonButton = { +export const decreaseFontSizeButton: RibbonButton = { key: 'buttonNameDecreaseFontSize', unlocalizedText: 'Decrease font size', iconName: 'FontDecrease', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/decreaseIndentButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/decreaseIndentButton.ts similarity index 60% rename from demo/scripts/controls/ribbonButtons/contentModel/decreaseIndentButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/decreaseIndentButton.ts index 0825b629428..ce5f514cd79 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/decreaseIndentButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/decreaseIndentButton.ts @@ -1,12 +1,12 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setIndentation } from 'roosterjs-content-model-api'; -import { DecreaseIndentButtonStringKey } from 'roosterjs-react'; +import type { DecreaseIndentButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { RibbonButton } from '../type/RibbonButton'; /** * @internal * "Decrease indent" button on the format ribbon */ -export const decreaseIndentButton: ContentModelRibbonButton = { +export const decreaseIndentButton: RibbonButton = { key: 'buttonNameDecreaseIndent', unlocalizedText: 'Decrease indent', iconName: 'DecreaseIndentLegacy', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/fontButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/fontButton.ts similarity index 97% rename from demo/scripts/controls/ribbonButtons/contentModel/fontButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/fontButton.ts index e2e714b881f..42b088221e5 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/fontButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/fontButton.ts @@ -1,6 +1,6 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setFontName } from 'roosterjs-content-model-api'; -import { FontButtonStringKey } from 'roosterjs-react'; +import type { FontButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { RibbonButton } from '../type/RibbonButton'; interface FontName { name: string; @@ -150,7 +150,7 @@ const FirstFontRegex = /^['"]?([^'",]+)/i; * @internal * "Font" button on the format ribbon */ -export const fontButton: ContentModelRibbonButton = { +export const fontButton: RibbonButton = { key: 'buttonNameFont', unlocalizedText: 'Font', iconName: 'Font', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/fontSizeButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/fontSizeButton.ts similarity index 76% rename from demo/scripts/controls/ribbonButtons/contentModel/fontSizeButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/fontSizeButton.ts index 6e33042b8a1..fbe32c8f8bd 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/fontSizeButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/fontSizeButton.ts @@ -1,6 +1,6 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setFontSize } from 'roosterjs-content-model-api'; -import { FontSizeButtonStringKey } from 'roosterjs-react'; +import type { RibbonButton } from '../type/RibbonButton'; +import type { FontSizeButtonStringKey } from '../type/RibbonButtonStringKeys'; const FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]; @@ -8,7 +8,7 @@ const FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 * @internal * "Font Size" button on the format ribbon */ -export const fontSizeButton: ContentModelRibbonButton = { +export const fontSizeButton: RibbonButton = { key: 'buttonNameFontSize', unlocalizedText: 'Font size', iconName: 'FontSize', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/increaseFontSizeButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/increaseFontSizeButton.ts similarity index 58% rename from demo/scripts/controls/ribbonButtons/contentModel/increaseFontSizeButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/increaseFontSizeButton.ts index d0a6e57af79..e6984d42723 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/increaseFontSizeButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/increaseFontSizeButton.ts @@ -1,12 +1,12 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { changeFontSize } from 'roosterjs-content-model-api'; -import { IncreaseFontSizeButtonStringKey } from 'roosterjs-react'; +import type { RibbonButton } from '../type/RibbonButton'; +import type { IncreaseFontSizeButtonStringKey } from '../type/RibbonButtonStringKeys'; /** * @internal * "Increase font size" button on the format ribbon */ -export const increaseFontSizeButton: ContentModelRibbonButton = { +export const increaseFontSizeButton: RibbonButton = { key: 'buttonNameIncreaseFontSize', unlocalizedText: 'Increase font size', iconName: 'FontIncrease', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/increaseIndentButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/increaseIndentButton.ts similarity index 60% rename from demo/scripts/controls/ribbonButtons/contentModel/increaseIndentButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/increaseIndentButton.ts index 397b02d56ef..5c38106d897 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/increaseIndentButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/increaseIndentButton.ts @@ -1,12 +1,12 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { setIndentation } from 'roosterjs-content-model-api'; -import { IncreaseIndentButtonStringKey } from 'roosterjs-react'; +import type { IncreaseIndentButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { RibbonButton } from '../type/RibbonButton'; /** * @internal * "Increase indent" button on the format ribbon */ -export const increaseIndentButton: ContentModelRibbonButton = { +export const increaseIndentButton: RibbonButton = { key: 'buttonNameIncreaseIndent', unlocalizedText: 'Increase indent', iconName: 'IncreaseIndentLegacy', diff --git a/demo/scripts/controls/ribbonButtons/contentModel/insertImageButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertImageButton.ts similarity index 56% rename from demo/scripts/controls/ribbonButtons/contentModel/insertImageButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertImageButton.ts index 6d88485b47d..87a6ed89b04 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/insertImageButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertImageButton.ts @@ -1,29 +1,28 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; -import { createElement } from 'roosterjs-editor-dom'; -import { CreateElementData } from 'roosterjs-editor-types'; import { insertImage } from 'roosterjs-content-model-api'; -import { InsertImageButtonStringKey } from 'roosterjs-react'; +import { InsertImageButtonStringKey } from '../type/RibbonButtonStringKeys'; +import { RibbonButton } from '../type/RibbonButton'; -const FileInput: CreateElementData = { - tag: 'input', - attributes: { - type: 'file', - accept: 'image/*', - display: 'none', - }, -}; +function createInput(doc: Document): HTMLInputElement { + const input = doc.createElement('input'); + + input.type = 'file'; + input.accept = 'image/*'; + input.setAttribute('display', 'none'); + + return input; +} /** * @internal * "Insert image" button on the format ribbon */ -export const insertImageButton: ContentModelRibbonButton = { +export const insertImageButton: RibbonButton = { key: 'buttonNameInsertImage', unlocalizedText: 'Insert image', iconName: 'Photo2', onClick: editor => { const document = editor.getDocument(); - const fileInput = createElement(FileInput, document) as HTMLInputElement; + const fileInput = createInput(document) as HTMLInputElement; document.body.appendChild(fileInput); fileInput.addEventListener('change', () => { diff --git a/demo/scripts/controls/ribbonButtons/contentModel/insertLinkButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertLinkButton.ts similarity index 85% rename from demo/scripts/controls/ribbonButtons/contentModel/insertLinkButton.ts rename to demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertLinkButton.ts index e380519986b..7187fa3e75d 100644 --- a/demo/scripts/controls/ribbonButtons/contentModel/insertLinkButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertLinkButton.ts @@ -1,17 +1,16 @@ -import ContentModelRibbonButton from './ContentModelRibbonButton'; import { adjustLinkSelection, insertLink } from 'roosterjs-content-model-api'; import { showInputDialog } from 'roosterjs-react/lib/inputDialog'; -import { InsertLinkButtonStringKey } from 'roosterjs-react'; +import type { InsertLinkButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { RibbonButton } from '../type/RibbonButton'; /** * @internal * "Insert link" button on the format ribbon */ -export const insertLinkButton: ContentModelRibbonButton = { +export const insertLinkButton: RibbonButton = { key: 'buttonNameInsertLink', unlocalizedText: 'Insert link', iconName: 'Link', - // isDisabled: formatState => !!formatState.isMultilineSelection, onClick: (editor, _, strings, uiUtilities) => { const [displayText, url] = adjustLinkSelection(editor); const items = { diff --git a/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertTableButton.tsx b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertTableButton.tsx new file mode 100644 index 00000000000..e0cd130e693 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertTableButton.tsx @@ -0,0 +1,172 @@ +import * as React from 'react'; +import { FocusZone, FocusZoneDirection } from '@fluentui/react/lib/FocusZone'; +import { insertTable } from 'roosterjs-content-model-api'; +import { isNodeOfType } from 'roosterjs-content-model-dom'; +import { mergeStyleSets } from '@fluentui/react/lib/Styling'; +import type { RibbonButton } from '../type/RibbonButton'; +import type { InsertTableButtonStringKey } from '../type/RibbonButtonStringKeys'; +import type { IContextualMenuItem } from '@fluentui/react/lib/ContextualMenu'; + +const MaxRows = 10; +const MaxCols = 10; +const classNames = mergeStyleSets({ + tableButton: { + width: '15px', + height: '15px', + margin: '1px 1px 0 0', + border: 'solid 1px #a19f9d', + display: 'inline-block', + cursor: 'pointer', + backgroundColor: 'transparent', + }, + hovered: { + border: 'solid 1px #DB626C', + }, + tablePane: { + width: '160px', + minWidth: 'auto', + padding: '4px', + boxSizing: 'content-box', + }, + tablePaneInner: { + lineHeight: '12px', + }, + title: { + margin: '5px 0', + }, +}); + +export const insertTableButton: RibbonButton = { + key: 'buttonNameInsertTable', + unlocalizedText: 'Insert table', + iconName: 'Table', + dropDownMenu: { + items: { + insertTablePane: '{0} x {1} table', + }, + itemRender: (item, onClick) => { + return ; + }, + commandBarSubMenuProperties: { + className: classNames.tablePane, + }, + }, + onClick: (editor, key) => { + const { row, col } = parseKey(key); + insertTable(editor, col, row); + }, +}; + +function parseKey(key: string): { row: number; col: number } { + const [row, col] = key.split(','); + return { + row: parseInt(row), + col: parseInt(col), + }; +} + +function InsertTablePane(props: { + item: IContextualMenuItem; + onClick: ( + e: React.MouseEvent | React.KeyboardEvent, + item: IContextualMenuItem + ) => void; +}) { + const { item, onClick } = props; + const [col, setCol] = React.useState(1); + const [row, setRow] = React.useState(1); + + const updateSize = React.useCallback( + (t?: Node) => { + if (t && isNodeOfType(t, 'ELEMENT_NODE')) { + const col = parseInt(t.dataset.col ?? '-1'); + const row = parseInt(t.dataset.row ?? '-1'); + + if (col > 0 && col <= MaxCols && row > 0 && row <= MaxRows) { + setCol(col); + setRow(row); + } + } + }, + [setCol, setRow] + ); + + const onMouseEnter = React.useCallback( + (e: React.MouseEvent) => { + updateSize(e.target as Node); + }, + [updateSize] + ); + + const onClickButton = React.useCallback( + (e: React.MouseEvent) => { + onClick(e, { + ...item, + key: createKey(row, col), + }); + }, + [row, col, onClick] + ); + + const ariaLabels = React.useMemo(() => { + const result: string[][] = []; + for (let i = 1; i <= MaxCols; i++) { + const col: string[] = []; + for (let j = 1; j <= MaxRows; j++) { + col[j] = formatText(item.text ?? '', i, j); + } + result[i] = col; + } + return result; + }, [item.text]); + + const items = React.useMemo(() => { + const items: JSX.Element[] = []; + + for (let i = 1; i <= MaxRows; i++) { + for (let j = 1; j <= MaxCols; j++) { + const key = `cell_${i}_${j}`; + const isSelected = j <= col && i <= row; + items.push( +
+ ); + } + + private renderPopout() { + return ( + <> + {this.renderSidePane(true /*fullWidth*/)} + {ReactDOM.createPortal( + + +
+ {this.renderRibbon(true /*isPopout*/)} +
{this.renderEditor()}
+
+
+
, + this.popoutRoot + )} + + ); + } + + private onMouseDown = (e: React.MouseEvent) => { + document.addEventListener('mousemove', this.onMouseMove, true); + document.addEventListener('mouseup', this.onMouseUp, true); + document.body.style.userSelect = 'none'; + this.mouseX = e.pageX; + }; + + private onMouseMove = (e: MouseEvent) => { + this.sidePane.current.changeWidth(this.mouseX - e.pageX); + this.mouseX = e.pageX; + }; + + private onMouseUp = (e: MouseEvent) => { + document.removeEventListener('mousemove', this.onMouseMove, true); + document.removeEventListener('mouseup', this.onMouseUp, true); + document.body.style.userSelect = ''; + }; + + private onUpdate = (model: ContentModelDocument) => { + this.model = model; + }; + + private onShowSidePane = () => { + this.setState({ + showSidePane: true, + }); + this.resetEditor(); + }; + + private onHideSidePane = () => { + this.setState({ + showSidePane: false, + }); + this.resetEditor(); + window.location.hash = ''; + }; + + private onThemeChange = () => { + this.setState({ + isDarkMode: this.themeMatch?.matches || false, + }); + }; + + private getSidePanePlugins(): SidePanePlugin[] { + return [ + this.formatStatePlugin, + this.editorOptionPlugin, + this.eventViewPlugin, + this.apiPlaygroundPlugin, + this.snapshotPlugin, + this.contentModelPanePlugin, + ]; + } + + private getToggleablePlugins(): EditorPlugin[] { + const { + pluginList, + allowExcelNoBorderTable, + listMenu, + tableMenu, + imageMenu, + } = this.state.initState; + return [ + pluginList.autoFormat && new AutoFormatPlugin(), + pluginList.edit && new EditPlugin(), + pluginList.paste && new PastePlugin(allowExcelNoBorderTable), + pluginList.shortcut && new ShortcutPlugin(), + pluginList.tableEdit && new TableEditPlugin(), + pluginList.emoji && createEmojiPlugin(), + pluginList.pasteOption && createPasteOptionPlugin(), + pluginList.sampleEntity && new SampleEntityPlugin(), + pluginList.contextMenu && createContextMenuPlugin(), + pluginList.contextMenu && listMenu && createListEditMenuProvider(), + pluginList.contextMenu && tableMenu && createTableEditMenuProvider(), + pluginList.contextMenu && imageMenu && createImageEditMenuProvider(), + ].filter(x => !!x); + } +} + +export function mount(parent: HTMLElement) { + ReactDOM.render(, parent); +} diff --git a/demo/scripts/controlsV2/mainPane/ribbonButtons.ts b/demo/scripts/controlsV2/mainPane/ribbonButtons.ts new file mode 100644 index 00000000000..e1f8e0a94d0 --- /dev/null +++ b/demo/scripts/controlsV2/mainPane/ribbonButtons.ts @@ -0,0 +1,132 @@ +import { alignCenterButton } from '../roosterjsReact/ribbon/buttons/alignCenterButton'; +import { alignJustifyButton } from '../roosterjsReact/ribbon/buttons/alignJustifyButton'; +import { alignLeftButton } from '../roosterjsReact/ribbon/buttons/alignLeftButton'; +import { alignRightButton } from '../roosterjsReact/ribbon/buttons/alignRightButton'; +import { backgroundColorButton } from '../roosterjsReact/ribbon/buttons/backgroundColorButton'; +import { blockQuoteButton } from '../roosterjsReact/ribbon/buttons/blockQuoteButton'; +import { boldButton } from '../roosterjsReact/ribbon/buttons/boldButton'; +import { bulletedListButton } from '../roosterjsReact/ribbon/buttons/bulletedListButton'; +import { changeImageButton } from '../demoButtons/changeImageButton'; +import { clearFormatButton } from '../roosterjsReact/ribbon/buttons/clearFormatButton'; +import { codeButton } from '../roosterjsReact/ribbon/buttons/codeButton'; +import { darkModeButton } from '../demoButtons/darkModeButton'; +import { decreaseFontSizeButton } from '../roosterjsReact/ribbon/buttons/decreaseFontSizeButton'; +import { decreaseIndentButton } from '../roosterjsReact/ribbon/buttons/decreaseIndentButton'; +import { exportContentButton } from '../demoButtons/exportContentButton'; +import { fontButton } from '../roosterjsReact/ribbon/buttons/fontButton'; +import { fontSizeButton } from '../roosterjsReact/ribbon/buttons/fontSizeButton'; +import { formatPainterButton } from '../demoButtons/formatPainterButton'; +import { formatTableButton } from '../demoButtons/formatTableButton'; +import { imageBorderColorButton } from '../demoButtons/imageBorderColorButton'; +import { imageBorderRemoveButton } from '../demoButtons/imageBorderRemoveButton'; +import { imageBorderStyleButton } from '../demoButtons/imageBorderStyleButton'; +import { imageBorderWidthButton } from '../demoButtons/imageBorderWidthButton'; +import { imageBoxShadowButton } from '../demoButtons/imageBoxShadowButton'; +import { increaseFontSizeButton } from '../roosterjsReact/ribbon/buttons/increaseFontSizeButton'; +import { increaseIndentButton } from '../roosterjsReact/ribbon/buttons/increaseIndentButton'; +import { insertImageButton } from '../roosterjsReact/ribbon/buttons/insertImageButton'; +import { insertLinkButton } from '../roosterjsReact/ribbon/buttons/insertLinkButton'; +import { insertTableButton } from '../roosterjsReact/ribbon/buttons/insertTableButton'; +import { italicButton } from '../roosterjsReact/ribbon/buttons/italicButton'; +import { listStartNumberButton } from '../demoButtons/listStartNumberButton'; +import { ltrButton } from '../roosterjsReact/ribbon/buttons/ltrButton'; +import { numberedListButton } from '../roosterjsReact/ribbon/buttons/numberedListButton'; +import { pasteButton } from '../demoButtons/pasteButton'; +import { popoutButton } from '../demoButtons/popoutButton'; +import { redoButton } from '../roosterjsReact/ribbon/buttons/redoButton'; +import { removeLinkButton } from '../roosterjsReact/ribbon/buttons/removeLinkButton'; +import { rtlButton } from '../roosterjsReact/ribbon/buttons/rtlButton'; +import { setBulletedListStyleButton } from '../demoButtons/setBulletedListStyleButton'; +import { setHeadingLevelButton } from '../roosterjsReact/ribbon/buttons/setHeadingLevelButton'; +import { setNumberedListStyleButton } from '../demoButtons/setNumberedListStyleButton'; +import { setTableCellShadeButton } from '../demoButtons/setTableCellShadeButton'; +import { setTableHeaderButton } from '../demoButtons/setTableHeaderButton'; +import { spaceAfterButton, spaceBeforeButton } from '../demoButtons/spaceBeforeAfterButtons'; +import { spacingButton } from '../demoButtons/spacingButton'; +import { strikethroughButton } from '../roosterjsReact/ribbon/buttons/strikethroughButton'; +import { subscriptButton } from '../roosterjsReact/ribbon/buttons/subscriptButton'; +import { superscriptButton } from '../roosterjsReact/ribbon/buttons/superscriptButton'; +import { tableBorderApplyButton } from '../demoButtons/tableBorderApplyButton'; +import { tableBorderColorButton } from '../demoButtons/tableBorderColorButton'; +import { tableBorderStyleButton } from '../demoButtons/tableBorderStyleButton'; +import { tableBorderWidthButton } from '../demoButtons/tableBorderWidthButton'; +import { textColorButton } from '../roosterjsReact/ribbon/buttons/textColorButton'; +import { underlineButton } from '../roosterjsReact/ribbon/buttons/underlineButton'; +import { undoButton } from '../roosterjsReact/ribbon/buttons/undoButton'; +import { zoomButton } from '../demoButtons/zoomButton'; +import { + tableAlignCellButton, + tableAlignTableButton, + tableDeleteButton, + tableInsertButton, + tableMergeButton, + tableSplitButton, +} from '../demoButtons/tableEditButtons'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; + +export const buttons: RibbonButton[] = [ + formatPainterButton, + boldButton, + italicButton, + underlineButton, + fontButton, + fontSizeButton, + increaseFontSizeButton, + decreaseFontSizeButton, + textColorButton, + backgroundColorButton, + bulletedListButton, + numberedListButton, + decreaseIndentButton, + increaseIndentButton, + blockQuoteButton, + alignLeftButton, + alignCenterButton, + alignRightButton, + alignJustifyButton, + insertLinkButton, + removeLinkButton, + insertTableButton, + insertImageButton, + superscriptButton, + subscriptButton, + strikethroughButton, + setHeadingLevelButton, + codeButton, + ltrButton, + rtlButton, + undoButton, + redoButton, + clearFormatButton, + setBulletedListStyleButton, + setNumberedListStyleButton, + listStartNumberButton, + formatTableButton, + setTableCellShadeButton, + setTableHeaderButton, + tableInsertButton, + tableDeleteButton, + tableMergeButton, + tableSplitButton, + tableAlignCellButton, + tableAlignTableButton, + tableBorderApplyButton, + tableBorderColorButton, + tableBorderWidthButton, + tableBorderStyleButton, + imageBorderColorButton, + imageBorderWidthButton, + imageBorderStyleButton, + imageBorderRemoveButton, + changeImageButton, + imageBoxShadowButton, + spacingButton, + spaceBeforeButton, + spaceAfterButton, + pasteButton, + darkModeButton, + zoomButton, + exportContentButton, +]; + +export const buttonsWithPopout: RibbonButton[] = buttons.concat(popoutButton); diff --git a/demo/scripts/controls/contentModel/plugins/ContentModelFormatPainterPlugin.ts b/demo/scripts/controlsV2/plugins/FormatPainterPlugin.ts similarity index 88% rename from demo/scripts/controls/contentModel/plugins/ContentModelFormatPainterPlugin.ts rename to demo/scripts/controlsV2/plugins/FormatPainterPlugin.ts index fe54d377874..8bf0968bb10 100644 --- a/demo/scripts/controls/contentModel/plugins/ContentModelFormatPainterPlugin.ts +++ b/demo/scripts/controlsV2/plugins/FormatPainterPlugin.ts @@ -1,5 +1,5 @@ -import MainPaneBase from '../../MainPaneBase'; import { applySegmentFormat, getFormatState } from 'roosterjs-content-model-api'; +import { MainPane } from '../mainPane/MainPane'; import { ContentModelSegmentFormat, EditorPlugin, @@ -10,14 +10,14 @@ import { const FORMATPAINTERCURSOR_SVG = require('./formatpaintercursor.svg'); const FORMATPAINTERCURSOR_STYLE = `cursor: url("${FORMATPAINTERCURSOR_SVG}") 8.5 16, auto`; -export default class ContentModelFormatPainterPlugin implements EditorPlugin { +export class FormatPainterPlugin implements EditorPlugin { private editor: IEditor | null = null; private styleNode: HTMLStyleElement | null = null; private painterFormat: ContentModelSegmentFormat | null = null; - private static instance: ContentModelFormatPainterPlugin | undefined; + private static instance: FormatPainterPlugin | undefined; constructor() { - ContentModelFormatPainterPlugin.instance = this; + FormatPainterPlugin.instance = this; } getName() { @@ -64,7 +64,7 @@ export default class ContentModelFormatPainterPlugin implements EditorPlugin { this.painterFormat = format; if (this.painterFormat) { - sheet.insertRule(`#${MainPaneBase.editorDivId} {${FORMATPAINTERCURSOR_STYLE}}`); + sheet.insertRule(`#${MainPane.editorDivId} {${FORMATPAINTERCURSOR_STYLE}}`); } } diff --git a/demo/scripts/controlsV2/plugins/SampleEntityPlugin.ts b/demo/scripts/controlsV2/plugins/SampleEntityPlugin.ts new file mode 100644 index 00000000000..b64402e438b --- /dev/null +++ b/demo/scripts/controlsV2/plugins/SampleEntityPlugin.ts @@ -0,0 +1,142 @@ +import { EntityState } from 'roosterjs-editor-types'; +import { insertEntity } from 'roosterjs-content-model-api'; +import type { EditorPlugin, Entity, IEditor, PluginEvent } from 'roosterjs-content-model-types'; + +const EntityType = 'SampleEntity'; + +interface EntityMetadata { + count: number; +} + +export default class SampleEntityPlugin implements EditorPlugin { + private editor: IEditor; + private hydratedEntities: Record = {}; + + getName() { + return 'SampleEntity'; + } + + initialize(editor: IEditor) { + this.editor = editor; + } + + dispose() { + this.editor = null; + } + + onPluginEvent(event: PluginEvent) { + if (event.eventType == 'keyDown' && event.rawEvent.key == 'm' && event.rawEvent.ctrlKey) { + insertEntity(this.editor, EntityType, true /*isBlock*/, 'focus', { + contentNode: this.createEntityNode(), + initialEntityState: '{}', + }); + + event.rawEvent.preventDefault(); + } else if (event.eventType == 'entityOperation' && event.entity.type == EntityType) { + const entity = event.entity; + const hydratedEntity = this.hydratedEntities[entity.id]; + + switch (event.operation) { + case 'newEntity': + hydratedEntity?.dehydrate(); + this.hydratedEntities[entity.id] = new HydratedEntity(entity, this.onClick); + + event.shouldPersist = true; + break; + + case 'removeFromEnd': + case 'removeFromStart': + case 'overwrite': + case 'replaceTemporaryContent': + hydratedEntity?.dehydrate(); + + break; + + case 'updateEntityState': + if (event.state) { + setMetadata(event.entity.wrapper, JSON.parse(event.state)); + hydratedEntity?.update(); + } + + break; + } + } + } + + private onClick = (state: EntityState) => { + this.editor.takeSnapshot(state); + }; + + private createEntityNode() { + const div = document.createElement('div'); + + return div; + } +} + +class HydratedEntity { + constructor(private entity: Entity, private onClick: (entityState: EntityState) => void) { + const containerDiv = entity.wrapper.querySelector('div'); + const span = document.createElement('span'); + const button = document.createElement('button'); + + containerDiv.appendChild(span); + containerDiv.appendChild(button); + + button.textContent = 'Test entity'; + button.addEventListener('click', this.onClickEntity); + + this.update(); + } + + update(increase: number = 0) { + const metadata = getMetadata(this.entity.wrapper); + const count = (metadata?.count || 0) + increase; + + setMetadata(this.entity.wrapper, { + count, + }); + + this.entity.wrapper.querySelector('span').textContent = 'Count: ' + count; + } + + dehydrate() { + const containerDiv = this.entity.wrapper.querySelector('div'); + const button = containerDiv.querySelector('button'); + + if (button) { + button.removeEventListener('click', this.onClickEntity); + containerDiv.removeChild(button); + } + } + + private onClickEntity = (e: MouseEvent) => { + this.update(1); + this.onClick({ + id: this.entity.id, + type: this.entity.type, + state: this.entity.wrapper.dataset.editingInfo, + }); + }; +} + +const MetadataDataSetName = 'editingInfo'; + +function getMetadata(element: HTMLElement): T | null { + const str = element.dataset[MetadataDataSetName]; + let obj: any; + + try { + obj = str ? JSON.parse(str) : null; + } catch {} + + if (typeof obj !== 'undefined') { + return obj as T; + } else { + return null; + } +} + +function setMetadata(element: HTMLElement, metadata: T) { + element.dataset[MetadataDataSetName] = JSON.stringify(metadata); +} diff --git a/demo/scripts/controlsV2/plugins/UpdateContentPlugin.ts b/demo/scripts/controlsV2/plugins/UpdateContentPlugin.ts new file mode 100644 index 00000000000..c4eef965759 --- /dev/null +++ b/demo/scripts/controlsV2/plugins/UpdateContentPlugin.ts @@ -0,0 +1,60 @@ +import type { + ContentModelDocument, + EditorPlugin, + IEditor, + PluginEvent, +} from 'roosterjs-content-model-types'; + +/** + * A plugin to help get HTML content from editor + */ +export class UpdateContentPlugin implements EditorPlugin { + private editor: IEditor | null = null; + + /** + * Create a new instance of UpdateContentPlugin class + * @param onUpdate A callback to be invoked when update happens + */ + constructor(private onUpdate: (model: ContentModelDocument) => void) {} + + /** + * Get a friendly name of this plugin + */ + getName() { + return 'UpdateContent'; + } + + /** + * Initialize this plugin + * @param editor The editor instance + */ + initialize(editor: IEditor) { + this.editor = editor; + } + + /** + * Dispose this plugin + */ + dispose() { + this.editor = null; + } + + /** + * Handle events triggered from editor + * @param event PluginEvent object + */ + onPluginEvent(event: PluginEvent) { + switch (event.eventType) { + case 'beforeDispose': + this.update(); + break; + } + } + + update() { + if (this.editor) { + const model = this.editor.getContentModelCopy('disconnected'); + this.onUpdate(model); + } + } +} diff --git a/demo/scripts/controlsV2/plugins/createLegacyPlugins.ts b/demo/scripts/controlsV2/plugins/createLegacyPlugins.ts new file mode 100644 index 00000000000..f6cd26cf4dc --- /dev/null +++ b/demo/scripts/controlsV2/plugins/createLegacyPlugins.ts @@ -0,0 +1,55 @@ +import { EditorPlugin as LegacyEditorPlugin, KnownAnnounceStrings } from 'roosterjs-editor-types'; +import { + Announce, + ContentEdit, + CustomReplace, + HyperLink, + ImageEdit, + TableCellSelection, + Watermark, +} from 'roosterjs-editor-plugins'; +import { + LegacyPluginList, + OptionState, + UrlPlaceholder, +} from '../sidePane/editorOptions/OptionState'; + +export function createLegacyPlugins(initState: OptionState): LegacyEditorPlugin[] { + const { pluginList, linkTitle } = initState; + + const plugins: Record = { + contentEdit: pluginList.contentEdit ? new ContentEdit(initState.contentEditFeatures) : null, + hyperlink: pluginList.hyperlink + ? new HyperLink( + linkTitle?.indexOf(UrlPlaceholder) >= 0 + ? url => linkTitle.replace(UrlPlaceholder, url) + : linkTitle + ? () => linkTitle + : null + ) + : null, + watermark: pluginList.watermark ? new Watermark(initState.watermarkText) : null, + imageEdit: pluginList.imageEdit + ? new ImageEdit({ + preserveRatio: initState.forcePreserveRatio, + applyChangesOnMouseUp: initState.applyChangesOnMouseUp, + }) + : null, + tableCellSelection: pluginList.tableCellSelection ? new TableCellSelection() : null, + customReplace: pluginList.customReplace ? new CustomReplace() : null, + announce: pluginList.announce ? new Announce(getDefaultStringsMap()) : null, + }; + + return Object.values(plugins).filter(x => !!x); +} + +function getDefaultStringsMap(): Map { + return new Map([ + [KnownAnnounceStrings.AnnounceListItemBullet, 'Autocorrected Bullet'], + [KnownAnnounceStrings.AnnounceListItemNumbering, 'Autocorrected {0}'], + [ + KnownAnnounceStrings.AnnounceOnFocusLastCell, + 'Warning, pressing tab here adds an extra row.', + ], + ]); +} diff --git a/demo/scripts/controls/contentModel/plugins/formatpaintercursor.svg b/demo/scripts/controlsV2/plugins/formatpaintercursor.svg similarity index 100% rename from demo/scripts/controls/contentModel/plugins/formatpaintercursor.svg rename to demo/scripts/controlsV2/plugins/formatpaintercursor.svg diff --git a/demo/scripts/controlsV2/roosterjsReact/rooster/component/Rooster.tsx b/demo/scripts/controlsV2/roosterjsReact/rooster/component/Rooster.tsx index 05da79f8456..1efdc590789 100644 --- a/demo/scripts/controlsV2/roosterjsReact/rooster/component/Rooster.tsx +++ b/demo/scripts/controlsV2/roosterjsReact/rooster/component/Rooster.tsx @@ -1,27 +1,11 @@ import * as React from 'react'; -import { createUIUtilities, ReactEditorPlugin, UIUtilities } from 'roosterjs-react'; +import { createUIUtilities } from '../../common/index'; import { divProperties, getNativeProps } from '@fluentui/react/lib/Utilities'; -import { EditorAdapter, EditorAdapterOptions } from 'roosterjs-editor-adapter'; -import { EditorOptions, EditorPlugin, IEditor } from 'roosterjs-content-model-types'; +import { Editor } from 'roosterjs-content-model-core'; import { useTheme } from '@fluentui/react/lib/Theme'; -import type { EditorPlugin as LegacyEditorPlugin } from 'roosterjs-editor-types'; - -/** - * Properties for Rooster react component - */ -export interface RoosterProps extends EditorAdapterOptions, React.HTMLAttributes { - /** - * Creator function used for creating the instance of roosterjs editor. - * Use this callback when you have your own sub class of roosterjs Editor or force trigging a reset of editor - */ - editorCreator?: (div: HTMLDivElement, options: EditorOptions) => IEditor; - - /** - * Whether editor should get focus once it is created - * Changing of this value after editor is created will not reset editor - */ - focusOnInit?: boolean; -} +import type { RoosterProps } from '../type/RoosterProps'; +import type { ReactEditorPlugin } from '../../common/index'; +import type { EditorPlugin, IEditor, EditorOptions } from 'roosterjs-content-model-types'; /** * Main component of react wrapper for roosterjs @@ -33,14 +17,17 @@ export function Rooster(props: RoosterProps) { const editor = React.useRef(null); const theme = useTheme(); - const { focusOnInit, editorCreator, inDarkMode, plugins, legacyPlugins } = props; + const { focusOnInit, editorCreator, inDarkMode, plugins } = props; React.useEffect(() => { - if (editorDiv.current) { + if (plugins && editorDiv.current) { const uiUtilities = createUIUtilities(editorDiv.current, theme); - setUIUtilities(uiUtilities, plugins); - setUIUtilities(uiUtilities, legacyPlugins); + plugins.forEach(plugin => { + if (isReactEditorPlugin(plugin)) { + plugin.setUIUtilities(uiUtilities); + } + }); } }, [theme, editorCreator]); @@ -69,23 +56,10 @@ export function Rooster(props: RoosterProps) { return
; } -function setUIUtilities( - uiUtilities: UIUtilities, - plugins: (LegacyEditorPlugin | EditorPlugin)[] | undefined -) { - plugins?.forEach(plugin => { - if (isReactEditorPlugin(plugin)) { - plugin.setUIUtilities(uiUtilities); - } - }); -} - -function defaultEditorCreator(div: HTMLDivElement, options: EditorAdapterOptions) { - return new EditorAdapter(div, options); +function defaultEditorCreator(div: HTMLDivElement, options: EditorOptions) { + return new Editor(div, options); } -function isReactEditorPlugin( - plugin: LegacyEditorPlugin | EditorPlugin -): plugin is ReactEditorPlugin { +function isReactEditorPlugin(plugin: EditorPlugin): plugin is ReactEditorPlugin { return !!(plugin as ReactEditorPlugin)?.setUIUtilities; } diff --git a/demo/scripts/controlsV2/roosterjsReact/rooster/index.ts b/demo/scripts/controlsV2/roosterjsReact/rooster/index.ts index cff86a2e34a..15514ad703e 100644 --- a/demo/scripts/controlsV2/roosterjsReact/rooster/index.ts +++ b/demo/scripts/controlsV2/roosterjsReact/rooster/index.ts @@ -1 +1,2 @@ +export { RoosterProps } from './type/RoosterProps'; export { Rooster } from './component/Rooster'; diff --git a/demo/scripts/controlsV2/roosterjsReact/rooster/type/RoosterProps.ts b/demo/scripts/controlsV2/roosterjsReact/rooster/type/RoosterProps.ts new file mode 100644 index 00000000000..d10231319a6 --- /dev/null +++ b/demo/scripts/controlsV2/roosterjsReact/rooster/type/RoosterProps.ts @@ -0,0 +1,18 @@ +import type { IEditor, EditorOptions } from 'roosterjs-content-model-types'; + +/** + * Properties for Rooster react component + */ +export interface RoosterProps extends EditorOptions, React.HTMLAttributes { + /** + * Creator function used for creating the instance of roosterjs editor. + * Use this callback when you have your own sub class of roosterjs Editor or force trigging a reset of editor + */ + editorCreator?: (div: HTMLDivElement, options: EditorOptions) => IEditor; + + /** + * Whether editor should get focus once it is created + * Changing of this value after editor is created will not reset editor + */ + focusOnInit?: boolean; +} diff --git a/demo/scripts/controls/sidePane/StandaloneSidePane.scss b/demo/scripts/controlsV2/sidePane/SidePane.scss similarity index 96% rename from demo/scripts/controls/sidePane/StandaloneSidePane.scss rename to demo/scripts/controlsV2/sidePane/SidePane.scss index fca6204b4cb..23207277414 100644 --- a/demo/scripts/controls/sidePane/StandaloneSidePane.scss +++ b/demo/scripts/controlsV2/sidePane/SidePane.scss @@ -1,4 +1,4 @@ -@import '../theme/standaloneEditorTheme.scss'; +@import '../theme/theme.scss'; .sidePane { display: flex; diff --git a/demo/scripts/controlsV2/sidePane/SidePane.tsx b/demo/scripts/controlsV2/sidePane/SidePane.tsx new file mode 100644 index 00000000000..887a2b47b08 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/SidePane.tsx @@ -0,0 +1,93 @@ +import * as React from 'react'; +import { SidePanePlugin } from './SidePanePlugin'; + +const styles = require('./SidePane.scss'); + +export interface SidePaneProps { + plugins: SidePanePlugin[]; + className?: string; +} + +export interface SidePaneState { + currentPane: SidePanePlugin; +} + +export class SidePane extends React.Component { + private div = React.createRef(); + + constructor(props: SidePaneProps) { + super(props); + this.state = { + currentPane: this.props.plugins[0], + }; + + window.addEventListener('hashchange', this.updateStateFromHash); + } + + componentDidMount() { + this.updateStateFromHash(); + } + + componentWillUnmount() { + window.removeEventListener('hashchange', this.updateStateFromHash); + } + + render() { + const className = (this.props.className || '') + ' ' + styles.sidePane; + + return ( +
+ {this.props.plugins.map(this.renderSidePane)} +
+ ); + } + + changeWidth(widthDelta: number) { + let div = this.div.current; + if (div) { + div.style.width = div.clientWidth + widthDelta + 'px'; + } + } + + updateHash = (pluginName?: string, path?: string[]) => { + window.location.hash = + (pluginName || this.state.currentPane.getName()) + (path ? '/' + path.join('/') : ''); + }; + + private updateStateFromHash = () => { + let hash = window.location.hash; + let hashes = (hash ? hash.substr(1) : '').split('/'); + let pluginName = hashes[0]; + let plugin = + pluginName && this.props.plugins.filter(plugin => plugin.getName() == pluginName)[0]; + + if (plugin) { + this.setState({ + currentPane: plugin, + }); + + window.setTimeout(() => { + hashes.splice(0, 1); + if (plugin.setHashPath) { + plugin.setHashPath(hashes); + } + }, 0); + } + }; + + private renderSidePane = (plugin: SidePanePlugin): JSX.Element => { + const title = plugin.getTitle(); + const isCurrent = this.state.currentPane == plugin; + + return ( +
+
this.updateHash(plugin.getName())}> + {title} +
+
+
{plugin.renderSidePane(this.updateHash)}
+
+
+ ); + }; +} diff --git a/demo/scripts/controlsV2/sidePane/SidePaneElement.ts b/demo/scripts/controlsV2/sidePane/SidePaneElement.ts new file mode 100644 index 00000000000..9bb59e4f42c --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/SidePaneElement.ts @@ -0,0 +1,7 @@ +export interface SidePaneElementProps { + updateHash: (pluginName?: string, path?: string[]) => void; +} + +export interface SidePaneElement { + setHashPath?: (path: string[]) => void; +} diff --git a/demo/scripts/controlsV2/sidePane/SidePanePlugin.ts b/demo/scripts/controlsV2/sidePane/SidePanePlugin.ts new file mode 100644 index 00000000000..f15a4fd4410 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/SidePanePlugin.ts @@ -0,0 +1,7 @@ +import type { EditorPlugin } from 'roosterjs-content-model-types'; + +export interface SidePanePlugin extends EditorPlugin { + getTitle: () => string; + renderSidePane: (updateHash: (pluginName?: string, path?: string[]) => void) => JSX.Element; + setHashPath?: (path: string[]) => void; +} diff --git a/demo/scripts/controlsV2/sidePane/SidePanePluginImpl.tsx b/demo/scripts/controlsV2/sidePane/SidePanePluginImpl.tsx new file mode 100644 index 00000000000..d33ddadcdd6 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/SidePanePluginImpl.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import { IEditor } from 'roosterjs-content-model-types'; +import { SidePaneElement, SidePaneElementProps } from './SidePaneElement'; +import { SidePanePlugin } from './SidePanePlugin'; + +interface SidePaneComponent

+ extends React.Component, + SidePaneElement {} + +export abstract class SidePanePluginImpl< + T extends SidePaneComponent

, + P extends SidePaneElementProps +> implements SidePanePlugin { + protected editor: IEditor; + private component = React.createRef(); + + constructor( + private readonly componentCtor: { new (props: P): T }, + private readonly pluginName: string, + private readonly title: string + ) {} + + getName() { + return this.pluginName; + } + + initialize(editor: IEditor) { + this.editor = editor; + } + + dispose() { + this.editor = null; + } + + getTitle() { + return this.title; + } + + renderSidePane(updateHash: (pluginName?: string, path?: string[]) => void) { + return React.createElement

(this.componentCtor, { + ...this.getComponentProps({ + updateHash, + }), + ref: this.component, + }); + } + + setHashPath(path: string[]) { + if (this.component.current && this.component.current.setHashPath) { + this.component.current.setHashPath(path); + } + } + + protected abstract getComponentProps(baseProps: SidePaneElementProps): P; + + protected getComponent(callback: (component: T) => void) { + if (this.component.current) { + callback(this.component.current); + } + } +} diff --git a/demo/scripts/controlsV2/sidePane/apiPlayground/ApiPaneProps.ts b/demo/scripts/controlsV2/sidePane/apiPlayground/ApiPaneProps.ts new file mode 100644 index 00000000000..c25f1ef17b2 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/apiPlayground/ApiPaneProps.ts @@ -0,0 +1,10 @@ +import type { SidePaneElementProps } from '../SidePaneElement'; +import type { IEditor, PluginEvent } from 'roosterjs-content-model-types'; + +export interface ApiPaneProps extends SidePaneElementProps { + getEditor: () => IEditor; +} + +export interface ApiPlaygroundComponent { + onPluginEvent?: (e: PluginEvent) => void; +} diff --git a/demo/scripts/controls/sidePane/contentModelApiPlayground/ApiPlaygroundPane.scss b/demo/scripts/controlsV2/sidePane/apiPlayground/ApiPlaygroundPane.scss similarity index 100% rename from demo/scripts/controls/sidePane/contentModelApiPlayground/ApiPlaygroundPane.scss rename to demo/scripts/controlsV2/sidePane/apiPlayground/ApiPlaygroundPane.scss diff --git a/demo/scripts/controls/sidePane/contentModelApiPlayground/ApiPlaygroundPane.tsx b/demo/scripts/controlsV2/sidePane/apiPlayground/ApiPlaygroundPane.tsx similarity index 88% rename from demo/scripts/controls/sidePane/contentModelApiPlayground/ApiPlaygroundPane.tsx rename to demo/scripts/controlsV2/sidePane/apiPlayground/ApiPlaygroundPane.tsx index 52c3bd3ee26..593bf356120 100644 --- a/demo/scripts/controls/sidePane/contentModelApiPlayground/ApiPlaygroundPane.tsx +++ b/demo/scripts/controlsV2/sidePane/apiPlayground/ApiPlaygroundPane.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import apiEntries, { ApiPlaygroundReactComponent } from './apiEntries'; -import ApiPaneProps from './ApiPaneProps'; -import { getObjectKeys } from 'roosterjs-editor-dom'; -import { PluginEvent } from 'roosterjs-editor-types'; +import { ApiPaneProps } from './ApiPaneProps'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import { PluginEvent } from 'roosterjs-content-model-types'; import { SidePaneElement } from '../SidePaneElement'; const styles = require('./ApiPlaygroundPane.scss'); @@ -11,7 +11,7 @@ export interface ApiPlaygroundPaneState { current: string; } -export default class ApiPlaygroundPane extends React.Component +export class ApiPlaygroundPane extends React.Component implements SidePaneElement { private select = React.createRef(); private pane = React.createRef(); diff --git a/demo/scripts/controlsV2/sidePane/apiPlayground/ApiPlaygroundPlugin.ts b/demo/scripts/controlsV2/sidePane/apiPlayground/ApiPlaygroundPlugin.ts new file mode 100644 index 00000000000..440aaa0429d --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/apiPlayground/ApiPlaygroundPlugin.ts @@ -0,0 +1,24 @@ +import { ApiPlaygroundPane } from './ApiPlaygroundPane'; +import { SidePanePluginImpl } from '../SidePanePluginImpl'; +import type { ApiPaneProps } from './ApiPaneProps'; +import type { PluginEvent } from 'roosterjs-content-model-types'; +import type { SidePaneElementProps } from '../SidePaneElement'; + +export class ApiPlaygroundPlugin extends SidePanePluginImpl { + constructor() { + super(ApiPlaygroundPane, 'api', 'API Playground'); + } + + getComponentProps(base: SidePaneElementProps) { + return { + ...base, + getEditor: () => { + return this.editor; + }, + }; + } + + onPluginEvent(e: PluginEvent) { + this.getComponent(component => component.onPluginEvent(e)); + } +} diff --git a/demo/scripts/controls/sidePane/contentModelApiPlayground/apiEntries.ts b/demo/scripts/controlsV2/sidePane/apiPlayground/apiEntries.ts similarity index 81% rename from demo/scripts/controls/sidePane/contentModelApiPlayground/apiEntries.ts rename to demo/scripts/controlsV2/sidePane/apiPlayground/apiEntries.ts index 4bd35162a73..f968198a1c2 100644 --- a/demo/scripts/controls/sidePane/contentModelApiPlayground/apiEntries.ts +++ b/demo/scripts/controlsV2/sidePane/apiPlayground/apiEntries.ts @@ -1,6 +1,6 @@ import * as React from 'react'; -import ApiPaneProps, { ApiPlaygroundComponent } from './ApiPaneProps'; import InsertEntityPane from './insertEntity/InsertEntityPane'; +import { ApiPaneProps, ApiPlaygroundComponent } from './ApiPaneProps'; export interface ApiPlaygroundReactComponent extends React.Component, @@ -8,7 +8,7 @@ export interface ApiPlaygroundReactComponent interface ApiEntry { name: string; - component?: { new (prpos: ApiPaneProps): ApiPlaygroundReactComponent }; + component?: { new (props: ApiPaneProps): ApiPlaygroundReactComponent }; } const apiEntries: { [key: string]: ApiEntry } = { diff --git a/demo/scripts/controls/sidePane/contentModelApiPlayground/insertEntity/InsertEntityPane.scss b/demo/scripts/controlsV2/sidePane/apiPlayground/insertEntity/InsertEntityPane.scss similarity index 100% rename from demo/scripts/controls/sidePane/contentModelApiPlayground/insertEntity/InsertEntityPane.scss rename to demo/scripts/controlsV2/sidePane/apiPlayground/insertEntity/InsertEntityPane.scss diff --git a/demo/scripts/controls/sidePane/contentModelApiPlayground/insertEntity/InsertEntityPane.tsx b/demo/scripts/controlsV2/sidePane/apiPlayground/insertEntity/InsertEntityPane.tsx similarity index 72% rename from demo/scripts/controls/sidePane/contentModelApiPlayground/insertEntity/InsertEntityPane.tsx rename to demo/scripts/controlsV2/sidePane/apiPlayground/insertEntity/InsertEntityPane.tsx index 6595fb0ae64..15d6f7f6f1c 100644 --- a/demo/scripts/controls/sidePane/contentModelApiPlayground/insertEntity/InsertEntityPane.tsx +++ b/demo/scripts/controlsV2/sidePane/apiPlayground/insertEntity/InsertEntityPane.tsx @@ -1,15 +1,17 @@ import * as React from 'react'; -import ApiPaneProps from '../ApiPaneProps'; -import { Entity, IEditor as ILegacyEditor } from 'roosterjs-editor-types'; -import { getEntityFromElement, getEntitySelector } from 'roosterjs-editor-dom'; -import { IEditor, InsertEntityOptions } from 'roosterjs-content-model-types'; +import { ApiPaneProps } from '../ApiPaneProps'; import { insertEntity } from 'roosterjs-content-model-api'; import { trustedHTMLHandler } from '../../../../utils/trustedHTMLHandler'; +import { + ContentModelBlockGroup, + ContentModelEntity, + InsertEntityOptions, +} from 'roosterjs-content-model-types'; const styles = require('./InsertEntityPane.scss'); interface InsertEntityPaneState { - entities: Entity[]; + entities: ContentModelEntity[]; } export default class InsertEntityPane extends React.Component { @@ -86,7 +88,7 @@ export default class InsertEntityPane extends React.Component

{this.state.entities.map(entity => ( - + ))}
@@ -114,7 +116,7 @@ export default class InsertEntityPane extends React.Component { - const selector = getEntitySelector(); - const nodes = this.props.getEditor().queryElements(selector); - const allEntities = nodes.map(node => getEntityFromElement(node)); + const model = this.props.getEditor().getContentModelCopy('connected'); + const allEntities: ContentModelEntity[] = []; + + findAllEntities(model, allEntities); this.setState({ entities: allEntities.filter(e => !!e), @@ -149,7 +152,39 @@ export default class InsertEntityPane extends React.Component { + switch (block.blockType) { + case 'BlockGroup': + findAllEntities(block, result); + break; + + case 'Entity': + result.push(block); + break; + + case 'Paragraph': + block.segments.forEach(segment => { + switch (segment.segmentType) { + case 'Entity': + result.push(segment); + break; + + case 'General': + findAllEntities(segment, result); + break; + } + }); + break; + + case 'Table': + block.rows.forEach(row => row.cells.forEach(cell => findAllEntities(cell, result))); + break; + } + }); +} + +function EntityButton({ entity }: { entity: ContentModelEntity }) { let background = ''; const onMouseOver = React.useCallback(() => { background = entity.wrapper.style.backgroundColor; @@ -161,12 +196,22 @@ function EntityButton({ entity }: { entity: Entity }) { }, [entity]); return ( -
- Type: {entity.type} +
+ Type: {entity.entityFormat.entityType} +
+ Id: {entity.entityFormat.id}
- Id: {entity.id} + Readonly: {entity.entityFormat.isReadonly ? 'True' : 'False'}
- Readonly: {entity.isReadonly ? 'True' : 'False'} + Fake entity: {entity.entityFormat.isFakeEntity ? 'True' : 'False'}
); diff --git a/demo/scripts/controls/sidePane/contentModel/ContentModelPane.scss b/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPane.scss similarity index 100% rename from demo/scripts/controls/sidePane/contentModel/ContentModelPane.scss rename to demo/scripts/controlsV2/sidePane/contentModel/ContentModelPane.scss diff --git a/demo/scripts/controls/sidePane/contentModel/ContentModelPane.tsx b/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPane.tsx similarity index 82% rename from demo/scripts/controls/sidePane/contentModel/ContentModelPane.tsx rename to demo/scripts/controlsV2/sidePane/contentModel/ContentModelPane.tsx index 746eae6c7d9..b7d4e006b90 100644 --- a/demo/scripts/controls/sidePane/contentModel/ContentModelPane.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPane.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { ContentModelDocument } from 'roosterjs-content-model-types'; -import { ContentModelDocumentView } from '../../../controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView'; +import { ContentModelDocumentView } from './components/model/ContentModelDocumentView'; import { exportButton } from './buttons/exportButton'; import { refreshButton } from './buttons/refreshButton'; -import { Ribbon, RibbonButton, RibbonPlugin } from '../../../controlsV2/roosterjsReact/ribbon'; +import { Ribbon, RibbonButton, RibbonPlugin } from '../../roosterjsReact/ribbon'; import { SidePaneElementProps } from '../SidePaneElement'; const styles = require('./ContentModelPane.scss'); @@ -16,7 +16,7 @@ export interface ContentModelPaneProps extends ContentModelPaneState, SidePaneEl ribbonPlugin: RibbonPlugin; } -export default class ContentModelPane extends React.Component< +export class ContentModelPane extends React.Component< ContentModelPaneProps, ContentModelPaneState > { diff --git a/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts b/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPanePlugin.ts similarity index 55% rename from demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts rename to demo/scripts/controlsV2/sidePane/contentModel/ContentModelPanePlugin.ts index 5d68e4c3afd..c56b67835bd 100644 --- a/demo/scripts/controls/sidePane/contentModel/ContentModelPanePlugin.ts +++ b/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPanePlugin.ts @@ -1,26 +1,25 @@ -import ContentModelPane, { ContentModelPaneProps } from './ContentModelPane'; -import SidePanePluginImpl from '../SidePanePluginImpl'; -import { createRibbonPlugin, RibbonPlugin } from '../../../controlsV2/roosterjsReact/ribbon'; -import { IEditor as ILegacyEditor, PluginEvent, PluginEventType } from 'roosterjs-editor-types'; -import { IEditor } from 'roosterjs-content-model-types'; +import { ContentModelPane, ContentModelPaneProps } from './ContentModelPane'; +import { createRibbonPlugin, RibbonPlugin } from '../../roosterjsReact/ribbon'; +import { IEditor, PluginEvent } from 'roosterjs-content-model-types'; import { setCurrentContentModel } from './currentModel'; import { SidePaneElementProps } from '../SidePaneElement'; +import { SidePanePluginImpl } from '../SidePanePluginImpl'; -export default class ContentModelPanePlugin extends SidePanePluginImpl< +export class ContentModelPanePlugin extends SidePanePluginImpl< ContentModelPane, ContentModelPaneProps > { private contentModelRibbon: RibbonPlugin; constructor() { - super(ContentModelPane, 'contentModel', 'Content Model (Under development)'); + super(ContentModelPane, 'contentModel', 'Content Model'); this.contentModelRibbon = createRibbonPlugin(); } - initialize(editor: ILegacyEditor): void { + initialize(editor: IEditor): void { super.initialize(editor); - this.contentModelRibbon.initialize(editor as ILegacyEditor & IEditor); // TODO: Port side pane to use IStandaloneEditor + this.contentModelRibbon.initialize(editor); editor.getDocument().addEventListener('selectionchange', this.onModelChangeFromSelection); } @@ -34,22 +33,21 @@ export default class ContentModelPanePlugin extends SidePanePluginImpl< } onPluginEvent(e: PluginEvent) { - if (e.eventType == PluginEventType.ContentChanged && e.source == 'RefreshModel') { + if (e.eventType == 'contentChanged' && e.source == 'RefreshModel') { this.getComponent(component => { - const model = (this.editor as ILegacyEditor & IEditor).getContentModelCopy( - 'connected' - ); + const model = this.editor.getContentModelCopy('connected'); component.setContentModel(model); setCurrentContentModel(model); }); } else if ( - e.eventType == PluginEventType.Input || - e.eventType == PluginEventType.ContentChanged + e.eventType == 'input' || + e.eventType == 'selectionChanged' || + e.eventType == 'editorReady' ) { this.onModelChange(); } - // this.contentModelRibbon.onPluginEvent(e); + this.contentModelRibbon.onPluginEvent(e); } getInnerRibbonPlugin() { @@ -72,8 +70,7 @@ export default class ContentModelPanePlugin extends SidePanePluginImpl< private onModelChange = () => { this.getComponent(component => { - // TODO: Port to use IStandaloneEditor and remove type cast here - const model = (this.editor as ILegacyEditor & IEditor).getContentModelCopy('connected'); + const model = this.editor.getContentModelCopy('connected'); component.setContentModel(model); setCurrentContentModel(model); }); diff --git a/demo/scripts/controls/sidePane/contentModel/buttons/exportButton.ts b/demo/scripts/controlsV2/sidePane/contentModel/buttons/exportButton.ts similarity index 86% rename from demo/scripts/controls/sidePane/contentModel/buttons/exportButton.ts rename to demo/scripts/controlsV2/sidePane/contentModel/buttons/exportButton.ts index 58aa78e3d35..026da4fecf8 100644 --- a/demo/scripts/controls/sidePane/contentModel/buttons/exportButton.ts +++ b/demo/scripts/controlsV2/sidePane/contentModel/buttons/exportButton.ts @@ -1,5 +1,5 @@ import { getCurrentContentModel } from '../currentModel'; -import { RibbonButton } from '../../../../controlsV2/roosterjsReact/ribbon'; +import { RibbonButton } from '../../../roosterjsReact/ribbon'; export const exportButton: RibbonButton<'buttonNameExport'> = { key: 'buttonNameExport', diff --git a/demo/scripts/controls/sidePane/contentModel/buttons/refreshButton.ts b/demo/scripts/controlsV2/sidePane/contentModel/buttons/refreshButton.ts similarity index 78% rename from demo/scripts/controls/sidePane/contentModel/buttons/refreshButton.ts rename to demo/scripts/controlsV2/sidePane/contentModel/buttons/refreshButton.ts index 0dc931d67f5..5c4be9d6902 100644 --- a/demo/scripts/controls/sidePane/contentModel/buttons/refreshButton.ts +++ b/demo/scripts/controlsV2/sidePane/contentModel/buttons/refreshButton.ts @@ -1,4 +1,4 @@ -import { RibbonButton } from '../../../../controlsV2/roosterjsReact/ribbon'; +import { RibbonButton } from '../../../roosterjsReact/ribbon'; export const refreshButton: RibbonButton<'buttonNameRefresh'> = { key: 'buttonNameRefresh', diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/ContentModelView.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/ContentModelView.scss index 480b778979e..09b0fc33631 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/ContentModelView.scss +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/ContentModelView.scss @@ -10,11 +10,11 @@ flex-direction: column; &.childSelected { - border-color: #0aa; + border-color: #ff99bb; } .selected { - border-color: #00f; + border-color: #aa4466; } } @@ -56,4 +56,5 @@ padding: 4px; display: flex; flex-direction: column; + color: black; } diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelJson.scss b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelJson.scss index a95a69f061a..7809914a215 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelJson.scss +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelJson.scss @@ -1,3 +1,5 @@ +@import '../../../../theme/theme.scss'; + .json { margin: 0; font-size: 13px; diff --git a/demo/scripts/controls/sidePane/contentModel/currentModel.ts b/demo/scripts/controlsV2/sidePane/contentModel/currentModel.ts similarity index 100% rename from demo/scripts/controls/sidePane/contentModel/currentModel.ts rename to demo/scripts/controlsV2/sidePane/contentModel/currentModel.ts diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/Code.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/Code.tsx new file mode 100644 index 00000000000..fb9905828e6 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/Code.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +export interface CodeProps { + code: string; +} + +export class Code extends React.Component { + render() { + return ( +
+
{this.props.code}
+
+ ); + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/ContentEditFeatures.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/ContentEditFeatures.tsx new file mode 100644 index 00000000000..e96cbfdb11f --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/ContentEditFeatures.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import { ContentEditFeatureSettings } from 'roosterjs-editor-types'; +import { getAllFeatures } from 'roosterjs-editor-plugins/lib/ContentEdit'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import { OptionState } from './OptionState'; + +type ContentEditItemId = keyof ContentEditFeatureSettings; + +const styles = require('./OptionsPane.scss'); +const EditFeatureDescriptionMap: Record = { + autoBullet: 'Auto Bullet / Numbering', + indentWhenTab: 'Indent list when Tab', + outdentWhenShiftTab: 'Outdent list when Shift + Tab', + outdentWhenBackspaceOnEmptyFirstLine: 'Outdent list when Backspace on empty first Line', + outdentWhenEnterOnEmptyLine: 'Outdent list when Enter on empty line', + mergeInNewLineWhenBackspaceOnFirstChar: + 'Merge in new line when Backspace on first char in list', + maintainListChain: 'Maintain the continued list numbers', + unquoteWhenBackspaceOnEmptyFirstLine: 'Unquote when Backspace on empty first line', + unquoteWhenEnterOnEmptyLine: 'Unquote when Enter on empty line', + tabInTable: 'Tab to jump cell in table', + upDownInTable: 'Up / Down to jump cell in table', + insertLineBeforeStructuredNodeFeature: + 'Enter to create new line before table/list at beginning of editor content', + autoLink: 'Auto link', + unlinkWhenBackspaceAfterLink: 'Auto unlink when backspace right after a hyperlink', + defaultShortcut: 'Default Shortcuts', + noCycleCursorMove: 'Avoid moving cycle moving cursor when Ctrl+Left/Right', + clickOnEntity: 'Fire an event when click on a readonly entity', + escapeFromEntity: 'Fire an event when Escape from a readonly entity', + enterBeforeReadonlyEntity: 'Start a new line when Enter before an event', + backspaceAfterEntity: 'Fire an event when Backspace after an entity', + deleteBeforeEntity: 'Fire an event when Delete before an event', + markdownBold: 'Markdown style Bolding', + markdownItalic: 'Markdown style Italics', + markdownStrikethru: 'Markdown style Strikethrough', + markdownInlineCode: 'Markdown style Code blocks', + maintainListChainWhenDelete: + 'Maintain the list of number in the right order after press delete before the first item', + indentTableOnTab: 'Indent the table if it is all cells are selected.', + indentWhenTabText: + 'On Tab indent the selection or add Tab, requires TabKeyFeatures Experimental Feature', + outdentWhenTabText: + 'On Shift + Tab outdent the selection, requires TabKeyFeatures Experimental Feature', + autoHyphen: 'Automatically transform -- into hyphen, if typed between two words.', + autoBulletList: + 'When press space after *, -, --, ->, -->, >, => in an empty line, toggle bullet', + autoNumberingList: + 'When press space after an number, a letter or roman number followed by ), ., -, or between parenthesis in an empty line, toggle numbering', + mergeListOnBackspaceAfterList: 'When backspacing between lists, merge the lists', + deleteTableWithBackspace: 'Delete table with backspace key with whole table is selected', + moveBetweenDelimitersFeature: + 'Content edit feature to move the cursor from Delimiters around Entities when using Right or Left Arrow Keys', + removeEntityBetweenDelimiters: + 'When using BACKSPACE or DELETE in a Readonly inline entity delimeter, trigger a Entity Operation', + removeCodeWhenEnterOnEmptyLine: 'Remove code line when enter on empty line', + removeCodeWhenBackspaceOnEmptyFirstLine: 'Remove code line when backspace on empty first line', + indentWhenAltShiftRight: 'Indent list item using Alt + Shift + Right', + outdentWhenAltShiftLeft: 'Outdent list item using Alt + Shift + Left', +}; + +export interface ContentEditFeaturessProps { + state: ContentEditFeatureSettings; + resetState: (callback: (state: OptionState) => void, resetEditor: boolean) => void; +} + +export default class ContentEditFeatures extends React.Component { + render() { + const features = getAllFeatures(); + return ( + + + {getObjectKeys(features).map(key => + this.renderContentEditItem(key, EditFeatureDescriptionMap[key]) + )} + +
+ ); + } + + private renderContentEditItem( + id: ContentEditItemId, + text: string, + moreOptions?: JSX.Element + ): JSX.Element { + const checked = this.props.state[id]; + + return ( + + + this.onContentEditClick(id)} + /> + + +
+ +
+ {checked && moreOptions} + + + ); + } + + private onContentEditClick = (id: ContentEditItemId) => { + this.props.resetState(state => { + let checkbox = document.getElementById(id) as HTMLInputElement; + state.contentEditFeatures[id] = checkbox.checked; + }, true); + }; +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/DefaultFormatPane.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/DefaultFormatPane.tsx new file mode 100644 index 00000000000..4756df115f0 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/DefaultFormatPane.tsx @@ -0,0 +1,145 @@ +import * as React from 'react'; +import { ContentModelSegmentFormat } from 'roosterjs-content-model-types'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import { OptionState } from './OptionState'; + +type ToggleFormatId = 'fontWeight' | 'italic' | 'underline'; +type SelectFormatId = 'textColor' | 'backgroundColor' | 'fontFamily' | 'fontSize'; + +const styles = require('./OptionsPane.scss'); +const NOT_SET = 'NotSet'; + +export interface DefaultFormatProps { + state: ContentModelSegmentFormat; + resetState: (callback: (state: OptionState) => void, resetEditor: boolean) => void; +} + +export class DefaultFormatPane extends React.Component { + render() { + return ( + <> + + + {this.renderFormatItem('fontWeight', 'Bold')} + {this.renderFormatItem('italic', 'Italic')} + {this.renderFormatItem('underline', 'Underline')} + +
+ + + {this.renderSelectItem('fontFamily', 'Font family: ', { + [NOT_SET]: 'Not Set', + Arial: 'Arial', + Calibri: 'Calibri', + 'Courier New': 'Courier New', + Tahoma: 'Tahoma', + 'Times New Roman': 'Times New Roman', + })} + {this.renderSelectItem('fontSize', 'Font size: ', { + [NOT_SET]: 'Not Set', + '8pt': '8', + '10pt': '10', + '12pt': '12', + '16pt': '16', + '20pt': '20', + '36pt': '36', + '72pt': '72', + })} + {this.renderSelectItem('textColor', 'Text color: ', { + [NOT_SET]: 'Not Set', + '#757b80': 'Gray', + '#bd1398': 'Violet', + '#7232ad': 'Purple', + '#006fc9': 'Blue', + '#4ba524': 'Green', + '#e2c501': 'Yellow', + '#d05c12': 'Orange', + '#ff0000': 'Red', + '#ffffff': 'White', + '#000000': 'Black', + })} + {this.renderSelectItem('backgroundColor', 'Back color: ', { + [NOT_SET]: 'Not Set', + '#ffff00': 'Yellow', + '#00ff00': 'Green', + '#00ffff': 'Cyan', + '#ff00ff': 'Purple', + '#0000ff': 'Blue', + '#ff0000': 'Red', + '#bebebe': 'Gray', + '#666666': 'Dark Gray', + '#ffffff': 'White', + '#000000': 'Black', + })} + +
+ + ); + } + + private renderFormatItem(id: ToggleFormatId, text: string): JSX.Element { + let checked = !!this.props.state[id]; + + return ( + + + this.onFormatClick(id)} + /> + + +
+ +
+ + + ); + } + + private renderSelectItem( + id: SelectFormatId, + label: string, + items: { [key: string]: string } + ): JSX.Element { + return ( + + {label} + + + + + ); + } + + private onFormatClick = (id: ToggleFormatId) => { + this.props.resetState(state => { + let checkbox = document.getElementById(id) as HTMLInputElement; + + if (id == 'fontWeight') { + state.defaultFormat.fontWeight = checkbox.checked ? 'bold' : undefined; + } else { + state.defaultFormat[id] = checkbox.checked; + } + }, true); + }; + + private onSelectChanged = (id: SelectFormatId) => { + this.props.resetState(state => { + let value = (document.getElementById(id) as HTMLSelectElement).value; + + state.defaultFormat[id] = value == NOT_SET ? undefined : value; + }, true); + }; +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts new file mode 100644 index 00000000000..d851acb0590 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts @@ -0,0 +1,60 @@ +import { getDefaultContentEditFeatureSettings } from './getDefaultContentEditFeatureSettings'; +import { OptionPaneProps, OptionState, UrlPlaceholder } from './OptionState'; +import { OptionsPane } from './OptionsPane'; +import { SidePaneElementProps } from '../SidePaneElement'; +import { SidePanePluginImpl } from '../SidePanePluginImpl'; + +const initialState: OptionState = { + pluginList: { + autoFormat: true, + edit: true, + paste: true, + shortcut: true, + tableEdit: true, + contextMenu: true, + emoji: true, + pasteOption: true, + sampleEntity: true, + + // Legacy plugins + contentEdit: false, + hyperlink: false, + watermark: false, + imageEdit: false, + tableCellSelection: false, + customReplace: false, + announce: false, + }, + contentEditFeatures: getDefaultContentEditFeatureSettings(), + defaultFormat: {}, + linkTitle: 'Ctrl+Click to follow the link:' + UrlPlaceholder, + watermarkText: 'Type content here ...', + forcePreserveRatio: false, + applyChangesOnMouseUp: false, + isRtl: false, + cacheModel: true, + tableFeaturesContainerSelector: '#' + 'EditorContainer', + allowExcelNoBorderTable: false, + imageMenu: true, + tableMenu: true, + listMenu: true, +}; + +export class EditorOptionsPlugin extends SidePanePluginImpl { + constructor() { + super(OptionsPane, 'options', 'Editor Options'); + } + + getBuildInPluginState(): OptionState { + let result: OptionState; + this.getComponent(component => (result = component.getState())); + return result || initialState; + } + + getComponentProps(base: SidePaneElementProps) { + return { + ...initialState, + ...base, + }; + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/OptionState.ts b/demo/scripts/controlsV2/sidePane/editorOptions/OptionState.ts new file mode 100644 index 00000000000..2fc3eb9767d --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/OptionState.ts @@ -0,0 +1,54 @@ +import type { ContentEditFeatureSettings } from 'roosterjs-editor-types'; +import type { SidePaneElementProps } from '../SidePaneElement'; +import type { ContentModelSegmentFormat } from 'roosterjs-content-model-types'; + +export interface LegacyPluginList { + contentEdit: boolean; + hyperlink: boolean; + watermark: boolean; + imageEdit: boolean; + tableCellSelection: boolean; + customReplace: boolean; + announce: boolean; +} + +export interface NewPluginList { + autoFormat: boolean; + edit: boolean; + paste: boolean; + shortcut: boolean; + tableEdit: boolean; + contextMenu: boolean; + emoji: boolean; + pasteOption: boolean; + sampleEntity: boolean; +} + +export interface BuildInPluginList extends LegacyPluginList, NewPluginList {} + +export interface OptionState { + pluginList: BuildInPluginList; + + // New plugin options + allowExcelNoBorderTable: boolean; + listMenu: boolean; + tableMenu: boolean; + imageMenu: boolean; + + // Legacy plugin options + contentEditFeatures: ContentEditFeatureSettings; + defaultFormat: ContentModelSegmentFormat; + linkTitle: string; + watermarkText: string; + forcePreserveRatio: boolean; + tableFeaturesContainerSelector: string; + + // Editor options + isRtl: boolean; + cacheModel: boolean; + applyChangesOnMouseUp: boolean; +} + +export interface OptionPaneProps extends OptionState, SidePaneElementProps {} + +export const UrlPlaceholder = '$url$'; diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.scss b/demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.scss new file mode 100644 index 00000000000..8cb7419562e --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.scss @@ -0,0 +1,7 @@ +.checkboxColumn { + vertical-align: top; +} + +.defaultFormatLabel { + white-space: nowrap; +} diff --git a/demo/scripts/controls/sidePane/editorOptions/ContentModelOptionsPane.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.tsx similarity index 65% rename from demo/scripts/controls/sidePane/editorOptions/ContentModelOptionsPane.tsx rename to demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.tsx index 18404b5867a..cb728ff3eb2 100644 --- a/demo/scripts/controls/sidePane/editorOptions/ContentModelOptionsPane.tsx +++ b/demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.tsx @@ -1,12 +1,10 @@ import * as React from 'react'; -import BuildInPluginState, { BuildInPluginProps } from '../../BuildInPluginState'; -import Code from './Code'; -import ContentEditFeatures from './ContentEditFeatures'; -import ContentModelEditorCode from './codes/ContentModelEditorCode'; -import ContentModelExperimentalFeaturesPane from './ContentModelExperimentalFeatures'; -import ContentModelPlugins from './ContentModelPlugins'; -import DefaultFormatPane from './DefaultFormat'; -import MainPaneBase from '../../MainPaneBase'; +import { Code } from './Code'; +import { DefaultFormatPane } from './DefaultFormatPane'; +import { EditorCode } from './codes/EditorCode'; +import { LegacyPlugins, Plugins } from './Plugins'; +import { MainPane } from '../../mainPane/MainPane'; +import { OptionPaneProps, OptionState } from './OptionState'; const htmlStart = '\n' + @@ -19,70 +17,52 @@ const htmlButtons = '\n' + '\n' + '\n' + - '\n'; -const darkButton = '\n'; -const htmlEnd = - '\n' + - '\n' + - '\n' + - ''; + '\n' + + '\n' + + '\n'; +'\n'; +const legacyJsCode = + '\n'; +const jsCode = + '\n'; +const htmlEnd = '\n' + ''; -export default class ContentModelOptionsPane extends React.Component< - BuildInPluginProps, - BuildInPluginState -> { +export class OptionsPane extends React.Component { private exportForm = React.createRef(); private exportData = React.createRef(); private rtl = React.createRef(); private cacheModel = React.createRef(); - constructor(props: BuildInPluginProps) { + constructor(props: OptionPaneProps) { super(props); this.state = { ...props }; } render() { + const editorCode = new EditorCode(this.state); + const html = this.getHtml(editorCode.requireLegacyCode()); + return (
-
- -
-
-
-
- Plugins: + Default Format - -
-
- - Content edit features: - -
- Default Format: + Plugins - +
- Experimental features: + Legacy Plugins - +

@@ -108,21 +88,25 @@ export default class ContentModelOptionsPane extends React.Component<

+
+ +
+
+
+
HTML Code: -
- -
{this.getHtml()}
-
-
+
Typescript Code: - +
void, resetEditor: boolean) => { - let state: BuildInPluginState = { + private resetState = (callback: (state: OptionState) => void, resetEditor: boolean) => { + let state: OptionState = { linkTitle: this.state.linkTitle, watermarkText: this.state.watermarkText, pluginList: { ...this.state.pluginList }, contentEditFeatures: { ...this.state.contentEditFeatures }, defaultFormat: { ...this.state.defaultFormat }, - experimentalFeatures: this.state.experimentalFeatures, forcePreserveRatio: this.state.forcePreserveRatio, applyChangesOnMouseUp: this.state.applyChangesOnMouseUp, isRtl: this.state.isRtl, cacheModel: this.state.cacheModel, tableFeaturesContainerSelector: this.state.tableFeaturesContainerSelector, + allowExcelNoBorderTable: this.state.allowExcelNoBorderTable, + listMenu: this.state.listMenu, + tableMenu: this.state.tableMenu, + imageMenu: this.state.imageMenu, }; if (callback) { @@ -160,16 +147,16 @@ export default class ContentModelOptionsPane extends React.Component< } if (resetEditor) { - MainPaneBase.getInstance().resetEditorPlugin(state); + MainPane.getInstance().resetEditorPlugin(state); } }; private onExportRoosterContentModel = () => { - let editor = new ContentModelEditorCode(this.state); + let editor = new EditorCode(this.state); let code = editor.getCode(); let json = { title: 'RoosterJs', - html: this.getHtml(), + html: this.getHtml(editor.requireLegacyCode()), head: '', js: code, js_pre_processor: 'typescript', @@ -183,7 +170,7 @@ export default class ContentModelOptionsPane extends React.Component< this.setState({ isRtl: isRtl, }); - MainPaneBase.getInstance().setPageDirection(isRtl); + MainPane.getInstance().setPageDirection(isRtl); }; private onToggleCacheModel = () => { @@ -192,7 +179,9 @@ export default class ContentModelOptionsPane extends React.Component< }, true); }; - private getHtml() { - return `${htmlStart}${htmlButtons}${darkButton}${htmlEnd}`; + private getHtml(requireLegacyCode: boolean) { + return `${htmlStart}${htmlButtons}${ + requireLegacyCode ? legacyJsCode : '' + }${jsCode}${htmlEnd}`; } } diff --git a/demo/scripts/controls/sidePane/editorOptions/ContentModelPlugins.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx similarity index 55% rename from demo/scripts/controls/sidePane/editorOptions/ContentModelPlugins.tsx rename to demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx index 8ec200746bf..ebc2503dab6 100644 --- a/demo/scripts/controls/sidePane/editorOptions/ContentModelPlugins.tsx +++ b/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx @@ -1,81 +1,28 @@ import * as React from 'react'; -import BuildInPluginState, { BuildInPluginList, UrlPlaceholder } from '../../BuildInPluginState'; - -type PluginItemId = keyof BuildInPluginList; +import ContentEditFeatures from './ContentEditFeatures'; +import { UrlPlaceholder } from './OptionState'; +import type { + BuildInPluginList, + LegacyPluginList, + NewPluginList, + OptionState, +} from './OptionState'; const styles = require('./OptionsPane.scss'); export interface PluginsProps { - state: BuildInPluginState; - resetState: (callback: (state: BuildInPluginState) => void, resetEditor: boolean) => void; + state: OptionState; + resetState: (callback: (state: OptionState) => void, resetEditor: boolean) => void; } -export default class ContentModelPlugins extends React.Component { - private linkTitle = React.createRef(); - private watermarkText = React.createRef(); - private forcePreserveRatio = React.createRef(); - private applyChangesOnMouseUp = React.createRef(); - - render() { - return ( - - - {this.renderPluginItem('contentEdit', 'Content Edit')} - {this.renderPluginItem( - 'hyperlink', - 'Hyperlink Plugin', - this.renderInputBox( - 'Label title: ', - this.linkTitle, - this.props.state.linkTitle, - 'Use "' + UrlPlaceholder + '" for the url string', - (state, value) => (state.linkTitle = value) - ) - )} - {this.renderPluginItem( - 'watermark', - 'Watermark Plugin', - this.renderInputBox( - 'Watermark text: ', - this.watermarkText, - this.props.state.watermarkText, - '', - (state, value) => (state.watermarkText = value) - ) - )} - {this.renderPluginItem( - 'imageEdit', - 'Image Edit Plugin', - this.renderCheckBox( - 'Force preserve ratio', - this.forcePreserveRatio, - this.props.state.forcePreserveRatio, - (state, value) => (state.forcePreserveRatio = value) - ) - )} - {this.renderPluginItem( - 'imageEdit', - 'Image Edit Plugin', - this.renderCheckBox( - 'Apply changed on mouse up', - this.applyChangesOnMouseUp, - this.props.state.applyChangesOnMouseUp, - (state, value) => (state.applyChangesOnMouseUp = value) - ) - )} - {this.renderPluginItem('customReplace', 'Custom Replace Plugin (autocomplete)')} - {this.renderPluginItem( - 'contextMenu', - 'Show customized context menu for special cases' - )} - {this.renderPluginItem('tableCellSelection', 'Table Cell Selection')} - -
- ); - } +abstract class PluginsBase extends React.Component< + PluginsProps, + {} +> { + abstract render(): JSX.Element; - private renderPluginItem( - id: PluginItemId, + protected renderPluginItem( + id: PluginKey, text: string, moreOptions?: JSX.Element ): JSX.Element { @@ -101,12 +48,12 @@ export default class ContentModelPlugins extends React.Component, value: string, placeholder: string, - onChange: (state: BuildInPluginState, value: string) => void + onChange: (state: OptionState, value: string) => void ): JSX.Element { return (
@@ -125,11 +72,11 @@ export default class ContentModelPlugins extends React.Component, value: boolean, - onChange: (state: BuildInPluginState, value: boolean) => void + onChange: (state: OptionState, value: boolean) => void ): JSX.Element { return (
@@ -147,10 +94,125 @@ export default class ContentModelPlugins extends React.Component { + private onPluginClick = (id: PluginKey) => { this.props.resetState(state => { let checkbox = document.getElementById(id) as HTMLInputElement; state.pluginList[id] = checkbox.checked; }, true); }; } + +export class LegacyPlugins extends PluginsBase { + private linkTitle = React.createRef(); + private watermarkText = React.createRef(); + private forcePreserveRatio = React.createRef(); + + render() { + return ( + + + {this.renderPluginItem( + 'contentEdit', + 'Content Edit', + + )} + {this.renderPluginItem( + 'hyperlink', + 'Hyperlink Plugin', + this.renderInputBox( + 'Label title: ', + this.linkTitle, + this.props.state.linkTitle, + 'Use "' + UrlPlaceholder + '" for the url string', + (state, value) => (state.linkTitle = value) + ) + )} + {this.renderPluginItem( + 'watermark', + 'Watermark Plugin', + this.renderInputBox( + 'Watermark text: ', + this.watermarkText, + this.props.state.watermarkText, + '', + (state, value) => (state.watermarkText = value) + ) + )} + {this.renderPluginItem( + 'imageEdit', + 'Image Edit Plugin', + this.renderCheckBox( + 'Force preserve ratio', + this.forcePreserveRatio, + this.props.state.forcePreserveRatio, + (state, value) => (state.forcePreserveRatio = value) + ) + )} + {this.renderPluginItem('customReplace', 'Custom Replace Plugin (autocomplete)')} + {this.renderPluginItem('tableCellSelection', 'Table Cell Selection')} + {this.renderPluginItem('announce', 'Announce')} + +
+ ); + } +} + +export class Plugins extends PluginsBase { + private allowExcelNoBorderTable = React.createRef(); + private listMenu = React.createRef(); + private tableMenu = React.createRef(); + private imageMenu = React.createRef(); + + render(): JSX.Element { + return ( + + + {this.renderPluginItem('autoFormat', 'AutoFormat')} + {this.renderPluginItem('edit', 'Edit')} + {this.renderPluginItem( + 'paste', + 'Paste', + this.renderCheckBox( + 'Do not add border for Excel table', + this.allowExcelNoBorderTable, + this.props.state.allowExcelNoBorderTable, + (state, value) => (state.allowExcelNoBorderTable = value) + ) + )} + {this.renderPluginItem('shortcut', 'Shortcut')} + {this.renderPluginItem('tableEdit', 'TableEdit')} + {this.renderPluginItem( + 'contextMenu', + 'ContextMenu', + <> + {this.renderCheckBox( + 'List menu', + this.listMenu, + this.props.state.listMenu, + (state, value) => (state.listMenu = value) + )} + {this.renderCheckBox( + 'Table menu', + this.tableMenu, + this.props.state.tableMenu, + (state, value) => (state.tableMenu = value) + )} + {this.renderCheckBox( + 'Image menu', + this.imageMenu, + this.props.state.imageMenu, + (state, value) => (state.imageMenu = value) + )} + + )} + {this.renderPluginItem('emoji', 'Emoji')} + {this.renderPluginItem('pasteOption', 'PasteOptions')} + {this.renderPluginItem('sampleEntity', 'SampleEntity')} + +
+ ); + } +} diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/ContentModelButtonsCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ButtonsCode.ts similarity index 58% rename from demo/scripts/controls/sidePane/editorOptions/codes/ContentModelButtonsCode.ts rename to demo/scripts/controlsV2/sidePane/editorOptions/codes/ButtonsCode.ts index 7455e77389f..4b7dfc326e5 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/ContentModelButtonsCode.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ButtonsCode.ts @@ -1,5 +1,5 @@ -import CodeElement from './CodeElement'; -import { getObjectKeys } from 'roosterjs-editor-dom'; +import { CodeElement } from './CodeElement'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; const codeMap: { [id: string]: string } = { buttonB: 'roosterjsContentModel.toggleBold(editor)', @@ -7,14 +7,15 @@ const codeMap: { [id: string]: string } = { buttonU: 'roosterjsContentModel.toggleUnderline(editor)', buttonBullet: 'roosterjsContentModel.toggleBullet(editor)', buttonNumbering: 'roosterjsContentModel.toggleNumbering(editor)', - buttonUndo: 'editor.undo()', - buttonRedo: 'editor.redo()', + buttonUndo: 'roosterjsContentModel.undo(editor)', + buttonRedo: 'roosterjsContentModel.redo(editor)', + buttonTable: 'roosterjsContentModel.insertTable(editor, 3, 3)', + buttonDark: 'editor.setDarkModeState(!editor.isDarkMode())', }; -const buttonDark = 'editor.setDarkModeState(!editor.isDarkMode())'; -export default class ContentModelButtonsCode extends CodeElement { +export class ButtonsCode extends CodeElement { getCode() { - const map = { ...codeMap, buttonDark: buttonDark }; + const map = { ...codeMap }; return getObjectKeys(map) .map( id => diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/CodeElement.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/CodeElement.ts new file mode 100644 index 00000000000..958d021e3ba --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/CodeElement.ts @@ -0,0 +1,14 @@ +export abstract class CodeElement { + abstract getCode(): string; + + protected encode(src: string): string { + return src.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); + } + + protected indent(src: string): string { + return src + .split('\n') + .map(line => (line == '' ? '' : ' ' + line + '\n')) + .join(''); + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditCode.ts new file mode 100644 index 00000000000..bf5cf63e53d --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditCode.ts @@ -0,0 +1,16 @@ +import { CodeElement } from './CodeElement'; +import { ContentEditFeaturesCode } from './ContentEditFeaturesCode'; +import { ContentEditFeatureSettings } from 'roosterjs-editor-types'; + +export class ContentEditCode extends CodeElement { + private features: ContentEditFeaturesCode; + + constructor(settings: ContentEditFeatureSettings) { + super(); + this.features = new ContentEditFeaturesCode(settings); + } + + getCode() { + return 'new roosterjs.ContentEdit(' + this.features.getCode() + ')'; + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditFeaturesCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditFeaturesCode.ts new file mode 100644 index 00000000000..0ec3e082a84 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditFeaturesCode.ts @@ -0,0 +1,24 @@ +import { CodeElement } from './CodeElement'; +import { ContentEditFeatureSettings } from 'roosterjs-editor-types'; +import { getDefaultContentEditFeatureSettings } from '../getDefaultContentEditFeatureSettings'; +import { getObjectKeys } from 'roosterjs-editor-dom'; + +export class ContentEditFeaturesCode extends CodeElement { + constructor(private state: ContentEditFeatureSettings) { + super(); + } + + getCode() { + let defaultValues = getDefaultContentEditFeatureSettings(); + let features = getObjectKeys(defaultValues) + .map(key => { + let checked = this.state[key]; + + return typeof checked != 'boolean' || checked == defaultValues[key] + ? null + : `${key}: ${checked ? 'true' : 'false'},\n`; + }) + .filter(line => !!line); + return features.length > 0 ? '{\n' + this.indent(features.join('')) + '}' : ''; + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/DarkModeCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/DarkModeCode.ts new file mode 100644 index 00000000000..0c2f9a4b852 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/DarkModeCode.ts @@ -0,0 +1,7 @@ +import { CodeElement } from './CodeElement'; + +export class DarkModeCode extends CodeElement { + getCode() { + return 'roosterjs.getDarkColor'; + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/DefaultFormatCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/DefaultFormatCode.ts new file mode 100644 index 00000000000..33b5cf63e3d --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/DefaultFormatCode.ts @@ -0,0 +1,31 @@ +import { CodeElement } from './CodeElement'; +import { ContentModelSegmentFormat } from 'roosterjs-content-model-types'; + +export class DefaultFormatCode extends CodeElement { + constructor(private defaultFormat: ContentModelSegmentFormat) { + super(); + } + + getCode() { + let { + fontWeight, + italic, + underline, + fontFamily, + fontSize, + textColor, + backgroundColor, + } = this.defaultFormat; + let lines = [ + fontWeight ? `fontWeight: '${fontWeight}',\n` : null, + italic ? 'italic: true,\n' : null, + underline ? 'underline: true,\n' : null, + fontFamily ? `fontFamily: '${this.encode(fontFamily)}',\n` : null, + fontSize ? `fontSize: '${this.encode(fontSize)}',\n` : null, + textColor ? `textColor: '${this.encode(textColor)}',\n` : null, + backgroundColor ? `backgroundColor: '${this.encode(backgroundColor)}',\n` : null, + ].filter(line => !!line); + + return lines.length > 0 ? '{\n' + this.indent(lines.join('')) + '}' : ''; + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/EditorCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/EditorCode.ts new file mode 100644 index 00000000000..5b1c5818511 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/EditorCode.ts @@ -0,0 +1,52 @@ +import { ButtonsCode } from './ButtonsCode'; +import { CodeElement } from './CodeElement'; +import { DarkModeCode } from './DarkModeCode'; +import { DefaultFormatCode } from './DefaultFormatCode'; +import { LegacyPluginCode, PluginsCode } from './PluginsCode'; +import type { OptionState } from '../OptionState'; + +export class EditorCode extends CodeElement { + private plugins: PluginsCode; + private legacyPlugins: LegacyPluginCode; + private defaultFormat: DefaultFormatCode; + private buttons: ButtonsCode; + private darkMode: DarkModeCode; + + constructor(state: OptionState) { + super(); + + this.plugins = new PluginsCode(state); + this.legacyPlugins = new LegacyPluginCode(state); + this.defaultFormat = new DefaultFormatCode(state.defaultFormat); + this.buttons = new ButtonsCode(); + this.darkMode = new DarkModeCode(); + } + + requireLegacyCode() { + return this.legacyPlugins.getPluginCount() > 0 || !!this.darkMode; + } + + getCode() { + let defaultFormat = this.defaultFormat.getCode(); + let code = "let contentDiv = document.getElementById('contentDiv') as HTMLDivElement;\n"; + let darkMode = this.darkMode.getCode(); + + const hasLegacyPlugin = this.legacyPlugins.getPluginCount() > 0; + + code += `let plugins = ${this.plugins.getCode()};\n`; + code += hasLegacyPlugin ? `let legacyPlugins = ${this.legacyPlugins.getCode()};\n` : ''; + code += defaultFormat ? `let defaultSegmentFormat = ${defaultFormat};\n` : ''; + code += 'let options: roosterjs.EditorOptions = {\n'; + code += this.indent('plugins: plugins,\n'); + code += hasLegacyPlugin ? this.indent('legacyPlugins: legacyPlugins,\n') : ''; + code += defaultFormat ? this.indent('defaultSegmentFormat: defaultSegmentFormat,\n') : ''; + code += darkMode ? this.indent(`getDarkColor: ${darkMode},\n`) : ''; + code += '};\n'; + code += `let editor = new ${ + hasLegacyPlugin ? 'roosterjs.EditorAdapter' : 'roosterjsContentModel.Editor' + }(contentDiv, options);\n`; + code += this.buttons ? this.buttons.getCode() : ''; + + return code; + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/HyperLinkCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/HyperLinkCode.ts new file mode 100644 index 00000000000..e4361c4f7c0 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/HyperLinkCode.ts @@ -0,0 +1,32 @@ +import { CodeElement } from './CodeElement'; +import { UrlPlaceholder } from '../OptionState'; + +export class HyperLinkCode extends CodeElement { + constructor(private linkTitle: string) { + super(); + } + + getCode() { + return 'new roosterjs.HyperLink(' + this.getLinkCallback() + ')'; + } + + private getLinkCallback() { + if (!this.linkTitle) { + return ''; + } + + let index = this.linkTitle.indexOf(UrlPlaceholder); + if (index >= 0) { + let left = this.linkTitle.substr(0, index); + let right = this.linkTitle.substr(index + UrlPlaceholder.length); + return ( + 'url => ' + + (left ? `'${this.encode(left)}' + ` : '') + + 'url' + + (right ? ` + '${this.encode(right)}'` : '') + ); + } else { + return `() => '${this.linkTitle}'`; + } + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/PluginsCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/PluginsCode.ts new file mode 100644 index 00000000000..7b298b17e6b --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/PluginsCode.ts @@ -0,0 +1,66 @@ +import { CodeElement } from './CodeElement'; +import { ContentEditCode } from './ContentEditCode'; +import { HyperLinkCode } from './HyperLinkCode'; +import { OptionState } from '../OptionState'; +import { WatermarkCode } from './WatermarkCode'; +import { + AutoFormatPluginCode, + CustomReplaceCode, + EditPluginCode, + ImageEditCode, + PastePluginCode, + TableCellSelectionCode, + TableEditPluginCode, + ShortcutPluginCode, +} from './SimplePluginCode'; + +export class PluginsCodeBase extends CodeElement { + private plugins: CodeElement[]; + constructor(plugins: CodeElement[]) { + super(); + + this.plugins = plugins.filter(plugin => !!plugin); + } + + getPluginCount() { + return this.plugins.length; + } + + getCode() { + let code = '[\n'; + code += this.indent(this.plugins.map(plugin => plugin.getCode() + ',\n').join('')); + code += ']'; + return code; + } +} + +export class PluginsCode extends PluginsCodeBase { + constructor(state: OptionState) { + const pluginList = state.pluginList; + + super([ + pluginList.autoFormat && new AutoFormatPluginCode(), + pluginList.edit && new EditPluginCode(), + pluginList.paste && new PastePluginCode(), + pluginList.tableEdit && new TableEditPluginCode(), + pluginList.shortcut && new ShortcutPluginCode(), + ]); + } +} + +export class LegacyPluginCode extends PluginsCodeBase { + constructor(state: OptionState) { + const pluginList = state.pluginList; + + const plugins: CodeElement[] = [ + pluginList.contentEdit && new ContentEditCode(state.contentEditFeatures), + pluginList.hyperlink && new HyperLinkCode(state.linkTitle), + pluginList.watermark && new WatermarkCode(state.watermarkText), + pluginList.imageEdit && new ImageEditCode(), + pluginList.customReplace && new CustomReplaceCode(), + pluginList.tableCellSelection && new TableCellSelectionCode(), + ]; + + super(plugins); + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/SimplePluginCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/SimplePluginCode.ts new file mode 100644 index 00000000000..5dc6d8d2f36 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/SimplePluginCode.ts @@ -0,0 +1,59 @@ +import { CodeElement } from './CodeElement'; + +class SimplePluginCode extends CodeElement { + constructor(private name: string, private namespace: string = 'roosterjsContentModel') { + super(); + } + + getCode() { + return `new ${this.namespace}.${this.name}()`; + } +} + +export class AutoFormatPluginCode extends SimplePluginCode { + constructor() { + super('AutoFormatPlugin'); + } +} + +export class EditPluginCode extends SimplePluginCode { + constructor() { + super('EditPlugin'); + } +} + +export class PastePluginCode extends SimplePluginCode { + constructor() { + super('PastePlugin'); + } +} + +export class ShortcutPluginCode extends SimplePluginCode { + constructor() { + super('ShortcutPlugin'); + } +} + +export class TableEditPluginCode extends SimplePluginCode { + constructor() { + super('TableEditPlugin'); + } +} + +export class ImageEditCode extends SimplePluginCode { + constructor() { + super('ImageEdit', 'roosterjs'); + } +} + +export class CustomReplaceCode extends SimplePluginCode { + constructor() { + super('CustomReplace', 'roosterjs'); + } +} + +export class TableCellSelectionCode extends SimplePluginCode { + constructor() { + super('TableCellSelection', 'roosterjs'); + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/WatermarkCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/WatermarkCode.ts new file mode 100644 index 00000000000..fbc1a9763c8 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/WatermarkCode.ts @@ -0,0 +1,11 @@ +import { CodeElement } from './CodeElement'; + +export class WatermarkCode extends CodeElement { + constructor(private watermarkText: string) { + super(); + } + + getCode() { + return `new roosterjs.Watermark('${this.encode(this.watermarkText)}')`; + } +} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts b/demo/scripts/controlsV2/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts new file mode 100644 index 00000000000..310789e1dee --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts @@ -0,0 +1,31 @@ +import { ContentEditFeatureSettings } from 'roosterjs-editor-types'; +import { getAllFeatures } from 'roosterjs-editor-plugins'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; + +const listFeatures = { + autoBullet: false, + indentWhenTab: false, + outdentWhenShiftTab: false, + outdentWhenBackspaceOnEmptyFirstLine: false, + outdentWhenEnterOnEmptyLine: false, + mergeInNewLineWhenBackspaceOnFirstChar: false, + maintainListChain: false, + maintainListChainWhenDelete: false, + autoNumberingList: false, + autoBulletList: false, + mergeListOnBackspaceAfterList: false, + outdentWhenAltShiftLeft: false, + indentWhenAltShiftRight: false, +}; + +export function getDefaultContentEditFeatureSettings(): ContentEditFeatureSettings { + const allFeatures = getAllFeatures(); + + return { + ...getObjectKeys(allFeatures).reduce((settings, key) => { + settings[key] = !allFeatures[key].defaultDisabled; + return settings; + }, {}), + ...listFeatures, + }; +} diff --git a/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.scss b/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.scss new file mode 100644 index 00000000000..b440db5a7da --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.scss @@ -0,0 +1,14 @@ +.img { + max-width: 100%; + max-height: 300px; +} + +.pasteContent { + font-family: 'Courier New'; + font-size: 10.5pt; + margin: 10px; +} + +.eventContent { + margin-left: 20px; +} diff --git a/demo/scripts/controls/sidePane/eventViewer/ContentModelEventViewPane.tsx b/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.tsx similarity index 58% rename from demo/scripts/controls/sidePane/eventViewer/ContentModelEventViewPane.tsx rename to demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.tsx index 06a70871549..5e14ff41c6b 100644 --- a/demo/scripts/controls/sidePane/eventViewer/ContentModelEventViewPane.tsx +++ b/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.tsx @@ -1,13 +1,8 @@ import * as React from 'react'; -import { EntityOperation, PluginEvent, PluginEventType } from 'roosterjs-editor-types'; +import { getObjectKeys } from 'roosterjs-content-model-dom'; +import { readFile } from 'roosterjs-content-model-core'; import { SidePaneElementProps } from '../SidePaneElement'; -import { - getObjectKeys, - getTagOfNode, - HtmlSanitizer, - readFile, - safeInstanceOf, -} from 'roosterjs-editor-dom'; +import type { PluginEvent } from 'roosterjs-content-model-types'; const styles = require('./EventViewPane.scss'); @@ -22,48 +17,6 @@ export interface EventViewPaneState { currentIndex: number; } -const EventTypeMap: { [key in PluginEventType]: string } = { - [PluginEventType.BeforeDispose]: 'BeforeDispose', - [PluginEventType.BeforePaste]: 'BeforePaste', - [PluginEventType.CompositionEnd]: 'CompositionEnd', - [PluginEventType.ContentChanged]: 'ContentChanged', - [PluginEventType.EditorReady]: 'EditorReady', - [PluginEventType.EntityOperation]: 'EntityOperation', - [PluginEventType.ExtractContentWithDom]: 'ExtractContentWithDom', - [PluginEventType.KeyDown]: 'KeyDown', - [PluginEventType.KeyPress]: 'KeyPress', - [PluginEventType.KeyUp]: 'KeyUp', - [PluginEventType.MouseDown]: 'MouseDown', - [PluginEventType.MouseUp]: 'MouseUp', - [PluginEventType.Input]: 'Input', - [PluginEventType.PendingFormatStateChanged]: 'PendingFormatStateChanged', - [PluginEventType.Scroll]: 'Scroll', - [PluginEventType.BeforeCutCopy]: 'BeforeCutCopy', - [PluginEventType.ContextMenu]: 'ContextMenu', - [PluginEventType.EnteredShadowEdit]: 'EnteredShadowEdit', - [PluginEventType.LeavingShadowEdit]: 'LeavingShadowEdit', - [PluginEventType.EditImage]: 'EditImage', - [PluginEventType.BeforeSetContent]: 'BeforeSetContent', - [PluginEventType.ZoomChanged]: 'ZoomChanged', - [PluginEventType.SelectionChanged]: 'SelectionChanged', - [PluginEventType.BeforeKeyboardEditing]: 'BeforeKeyboardEditing', -}; - -const EntityOperationMap: { [key in EntityOperation]: string } = { - [EntityOperation.AddShadowRoot]: 'AddShadowRoot', - [EntityOperation.RemoveShadowRoot]: 'RemoveShadowRoot', - [EntityOperation.Click]: 'Click', - [EntityOperation.ContextMenu]: 'ContextMenu', - [EntityOperation.Escape]: 'Escape', - [EntityOperation.NewEntity]: 'NewEntity', - [EntityOperation.Overwrite]: 'Overwrite', - [EntityOperation.PartialOverwrite]: 'PartialOverwrite', - [EntityOperation.RemoveFromEnd]: 'RemoveFromEnd', - [EntityOperation.RemoveFromStart]: 'RemoveFromStart', - [EntityOperation.ReplaceTemporaryContent]: 'ReplaceTemporaryContent', - [EntityOperation.UpdateEntityState]: 'UpdateEntityState', -}; - export default class ContentModelEventViewPane extends React.Component< SidePaneElementProps, EventViewPaneState @@ -106,7 +59,7 @@ export default class ContentModelEventViewPane extends React.Component<
{`${event.time.getHours()}:${event.time.getMinutes()}:${event.time.getSeconds()}.${event.time.getMilliseconds()} `} - {EventTypeMap[event.event.eventType]} + {event.event.eventType}
{this.renderEvent(event.event)} @@ -120,15 +73,6 @@ export default class ContentModelEventViewPane extends React.Component< addEvent(event: PluginEvent) { if (this.state.displayCount > 0) { - if (event.eventType == PluginEventType.BeforePaste) { - const sanitizer = new HtmlSanitizer(event.sanitizingOption); - const fragment = event.fragment.cloneNode(true /*deep*/) as DocumentFragment; - - sanitizer.convertGlobalCssToInlineCss(fragment); - sanitizer.sanitize(fragment); - (event.clipboardData as any).html = this.getHtml(fragment); - } - this.events.push({ time: new Date(), event: event, @@ -146,31 +90,31 @@ export default class ContentModelEventViewPane extends React.Component< private renderEvent(event: PluginEvent): JSX.Element { switch (event.eventType) { - case PluginEventType.KeyDown: - case PluginEventType.KeyPress: - case PluginEventType.KeyUp: + case 'keyDown': + case 'keyPress': + case 'keyUp': return ( Key= - {event.rawEvent.which} + {event.rawEvent.key} ); - case PluginEventType.MouseDown: - case PluginEventType.MouseUp: - case PluginEventType.ContextMenu: + case 'mouseDown': + case 'mouseUp': + case 'contextMenu': return ( Button= {event.rawEvent.button}, SrcElement= - {event.rawEvent.target && getTagOfNode(event.rawEvent.target as Node)}, + {event.rawEvent.target && (event.rawEvent.target as HTMLElement).tagName}, PageX= {event.rawEvent.pageX}, PageY= {event.rawEvent.pageY} ); - case PluginEventType.ContentChanged: + case 'contentChanged': return ( Source= @@ -179,16 +123,13 @@ export default class ContentModelEventViewPane extends React.Component< ); - case PluginEventType.BeforePaste: + case 'beforePaste': return ( Types= {event.clipboardData.types.join()} {this.renderPasteContent('Plain text', event.clipboardData.text)} - {this.renderPasteContent( - 'Sanitized HTML', - (event.clipboardData as any).html - )} + {this.renderPasteContent('Sanitized HTML', event.clipboardData.html)} {this.renderPasteContent('Original HTML', event.clipboardData.rawHtml)} {this.renderPasteContent('Image', event.clipboardData.image, img => ( ); - case PluginEventType.PendingFormatStateChanged: - const formatState = event.formatState; - const keys = getObjectKeys(formatState); - return {keys.map(key => `${key}=${event.formatState[key]}; `)}; - case PluginEventType.EntityOperation: + case 'entityOperation': const { operation, entity: { id, type }, } = event; return ( - Operation={EntityOperationMap[operation]} Type={type}; Id={id} + Operation={operation} Type={type}; Id={id} ); - case PluginEventType.BeforeCutCopy: + case 'beforeCutCopy': const { isCut } = event; return isCut={isCut ? 'true' : 'false'}; - case PluginEventType.EditImage: + case 'editImage': return ( <> new src={event.newSrc.substr(0, 100)} ); - case PluginEventType.ZoomChanged: - return ( - - Old value={event.oldZoomScale} New value={event.newZoomScale} - - ); + case 'zoomChanged': + return New value={event.newZoomScale}; - case PluginEventType.BeforeKeyboardEditing: + case 'beforeKeyboardEditing': return Key code={event.rawEvent.which}; - case PluginEventType.Input: + case 'input': return Input type={event.rawEvent.inputType}; default: @@ -289,19 +222,4 @@ export default class ContentModelEventViewPane extends React.Component< ) ); } - - private getHtml(fragment: DocumentFragment) { - const stringArray: string[] = []; - for (let child = fragment.firstChild; child; child = child.nextSibling) { - stringArray.push( - safeInstanceOf(child, 'HTMLElement') - ? child.outerHTML - : safeInstanceOf(child, 'Text') - ? child.nodeValue - : '' - ); - } - - return stringArray.join(''); - } } diff --git a/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPlugin.ts b/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPlugin.ts new file mode 100644 index 00000000000..b2c19366e5d --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPlugin.ts @@ -0,0 +1,18 @@ +import EventViewPane from './EventViewPane'; +import { PluginEvent } from 'roosterjs-content-model-types'; +import { SidePaneElementProps } from '../SidePaneElement'; +import { SidePanePluginImpl } from '../SidePanePluginImpl'; + +export class EventViewPlugin extends SidePanePluginImpl { + constructor() { + super(EventViewPane, 'event', 'Event Viewer'); + } + + onPluginEvent(e: PluginEvent) { + this.getComponent(component => component.addEvent(e)); + } + + getComponentProps(base: SidePaneElementProps) { + return base; + } +} diff --git a/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.scss b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.scss new file mode 100644 index 00000000000..9db9fb17f96 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.scss @@ -0,0 +1,17 @@ +.inactive { + color: #eee; +} + +.title { + font-weight: bold; +} + +@media (prefers-color-scheme: dark) { + .inactive { + color: #555; + } +} + +:global(.dark) .inactive { + color: #555; +} diff --git a/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx new file mode 100644 index 00000000000..d6cd8cc9658 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import { ContentModelFormatState, EditorEnvironment } from 'roosterjs-content-model-types'; +import { SidePaneElementProps } from '../SidePaneElement'; + +const styles = require('./FormatStatePane.scss'); + +export interface FormatStatePaneState { + format: ContentModelFormatState; + x: number; + y: number; +} +export interface FormatStatePaneProps extends FormatStatePaneState, SidePaneElementProps { + env?: EditorEnvironment; +} + +export default class FormatStatePane extends React.Component< + FormatStatePaneProps, + FormatStatePaneState +> { + constructor(props: FormatStatePaneProps) { + super(props); + this.state = { + format: props.format, + x: props.x, + y: props.y, + }; + } + + setFormatState(state: FormatStatePaneState) { + this.setState(state); + } + + render() { + const { format, x, y } = this.state; + const { isMac, isAndroid, isSafari, isMobileOrTablet } = this.props.env ?? {}; + + return format ? ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Position{`${x},${y}`}
Font + {`${format.fontName}, ${format.fontSize}`} +
Colors + {`${format.textColor} / ${format.backgroundColor}`} +
Formats + {this.renderSpan(format.isBold, 'Bold')} + {this.renderSpan(format.isItalic, 'Italic')} + {this.renderSpan(format.isUnderline, 'Underline')} + {this.renderSpan(format.isStrikeThrough, 'Strike')} + {this.renderSpan(format.isSubscript, 'Subscript')} + {this.renderSpan(format.isSuperscript, 'Superscript')} +
Font weight{format.fontWeight}
Structure + {this.renderSpan(format.isBullet, 'Bullet')} + {this.renderSpan(format.isNumbering, 'Numbering')} + {this.renderSpan(format.isBlockQuote, 'Quote')} + {this.renderSpan(format.canUnlink, 'In Link')} + {this.renderSpan(format.canAddImageAltText, 'In Image')} + {this.renderSpan(format.isInTable, 'In Table')} + {this.renderSpan(format.tableHasHeader, 'Table Has Header')} +
Heading{format.headingLevel}
Undo + {this.renderSpan(format.canUndo, 'Can Undo')} + {this.renderSpan(format.canRedo, 'Can Redo')} +
Environment + {this.renderSpan(isMac, 'MacOS')} + {this.renderSpan(isAndroid, 'Android')} + {this.renderSpan(isSafari, 'Safari')} + {this.renderSpan(isMobileOrTablet, 'Mobile or Tablet')} +
User Agent{window.navigator.userAgent}
App Version{window.navigator.appVersion}
+ ) : ( +
Please focus into editor
+ ); + } + + private renderSpan(formatState: boolean, text: string): JSX.Element { + return {text + ' '}; + } +} diff --git a/demo/scripts/controlsV2/sidePane/formatState/FormatStatePlugin.ts b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePlugin.ts new file mode 100644 index 00000000000..c4ab238389c --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePlugin.ts @@ -0,0 +1,63 @@ +import FormatStatePane, { FormatStatePaneProps, FormatStatePaneState } from './FormatStatePane'; +import { getFormatState } from 'roosterjs-content-model-api'; +import { getPositionRect } from '../../roosterjsReact/pasteOptions/utils/getPositionRect'; +import { PluginEvent } from 'roosterjs-content-model-types'; +import { SidePaneElementProps } from '../SidePaneElement'; +import { SidePanePluginImpl } from '../SidePanePluginImpl'; + +export class FormatStatePlugin extends SidePanePluginImpl { + constructor() { + super(FormatStatePane, 'format', 'Format State'); + } + + getComponentProps(base: SidePaneElementProps) { + return { + ...base, + ...this.getFormatState(), + env: this.editor?.getEnvironment(), + }; + } + + onPluginEvent(event: PluginEvent) { + switch (event.eventType) { + case 'editorReady': + case 'keyUp': + case 'mouseUp': + case 'contentChanged': + this.updateFormatState(); + break; + } + } + + updateFormatState() { + this.getComponent(component => component.setFormatState(this.getFormatState())); + } + + private getFormatState(): FormatStatePaneState { + if (!this.editor) { + return null; + } + + const format = getFormatState(this.editor); + const selection = this.editor?.getDOMSelection(); + let x = 0; + let y = 0; + + if (selection?.type == 'range') { + const node = selection.isReverted + ? selection.range.startContainer + : selection.range.endContainer; + const offset = selection.isReverted + ? selection.range.startOffset + : selection.range.endOffset; + const rect = getPositionRect(node, offset); + + if (rect) { + x = rect.left; + y = rect.top; + } + } + + return { format, x, y }; + } +} diff --git a/demo/scripts/controlsV2/sidePane/snapshot/SnapshotPane.scss b/demo/scripts/controlsV2/sidePane/snapshot/SnapshotPane.scss new file mode 100644 index 00000000000..7a97368159d --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/snapshot/SnapshotPane.scss @@ -0,0 +1,57 @@ +@import '../../theme/theme.scss'; + +.snapshotPane { + flex: 1 1 auto; + display: flex; + flex-direction: column; +} + +.buttons { + margin-bottom: 10px; + flex: 0 0 auto; +} + +.textarea { + flex: 1 1 auto; + resize: none; + min-height: 100px; + border-color: $primaryBorder; +} + +.snapshotList { + min-height: 100px; + max-height: 200px; + overflow: hidden auto; + border: solid 1px $primaryBorder; + + pre { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + margin: 0; + &:hover { + background-color: #eee; + } + + &.current { + font-weight: bold; + } + + &.autoComplete { + background-color: #ff0; + } + } +} + +@media (prefers-color-scheme: dark) { + .snapshotList { + border: solid 1px $primaryBorderDark; + } + .textarea { + border-color: $primaryBorderDark; + } + .snapshotList { + border: solid 1px $primaryBorderDark; + } +} diff --git a/demo/scripts/controls/sidePane/snapshot/ContentModelSnapshotPane.tsx b/demo/scripts/controlsV2/sidePane/snapshot/SnapshotPane.tsx similarity index 95% rename from demo/scripts/controls/sidePane/snapshot/ContentModelSnapshotPane.tsx rename to demo/scripts/controlsV2/sidePane/snapshot/SnapshotPane.tsx index d9423c049df..275d2fe6670 100644 --- a/demo/scripts/controls/sidePane/snapshot/ContentModelSnapshotPane.tsx +++ b/demo/scripts/controlsV2/sidePane/snapshot/SnapshotPane.tsx @@ -3,28 +3,25 @@ import { EntityState, Snapshot, SnapshotSelection } from 'roosterjs-content-mode const styles = require('./SnapshotPane.scss'); -export interface ContentModelSnapshotPaneProps { +export interface SnapshotPaneProps { onTakeSnapshot: () => Snapshot; onRestoreSnapshot: (snapshot: Snapshot, triggerContentChangedEvent: boolean) => void; onMove: (moveStep: number) => void; } -export interface ContentModelSnapshotPaneState { +export interface SnapshotPaneState { snapshots: Snapshot[]; currentIndex: number; autoCompleteIndex: number; } -export default class ContentModelSnapshotPane extends React.Component< - ContentModelSnapshotPaneProps, - ContentModelSnapshotPaneState -> { +export class SnapshotPane extends React.Component { private html = React.createRef(); private entityStates = React.createRef(); private isDarkColor = React.createRef(); private selection = React.createRef(); - constructor(props: ContentModelSnapshotPaneProps) { + constructor(props: SnapshotPaneProps) { super(props); this.state = { diff --git a/demo/scripts/controls/sidePane/snapshot/ContentModelSnapshotPlugin.tsx b/demo/scripts/controlsV2/sidePane/snapshot/SnapshotPlugin.tsx similarity index 55% rename from demo/scripts/controls/sidePane/snapshot/ContentModelSnapshotPlugin.tsx rename to demo/scripts/controlsV2/sidePane/snapshot/SnapshotPlugin.tsx index cbf5e866e82..968f93f5097 100644 --- a/demo/scripts/controls/sidePane/snapshot/ContentModelSnapshotPlugin.tsx +++ b/demo/scripts/controlsV2/sidePane/snapshot/SnapshotPlugin.tsx @@ -1,12 +1,11 @@ import * as React from 'react'; -import ContentModelSnapshotPane from './ContentModelSnapshotPane'; -import SidePanePlugin from '../../SidePanePlugin'; -import { IEditor as ILegacyEDitor, PluginEvent, PluginEventType } from 'roosterjs-editor-types'; -import { IEditor, Snapshot, Snapshots } from 'roosterjs-content-model-types'; +import { IEditor, PluginEvent, Snapshot, Snapshots } from 'roosterjs-content-model-types'; +import { SidePanePlugin } from '../SidePanePlugin'; +import { SnapshotPane } from './SnapshotPane'; -export default class ContentModelSnapshotPlugin implements SidePanePlugin { - private editorInstance: ILegacyEDitor & IEditor; - private component: ContentModelSnapshotPane; +export class SnapshotPlugin implements SidePanePlugin { + private editor: IEditor; + private component: SnapshotPane; constructor(private snapshots: Snapshots) { this.snapshots.onChanged = () => this.updateSnapshots(); @@ -16,16 +15,16 @@ export default class ContentModelSnapshotPlugin implements SidePanePlugin { return 'Snapshot'; } - initialize(editor: ILegacyEDitor) { - this.editorInstance = editor as ILegacyEDitor & IEditor; + initialize(editor: IEditor) { + this.editor = editor; } dispose() { - this.editorInstance = null; + this.editor = null; } onPluginEvent(e: PluginEvent) { - if (e.eventType == PluginEventType.EditorReady) { + if (e.eventType == 'editorReady') { this.updateSnapshots(); } } @@ -35,14 +34,14 @@ export default class ContentModelSnapshotPlugin implements SidePanePlugin { } renderSidePane() { - return ; + return ; } getSnapshots() { return this.snapshots; } - private refCallback = (ref: ContentModelSnapshotPane) => { + private refCallback = (ref: SnapshotPane) => { this.component = ref; if (ref) { this.updateSnapshots(); @@ -58,18 +57,18 @@ export default class ContentModelSnapshotPlugin implements SidePanePlugin { } private onTakeSnapshot = (): Snapshot => { - return this.editorInstance.takeSnapshot(); + return this.editor.takeSnapshot(); }; private onMove = (step: number) => { - const snapshotsManager = this.editorInstance.getSnapshotsManager(); + const snapshotsManager = this.editor.getSnapshotsManager(); const snapshot = snapshotsManager.move(step); this.onRestoreSnapshot(snapshot); }; private onRestoreSnapshot = (snapshot: Snapshot) => { - this.editorInstance.focus(); - this.editorInstance.restoreSnapshot(snapshot); + this.editor.focus(); + this.editor.restoreSnapshot(snapshot); }; private updateSnapshots = () => { diff --git a/demo/scripts/controls/theme/contentModelEditorTheme.scss b/demo/scripts/controlsV2/theme/theme.scss similarity index 100% rename from demo/scripts/controls/theme/contentModelEditorTheme.scss rename to demo/scripts/controlsV2/theme/theme.scss diff --git a/demo/scripts/controlsV2/theme/themes.ts b/demo/scripts/controlsV2/theme/themes.ts new file mode 100644 index 00000000000..d82e7571f32 --- /dev/null +++ b/demo/scripts/controlsV2/theme/themes.ts @@ -0,0 +1,59 @@ +import { PartialTheme } from '@fluentui/react/lib/Theme'; + +const LightTheme: PartialTheme = { + palette: { + themePrimary: '#cc6688', + themeLighterAlt: '#080405', + themeLighter: '#211016', + themeLight: '#3d1f29', + themeTertiary: '#7a3d52', + themeSecondary: '#b45a78', + themeDarkAlt: '#d17392', + themeDark: '#d886a1', + themeDarker: '#e2a3b8', + neutralLighterAlt: '#f8f8f8', + neutralLighter: '#f4f4f4', + neutralLight: '#eaeaea', + neutralQuaternaryAlt: '#dadada', + neutralQuaternary: '#d0d0d0', + neutralTertiaryAlt: '#c8c8c8', + neutralTertiary: '#595959', + neutralSecondary: '#373737', + neutralPrimaryAlt: '#2f2f2f', + neutralPrimary: '#000000', + neutralDark: '#151515', + black: '#0b0b0b', + white: '#ffffff', + }, +}; + +const DarkTheme: PartialTheme = { + palette: { + themePrimary: '#cb6587', + themeLighterAlt: '#fdf8fa', + themeLighter: '#f7e3ea', + themeLight: '#f0ccd8', + themeTertiary: '#e09db4', + themeSecondary: '#d27694', + themeDarkAlt: '#b85c7a', + themeDark: '#9b4e67', + themeDarker: '#72394c', + neutralLighterAlt: '#3c3c3c', + neutralLighter: '#444444', + neutralLight: '#515151', + neutralQuaternaryAlt: '#595959', + neutralQuaternary: '#5f5f5f', + neutralTertiaryAlt: '#7a7a7a', + neutralTertiary: '#c8c8c8', + neutralSecondary: '#d0d0d0', + neutralPrimaryAlt: '#dadada', + neutralPrimary: '#ffffff', + neutralDark: '#f4f4f4', + black: '#f8f8f8', + white: '#333333', + }, +}; + +export function getTheme(isDark: boolean): PartialTheme { + return isDark ? DarkTheme : LightTheme; +} diff --git a/demo/scripts/controls/titleBar/StandaloneTitleBar.scss b/demo/scripts/controlsV2/titleBar/TitleBar.scss similarity index 95% rename from demo/scripts/controls/titleBar/StandaloneTitleBar.scss rename to demo/scripts/controlsV2/titleBar/TitleBar.scss index fcc4702cd0f..1e492c942aa 100644 --- a/demo/scripts/controls/titleBar/StandaloneTitleBar.scss +++ b/demo/scripts/controlsV2/titleBar/TitleBar.scss @@ -1,4 +1,4 @@ -@import '../theme/standaloneEditorTheme.scss'; +@import '../theme/theme.scss'; .titleBar { display: flex; diff --git a/demo/scripts/controlsV2/titleBar/TitleBar.tsx b/demo/scripts/controlsV2/titleBar/TitleBar.tsx new file mode 100644 index 00000000000..7eb3bc402c5 --- /dev/null +++ b/demo/scripts/controlsV2/titleBar/TitleBar.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; + +const styles = require('./TitleBar.scss'); +const github = require('./iconmonstr-github-1.svg'); + +export interface TitleBarProps { + className?: string; +} + +export class TitleBar extends React.Component { + render() { + const { className: baseClassName } = this.props; + const className = styles.titleBar + ' ' + (baseClassName || ''); + const titleText = 'RoosterJs Demo Site'; + + return ( +
+ ); + } +} diff --git a/demo/scripts/controlsV2/titleBar/iconmonstr-github-1.svg b/demo/scripts/controlsV2/titleBar/iconmonstr-github-1.svg new file mode 100644 index 00000000000..aa05db9c5c2 --- /dev/null +++ b/demo/scripts/controlsV2/titleBar/iconmonstr-github-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/demo/scripts/index.ts b/demo/scripts/index.ts index 9e007bdd317..c49750c8b51 100644 --- a/demo/scripts/index.ts +++ b/demo/scripts/index.ts @@ -1,13 +1,11 @@ -import { mount as mountClassicalEditorMainPane } from './controls/MainPane'; -import { mount as mountContentModelEditorMainPane } from './controls/ContentModelEditorMainPane'; -import { mount as mountStandaloneEditorMainPane } from './controls/StandaloneEditorMainPane'; +import { mount } from './controls/MainPane'; +import { mount as mountV2 } from './controlsV2/mainPane/MainPane'; const search = document.location.search.substring(1).split('&'); +const mainPaneDiv = document.getElementById('mainPane'); -if (search.some(x => x == 'cm=1')) { - mountContentModelEditorMainPane(document.getElementById('mainPane')); -} else if (search.some(x => x == 'cm=2')) { - mountStandaloneEditorMainPane(document.getElementById('mainPane')); +if (search.some(x => x == 'legacy=1')) { + mount(mainPaneDiv); } else { - mountClassicalEditorMainPane(document.getElementById('mainPane')); + mountV2(mainPaneDiv); } From d1b2680dfac57a6abb29263373611dbbdd9c34de Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Tue, 5 Mar 2024 22:24:28 -0800 Subject: [PATCH 58/80] Public normalizeRect function (#2482) * Public normalizeRect function * fix build and test --- .../pasteOptions/utils/getPositionRect.ts | 15 +----- .../lib/coreApi/getVisibleViewport.ts | 14 +---- .../lib/domUtils}/normalizeRect.ts | 2 +- .../roosterjs-content-model-dom/lib/index.ts | 1 + .../test/domUtils/normalizeRectTest.ts | 52 +++++++++++++++++++ .../pluginUtils/Rect/getIntersectedRect.ts | 2 +- .../lib/tableEdit/TableEditPlugin.ts | 3 +- .../lib/tableEdit/editors/TableEditor.ts | 3 +- .../tableEdit/editors/features/CellResizer.ts | 3 +- .../editors/features/TableInserter.ts | 3 +- .../tableEdit/editors/features/TableMover.ts | 3 +- .../editors/features/TableResizer.ts | 3 +- 12 files changed, 63 insertions(+), 41 deletions(-) rename packages/{roosterjs-content-model-plugins/lib/pluginUtils/Rect => roosterjs-content-model-dom/lib/domUtils}/normalizeRect.ts (88%) create mode 100644 packages/roosterjs-content-model-dom/test/domUtils/normalizeRectTest.ts diff --git a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/getPositionRect.ts b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/getPositionRect.ts index 88b077e1cf9..7b0cab07551 100644 --- a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/getPositionRect.ts +++ b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/utils/getPositionRect.ts @@ -1,4 +1,4 @@ -import { isNodeOfType } from 'roosterjs-content-model-dom'; +import { isNodeOfType, normalizeRect } from 'roosterjs-content-model-dom'; import { Rect } from 'roosterjs-content-model-types'; /** @@ -59,16 +59,3 @@ export function getPositionRect(container: Node, offset: number): Rect | null { return null; } - -function normalizeRect(clientRect: DOMRect): Rect | null { - const { left, right, top, bottom } = - clientRect || { left: 0, right: 0, top: 0, bottom: 0 }; - return left === 0 && right === 0 && top === 0 && bottom === 0 - ? null - : { - left: Math.round(left), - right: Math.round(right), - top: Math.round(top), - bottom: Math.round(bottom), - }; -} diff --git a/packages/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts b/packages/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts index 74609933caa..9ced6d42cc4 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/getVisibleViewport.ts @@ -1,3 +1,4 @@ +import { normalizeRect } from 'roosterjs-content-model-dom'; import type { GetVisibleViewport, Rect } from 'roosterjs-content-model-types'; /** @@ -55,16 +56,3 @@ function getIntersectedRect(elements: HTMLElement[], additionalRects: Rect[] = [ return result.top < result.bottom && result.left < result.right ? result : null; } - -function normalizeRect(clientRect: DOMRect): Rect | null { - const { left, right, top, bottom } = - clientRect || { left: 0, right: 0, top: 0, bottom: 0 }; - return left === 0 && right === 0 && top === 0 && bottom === 0 - ? null - : { - left: Math.round(left), - right: Math.round(right), - top: Math.round(top), - bottom: Math.round(bottom), - }; -} diff --git a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts b/packages/roosterjs-content-model-dom/lib/domUtils/normalizeRect.ts similarity index 88% rename from packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts rename to packages/roosterjs-content-model-dom/lib/domUtils/normalizeRect.ts index f4ff23e8e30..e29cc74a5ea 100644 --- a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/normalizeRect.ts +++ b/packages/roosterjs-content-model-dom/lib/domUtils/normalizeRect.ts @@ -1,9 +1,9 @@ import type { Rect } from 'roosterjs-content-model-types'; /** - * @internal * A ClientRect of all 0 is possible. i.e. chrome returns a ClientRect of 0 when the cursor is on an empty p * We validate that and only return a rect when the passed in ClientRect is valid + * @param clientRect Client rect object normally retrieved from getBoundingClientRect function */ export function normalizeRect(clientRect: DOMRect): Rect | null { const { left, right, top, bottom } = diff --git a/packages/roosterjs-content-model-dom/lib/index.ts b/packages/roosterjs-content-model-dom/lib/index.ts index e46cc69b0cc..442f048b043 100644 --- a/packages/roosterjs-content-model-dom/lib/index.ts +++ b/packages/roosterjs-content-model-dom/lib/index.ts @@ -31,6 +31,7 @@ export { } from './domUtils/entityUtils'; export { reuseCachedElement } from './domUtils/reuseCachedElement'; export { isWhiteSpacePreserved } from './domUtils/isWhiteSpacePreserved'; +export { normalizeRect } from './domUtils/normalizeRect'; export { createBr } from './modelApi/creators/createBr'; export { createListItem } from './modelApi/creators/createListItem'; diff --git a/packages/roosterjs-content-model-dom/test/domUtils/normalizeRectTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/normalizeRectTest.ts new file mode 100644 index 00000000000..2bdbab8f804 --- /dev/null +++ b/packages/roosterjs-content-model-dom/test/domUtils/normalizeRectTest.ts @@ -0,0 +1,52 @@ +import { normalizeRect } from '../../lib/domUtils/normalizeRect'; + +describe('normalizeRect', () => { + it('valid rect - all have positive values', () => { + const rect: DOMRect = { + left: 1, + right: 2, + top: 3, + bottom: 4, + } as any; + + const result = normalizeRect(rect); + + expect(result).toEqual({ + left: 1, + right: 2, + top: 3, + bottom: 4, + }); + }); + + it('valid rect - some have 0 values', () => { + const rect: DOMRect = { + left: 1, + right: 0, + top: 0, + bottom: 0, + } as any; + + const result = normalizeRect(rect); + + expect(result).toEqual({ + left: 1, + right: 0, + top: 0, + bottom: 0, + }); + }); + + it('invalid rect', () => { + const rect: DOMRect = { + left: 0, + right: 0, + top: 0, + bottom: 0, + } as any; + + const result = normalizeRect(rect); + + expect(result).toBeNull(); + }); +}); diff --git a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts index 3f087cee05e..189722e84cb 100644 --- a/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts +++ b/packages/roosterjs-content-model-plugins/lib/pluginUtils/Rect/getIntersectedRect.ts @@ -1,4 +1,4 @@ -import { normalizeRect } from './normalizeRect'; +import { normalizeRect } from 'roosterjs-content-model-dom'; import type { Rect } from 'roosterjs-content-model-types'; /** diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts index 2634b9b13d4..7f872b4f575 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts @@ -1,5 +1,4 @@ -import { isNodeOfType } from 'roosterjs-content-model-dom'; -import { normalizeRect } from '../pluginUtils/Rect/normalizeRect'; +import { isNodeOfType, normalizeRect } from 'roosterjs-content-model-dom'; import { TableEditor } from './editors/TableEditor'; import type { EditorPlugin, IEditor, PluginEvent, Rect } from 'roosterjs-content-model-types'; diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts index 752cb15f98a..179f4dbd4c6 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts @@ -3,8 +3,7 @@ import { createTableInserter } from './features/TableInserter'; import { createTableMover } from './features/TableMover'; import { createTableResizer } from './features/TableResizer'; import { disposeTableEditFeature } from './features/TableEditFeature'; -import { isNodeOfType } from 'roosterjs-content-model-dom'; -import { normalizeRect } from '../../pluginUtils/Rect/normalizeRect'; +import { isNodeOfType, normalizeRect } from 'roosterjs-content-model-dom'; import type { TableEditFeature } from './features/TableEditFeature'; import type { IEditor, TableSelection } from 'roosterjs-content-model-types'; diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts index 16d298cd96b..ccd3864696d 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts @@ -1,7 +1,6 @@ import { createElement } from '../../../pluginUtils/CreateElement/createElement'; import { DragAndDropHelper } from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; -import { isElementOfType } from 'roosterjs-content-model-dom'; -import { normalizeRect } from '../../../pluginUtils/Rect/normalizeRect'; +import { isElementOfType, normalizeRect } from 'roosterjs-content-model-dom'; import { getFirstSelectedTable, MIN_ALLOWED_TABLE_CELL_WIDTH, diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts index b1d4a9870db..48ea79d805c 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts @@ -1,7 +1,6 @@ import { createElement } from '../../../pluginUtils/CreateElement/createElement'; import { getIntersectedRect } from '../../../pluginUtils/Rect/getIntersectedRect'; -import { isElementOfType } from 'roosterjs-content-model-dom'; -import { normalizeRect } from '../../../pluginUtils/Rect/normalizeRect'; +import { isElementOfType, normalizeRect } from 'roosterjs-content-model-dom'; import { formatTableWithContentModel, insertTableColumn, diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts index 8b366a771c1..7cf29617a27 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts @@ -1,7 +1,6 @@ import { createElement } from '../../../pluginUtils/CreateElement/createElement'; import { DragAndDropHelper } from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; -import { isNodeOfType } from 'roosterjs-content-model-dom'; -import { normalizeRect } from '../../../pluginUtils/Rect/normalizeRect'; +import { isNodeOfType, normalizeRect } from 'roosterjs-content-model-dom'; import type { DragAndDropHandler } from '../../../pluginUtils/DragAndDrop/DragAndDropHandler'; import type { IEditor, Rect } from 'roosterjs-content-model-types'; import type { TableEditFeature } from './TableEditFeature'; diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts index 998f9557865..6b3bb26b338 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts @@ -1,8 +1,7 @@ import { createElement } from '../../../pluginUtils/CreateElement/createElement'; import { DragAndDropHelper } from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; import { getFirstSelectedTable, normalizeTable } from 'roosterjs-content-model-core'; -import { isNodeOfType } from 'roosterjs-content-model-dom'; -import { normalizeRect } from '../../../pluginUtils/Rect/normalizeRect'; +import { isNodeOfType, normalizeRect } from 'roosterjs-content-model-dom'; import type { ContentModelTable, IEditor, Rect } from 'roosterjs-content-model-types'; import type { TableEditFeature } from './TableEditFeature'; From d7b4c7c750f7d74679830806e3050af089ad86d4 Mon Sep 17 00:00:00 2001 From: "Julia Roldi (from Dev Box)" Date: Wed, 6 Mar 2024 11:16:09 -0300 Subject: [PATCH 59/80] fixes --- .../getDefaultContentEditFeatureSettings.ts | 17 ----------------- .../roosterjs-content-model-api/lib/index.ts | 2 +- .../lib/modelApi/link/matchLink.ts | 2 +- .../lib/autoFormat/link/createLink.ts | 2 +- .../lib/autoFormat/link/getLinkSegment.ts | 4 ++-- .../lib/autoFormat/link/unlink.ts | 2 +- .../test/autoFormat/link/getLinkSegmentTest.ts | 2 +- 7 files changed, 7 insertions(+), 24 deletions(-) diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts b/demo/scripts/controlsV2/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts index 310789e1dee..75840bd3ab2 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/getDefaultContentEditFeatureSettings.ts @@ -2,22 +2,6 @@ import { ContentEditFeatureSettings } from 'roosterjs-editor-types'; import { getAllFeatures } from 'roosterjs-editor-plugins'; import { getObjectKeys } from 'roosterjs-content-model-dom'; -const listFeatures = { - autoBullet: false, - indentWhenTab: false, - outdentWhenShiftTab: false, - outdentWhenBackspaceOnEmptyFirstLine: false, - outdentWhenEnterOnEmptyLine: false, - mergeInNewLineWhenBackspaceOnFirstChar: false, - maintainListChain: false, - maintainListChainWhenDelete: false, - autoNumberingList: false, - autoBulletList: false, - mergeListOnBackspaceAfterList: false, - outdentWhenAltShiftLeft: false, - indentWhenAltShiftRight: false, -}; - export function getDefaultContentEditFeatureSettings(): ContentEditFeatureSettings { const allFeatures = getAllFeatures(); @@ -26,6 +10,5 @@ export function getDefaultContentEditFeatureSettings(): ContentEditFeatureSettin settings[key] = !allFeatures[key].defaultDisabled; return settings; }, {}), - ...listFeatures, }; } diff --git a/packages/roosterjs-content-model-api/lib/index.ts b/packages/roosterjs-content-model-api/lib/index.ts index 93272c2ac75..644dd4ee6e7 100644 --- a/packages/roosterjs-content-model-api/lib/index.ts +++ b/packages/roosterjs-content-model-api/lib/index.ts @@ -51,4 +51,4 @@ export { formatSegmentWithContentModel } from './publicApi/utils/formatSegmentWi export { setListType } from './modelApi/list/setListType'; export { findListItemsInSameThread } from './modelApi/list/findListItemsInSameThread'; export { setModelIndentation } from './modelApi/block/setModelIndentation'; -export { matchLink } from './modelApi/link/matchLink'; +export { matchLink, LinkData } from './modelApi/link/matchLink'; diff --git a/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts b/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts index 0d56c66ac9b..44690b437c7 100644 --- a/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts +++ b/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts @@ -1,7 +1,7 @@ import { getObjectKeys } from 'roosterjs-content-model-dom'; /** - * @internal + * Data of aS link */ export interface LinkData { /** diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts index a51b94c73d3..82b9c7d29d8 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts @@ -1,5 +1,5 @@ -import getLinkSegment from './getLinkSegment'; import { addLink } from 'roosterjs-content-model-dom'; +import { getLinkSegment } from './getLinkSegment'; import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import type { IEditor } from 'roosterjs-content-model-types'; diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts index d31d211df68..0cf97f8123f 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/getLinkSegment.ts @@ -1,11 +1,11 @@ import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; -import { matchLink } from 'roosterjs-editor-dom'; +import { matchLink } from 'roosterjs-content-model-api'; import type { ContentModelDocument } from 'roosterjs-content-model-types'; /** * @internal */ -export default function getLinkSegment(model: ContentModelDocument) { +export function getLinkSegment(model: ContentModelDocument) { const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( model, false /* includingFormatHolder */ diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts index 3be931723ca..511018d0d35 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts @@ -1,4 +1,4 @@ -import getLinkSegment from './getLinkSegment'; +import { getLinkSegment } from './getLinkSegment'; import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import type { IEditor } from 'roosterjs-content-model-types'; diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/getLinkSegmentTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/getLinkSegmentTest.ts index a21123f8c43..5bb356c56f3 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/link/getLinkSegmentTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/getLinkSegmentTest.ts @@ -1,5 +1,5 @@ -import getLinkSegment from '../../../lib/autoFormat/link/getLinkSegment'; import { ContentModelDocument, ContentModelText } from 'roosterjs-content-model-types'; +import { getLinkSegment } from '../../../lib/autoFormat/link/getLinkSegment'; describe('getLinkSegment', () => { function runTest(model: ContentModelDocument, link: ContentModelText | undefined) { From d2a02ab81ae455ef26ebbebae3ce770bf096fdf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Wed, 6 Mar 2024 11:41:52 -0300 Subject: [PATCH 60/80] fix comment --- .../lib/modelApi/link/matchLink.ts | 2 +- .../autoFormat/link/createLinkWhileTyping.ts | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkWhileTyping.ts diff --git a/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts b/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts index 44690b437c7..06852c13ee1 100644 --- a/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts +++ b/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts @@ -1,7 +1,7 @@ import { getObjectKeys } from 'roosterjs-content-model-dom'; /** - * Data of aS link + * Data of the matched link */ export interface LinkData { /** diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkWhileTyping.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkWhileTyping.ts new file mode 100644 index 00000000000..b9eefab1f6d --- /dev/null +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkWhileTyping.ts @@ -0,0 +1,38 @@ +import { addLink } from 'roosterjs-content-model-dom'; +import { getLinkSegment } from './getLinkSegment'; +import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; +import type { IEditor } from 'roosterjs-content-model-types'; + +/** + * @internal + */ +export function createLinkWhileTyping(editor: IEditor) { + editor.formatContentModel((model, context) => { + const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( + model, + false /* includingFormatHolder */ + ); + const link = getLinkSegment(model); + if ( + link && + link.text.length > 5 && + selectedSegmentsAndParagraphs[0] && + selectedSegmentsAndParagraphs[0][1] + ) { + addLink(link, { + format: { + href: link.text, + underline: true, + }, + dataset: {}, + }); + + const selectedParagraph = selectedSegmentsAndParagraphs[0][1]; + selectedParagraph.segments.splice(selectedParagraph.segments.length - 2, 1, link); + + return true; + } + + return false; + }); +} From fd79ae399f4dbc0b370a543a36c207532eedd3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Wed, 6 Mar 2024 13:15:33 -0300 Subject: [PATCH 61/80] WIP --- .../lib/autoFormat/AutoFormatPlugin.ts | 6 ++- .../lib/autoFormat/link/createLink.ts | 14 +------ .../autoFormat/link/createLinkAfterSpace.ts | 38 +++++++++++++++++++ .../lib/autoFormat/link/unlink.ts | 9 +---- 4 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts index 09e2a119787..f4d2eb67a1c 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts @@ -1,4 +1,5 @@ import { createLink } from './link/createLink'; +import { createLinkAfterSpace } from './link/createLinkAfterSpace'; import { keyboardListTrigger } from './list/keyboardListTrigger'; import { unlink } from './link/unlink'; import type { @@ -106,12 +107,15 @@ export class AutoFormatPlugin implements EditorPlugin { private handleKeyDownEvent(editor: IEditor, event: KeyDownEvent) { const rawEvent = event.rawEvent; if (!rawEvent.defaultPrevented && !event.handledByEditFeature) { - const { autoBullet, autoNumbering, autoUnlink } = this.options; + const { autoBullet, autoNumbering, autoUnlink, autoLink } = this.options; switch (rawEvent.key) { case ' ': if (autoBullet || autoNumbering) { keyboardListTrigger(editor, rawEvent, autoBullet, autoNumbering); } + if (autoLink) { + createLinkAfterSpace(editor); + } break; case 'Backspace': if (autoUnlink) { diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts index 82b9c7d29d8..2c86cf7cabc 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts @@ -1,6 +1,5 @@ import { addLink } from 'roosterjs-content-model-dom'; import { getLinkSegment } from './getLinkSegment'; -import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import type { IEditor } from 'roosterjs-content-model-types'; /** @@ -8,17 +7,8 @@ import type { IEditor } from 'roosterjs-content-model-types'; */ export function createLink(editor: IEditor) { editor.formatContentModel(model => { - const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( - model, - false /* includingFormatHolder */ - ); const link = getLinkSegment(model); - if ( - link && - !link.link && - selectedSegmentsAndParagraphs[0] && - selectedSegmentsAndParagraphs[0][1] - ) { + if (link && !link.link) { addLink(link, { format: { href: link.text, @@ -26,8 +16,6 @@ export function createLink(editor: IEditor) { }, dataset: {}, }); - const selectedParagraph = selectedSegmentsAndParagraphs[0][1]; - selectedParagraph.segments.splice(selectedParagraph.segments.length - 2, 1, link); return true; } diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts new file mode 100644 index 00000000000..232ab97c638 --- /dev/null +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts @@ -0,0 +1,38 @@ +import { createText } from 'roosterjs-content-model-dom/lib'; +import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; +import { matchLink } from 'roosterjs-content-model-api'; +import type { IEditor } from 'roosterjs-content-model-types'; + +/** + * @internal + */ +export function createLinkAfterSpace(editor: IEditor) { + editor.formatContentModel(model => { + const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( + model, + false /* includingFormatHolder */ + ); + if (selectedSegmentsAndParagraphs[0] && selectedSegmentsAndParagraphs[0][1]) { + const length = selectedSegmentsAndParagraphs[0][1].segments.length; + const marker = selectedSegmentsAndParagraphs[0][1].segments[length - 1]; + const textSegment = selectedSegmentsAndParagraphs[0][1].segments[length - 2]; + if (marker.segmentType == 'SelectionMarker' && textSegment.segmentType == 'Text') { + const link = textSegment.text.split(' ').pop(); + if (link && matchLink(link)) { + textSegment.text = textSegment.text.replace(link, ''); + const linkSegment = createText(link, marker.format, { + format: { + href: link, + underline: true, + }, + dataset: {}, + }); + selectedSegmentsAndParagraphs[0][1].segments.splice(length - 1, 0, linkSegment); + return true; + } + } + } + + return false; + }); +} diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts index 511018d0d35..4648cc5b3e1 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts @@ -1,5 +1,4 @@ import { getLinkSegment } from './getLinkSegment'; -import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import type { IEditor } from 'roosterjs-content-model-types'; /** @@ -7,15 +6,9 @@ import type { IEditor } from 'roosterjs-content-model-types'; */ export function unlink(editor: IEditor, rawEvent: KeyboardEvent) { editor.formatContentModel(model => { - const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( - model, - false /* includingFormatHolder */ - ); const link = getLinkSegment(model); - if (link?.link && selectedSegmentsAndParagraphs[0] && selectedSegmentsAndParagraphs[0][1]) { + if (link?.link) { link.link = undefined; - const selectedParagraph = selectedSegmentsAndParagraphs[0][1]; - selectedParagraph.segments.splice(selectedParagraph.segments.length - 2, 1, link); rawEvent.preventDefault(); return true; } From cbe3ff97799a8e2114f45594d0afe49641f20ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Wed, 6 Mar 2024 13:17:58 -0300 Subject: [PATCH 62/80] remove unnecessary code --- .../lib/autoFormat/link/createLink.ts | 15 ++------------- .../lib/autoFormat/link/unlink.ts | 9 +-------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts index 82b9c7d29d8..168c6d42fb1 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts @@ -1,6 +1,5 @@ import { addLink } from 'roosterjs-content-model-dom'; import { getLinkSegment } from './getLinkSegment'; -import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import type { IEditor } from 'roosterjs-content-model-types'; /** @@ -8,17 +7,8 @@ import type { IEditor } from 'roosterjs-content-model-types'; */ export function createLink(editor: IEditor) { editor.formatContentModel(model => { - const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( - model, - false /* includingFormatHolder */ - ); const link = getLinkSegment(model); - if ( - link && - !link.link && - selectedSegmentsAndParagraphs[0] && - selectedSegmentsAndParagraphs[0][1] - ) { + if (link && !link.link) { addLink(link, { format: { href: link.text, @@ -26,8 +16,7 @@ export function createLink(editor: IEditor) { }, dataset: {}, }); - const selectedParagraph = selectedSegmentsAndParagraphs[0][1]; - selectedParagraph.segments.splice(selectedParagraph.segments.length - 2, 1, link); + return true; } diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts index 511018d0d35..4648cc5b3e1 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts @@ -1,5 +1,4 @@ import { getLinkSegment } from './getLinkSegment'; -import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import type { IEditor } from 'roosterjs-content-model-types'; /** @@ -7,15 +6,9 @@ import type { IEditor } from 'roosterjs-content-model-types'; */ export function unlink(editor: IEditor, rawEvent: KeyboardEvent) { editor.formatContentModel(model => { - const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( - model, - false /* includingFormatHolder */ - ); const link = getLinkSegment(model); - if (link?.link && selectedSegmentsAndParagraphs[0] && selectedSegmentsAndParagraphs[0][1]) { + if (link?.link) { link.link = undefined; - const selectedParagraph = selectedSegmentsAndParagraphs[0][1]; - selectedParagraph.segments.splice(selectedParagraph.segments.length - 2, 1, link); rawEvent.preventDefault(); return true; } From 4aa88fdbb1b703c5e5b92090e9729b3e30c6e705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Wed, 6 Mar 2024 13:18:59 -0300 Subject: [PATCH 63/80] remove unnecessary code --- .../autoFormat/link/createLinkWhileTyping.ts | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkWhileTyping.ts diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkWhileTyping.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkWhileTyping.ts deleted file mode 100644 index b9eefab1f6d..00000000000 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkWhileTyping.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { addLink } from 'roosterjs-content-model-dom'; -import { getLinkSegment } from './getLinkSegment'; -import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; -import type { IEditor } from 'roosterjs-content-model-types'; - -/** - * @internal - */ -export function createLinkWhileTyping(editor: IEditor) { - editor.formatContentModel((model, context) => { - const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( - model, - false /* includingFormatHolder */ - ); - const link = getLinkSegment(model); - if ( - link && - link.text.length > 5 && - selectedSegmentsAndParagraphs[0] && - selectedSegmentsAndParagraphs[0][1] - ) { - addLink(link, { - format: { - href: link.text, - underline: true, - }, - dataset: {}, - }); - - const selectedParagraph = selectedSegmentsAndParagraphs[0][1]; - selectedParagraph.segments.splice(selectedParagraph.segments.length - 2, 1, link); - - return true; - } - - return false; - }); -} From ad391f1c60b57ea4c7cb8640370c14984e1a91e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Wed, 6 Mar 2024 14:39:51 -0300 Subject: [PATCH 64/80] part 2 auto link --- .../lib/autoFormat/AutoFormatPlugin.ts | 17 +- .../lib/autoFormat/link/createLink.ts | 5 +- .../autoFormat/link/createLinkAfterSpace.ts | 11 +- .../lib/autoFormat/link/unlink.ts | 5 +- .../autoFormat/list/keyboardListTrigger.ts | 40 +-- .../test/autoFormat/AutoFormatPluginTest.ts | 82 +++++- .../link/createLinkAfterSpaceTest.ts | 262 ++++++++++++++++++ .../test/autoFormat/link/createLinkTest.ts | 11 +- .../test/autoFormat/link/unlinkTest.ts | 3 +- 9 files changed, 386 insertions(+), 50 deletions(-) create mode 100644 packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkAfterSpaceTest.ts diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts index f4d2eb67a1c..45fc951323c 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts @@ -110,18 +110,11 @@ export class AutoFormatPlugin implements EditorPlugin { const { autoBullet, autoNumbering, autoUnlink, autoLink } = this.options; switch (rawEvent.key) { case ' ': - if (autoBullet || autoNumbering) { - keyboardListTrigger(editor, rawEvent, autoBullet, autoNumbering); - } - if (autoLink) { - createLinkAfterSpace(editor); - } + keyboardListTrigger(editor, rawEvent, autoBullet, autoNumbering); + createLinkAfterSpace(editor, autoLink); break; case 'Backspace': - if (autoUnlink) { - unlink(editor, rawEvent); - } - + unlink(editor, rawEvent, autoUnlink); break; } } @@ -129,8 +122,8 @@ export class AutoFormatPlugin implements EditorPlugin { private handleContentChangedEvent(editor: IEditor, event: ContentChangedEvent) { const { autoLink } = this.options; - if (event.source == 'Paste' && autoLink) { - createLink(editor); + if (event.source == 'Paste') { + createLink(editor, autoLink); } } } diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts index baaa7a00108..9ed1ed40ec6 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts @@ -5,7 +5,10 @@ import type { IEditor } from 'roosterjs-content-model-types'; /** * @internal */ -export function createLink(editor: IEditor) { +export function createLink(editor: IEditor, autoLink: boolean) { + if (!autoLink) { + return; + } editor.formatContentModel(model => { const link = getLinkSegment(model); if (link && !link.link) { diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts index 232ab97c638..8737ff1ee9c 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts @@ -6,7 +6,10 @@ import type { IEditor } from 'roosterjs-content-model-types'; /** * @internal */ -export function createLinkAfterSpace(editor: IEditor) { +export function createLinkAfterSpace(editor: IEditor, autoLink: boolean) { + if (!autoLink) { + return; + } editor.formatContentModel(model => { const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( model, @@ -16,7 +19,11 @@ export function createLinkAfterSpace(editor: IEditor) { const length = selectedSegmentsAndParagraphs[0][1].segments.length; const marker = selectedSegmentsAndParagraphs[0][1].segments[length - 1]; const textSegment = selectedSegmentsAndParagraphs[0][1].segments[length - 2]; - if (marker.segmentType == 'SelectionMarker' && textSegment.segmentType == 'Text') { + if ( + marker.segmentType == 'SelectionMarker' && + textSegment.segmentType == 'Text' && + !textSegment.link + ) { const link = textSegment.text.split(' ').pop(); if (link && matchLink(link)) { textSegment.text = textSegment.text.replace(link, ''); diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts index 4648cc5b3e1..75bd3339f9f 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts @@ -4,7 +4,10 @@ import type { IEditor } from 'roosterjs-content-model-types'; /** * @internal */ -export function unlink(editor: IEditor, rawEvent: KeyboardEvent) { +export function unlink(editor: IEditor, rawEvent: KeyboardEvent, unlink: boolean) { + if (!unlink) { + return; + } editor.formatContentModel(model => { const link = getLinkSegment(model); if (link?.link) { diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts index 36ce2ec95cc..8ecc68b7919 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/list/keyboardListTrigger.ts @@ -13,26 +13,28 @@ export function keyboardListTrigger( shouldSearchForBullet: boolean = true, shouldSearchForNumbering: boolean = true ) { - editor.formatContentModel((model, _context) => { - const listStyleType = getListTypeStyle( - model, - shouldSearchForBullet, - shouldSearchForNumbering - ); - if (listStyleType) { - const segmentsAndParagraphs = getSelectedSegmentsAndParagraphs(model, false); - if (segmentsAndParagraphs[0] && segmentsAndParagraphs[0][1]) { - segmentsAndParagraphs[0][1].segments.splice(0, 1); - } - const { listType, styleType, index } = listStyleType; - triggerList(editor, model, listType, styleType, index); - rawEvent.preventDefault(); - normalizeContentModel(model); + if (shouldSearchForBullet || shouldSearchForNumbering) { + editor.formatContentModel((model, _context) => { + const listStyleType = getListTypeStyle( + model, + shouldSearchForBullet, + shouldSearchForNumbering + ); + if (listStyleType) { + const segmentsAndParagraphs = getSelectedSegmentsAndParagraphs(model, false); + if (segmentsAndParagraphs[0] && segmentsAndParagraphs[0][1]) { + segmentsAndParagraphs[0][1].segments.splice(0, 1); + } + const { listType, styleType, index } = listStyleType; + triggerList(editor, model, listType, styleType, index); + rawEvent.preventDefault(); + normalizeContentModel(model); - return true; - } - return false; - }); + return true; + } + return false; + }); + } } const triggerList = ( diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts index a2bb6501d79..8780a8b3ff1 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts @@ -1,4 +1,5 @@ import * as createLink from '../../lib/autoFormat/link/createLink'; +import * as createLinkAfterSpace from '../../lib/autoFormat/link/createLinkAfterSpace'; import * as keyboardTrigger from '../../lib/autoFormat/list/keyboardListTrigger'; import * as unlink from '../../lib/autoFormat/link/unlink'; import { AutoFormatOptions, AutoFormatPlugin } from '../../lib/autoFormat/AutoFormatPlugin'; @@ -31,11 +32,9 @@ describe('Content Model Auto Format Plugin Test', () => { options?: { autoBullet: boolean; autoNumbering: boolean; - autoUnlink: boolean; - autoLink: boolean; } ) { - const plugin = new AutoFormatPlugin(options); + const plugin = new AutoFormatPlugin(options as AutoFormatOptions); plugin.initialize(editor); plugin.onPluginEvent(event); @@ -44,8 +43,8 @@ describe('Content Model Auto Format Plugin Test', () => { expect(keyboardListTriggerSpy).toHaveBeenCalledWith( editor, event.rawEvent, - options?.autoBullet ?? true, - options?.autoNumbering ?? true + options ? options.autoBullet : true, + options ? options.autoNumbering : true ); } else { expect(keyboardListTriggerSpy).not.toHaveBeenCalled(); @@ -78,7 +77,7 @@ describe('Content Model Auto Format Plugin Test', () => { handledByEditFeature: false, }; - runTest(event, false, { autoBullet: false, autoNumbering: false } as AutoFormatOptions); + runTest(event, true, { autoBullet: false, autoNumbering: false } as AutoFormatOptions); }); it('should trigger keyboardListTrigger with auto bullet only', () => { @@ -140,7 +139,10 @@ describe('Content Model Auto Format Plugin Test', () => { plugin.onPluginEvent(event); if (shouldCallTrigger) { - expect(createLinkSpy).toHaveBeenCalledWith(editor); + expect(createLinkSpy).toHaveBeenCalledWith( + editor, + options ? options.autoLink : true + ); } else { expect(createLinkSpy).not.toHaveBeenCalled(); } @@ -159,7 +161,7 @@ describe('Content Model Auto Format Plugin Test', () => { eventType: 'contentChanged', source: 'Paste', }; - runTest(event, false, { autoLink: false }); + runTest(event, true, { autoLink: false }); }); it('should not call createLink - not paste', () => { @@ -191,7 +193,11 @@ describe('Content Model Auto Format Plugin Test', () => { plugin.onPluginEvent(event); if (shouldCallTrigger) { - expect(unlinkSpy).toHaveBeenCalledWith(editor, event.rawEvent); + expect(unlinkSpy).toHaveBeenCalledWith( + editor, + event.rawEvent, + options ? options.autoUnlink : true + ); } else { expect(unlinkSpy).not.toHaveBeenCalled(); } @@ -210,7 +216,7 @@ describe('Content Model Auto Format Plugin Test', () => { eventType: 'keyDown', rawEvent: { key: 'Backspace', preventDefault: () => {} } as any, }; - runTest(event, false, { + runTest(event, true, { autoUnlink: false, }); }); @@ -223,4 +229,60 @@ describe('Content Model Auto Format Plugin Test', () => { runTest(event, false); }); }); + + describe('onPluginEvent - createLinkAfterSpace', () => { + let createLinkAfterSpaceSpy: jasmine.Spy; + + beforeEach(() => { + createLinkAfterSpaceSpy = spyOn(createLinkAfterSpace, 'createLinkAfterSpace'); + }); + + function runTest( + event: KeyDownEvent, + shouldCallTrigger: boolean, + options?: { + autoLink: boolean; + } + ) { + const plugin = new AutoFormatPlugin(options as AutoFormatOptions); + plugin.initialize(editor); + + plugin.onPluginEvent(event); + + if (shouldCallTrigger) { + expect(createLinkAfterSpaceSpy).toHaveBeenCalledWith( + editor, + options ? options.autoLink : true + ); + } else { + expect(createLinkAfterSpaceSpy).not.toHaveBeenCalled(); + } + } + + it('should call createLinkAfterSpace', () => { + const event: KeyDownEvent = { + eventType: 'keyDown', + rawEvent: { key: ' ', preventDefault: () => {} } as any, + }; + runTest(event, true); + }); + + it('should not call createLinkAfterSpace - disable options', () => { + const event: KeyDownEvent = { + eventType: 'keyDown', + rawEvent: { key: ' ', preventDefault: () => {} } as any, + }; + runTest(event, true, { + autoLink: false, + }); + }); + + it('should not call createLinkAfterSpace - not backspace', () => { + const event: KeyDownEvent = { + eventType: 'keyDown', + rawEvent: { key: 'Backspace', preventDefault: () => {} } as any, + }; + runTest(event, false); + }); + }); }); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkAfterSpaceTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkAfterSpaceTest.ts new file mode 100644 index 00000000000..04a196e977f --- /dev/null +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkAfterSpaceTest.ts @@ -0,0 +1,262 @@ +import { ContentModelDocument } from 'roosterjs-content-model-types'; +import { createLinkAfterSpace } from '../../../lib/autoFormat/link/createLinkAfterSpace'; + +describe('createLinkAfterSpace', () => { + function runTest( + input: ContentModelDocument, + expectedModel: ContentModelDocument, + expectedResult: boolean + ) { + const formatWithContentModelSpy = jasmine + .createSpy('formatWithContentModel') + .and.callFake((callback, options) => { + const result = callback(input, { + newEntities: [], + deletedEntities: [], + newImages: [], + }); + expect(result).toBe(expectedResult); + }); + + createLinkAfterSpace( + { + focus: () => {}, + formatContentModel: formatWithContentModelSpy, + } as any, + true + ); + + expect(formatWithContentModelSpy).toHaveBeenCalled(); + expect(input).toEqual(expectedModel); + } + + it('no selected segments', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, input, false); + }); + + it('no link segment', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(input, input, false); + }); + + it('link segment with WWW', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + const expected: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: '', + format: {}, + }, + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, expected, true); + }); + + it('link segment with hyperlink', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + link: { + format: { + href: 'www.bing.com', + underline: true, + }, + dataset: {}, + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(input, input, false); + }); + + it('link with text', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'this is the link www.bing.com', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + const expected: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'this is the link ', + format: {}, + }, + { + segmentType: 'Text', + text: 'www.bing.com', + format: {}, + link: { + format: { + underline: true, + href: 'www.bing.com', + }, + dataset: {}, + }, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(input, expected, true); + }); + + it('link before text', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'www.bing.com this is the link', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + format: {}, + }; + runTest(input, input, false); + }); +}); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts index d87b4f9c562..ebb31cc335b 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts @@ -18,10 +18,13 @@ describe('createLink', () => { expect(result).toBe(expectedResult); }); - createLink({ - focus: () => {}, - formatContentModel: formatWithContentModelSpy, - } as any); + createLink( + { + focus: () => {}, + formatContentModel: formatWithContentModelSpy, + } as any, + true + ); expect(formatWithContentModelSpy).toHaveBeenCalled(); expect(input).toEqual(expectedModel); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts index caa0b734a93..721268c07aa 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts @@ -26,7 +26,8 @@ describe('unlink', () => { focus: () => {}, formatContentModel: formatWithContentModelSpy, } as any, - rawEvent + rawEvent, + true ); expect(formatWithContentModelSpy).toHaveBeenCalled(); From ac1837ddc7ee28efbd26299e9a09a069189f75c5 Mon Sep 17 00:00:00 2001 From: "Julia Roldi (from Dev Box)" Date: Wed, 6 Mar 2024 14:52:47 -0300 Subject: [PATCH 65/80] fix build --- .../lib/autoFormat/link/createLinkAfterSpace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts index 8737ff1ee9c..230dba46634 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts @@ -1,4 +1,4 @@ -import { createText } from 'roosterjs-content-model-dom/lib'; +import { createText } from 'roosterjs-content-model-dom'; import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-core'; import { matchLink } from 'roosterjs-content-model-api'; import type { IEditor } from 'roosterjs-content-model-types'; From 542e0f162d5154e2e8bff7f9bae5bc9605d5a4ae Mon Sep 17 00:00:00 2001 From: "Julia Roldi (from Dev Box)" Date: Thu, 7 Mar 2024 13:57:09 -0300 Subject: [PATCH 66/80] fixes --- .../roosterjs-content-model-api/lib/index.ts | 2 +- .../lib/modelApi/link/matchLink.ts | 21 +------------------ .../test/modelApi/link/matchLinkTest.ts | 3 ++- .../lib/autoFormat/AutoFormatPlugin.ts | 2 +- .../test/autoFormat/AutoFormatPluginTest.ts | 4 +++- .../lib/index.ts | 1 + .../lib/parameter/LinkData.ts | 19 +++++++++++++++++ 7 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 packages/roosterjs-content-model-types/lib/parameter/LinkData.ts diff --git a/packages/roosterjs-content-model-api/lib/index.ts b/packages/roosterjs-content-model-api/lib/index.ts index 644dd4ee6e7..93272c2ac75 100644 --- a/packages/roosterjs-content-model-api/lib/index.ts +++ b/packages/roosterjs-content-model-api/lib/index.ts @@ -51,4 +51,4 @@ export { formatSegmentWithContentModel } from './publicApi/utils/formatSegmentWi export { setListType } from './modelApi/list/setListType'; export { findListItemsInSameThread } from './modelApi/list/findListItemsInSameThread'; export { setModelIndentation } from './modelApi/block/setModelIndentation'; -export { matchLink, LinkData } from './modelApi/link/matchLink'; +export { matchLink } from './modelApi/link/matchLink'; diff --git a/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts b/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts index 06852c13ee1..ea224958902 100644 --- a/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts +++ b/packages/roosterjs-content-model-api/lib/modelApi/link/matchLink.ts @@ -1,24 +1,5 @@ import { getObjectKeys } from 'roosterjs-content-model-dom'; - -/** - * Data of the matched link - */ -export interface LinkData { - /** - * Schema of a hyperlink - */ - scheme: string; - - /** - * Original url of a hyperlink - */ - originalUrl: string; - - /** - * Normalized url of a hyperlink - */ - normalizedUrl: string; -} +import type { LinkData } from 'roosterjs-content-model-types'; interface LinkMatchRule { match: RegExp; diff --git a/packages/roosterjs-content-model-api/test/modelApi/link/matchLinkTest.ts b/packages/roosterjs-content-model-api/test/modelApi/link/matchLinkTest.ts index ad25b487f07..923f860b06d 100644 --- a/packages/roosterjs-content-model-api/test/modelApi/link/matchLinkTest.ts +++ b/packages/roosterjs-content-model-api/test/modelApi/link/matchLinkTest.ts @@ -1,4 +1,5 @@ -import { LinkData, matchLink } from '../../../lib/modelApi/link/matchLink'; +import { matchLink } from '../../../lib/modelApi/link/matchLink'; +import type { LinkData } from 'roosterjs-content-model-types'; function runMatchTestWithValidLink(link: string, expected: LinkData): void { let resultData = matchLink(link); diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts index 09e2a119787..844a5650133 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts @@ -40,7 +40,7 @@ export type AutoFormatOptions = { const DefaultOptions: Required = { autoBullet: true, autoNumbering: true, - autoUnlink: true, + autoUnlink: false, autoLink: true, }; diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts index a2bb6501d79..a94ede99d6c 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts @@ -202,7 +202,9 @@ describe('Content Model Auto Format Plugin Test', () => { eventType: 'keyDown', rawEvent: { key: 'Backspace', preventDefault: () => {} } as any, }; - runTest(event, true); + runTest(event, true, { + autoUnlink: true, + }); }); it('should not call unlink - disable options', () => { diff --git a/packages/roosterjs-content-model-types/lib/index.ts b/packages/roosterjs-content-model-types/lib/index.ts index ea49333ccc1..c5077e1fd12 100644 --- a/packages/roosterjs-content-model-types/lib/index.ts +++ b/packages/roosterjs-content-model-types/lib/index.ts @@ -285,6 +285,7 @@ export { Rect } from './parameter/Rect'; export { ValueSanitizer } from './parameter/ValueSanitizer'; export { DOMHelper } from './parameter/DOMHelper'; export { ImageEditOperation, ImageEditor } from './parameter/ImageEditor'; +export { LinkData } from './parameter/LinkData'; export { BasePluginEvent, BasePluginDomEvent } from './event/BasePluginEvent'; export { BeforeCutCopyEvent } from './event/BeforeCutCopyEvent'; diff --git a/packages/roosterjs-content-model-types/lib/parameter/LinkData.ts b/packages/roosterjs-content-model-types/lib/parameter/LinkData.ts new file mode 100644 index 00000000000..34e061fc4a8 --- /dev/null +++ b/packages/roosterjs-content-model-types/lib/parameter/LinkData.ts @@ -0,0 +1,19 @@ +/** + * Data of the matched link + */ +export interface LinkData { + /** + * Schema of a hyperlink + */ + scheme: string; + + /** + * Original url of a hyperlink + */ + originalUrl: string; + + /** + * Normalized url of a hyperlink + */ + normalizedUrl: string; +} From 96616f7daabac0e4dd7d941d3c145d1de9b71db9 Mon Sep 17 00:00:00 2001 From: Andres-CT98 <107568016+Andres-CT98@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:36:36 -0600 Subject: [PATCH 67/80] Fix #2474 - Table Resizer test failing (#2487) * reenable test, check disposed --- .../lib/tableEdit/editors/features/TableResizer.ts | 1 + .../test/tableEdit/TableEditTestHelper.ts | 2 +- .../test/tableEdit/tableResizerTest.ts | 4 +--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts index 6b3bb26b338..995c121c95a 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts @@ -199,6 +199,7 @@ function onDragEnd( initValue: DragAndDropInitValue | undefined ) { if ( + !context.editor.isDisposed() && isTableBottomVisible( context.editor, normalizeRect(context.table.getBoundingClientRect()), diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts index 4450662d622..066e66501f9 100644 --- a/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts @@ -52,7 +52,7 @@ export function beforeTableTest(TEST_ID: string) { * @param TEST_ID The id of the editor div */ export function afterTableTest(editor: IEditor, plugin: TableEditPlugin, TEST_ID: string) { - editor.dispose(); + !editor.isDisposed() && editor.dispose(); plugin.dispose(); TestHelper.removeElement(TEST_ID); document.body = document.createElement('body'); diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts index beebeb06f42..87b8d85bba5 100644 --- a/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts @@ -20,9 +20,7 @@ import { const TABLE_RESIZER_ID = '_Table_Resizer'; -// TODO: Reenable this test -// https://github.com/microsoft/roosterjs/issues/2474 -xdescribe('Table Resizer tests', () => { +describe('Table Resizer tests', () => { let editor: IEditor; let plugin: TableEditPlugin; const TEST_ID = 'resizerTest'; From 256cff82e2f36c5c35f754f60a443a271f7ab022 Mon Sep 17 00:00:00 2001 From: Andres-CT98 <107568016+Andres-CT98@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:45:35 -0600 Subject: [PATCH 68/80] expand isDisposed check (#2488) --- .../lib/tableEdit/editors/features/TableResizer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts index 995c121c95a..81cfcc0eb0d 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts @@ -198,8 +198,10 @@ function onDragEnd( event: MouseEvent, initValue: DragAndDropInitValue | undefined ) { + if (context.editor.isDisposed()) { + return false; + } if ( - !context.editor.isDisposed() && isTableBottomVisible( context.editor, normalizeRect(context.table.getBoundingClientRect()), From 0a6339f49c099ba3c8f32a63183265ea70fd1201 Mon Sep 17 00:00:00 2001 From: Bryan Valverde U Date: Thu, 7 Mar 2024 16:02:51 -0600 Subject: [PATCH 69/80] init (#2477) --- .../lib/publicApi/model/mergeModel.ts | 5 ++ .../test/coreApi/pasteTest.ts | 4 +- .../test/publicApi/model/mergeModelTest.ts | 56 +++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts index bb499451ad1..94a90f13b35 100644 --- a/packages/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts +++ b/packages/roosterjs-content-model-core/lib/publicApi/model/mergeModel.ts @@ -184,6 +184,11 @@ function mergeParagraph( if (!mergeToCurrentParagraph) { newParagraph.format = newPara.format; + } else { + newParagraph.format = { + ...newParagraph.format, + ...newPara.format, + }; } } diff --git a/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts b/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts index b291d2132d5..032a74fe304 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts @@ -345,8 +345,8 @@ describe('Paste with clipboardData', () => { }, ], format: { - marginTop: '0px', - marginBottom: '0px', + marginTop: '1em', + marginBottom: '1em', }, decorator: { tagName: 'p', diff --git a/packages/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts b/packages/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts index 4cde086cbb4..e520f9d30e0 100644 --- a/packages/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts +++ b/packages/roosterjs-content-model-core/test/publicApi/model/mergeModelTest.ts @@ -2028,6 +2028,62 @@ describe('mergeModel', () => { }); }); + it('Divider to single selected paragraph both models with block format', () => { + const majorModel = createContentModelDocument(); + const sourceModel = createContentModelDocument(); + const para1 = createParagraph(false, { + marginBottom: 'ShouldBeRemoved', + marginTop: 'ShouldBeRemoved', + marginLeft: 'ShouldBeRemoved', + marginRight: 'ShouldBeRemoved', + }); + const marker = createSelectionMarker(); + const text1 = createText('test1'); + const text2 = createText('test2'); + + const divider = createParagraph(false, { + marginBottom: 'KeepThisStyle', + marginTop: 'KeepThisStyle', + marginLeft: 'KeepThisStyle', + marginRight: 'KeepThisStyle', + }); + + para1.segments.push(text1, marker, text2); + majorModel.blocks.push(para1); + + sourceModel.blocks.push(divider); + + mergeModel(majorModel, sourceModel, { + newEntities: [], + deletedEntities: [], + newImages: [], + }); + + expect(majorModel).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { segmentType: 'Text', text: 'test1', format: {} }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { segmentType: 'Text', text: 'test2', format: {} }, + ], + format: { + marginBottom: 'KeepThisStyle', + marginTop: 'KeepThisStyle', + marginLeft: 'KeepThisStyle', + marginRight: 'KeepThisStyle', + }, + }, + ], + }); + }); + it('Keep Paragraph format of source on merge', () => { const majorModel = createContentModelDocument(); const sourceModel = createContentModelDocument(); From 3b2f7154ce53d0c097d438e424daa2a263b434b7 Mon Sep 17 00:00:00 2001 From: Bryan Valverde U Date: Thu, 7 Mar 2024 16:15:33 -0600 Subject: [PATCH 70/80] Remove LineSpacing limitation (#2483) --- .../test/coreApi/pasteTest.ts | 2 +- .../processPastedContentFromWordDesktop.ts | 22 ------------------- .../test/paste/ContentModelPastePluginTest.ts | 2 +- ...processPastedContentFromWordDesktopTest.ts | 4 ++-- 4 files changed, 4 insertions(+), 26 deletions(-) diff --git a/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts b/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts index 032a74fe304..780535f1d82 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts @@ -140,7 +140,7 @@ describe('paste with content model & paste plugin', () => { editor?.pasteFromClipboard(clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(1); - expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 5); + expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 4); expect(WordDesktopFile.processPastedContentFromWordDesktop).toHaveBeenCalledTimes(1); }); diff --git a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts index 972b1be8ec1..a3840002df5 100644 --- a/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts +++ b/packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop.ts @@ -8,7 +8,6 @@ import { setProcessor } from '../utils/setProcessor'; import type { WordMetadata } from './WordMetadata'; import type { BeforePasteEvent, - ContentModelBlockFormat, ContentModelListItemLevelFormat, ContentModelTableFormat, DomToModelContext, @@ -16,9 +15,6 @@ import type { FormatParser, } from 'roosterjs-content-model-types'; -const PERCENTAGE_REGEX = /%/; -const DEFAULT_BROWSER_LINE_HEIGHT_PERCENTAGE = 120; - /** * @internal * Handles Pasted content when source is Word Desktop @@ -31,7 +27,6 @@ export function processPastedContentFromWordDesktop( const metadataMap: Map = getStyleMetadata(ev, trustedHTMLHandler); setProcessor(ev.domToModelOption, 'element', wordDesktopElementProcessor(metadataMap)); - addParser(ev.domToModelOption, 'block', removeNonValidLineHeight); addParser(ev.domToModelOption, 'block', removeNegativeTextIndentParser); addParser(ev.domToModelOption, 'listLevel', listLevelParser); addParser(ev.domToModelOption, 'container', wordTableParser); @@ -55,23 +50,6 @@ const wordDesktopElementProcessor = ( }; }; -function removeNonValidLineHeight( - format: ContentModelBlockFormat, - element: HTMLElement, - context: DomToModelContext, - defaultStyle: Readonly> -): void { - //If the line height is less than the browser default line height, line between the text is going to be too narrow - let parsedLineHeight: number; - if ( - PERCENTAGE_REGEX.test(element.style.lineHeight) && - !isNaN((parsedLineHeight = parseInt(element.style.lineHeight))) && - parsedLineHeight < DEFAULT_BROWSER_LINE_HEIGHT_PERCENTAGE - ) { - format.lineHeight = defaultStyle.lineHeight; - } -} - function listLevelParser( format: ContentModelListItemLevelFormat, element: HTMLElement, diff --git a/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts b/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts index 3ed5d3196f7..597f3128127 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/ContentModelPastePluginTest.ts @@ -53,7 +53,7 @@ describe('Content Model Paste Plugin Test', () => { plugin.initialize(editor); plugin.onPluginEvent(event); - expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 5); + expect(addParser.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 4); expect(setProcessor.setProcessor).toHaveBeenCalledTimes(1); }); diff --git a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts index 7b186403d55..19890f0f8a9 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts @@ -144,7 +144,7 @@ describe('processPastedContentFromWordDesktopTest', () => { runTest(source, { blockGroupType: 'Document', blocks: [] }, true); }); - it('Remove Line height less than default', () => { + it('Dont remove Line height less than default', () => { let source = '

Test

'; runTest( source, @@ -154,7 +154,7 @@ describe('processPastedContentFromWordDesktopTest', () => { { segments: [{ text: 'Test', segmentType: 'Text', format: {} }], blockType: 'Paragraph', - format: { marginTop: '1em', marginBottom: '1em' }, + format: { marginTop: '1em', marginBottom: '1em', lineHeight: '102%' }, decorator: { tagName: 'p', format: {} }, }, ], From 430acd72e5e3f8387a164b7d7960161ec279102c Mon Sep 17 00:00:00 2001 From: Andres-CT98 <107568016+Andres-CT98@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:12:23 -0600 Subject: [PATCH 71/80] Add table plugins and data to demo (#2489) * restore tableResize * activate tableCellSelection * disable some resizer tests * demo table changes * change dispose order * rework test attempt * disable one test * revert to all tests disabled --- demo/scripts/controls/BuildInPluginState.ts | 1 + demo/scripts/controls/getToggleablePlugins.ts | 2 + .../editorOptions/EditorOptionsPlugin.ts | 1 + .../editorOptions/EditorOptionsPlugin.ts | 2 +- .../sidePane/formatState/FormatStatePane.tsx | 41 +++++++++++++++++-- .../test/tableEdit/TableEditTestHelper.ts | 2 +- .../test/tableEdit/tableResizerTest.ts | 5 ++- 7 files changed, 46 insertions(+), 8 deletions(-) diff --git a/demo/scripts/controls/BuildInPluginState.ts b/demo/scripts/controls/BuildInPluginState.ts index 919c41da93b..fb61f825b38 100644 --- a/demo/scripts/controls/BuildInPluginState.ts +++ b/demo/scripts/controls/BuildInPluginState.ts @@ -15,6 +15,7 @@ export interface BuildInPluginList { imageEdit: boolean; cutPasteListChain: boolean; tableCellSelection: boolean; + tableResize: boolean; customReplace: boolean; listEditMenu: boolean; imageEditMenu: boolean; diff --git a/demo/scripts/controls/getToggleablePlugins.ts b/demo/scripts/controls/getToggleablePlugins.ts index 27ad93ed877..8f005219b01 100644 --- a/demo/scripts/controls/getToggleablePlugins.ts +++ b/demo/scripts/controls/getToggleablePlugins.ts @@ -9,6 +9,7 @@ import { HyperLink } from 'roosterjs-editor-plugins/lib/HyperLink'; import { ImageEdit } from 'roosterjs-editor-plugins/lib/ImageEdit'; import { Paste } from 'roosterjs-editor-plugins/lib/Paste'; import { TableCellSelection } from 'roosterjs-editor-plugins/lib/TableCellSelection'; +import { TableResize } from 'roosterjs-editor-plugins'; import { Watermark } from 'roosterjs-editor-plugins/lib/Watermark'; import { createContextMenuPlugin, @@ -42,6 +43,7 @@ export default function getToggleablePlugins(initState: BuildInPluginState) { imageEdit, cutPasteListChain: pluginList.cutPasteListChain ? new CutPasteListChain() : null, tableCellSelection: pluginList.tableCellSelection ? new TableCellSelection() : null, + tableResize: pluginList.tableResize ? new TableResize() : null, customReplace: pluginList.customReplace ? new CustomReplacePlugin() : null, autoFormat: pluginList.autoFormat ? new AutoFormat() : null, listEditMenu: diff --git a/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts b/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts index 47528ae642d..01c06bc44d0 100644 --- a/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts +++ b/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts @@ -13,6 +13,7 @@ const initialState: BuildInPluginState = { imageEdit: true, cutPasteListChain: true, tableCellSelection: true, + tableResize: true, customReplace: true, listEditMenu: true, imageEditMenu: true, diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts index d851acb0590..871a524ace4 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts @@ -21,7 +21,7 @@ const initialState: OptionState = { hyperlink: false, watermark: false, imageEdit: false, - tableCellSelection: false, + tableCellSelection: true, customReplace: false, announce: false, }, diff --git a/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx index d6cd8cc9658..d9ef783395f 100644 --- a/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx +++ b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx @@ -1,6 +1,10 @@ import * as React from 'react'; -import { ContentModelFormatState, EditorEnvironment } from 'roosterjs-content-model-types'; import { SidePaneElementProps } from '../SidePaneElement'; +import { + ContentModelFormatState, + EditorEnvironment, + TableMetadataFormat, +} from 'roosterjs-content-model-types'; const styles = require('./FormatStatePane.scss'); @@ -34,9 +38,40 @@ export default class FormatStatePane extends React.Component< const { format, x, y } = this.state; const { isMac, isAndroid, isSafari, isMobileOrTablet } = this.props.env ?? {}; + const TableFormat = () => { + const tableFormat = format.tableFormat; + if (!tableFormat) { + return <>; + } + const tableFromatRows = Object.keys(tableFormat).map( + (key: keyof TableMetadataFormat, index: number, array) => { + const rowStyle: React.CSSProperties = + index == 0 + ? { borderTop: 'solid' } + : index == array.length - 1 + ? { borderBottom: 'solid' } + : {}; + return ( + + {key} + {String(tableFormat[key])} + + ); + } + ); + return tableFromatRows; + }; return format ? ( - +
+ + + + + {TableFormat()} @@ -80,8 +115,6 @@ export default class FormatStatePane extends React.Component< {this.renderSpan(format.isBlockQuote, 'Quote')} {this.renderSpan(format.canUnlink, 'In Link')} {this.renderSpan(format.canAddImageAltText, 'In Image')} - {this.renderSpan(format.isInTable, 'In Table')} - {this.renderSpan(format.tableHasHeader, 'Table Has Header')} diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts index 066e66501f9..1294e7f4569 100644 --- a/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts @@ -52,8 +52,8 @@ export function beforeTableTest(TEST_ID: string) { * @param TEST_ID The id of the editor div */ export function afterTableTest(editor: IEditor, plugin: TableEditPlugin, TEST_ID: string) { - !editor.isDisposed() && editor.dispose(); plugin.dispose(); + !editor.isDisposed() && editor.dispose(); TestHelper.removeElement(TEST_ID); document.body = document.createElement('body'); } diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts index 87b8d85bba5..9a32065e85a 100644 --- a/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts @@ -1,3 +1,4 @@ +import * as TestHelper from '../TestHelper'; import { getModelTable } from './tableData'; import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; import { @@ -20,7 +21,7 @@ import { const TABLE_RESIZER_ID = '_Table_Resizer'; -describe('Table Resizer tests', () => { +xdescribe('Table Resizer tests', () => { let editor: IEditor; let plugin: TableEditPlugin; const TEST_ID = 'resizerTest'; @@ -73,7 +74,7 @@ describe('Table Resizer tests', () => { it('removes table resizer on scrolling', () => { const pluginEvent: PluginEvent = { eventType: 'scroll', - scrollContainer: editor.getDocument().body as HTMLElement, + scrollContainer: editor.getScrollContainer(), rawEvent: null, }; removeResizerTest(pluginEvent); From 570bbfeb11ea0fec0ef7dbf7210ca239cb67d763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Fri, 8 Mar 2024 11:04:25 -0300 Subject: [PATCH 72/80] fix text --- .../lib/autoFormat/AutoFormatPlugin.ts | 12 ++++++---- .../lib/autoFormat/link/createLink.ts | 5 +---- .../autoFormat/link/createLinkAfterSpace.ts | 5 +---- .../lib/autoFormat/link/unlink.ts | 5 +---- .../test/autoFormat/AutoFormatPluginTest.ts | 22 +++++-------------- .../link/createLinkAfterSpaceTest.ts | 11 ++++------ .../test/autoFormat/link/createLinkTest.ts | 11 ++++------ .../test/autoFormat/link/unlinkTest.ts | 3 +-- 8 files changed, 26 insertions(+), 48 deletions(-) diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts index 531bec6cb43..8bac0479500 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/AutoFormatPlugin.ts @@ -111,10 +111,14 @@ export class AutoFormatPlugin implements EditorPlugin { switch (rawEvent.key) { case ' ': keyboardListTrigger(editor, rawEvent, autoBullet, autoNumbering); - createLinkAfterSpace(editor, autoLink); + if (autoLink) { + createLinkAfterSpace(editor); + } break; case 'Backspace': - unlink(editor, rawEvent, autoUnlink); + if (autoUnlink) { + unlink(editor, rawEvent); + } break; } } @@ -122,8 +126,8 @@ export class AutoFormatPlugin implements EditorPlugin { private handleContentChangedEvent(editor: IEditor, event: ContentChangedEvent) { const { autoLink } = this.options; - if (event.source == 'Paste') { - createLink(editor, autoLink); + if (event.source == 'Paste' && autoLink) { + createLink(editor); } } } diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts index 9ed1ed40ec6..baaa7a00108 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLink.ts @@ -5,10 +5,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; /** * @internal */ -export function createLink(editor: IEditor, autoLink: boolean) { - if (!autoLink) { - return; - } +export function createLink(editor: IEditor) { editor.formatContentModel(model => { const link = getLinkSegment(model); if (link && !link.link) { diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts index 230dba46634..c322c470dd0 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/createLinkAfterSpace.ts @@ -6,10 +6,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; /** * @internal */ -export function createLinkAfterSpace(editor: IEditor, autoLink: boolean) { - if (!autoLink) { - return; - } +export function createLinkAfterSpace(editor: IEditor) { editor.formatContentModel(model => { const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs( model, diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts index 75bd3339f9f..4648cc5b3e1 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/link/unlink.ts @@ -4,10 +4,7 @@ import type { IEditor } from 'roosterjs-content-model-types'; /** * @internal */ -export function unlink(editor: IEditor, rawEvent: KeyboardEvent, unlink: boolean) { - if (!unlink) { - return; - } +export function unlink(editor: IEditor, rawEvent: KeyboardEvent) { editor.formatContentModel(model => { const link = getLinkSegment(model); if (link?.link) { diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts index c1888588db7..4b210b300fd 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/AutoFormatPluginTest.ts @@ -139,10 +139,7 @@ describe('Content Model Auto Format Plugin Test', () => { plugin.onPluginEvent(event); if (shouldCallTrigger) { - expect(createLinkSpy).toHaveBeenCalledWith( - editor, - options ? options.autoLink : true - ); + expect(createLinkSpy).toHaveBeenCalledWith(editor); } else { expect(createLinkSpy).not.toHaveBeenCalled(); } @@ -161,7 +158,7 @@ describe('Content Model Auto Format Plugin Test', () => { eventType: 'contentChanged', source: 'Paste', }; - runTest(event, true, { autoLink: false }); + runTest(event, false, { autoLink: false }); }); it('should not call createLink - not paste', () => { @@ -193,11 +190,7 @@ describe('Content Model Auto Format Plugin Test', () => { plugin.onPluginEvent(event); if (shouldCallTrigger) { - expect(unlinkSpy).toHaveBeenCalledWith( - editor, - event.rawEvent, - options ? options.autoUnlink : true - ); + expect(unlinkSpy).toHaveBeenCalledWith(editor, event.rawEvent); } else { expect(unlinkSpy).not.toHaveBeenCalled(); } @@ -218,7 +211,7 @@ describe('Content Model Auto Format Plugin Test', () => { eventType: 'keyDown', rawEvent: { key: 'Backspace', preventDefault: () => {} } as any, }; - runTest(event, true, { + runTest(event, false, { autoUnlink: false, }); }); @@ -252,10 +245,7 @@ describe('Content Model Auto Format Plugin Test', () => { plugin.onPluginEvent(event); if (shouldCallTrigger) { - expect(createLinkAfterSpaceSpy).toHaveBeenCalledWith( - editor, - options ? options.autoLink : true - ); + expect(createLinkAfterSpaceSpy).toHaveBeenCalledWith(editor); } else { expect(createLinkAfterSpaceSpy).not.toHaveBeenCalled(); } @@ -274,7 +264,7 @@ describe('Content Model Auto Format Plugin Test', () => { eventType: 'keyDown', rawEvent: { key: ' ', preventDefault: () => {} } as any, }; - runTest(event, true, { + runTest(event, false, { autoLink: false, }); }); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkAfterSpaceTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkAfterSpaceTest.ts index 04a196e977f..7c01772f662 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkAfterSpaceTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkAfterSpaceTest.ts @@ -18,13 +18,10 @@ describe('createLinkAfterSpace', () => { expect(result).toBe(expectedResult); }); - createLinkAfterSpace( - { - focus: () => {}, - formatContentModel: formatWithContentModelSpy, - } as any, - true - ); + createLinkAfterSpace({ + focus: () => {}, + formatContentModel: formatWithContentModelSpy, + } as any); expect(formatWithContentModelSpy).toHaveBeenCalled(); expect(input).toEqual(expectedModel); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts index ebb31cc335b..d87b4f9c562 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/createLinkTest.ts @@ -18,13 +18,10 @@ describe('createLink', () => { expect(result).toBe(expectedResult); }); - createLink( - { - focus: () => {}, - formatContentModel: formatWithContentModelSpy, - } as any, - true - ); + createLink({ + focus: () => {}, + formatContentModel: formatWithContentModelSpy, + } as any); expect(formatWithContentModelSpy).toHaveBeenCalled(); expect(input).toEqual(expectedModel); diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts index 721268c07aa..caa0b734a93 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/link/unlinkTest.ts @@ -26,8 +26,7 @@ describe('unlink', () => { focus: () => {}, formatContentModel: formatWithContentModelSpy, } as any, - rawEvent, - true + rawEvent ); expect(formatWithContentModelSpy).toHaveBeenCalled(); From 6f26057d769cddfec62b30021721a6ca12f454bb Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Fri, 8 Mar 2024 09:01:51 -0800 Subject: [PATCH 73/80] Respect "shouldPersist" from EntityOperationEvent (#2486) --- .../controlsV2/plugins/SampleEntityPlugin.ts | 1 - .../lib/corePlugin/EntityPlugin.ts | 1 + .../lib/utils/restoreSnapshotHTML.ts | 17 +++- .../test/corePlugin/EntityPluginTest.ts | 15 ++++ .../test/utils/restoreSnapshotHTMLTest.ts | 81 +++++++++++++++++++ 5 files changed, 110 insertions(+), 5 deletions(-) diff --git a/demo/scripts/controlsV2/plugins/SampleEntityPlugin.ts b/demo/scripts/controlsV2/plugins/SampleEntityPlugin.ts index b64402e438b..3ea5fc36e0f 100644 --- a/demo/scripts/controlsV2/plugins/SampleEntityPlugin.ts +++ b/demo/scripts/controlsV2/plugins/SampleEntityPlugin.ts @@ -41,7 +41,6 @@ export default class SampleEntityPlugin implements EditorPlugin { hydratedEntity?.dehydrate(); this.hydratedEntities[entity.id] = new HydratedEntity(entity, this.onClick); - event.shouldPersist = true; break; case 'removeFromEnd': diff --git a/packages/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts index 460ef490da6..4626936904a 100644 --- a/packages/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts +++ b/packages/roosterjs-content-model-core/lib/corePlugin/EntityPlugin.ts @@ -259,6 +259,7 @@ class EntityPlugin implements PluginWithState { wrapper, }, state: operation == 'updateEntityState' ? state : undefined, + shouldPersist: operation == 'newEntity' ? true : undefined, // By default, we always persist entity now }) : null; } diff --git a/packages/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts b/packages/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts index ad4a2e1a5f7..8d88512ca4e 100644 --- a/packages/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts +++ b/packages/roosterjs-content-model-core/lib/utils/restoreSnapshotHTML.ts @@ -42,8 +42,8 @@ export function restoreSnapshotHTML(core: EditorCore, snapshot: Snapshot) { if (wrapper) { if (wrapper == refNode) { // In case the node we are moving is just the ref node, - // We create a temporary clone and insert it before the refNode, the use this cloned node as refNode - // Then after replaceChild(), the original refNode will be moved away + // We create a temporary clone and insert it before the refNode, and use this cloned node as refNode + // After replaceChild(), the original refNode will be moved away const markerNode = wrapper.cloneNode(); physicalRoot.insertBefore(markerNode, refNode); @@ -76,7 +76,7 @@ function tryGetEntityElement( if (isEntityElement(node)) { const format = parseEntityFormat(node); - result = (format.id && entityMap[format.id]?.element) || null; + result = getEntityWrapperForReuse(entityMap, format.id); } else if (isBlockEntityContainer(node)) { result = tryGetEntityFromContainer(node, entityMap); } @@ -96,7 +96,7 @@ function tryGetEntityFromContainer( for (let node = element.firstChild; node; node = node.nextSibling) { if (isEntityElement(node) && isNodeOfType(node, 'ELEMENT_NODE')) { const format = parseEntityFormat(node); - const parent = format.id ? entityMap[format.id]?.element.parentElement : null; + const parent = getEntityWrapperForReuse(entityMap, format.id)?.parentElement; return isNodeOfType(parent, 'ELEMENT_NODE') && isBlockEntityContainer(parent) ? parent @@ -106,3 +106,12 @@ function tryGetEntityFromContainer( return null; } + +function getEntityWrapperForReuse( + entityMap: Record, + entityId: string | undefined +): HTMLElement | null { + const entry = entityId ? entityMap[entityId] : undefined; + + return entry?.canPersist ? entry.element : null; +} diff --git a/packages/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts index 0f9bf77b654..a79a9fa2f79 100644 --- a/packages/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts +++ b/packages/roosterjs-content-model-core/test/corePlugin/EntityPluginTest.ts @@ -110,6 +110,7 @@ describe('EntityPlugin', () => { wrapper: wrapper, }, state: undefined, + shouldPersist: true, }); expect(transformColorSpy).not.toHaveBeenCalled(); expect(DelimiterUtils.handleDelimiterContentChangedEvent).toHaveBeenCalled(); @@ -154,6 +155,7 @@ describe('EntityPlugin', () => { wrapper: wrapper, }, state: undefined, + shouldPersist: true, }); expect(transformColorSpy).not.toHaveBeenCalled(); expect(DelimiterUtils.handleDelimiterContentChangedEvent).toHaveBeenCalled(); @@ -200,6 +202,7 @@ describe('EntityPlugin', () => { wrapper: wrapper, }, state: undefined, + shouldPersist: true, }); expect(transformColorSpy).not.toHaveBeenCalled(); expect(DelimiterUtils.handleDelimiterContentChangedEvent).toHaveBeenCalled(); @@ -242,6 +245,7 @@ describe('EntityPlugin', () => { wrapper: wrapper, }, state: undefined, + shouldPersist: true, }); expect(transformColorSpy).toHaveBeenCalledTimes(1); expect(transformColorSpy).toHaveBeenCalledWith( @@ -300,6 +304,7 @@ describe('EntityPlugin', () => { wrapper: wrapper, }, state: undefined, + shouldPersist: true, }); expect(triggerPluginEventSpy).toHaveBeenCalledWith('entityOperation', { operation: 'overwrite', @@ -311,6 +316,7 @@ describe('EntityPlugin', () => { wrapper: wrapper2, }, state: undefined, + shouldPersist: undefined, }); expect(transformColorSpy).not.toHaveBeenCalled(); expect(DelimiterUtils.handleDelimiterContentChangedEvent).toHaveBeenCalled(); @@ -388,6 +394,7 @@ describe('EntityPlugin', () => { wrapper: wrapper, }, state: undefined, + shouldPersist: true, }); expect(transformColorSpy).not.toHaveBeenCalled(); expect(DelimiterUtils.handleDelimiterContentChangedEvent).toHaveBeenCalled(); @@ -454,6 +461,7 @@ describe('EntityPlugin', () => { wrapper: wrapper2, }, state: undefined, + shouldPersist: true, }); expect(triggerPluginEventSpy).toHaveBeenCalledWith('entityOperation', { operation: 'removeFromStart', @@ -465,6 +473,7 @@ describe('EntityPlugin', () => { wrapper: wrapper1, }, state: undefined, + shouldPersist: undefined, }); expect(transformColorSpy).not.toHaveBeenCalled(); expect(DelimiterUtils.handleDelimiterContentChangedEvent).toHaveBeenCalled(); @@ -524,6 +533,7 @@ describe('EntityPlugin', () => { wrapper: wrapper2, }, state: undefined, + shouldPersist: true, }); expect(transformColorSpy).not.toHaveBeenCalled(); expect(DelimiterUtils.handleDelimiterContentChangedEvent).toHaveBeenCalled(); @@ -571,6 +581,7 @@ describe('EntityPlugin', () => { wrapper, }, state: entityState, + shouldPersist: undefined, }); expect(DelimiterUtils.handleDelimiterContentChangedEvent).toHaveBeenCalled(); }); @@ -625,6 +636,7 @@ describe('EntityPlugin', () => { wrapper: mockedNode, }, state: undefined, + shouldPersist: undefined, }); }); @@ -661,6 +673,7 @@ describe('EntityPlugin', () => { wrapper: mockedNode1, }, state: undefined, + shouldPersist: undefined, }); }); @@ -721,6 +734,7 @@ describe('EntityPlugin', () => { wrapper: wrapper1, }, state: undefined, + shouldPersist: undefined, }); expect(triggerPluginEventSpy).toHaveBeenCalledWith('entityOperation', { operation: 'replaceTemporaryContent', @@ -732,6 +746,7 @@ describe('EntityPlugin', () => { wrapper: wrapper2, }, state: undefined, + shouldPersist: undefined, }); }); }); diff --git a/packages/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts b/packages/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts index 37404206593..fe16dd607f3 100644 --- a/packages/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts +++ b/packages/roosterjs-content-model-core/test/utils/restoreSnapshotHTMLTest.ts @@ -64,6 +64,7 @@ describe('restoreSnapshotHTML', () => { entityWrapper.id = 'div2'; core.entity.entityMap.C = { element: entityWrapper, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -83,6 +84,7 @@ describe('restoreSnapshotHTML', () => { entityWrapper.id = 'div2'; core.entity.entityMap.B = { element: entityWrapper, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -105,6 +107,7 @@ describe('restoreSnapshotHTML', () => { entityWrapper.id = 'div2'; core.entity.entityMap.B = { element: entityWrapper, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -113,6 +116,31 @@ describe('restoreSnapshotHTML', () => { expect(div.childNodes[1]).toBe(entityWrapper); }); + it('HTML with block entity at root level, cannot persist, entity is already in editor', () => { + const snapshot: Snapshot = { + html: '
test1

test2
', + } as any; + + const entityWrapper = document.createElement('DIV'); + + div.appendChild(document.createTextNode('test1')); + div.appendChild(entityWrapper); + div.appendChild(document.createTextNode('test2')); + + entityWrapper.id = 'div2'; + core.entity.entityMap.B = { + element: entityWrapper, + canPersist: false, + }; + + restoreSnapshotHTML(core, snapshot); + + expect(div.innerHTML).toBe( + '
test1

test2
' + ); + expect(div.childNodes[1]).not.toBe(entityWrapper); + }); + it('HTML with double block entity at root level, entity is already in editor in the same order', () => { const snapshot: Snapshot = { html: @@ -133,9 +161,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -166,9 +196,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -200,9 +232,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -234,9 +268,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -267,9 +303,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -301,9 +339,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -334,9 +374,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -365,9 +407,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -389,6 +433,7 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -411,6 +456,7 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper, + canPersist: true, }; div.appendChild(entityWrapper); @@ -434,6 +480,7 @@ describe('restoreSnapshotHTML', () => { entityWrapper.id = 'div2'; core.entity.entityMap.C = { element: entityWrapper, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -455,6 +502,7 @@ describe('restoreSnapshotHTML', () => { entityWrapper.id = 'div2'; core.entity.entityMap.B = { element: entityWrapper, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -482,6 +530,7 @@ describe('restoreSnapshotHTML', () => { entityWrapper.id = 'div2'; core.entity.entityMap.B = { element: entityWrapper, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -515,9 +564,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -552,9 +603,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -590,9 +643,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -628,9 +683,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -665,9 +722,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -703,9 +762,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -740,9 +801,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -775,9 +838,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -812,9 +877,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -847,9 +914,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -883,9 +952,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -919,9 +990,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -954,9 +1027,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -990,9 +1065,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -1025,9 +1102,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); @@ -1058,9 +1137,11 @@ describe('restoreSnapshotHTML', () => { core.entity.entityMap.B1 = { element: entityWrapper1, + canPersist: true, }; core.entity.entityMap.B2 = { element: entityWrapper2, + canPersist: true, }; restoreSnapshotHTML(core, snapshot); From 0296491ffecc596fb4186abf2da08d9a4f865610 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Fri, 8 Mar 2024 09:07:37 -0800 Subject: [PATCH 74/80] Export content without selection (#2491) --- .../lib/coreApi/createContentModel.ts | 5 +++- .../lib/editor/Editor.ts | 20 +++++++++++--- .../lib/publicApi/model/exportContent.ts | 2 +- .../test/coreApi/createContentModelTest.ts | 27 +++++++++++++++++++ .../test/editor/EditorTest.ts | 4 +-- .../test/publicApi/model/exportContentTest.ts | 7 ++--- .../lib/editor/EditorCore.ts | 11 +++++--- .../lib/editor/IEditor.ts | 6 ++++- 8 files changed, 66 insertions(+), 16 deletions(-) diff --git a/packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts b/packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts index 64640a328c4..3f0c05e3411 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts @@ -27,7 +27,10 @@ export const createContentModel: CreateContentModel = (core, option, selectionOv if (cachedModel) { return cachedModel; } else { - const selection = selectionOverride || core.api.getDOMSelection(core) || undefined; + const selection = + selectionOverride == 'none' + ? undefined + : selectionOverride || core.api.getDOMSelection(core) || undefined; const saveIndex = !option && !selectionOverride; const editorContext = core.api.createEditorContext(core, saveIndex); const domToModelContext = option diff --git a/packages/roosterjs-content-model-core/lib/editor/Editor.ts b/packages/roosterjs-content-model-core/lib/editor/Editor.ts index 5477ab1dcdf..c31b2010b04 100644 --- a/packages/roosterjs-content-model-core/lib/editor/Editor.ts +++ b/packages/roosterjs-content-model-core/lib/editor/Editor.ts @@ -91,8 +91,12 @@ export class Editor implements IEditor { * If editor is in dark mode, the cloned entity will be converted back to light mode. * - reduced: Returns a reduced Content Model that only contains the model of current selection. If there is already a up-to-date cached model, use it * instead to improve performance. This is mostly used for retrieve current format state. + * - clean: Similar with disconnected, this will return a disconnected model, the difference is "clean" mode will not include any selection info. + * This is usually used for exporting content */ - getContentModelCopy(mode: 'connected' | 'disconnected' | 'reduced'): ContentModelDocument { + getContentModelCopy( + mode: 'connected' | 'disconnected' | 'reduced' | 'clean' + ): ContentModelDocument { const core = this.getCore(); switch (mode) { @@ -104,9 +108,17 @@ export class Editor implements IEditor { }); case 'disconnected': - return cloneModel(core.api.createContentModel(core), { - includeCachedElement: this.cloneOptionCallback, - }); + case 'clean': + return cloneModel( + core.api.createContentModel( + core, + undefined /*option*/, + mode == 'clean' ? 'none' : undefined /*selectionOverride*/ + ), + { + includeCachedElement: this.cloneOptionCallback, + } + ); case 'reduced': return core.api.createContentModel(core, { processorOverride: { diff --git a/packages/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts index fc3096adbde..3ddf0e27006 100644 --- a/packages/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts +++ b/packages/roosterjs-content-model-core/lib/publicApi/model/exportContent.ts @@ -22,7 +22,7 @@ export function exportContent( if (mode == 'PlainTextFast') { return editor.getDOMHelper().getTextContent(); } else { - const model = editor.getContentModelCopy('disconnected'); + const model = editor.getContentModelCopy('clean'); if (mode == 'PlainText') { return contentModelToText(model); diff --git a/packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts b/packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts index 5d738afd2b2..6e1cc6748f7 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts @@ -223,4 +223,31 @@ describe('createContentModel with selection', () => { expect(model).toBe(updatedModel); expect(flushMutationsSpy).toHaveBeenCalledTimes(1); }); + + it('With selection override', () => { + const MockedContainer = 'MockedContainer'; + const MockedRange = { + name: 'MockedRange', + commonAncestorContainer: MockedContainer, + } as any; + const mockedSelection = { + type: 'range', + range: MockedRange, + } as any; + + createContentModel(core, undefined, mockedSelection); + + expect(domToContentModelSpy).toHaveBeenCalledTimes(1); + expect(domToContentModelSpy).toHaveBeenCalledWith(MockedDiv, mockedContext, { + type: 'range', + range: MockedRange, + } as any); + }); + + it('With selection override, selection=none', () => { + createContentModel(core, undefined, 'none'); + + expect(domToContentModelSpy).toHaveBeenCalledTimes(1); + expect(domToContentModelSpy).toHaveBeenCalledWith(MockedDiv, mockedContext, undefined); + }); }); diff --git a/packages/roosterjs-content-model-core/test/editor/EditorTest.ts b/packages/roosterjs-content-model-core/test/editor/EditorTest.ts index 591c3df519b..02b1d9fb141 100644 --- a/packages/roosterjs-content-model-core/test/editor/EditorTest.ts +++ b/packages/roosterjs-content-model-core/test/editor/EditorTest.ts @@ -209,7 +209,7 @@ describe('Editor', () => { expect(cloneNodeSpy).toHaveBeenCalledWith(true); expect(model).toBe(mockedClonedModel); - expect(createContentModelSpy).toHaveBeenCalledWith(mockedCore); + expect(createContentModelSpy).toHaveBeenCalledWith(mockedCore, undefined, undefined); expect(transformColorSpy).not.toHaveBeenCalled(); // Clone in dark mode @@ -218,7 +218,7 @@ describe('Editor', () => { expect(cloneNodeSpy).toHaveBeenCalledWith(true); expect(model).toBe(mockedClonedModel); - expect(createContentModelSpy).toHaveBeenCalledWith(mockedCore); + expect(createContentModelSpy).toHaveBeenCalledWith(mockedCore, undefined, undefined); expect(transformColorSpy).toHaveBeenCalledWith( clonedNode, true, diff --git a/packages/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts b/packages/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts index 494f2c12033..162387629ec 100644 --- a/packages/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts +++ b/packages/roosterjs-content-model-core/test/publicApi/model/exportContentTest.ts @@ -39,7 +39,7 @@ describe('exportContent', () => { const text = exportContent(editor, 'PlainText'); expect(text).toBe(mockedText); - expect(getContentModelCopySpy).toHaveBeenCalledWith('disconnected'); + expect(getContentModelCopySpy).toHaveBeenCalledWith('clean'); expect(contentModelToTextSpy).toHaveBeenCalledWith(mockedModel); }); @@ -71,7 +71,8 @@ describe('exportContent', () => { const html = exportContent(editor, 'HTML'); expect(html).toBe(mockedHTML); - expect(getContentModelCopySpy).toHaveBeenCalledWith('disconnected'); + expect(getContentModelCopySpy).toHaveBeenCalledWith('clean'); + 1; expect(createModelToDomContextSpy).toHaveBeenCalledWith(undefined, undefined); expect(contentModelToDomSpy).toHaveBeenCalledWith( mockedDoc, @@ -115,7 +116,7 @@ describe('exportContent', () => { const html = exportContent(editor, 'HTML', mockedOptions); expect(html).toBe(mockedHTML); - expect(getContentModelCopySpy).toHaveBeenCalledWith('disconnected'); + expect(getContentModelCopySpy).toHaveBeenCalledWith('clean'); expect(createModelToDomContextSpy).toHaveBeenCalledWith(undefined, mockedOptions); expect(contentModelToDomSpy).toHaveBeenCalledWith( mockedDoc, diff --git a/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts b/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts index dfacba10408..bab1cc85a38 100644 --- a/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts +++ b/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts @@ -34,12 +34,13 @@ export type CreateEditorContext = (core: EditorCore, saveIndex: boolean) => Edit * Create Content Model from DOM tree in this editor * @param core The EditorCore object * @param option The option to customize the behavior of DOM to Content Model conversion - * @param selectionOverride When passed, use this selection range instead of current selection in editor + * @param selectionOverride When passed a valid selection, use this selection range instead of current selection in editor. + * When pass "none", it means we don't need a selection in content model */ export type CreateContentModel = ( core: EditorCore, option?: DomToModelOption, - selectionOverride?: DOMSelection + selectionOverride?: DOMSelection | 'none' ) => ContentModelDocument; /** @@ -168,9 +169,11 @@ export type Paste = (core: EditorCore, clipboardData: ClipboardData, pasteType: */ export interface CoreApiMap { /** - * Create a EditorContext object used by ContentModel API + * Create Content Model from DOM tree in this editor * @param core The EditorCore object - * @param saveIndex True to allow saving index info into node using domIndexer, otherwise false + * @param option The option to customize the behavior of DOM to Content Model conversion + * @param selectionOverride When passed a valid selection, use this selection range instead of current selection in editor. + * When pass "none", it means we don't need a selection in content model */ createEditorContext: CreateEditorContext; diff --git a/packages/roosterjs-content-model-types/lib/editor/IEditor.ts b/packages/roosterjs-content-model-types/lib/editor/IEditor.ts index 301a2367ace..733470dd42c 100644 --- a/packages/roosterjs-content-model-types/lib/editor/IEditor.ts +++ b/packages/roosterjs-content-model-types/lib/editor/IEditor.ts @@ -34,8 +34,12 @@ export interface IEditor { * If editor is in dark mode, the cloned entity will be converted back to light mode. * - reduced: Returns a reduced Content Model that only contains the model of current selection. If there is already a up-to-date cached model, use it * instead to improve performance. This is mostly used for retrieve current format state. + * - clean: Similar with disconnected, this will return a disconnected model, the difference is "clean" mode will not include any selection info. + * This is usually used for exporting content */ - getContentModelCopy(mode: 'connected' | 'disconnected' | 'reduced'): ContentModelDocument; + getContentModelCopy( + mode: 'connected' | 'disconnected' | 'reduced' | 'clean' + ): ContentModelDocument; /** * Get current running environment, such as if editor is running on Mac From be79cb006ecce18a4d0cfb6181f3ba06e163db3d Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Fri, 8 Mar 2024 09:13:25 -0800 Subject: [PATCH 75/80] Clean up core API (#2481) * Clean up core API * fix build * fix build * fix test --- .../controlsV2/demoButtons/pasteButton.ts | 6 +- .../plugin/createPasteOptionPlugin.ts | 10 +- .../lib/coreApi/createContentModel.ts | 10 +- .../lib/coreApi/focus.ts | 6 +- .../lib/coreApi/formatContentModel.ts | 2 +- .../lib/coreApi/getDOMSelection.ts | 2 +- .../lib/coreApi/hasFocus.ts | 12 - .../lib/coreApi/setContentModel.ts | 9 +- .../lib/coreApi/setDOMSelection.ts | 2 +- .../lib/corePlugin/CopyPastePlugin.ts | 3 +- .../lib/editor/DOMHelperImpl.ts | 5 + .../lib/editor/Editor.ts | 17 +- .../lib/editor/coreApiMap.ts | 14 +- .../lib/editor/createEditorCore.ts | 13 +- .../roosterjs-content-model-core/lib/index.ts | 3 +- .../lib/publicApi/model/cloneModel.ts | 29 +- .../lib/{coreApi => publicApi/paste}/paste.ts | 50 ++- .../paste/generatePasteOptionFromPlugins.ts | 12 +- .../lib/utils/paste/mergePasteContent.ts | 22 +- .../test/coreApi/createContentModelTest.ts | 8 +- .../test/coreApi/focusTest.ts | 4 +- .../test/coreApi/formatContentModelTest.ts | 4 +- .../test/coreApi/getDOMSelectionTest.ts | 2 +- .../test/coreApi/hasFocusTest.ts | 50 --- .../test/coreApi/setContentModelTest.ts | 8 +- .../test/coreApi/setDOMSelectionTest.ts | 4 +- .../test/corePlugin/CopyPastePluginTest.ts | 36 +- .../test/editor/DOMHelperImplTest.ts | 336 +++++++++++------- .../test/editor/EditorTest.ts | 46 +-- .../test/editor/createEditorCoreTest.ts | 14 +- .../{coreApi => publicApi/paste}/pasteTest.ts | 44 ++- .../generatePasteOptionFromPluginsTest.ts | 30 +- .../test/utils/paste/mergePasteContentTest.ts | 68 ++-- .../paste/e2e/cmPasteFromExcelOnlineTest.ts | 5 +- .../test/paste/e2e/cmPasteFromExcelTest.ts | 33 +- .../test/paste/e2e/cmPasteFromWacTest.ts | 5 +- .../test/paste/e2e/cmPasteFromWordTest.ts | 6 +- .../test/paste/e2e/cmPasteTest.ts | 3 +- .../test/shortcut/ShortcutPluginTest.ts | 4 +- .../lib/editor/EditorCore.ts | 67 +--- .../lib/editor/IEditor.ts | 9 - .../lib/index.ts | 6 +- .../lib/parameter/CloneModelOptions.ts | 27 ++ .../lib/parameter/DOMHelper.ts | 6 + .../lib/parameter/EditorEnvironment.ts | 45 ++- .../lib/editor/EditorAdapter.ts | 8 +- 46 files changed, 531 insertions(+), 574 deletions(-) delete mode 100644 packages/roosterjs-content-model-core/lib/coreApi/hasFocus.ts rename packages/roosterjs-content-model-core/lib/{coreApi => publicApi/paste}/paste.ts (50%) delete mode 100644 packages/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts rename packages/roosterjs-content-model-core/test/{coreApi => publicApi/paste}/pasteTest.ts (93%) create mode 100644 packages/roosterjs-content-model-types/lib/parameter/CloneModelOptions.ts diff --git a/demo/scripts/controlsV2/demoButtons/pasteButton.ts b/demo/scripts/controlsV2/demoButtons/pasteButton.ts index 99e51cac76a..4d97de90a8e 100644 --- a/demo/scripts/controlsV2/demoButtons/pasteButton.ts +++ b/demo/scripts/controlsV2/demoButtons/pasteButton.ts @@ -1,5 +1,5 @@ -import { extractClipboardItems } from 'roosterjs-content-model-core'; -import { RibbonButton } from '../roosterjsReact/ribbon'; +import { extractClipboardItems, paste } from 'roosterjs-content-model-core'; +import type { RibbonButton } from '../roosterjsReact/ribbon'; /** * @internal @@ -19,7 +19,7 @@ export const pasteButton: RibbonButton<'buttonNamePaste'> = { createDataTransferItems(clipboardItems) ); const clipboardData = await extractClipboardItems(dataTransferItems); - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); } catch {} } }, diff --git a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts index cbdf9bd300f..c5a5fd570ef 100644 --- a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts +++ b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { ButtonKeys, Buttons } from '../utils/buttons'; -import { ChangeSource } from 'roosterjs-content-model-core'; +import { ChangeSource, paste } from 'roosterjs-content-model-core'; import { ClipboardData, IEditor, PluginEvent } from 'roosterjs-content-model-types'; import { showPasteOptionPane } from '../component/showPasteOptionPane'; import type { PasteOptionPane } from '../component/showPasteOptionPane'; @@ -130,18 +130,18 @@ class PasteOptionPlugin implements ReactEditorPlugin { switch (key) { case 'pasteOptionPasteAsIs': - this.editor.pasteFromClipboard(this.clipboardData); + paste(this.editor, this.clipboardData); break; case 'pasteOptionPasteText': - this.editor.pasteFromClipboard(this.clipboardData, 'asPlainText'); + paste(this.editor, this.clipboardData, 'asPlainText'); break; case 'pasteOptionMergeFormat': - this.editor.pasteFromClipboard(this.clipboardData, 'mergeFormat'); + paste(this.editor, this.clipboardData, 'mergeFormat'); break; case 'pasteOptionPasteAsImage': - this.editor.pasteFromClipboard(this.clipboardData, 'asImage'); + paste(this.editor, this.clipboardData, 'asImage'); } this.pasteOptionRef.current?.setSelectedKey(key); diff --git a/packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts b/packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts index 3f0c05e3411..992ed6af493 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/createContentModel.ts @@ -33,14 +33,10 @@ export const createContentModel: CreateContentModel = (core, option, selectionOv : selectionOverride || core.api.getDOMSelection(core) || undefined; const saveIndex = !option && !selectionOverride; const editorContext = core.api.createEditorContext(core, saveIndex); + const settings = core.environment.domToModelSettings; const domToModelContext = option - ? createDomToModelContext( - editorContext, - core.domToModelSettings.builtIn, - core.domToModelSettings.customized, - option - ) - : createDomToModelContextWithConfig(core.domToModelSettings.calculated, editorContext); + ? createDomToModelContext(editorContext, settings.builtIn, settings.customized, option) + : createDomToModelContextWithConfig(settings.calculated, editorContext); const model = domToContentModel(core.logicalRoot, domToModelContext, selection); diff --git a/packages/roosterjs-content-model-core/lib/coreApi/focus.ts b/packages/roosterjs-content-model-core/lib/coreApi/focus.ts index 6d51b4a71f7..b02c5af9947 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/focus.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/focus.ts @@ -7,14 +7,14 @@ import type { Focus } from 'roosterjs-content-model-types'; */ export const focus: Focus = core => { if (!core.lifecycle.shadowEditFragment) { - const { api, selection } = core; + const { api, domHelper, selection } = core; - if (!api.hasFocus(core) && selection.selection?.type == 'range') { + if (!domHelper.hasFocus() && selection.selection?.type == 'range') { api.setDOMSelection(core, selection.selection, true /*skipSelectionChangedEvent*/); } // fallback, in case editor still have no focus - if (!core.api.hasFocus(core)) { + if (!domHelper.hasFocus()) { core.logicalRoot.focus(); } } diff --git a/packages/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts b/packages/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts index 3e01ee49079..dec67394a8a 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/formatContentModel.ts @@ -29,7 +29,7 @@ export const formatContentModel: FormatContentModel = (core, formatter, options) newImages: [], }; - const hasFocus = core.api.hasFocus(core); + const hasFocus = core.domHelper.hasFocus(); const changed = formatter(model, context); const { skipUndoSnapshot, clearModelCache, entityStates, canUndoByBackspace } = context; diff --git a/packages/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts b/packages/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts index 11f64f26b23..c2e0404f19a 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/getDOMSelection.ts @@ -9,7 +9,7 @@ export const getDOMSelection: GetDOMSelection = core => { } else { const selection = core.selection.selection; - return selection && (selection.type != 'range' || !core.api.hasFocus(core)) + return selection && (selection.type != 'range' || !core.domHelper.hasFocus()) ? selection : getNewSelection(core); } diff --git a/packages/roosterjs-content-model-core/lib/coreApi/hasFocus.ts b/packages/roosterjs-content-model-core/lib/coreApi/hasFocus.ts deleted file mode 100644 index ba41f871a56..00000000000 --- a/packages/roosterjs-content-model-core/lib/coreApi/hasFocus.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { HasFocus } from 'roosterjs-content-model-types'; - -/** - * @internal - * Check if the editor has focus now - * @param core The EditorCore object - * @returns True if the editor has focus, otherwise false - */ -export const hasFocus: HasFocus = core => { - const activeElement = core.logicalRoot.ownerDocument.activeElement; - return !!(activeElement && core.logicalRoot.contains(activeElement)); -}; diff --git a/packages/roosterjs-content-model-core/lib/coreApi/setContentModel.ts b/packages/roosterjs-content-model-core/lib/coreApi/setContentModel.ts index 39409e3e7df..74ea0e82f47 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/setContentModel.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/setContentModel.ts @@ -17,11 +17,14 @@ export const setContentModel: SetContentModel = (core, model, option, onNodeCrea const modelToDomContext = option ? createModelToDomContext( editorContext, - core.modelToDomSettings.builtIn, - core.modelToDomSettings.customized, + core.environment.modelToDomSettings.builtIn, + core.environment.modelToDomSettings.customized, option ) - : createModelToDomContextWithConfig(core.modelToDomSettings.calculated, editorContext); + : createModelToDomContextWithConfig( + core.environment.modelToDomSettings.calculated, + editorContext + ); modelToDomContext.onNodeCreated = onNodeCreated; diff --git a/packages/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts b/packages/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts index 5c9b1fb63b6..db7ba578f61 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts +++ b/packages/roosterjs-content-model-core/lib/coreApi/setDOMSelection.ts @@ -60,7 +60,7 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC case 'range': addRangeToSelection(doc, selection.range, selection.isReverted); - core.selection.selection = core.api.hasFocus(core) ? null : selection; + core.selection.selection = core.domHelper.hasFocus() ? null : selection; break; default: diff --git a/packages/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts index ef98518470a..1ae1946bb52 100644 --- a/packages/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts +++ b/packages/roosterjs-content-model-core/lib/corePlugin/CopyPastePlugin.ts @@ -6,6 +6,7 @@ import { extractClipboardItems } from '../utils/extractClipboardItems'; import { getSelectedCells } from '../publicApi/table/getSelectedCells'; import { iterateSelections } from '../publicApi/selection/iterateSelections'; import { onCreateCopyEntityNode } from '../override/pasteCopyBlockEntityParser'; +import { paste } from '../publicApi/paste/paste'; import { contentModelToDom, @@ -200,7 +201,7 @@ class CopyPastePlugin implements PluginWithState { this.state.allowedCustomPasteType ).then((clipboardData: ClipboardData) => { if (!editor.isDisposed()) { - editor.pasteFromClipboard(clipboardData, this.state.defaultPasteType); + paste(editor, clipboardData, this.state.defaultPasteType); } }); } diff --git a/packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts b/packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts index db6bbdb86eb..ec959e18188 100644 --- a/packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts +++ b/packages/roosterjs-content-model-core/lib/editor/DOMHelperImpl.ts @@ -55,6 +55,11 @@ class DOMHelperImpl implements DOMHelper { ? closestElement : null; } + + hasFocus(): boolean { + const activeElement = this.contentDiv.ownerDocument.activeElement; + return !!(activeElement && this.contentDiv.contains(activeElement)); + } } /** diff --git a/packages/roosterjs-content-model-core/lib/editor/Editor.ts b/packages/roosterjs-content-model-core/lib/editor/Editor.ts index c31b2010b04..7b4f4a6660a 100644 --- a/packages/roosterjs-content-model-core/lib/editor/Editor.ts +++ b/packages/roosterjs-content-model-core/lib/editor/Editor.ts @@ -4,9 +4,7 @@ import { createEditorCore } from './createEditorCore'; import { createEmptyModel, tableProcessor } from 'roosterjs-content-model-dom'; import { reducedModelChildProcessor } from '../override/reducedModelChildProcessor'; import { transformColor } from '../publicApi/color/transformColor'; -import type { CachedElementHandler } from '../publicApi/model/cloneModel'; import type { - ClipboardData, ContentModelDocument, ContentModelFormatter, ContentModelSegmentFormat, @@ -17,7 +15,6 @@ import type { EditorEnvironment, FormatContentModelOptions, IEditor, - PasteType, PluginEventData, PluginEventFromType, PluginEventType, @@ -28,6 +25,7 @@ import type { TrustedHTMLHandler, Rect, EntityState, + CachedElementHandler, } from 'roosterjs-content-model-types'; /** @@ -231,7 +229,7 @@ export class Editor implements IEditor { */ hasFocus(): boolean { const core = this.getCore(); - return core.api.hasFocus(core); + return core.domHelper.hasFocus(); } /** @@ -342,17 +340,6 @@ export class Editor implements IEditor { core.api.switchShadowEdit(core, false /*isOn*/); } - /** - * Paste into editor using a clipboardData object - * @param clipboardData Clipboard data retrieved from clipboard - * @param pasteType Type of paste - */ - pasteFromClipboard(clipboardData: ClipboardData, pasteType: PasteType = 'normal') { - const core = this.getCore(); - - core.api.paste(core, clipboardData, pasteType); - } - /** * Get a color manager object for this editor. */ diff --git a/packages/roosterjs-content-model-core/lib/editor/coreApiMap.ts b/packages/roosterjs-content-model-core/lib/editor/coreApiMap.ts index 8626ea508eb..06d323c5227 100644 --- a/packages/roosterjs-content-model-core/lib/editor/coreApiMap.ts +++ b/packages/roosterjs-content-model-core/lib/editor/coreApiMap.ts @@ -6,8 +6,6 @@ import { focus } from '../coreApi/focus'; import { formatContentModel } from '../coreApi/formatContentModel'; import { getDOMSelection } from '../coreApi/getDOMSelection'; import { getVisibleViewport } from '../coreApi/getVisibleViewport'; -import { hasFocus } from '../coreApi/hasFocus'; -import { paste } from '../coreApi/paste'; import { restoreUndoSnapshot } from '../coreApi/restoreUndoSnapshot'; import { setContentModel } from '../coreApi/setContentModel'; import { setDOMSelection } from '../coreApi/setDOMSelection'; @@ -23,16 +21,18 @@ export const coreApiMap: CoreApiMap = { createContentModel: createContentModel, createEditorContext: createEditorContext, formatContentModel: formatContentModel, - getDOMSelection: getDOMSelection, setContentModel: setContentModel, + + getDOMSelection: getDOMSelection, setDOMSelection: setDOMSelection, - switchShadowEdit: switchShadowEdit, - getVisibleViewport: getVisibleViewport, focus: focus, - hasFocus: hasFocus, + addUndoSnapshot: addUndoSnapshot, restoreUndoSnapshot: restoreUndoSnapshot, + attachDomEvent: attachDomEvent, triggerEvent: triggerEvent, - paste: paste, + + switchShadowEdit: switchShadowEdit, + getVisibleViewport: getVisibleViewport, }; diff --git a/packages/roosterjs-content-model-core/lib/editor/createEditorCore.ts b/packages/roosterjs-content-model-core/lib/editor/createEditorCore.ts index 9fefb9a34d2..bd2ca2f18aa 100644 --- a/packages/roosterjs-content-model-core/lib/editor/createEditorCore.ts +++ b/packages/roosterjs-content-model-core/lib/editor/createEditorCore.ts @@ -1,8 +1,8 @@ import { coreApiMap } from './coreApiMap'; import { createDarkColorHandler } from './DarkColorHandlerImpl'; import { createDOMHelper } from './DOMHelperImpl'; -import { createEditorCorePlugins } from '../corePlugin/createEditorCorePlugins'; import { createDomToModelSettings, createModelToDomSettings } from './createEditorDefaultSettings'; +import { createEditorCorePlugins } from '../corePlugin/createEditorCorePlugins'; import type { EditorEnvironment, PluginState, @@ -36,27 +36,30 @@ export function createEditorCore(contentDiv: HTMLDivElement, options: EditorOpti corePlugins.contextMenu, corePlugins.lifecycle, ], - environment: createEditorEnvironment(contentDiv), + environment: createEditorEnvironment(contentDiv, options), darkColorHandler: createDarkColorHandler( contentDiv, options.getDarkColor ?? getDarkColorFallback, options.knownColors ), trustedHTMLHandler: options.trustedHTMLHandler || defaultTrustHtmlHandler, - domToModelSettings: createDomToModelSettings(options), - modelToDomSettings: createModelToDomSettings(options), domHelper: createDOMHelper(contentDiv), ...getPluginState(corePlugins), disposeErrorHandler: options.disposeErrorHandler, }; } -function createEditorEnvironment(contentDiv: HTMLElement): EditorEnvironment { +function createEditorEnvironment( + contentDiv: HTMLElement, + options: EditorOptions +): EditorEnvironment { const navigator = contentDiv.ownerDocument.defaultView?.navigator; const userAgent = navigator?.userAgent ?? ''; const appVersion = navigator?.appVersion ?? ''; return { + domToModelSettings: createDomToModelSettings(options), + modelToDomSettings: createModelToDomSettings(options), isMac: appVersion.indexOf('Mac') != -1, isAndroid: /android/i.test(userAgent), isSafari: diff --git a/packages/roosterjs-content-model-core/lib/index.ts b/packages/roosterjs-content-model-core/lib/index.ts index 13250cc598a..c52185ef8ee 100644 --- a/packages/roosterjs-content-model-core/lib/index.ts +++ b/packages/roosterjs-content-model-core/lib/index.ts @@ -1,4 +1,4 @@ -export { CachedElementHandler, CloneModelOptions, cloneModel } from './publicApi/model/cloneModel'; +export { cloneModel } from './publicApi/model/cloneModel'; export { mergeModel, MergeModelOption } from './publicApi/model/mergeModel'; export { isBlockGroupOfType } from './publicApi/model/isBlockGroupOfType'; export { @@ -47,6 +47,7 @@ export { cacheGetEventData } from './publicApi/domUtils/cacheGetEventData'; export { undo } from './publicApi/undo/undo'; export { redo } from './publicApi/undo/redo'; +export { paste } from './publicApi/paste/paste'; export { transformColor } from './publicApi/color/transformColor'; export { retrieveModelFormatState } from './publicApi/format/retrieveModelFormatState'; diff --git a/packages/roosterjs-content-model-core/lib/publicApi/model/cloneModel.ts b/packages/roosterjs-content-model-core/lib/publicApi/model/cloneModel.ts index 7488c0e6bb3..2bf5dc15344 100644 --- a/packages/roosterjs-content-model-core/lib/publicApi/model/cloneModel.ts +++ b/packages/roosterjs-content-model-core/lib/publicApi/model/cloneModel.ts @@ -25,36 +25,9 @@ import type { ContentModelText, ContentModelTableRow, ContentModelListLevel, + CloneModelOptions, } from 'roosterjs-content-model-types'; -/** - * Function type used for cloneModel API to specify how to handle cached element when clone a model - * @param node The cached node - * @param type Type of the node, it can be - * - general: DOM element of ContentModelGeneralSegment or ContentModelGeneralBlock - * - entity: Wrapper element in ContentModelEntity - * - cache: Cached node in other model element that supports cache - */ -export type CachedElementHandler = ( - node: HTMLElement, - type: 'general' | 'entity' | 'cache' -) => HTMLElement | undefined; - -/** - * - * Options for cloneModel API - */ -export interface CloneModelOptions { - /** - * Specify how to deal with cached element, including cached block element, element in General Model, and wrapper element in Entity - * - True: Cloned model will have the same reference to the cached element - * - False/Not passed: For cached block element, cached element will be undefined. For General Model and Entity, the element will have deep clone and assign to the cloned model - * - A callback: invoke the callback with the source cached element and a string to specify model type, let the callback return the expected value of cached element. - * For General Model and Entity, the callback must return a valid element, otherwise there will be exception thrown. - */ - includeCachedElement?: boolean | CachedElementHandler; -} - /** * Clone a content model * @param model The content model to clone diff --git a/packages/roosterjs-content-model-core/lib/coreApi/paste.ts b/packages/roosterjs-content-model-core/lib/publicApi/paste/paste.ts similarity index 50% rename from packages/roosterjs-content-model-core/lib/coreApi/paste.ts rename to packages/roosterjs-content-model-core/lib/publicApi/paste/paste.ts index ea1654ecdc0..29a08271e81 100644 --- a/packages/roosterjs-content-model-core/lib/coreApi/paste.ts +++ b/packages/roosterjs-content-model-core/lib/publicApi/paste/paste.ts @@ -1,62 +1,54 @@ -import { cloneModel } from '../publicApi/model/cloneModel'; -import { convertInlineCss } from '../utils/convertInlineCss'; -import { createPasteFragment } from '../utils/paste/createPasteFragment'; -import { generatePasteOptionFromPlugins } from '../utils/paste/generatePasteOptionFromPlugins'; -import { mergePasteContent } from '../utils/paste/mergePasteContent'; -import { retrieveHtmlInfo } from '../utils/paste/retrieveHtmlInfo'; -import type { CloneModelOptions } from '../publicApi/model/cloneModel'; +import { convertInlineCss } from '../../utils/convertInlineCss'; +import { createPasteFragment } from '../../utils/paste/createPasteFragment'; +import { generatePasteOptionFromPlugins } from '../../utils/paste/generatePasteOptionFromPlugins'; +import { mergePasteContent } from '../../utils/paste/mergePasteContent'; +import { retrieveHtmlInfo } from '../../utils/paste/retrieveHtmlInfo'; import type { PasteType, ClipboardData, - Paste, - EditorCore, TrustedHTMLHandler, + IEditor, } from 'roosterjs-content-model-types'; -const CloneOption: CloneModelOptions = { - includeCachedElement: (node, type) => (type == 'cache' ? undefined : node), -}; - /** - * @internal * Paste into editor using a clipboardData object - * @param core The EditorCore object. + * @param editor The Editor object. * @param clipboardData Clipboard data retrieved from clipboard * @param pasteType Type of content to paste. @default normal */ -export const paste: Paste = ( - core: EditorCore, +export function paste( + editor: IEditor, clipboardData: ClipboardData, pasteType: PasteType = 'normal' -) => { - core.api.focus(core); +) { + editor.focus(); + + const trustedHTMLHandler = editor.getTrustedHTMLHandler(); - if (clipboardData.modelBeforePaste) { - core.api.setContentModel(core, cloneModel(clipboardData.modelBeforePaste, CloneOption)); - } else { - clipboardData.modelBeforePaste = cloneModel(core.api.createContentModel(core), CloneOption); + if (!clipboardData.modelBeforePaste) { + clipboardData.modelBeforePaste = editor.getContentModelCopy('connected'); } // 1. Prepare variables - const doc = createDOMFromHtml(clipboardData.rawHtml, core.trustedHTMLHandler); + const doc = createDOMFromHtml(clipboardData.rawHtml, trustedHTMLHandler); // 2. Handle HTML from clipboard const htmlFromClipboard = retrieveHtmlInfo(doc, clipboardData); // 3. Create target fragment const sourceFragment = createPasteFragment( - core.physicalRoot.ownerDocument, + editor.getDocument(), clipboardData, pasteType, (clipboardData.rawHtml == clipboardData.html ? doc - : createDOMFromHtml(clipboardData.html, core.trustedHTMLHandler) + : createDOMFromHtml(clipboardData.html, trustedHTMLHandler) )?.body ); // 4. Trigger BeforePaste event to allow plugins modify the fragment const eventResult = generatePasteOptionFromPlugins( - core, + editor, clipboardData, sourceFragment, htmlFromClipboard, @@ -67,8 +59,8 @@ export const paste: Paste = ( convertInlineCss(eventResult.fragment, htmlFromClipboard.globalCssRules); // 6. Merge pasted content into main Content Model - mergePasteContent(core, eventResult, clipboardData); -}; + mergePasteContent(editor, eventResult, clipboardData); +} function createDOMFromHtml( html: string | null | undefined, diff --git a/packages/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts b/packages/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts index 2d85729aac1..3b8fd283fb8 100644 --- a/packages/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts +++ b/packages/roosterjs-content-model-core/lib/utils/paste/generatePasteOptionFromPlugins.ts @@ -4,14 +4,14 @@ import type { ClipboardData, DomToModelOptionForSanitizing, PasteType, - EditorCore, + IEditor, } from 'roosterjs-content-model-types'; /** * @internal */ export function generatePasteOptionFromPlugins( - core: EditorCore, + editor: IEditor, clipboardData: ClipboardData, fragment: DocumentFragment, htmlFromClipboard: HtmlFromClipboard, @@ -38,9 +38,7 @@ export function generatePasteOptionFromPlugins( domToModelOption, }; - if (pasteType !== 'asPlainText') { - core.api.triggerEvent(core, event, true /* broadcast */); - } - - return event; + return pasteType == 'asPlainText' + ? event + : editor.triggerEvent('beforePaste', event, true /* broadcast */); } diff --git a/packages/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts b/packages/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts index 87b8bf9af71..d28afd81b7e 100644 --- a/packages/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts +++ b/packages/roosterjs-content-model-core/lib/utils/paste/mergePasteContent.ts @@ -1,17 +1,18 @@ import { ChangeSource } from '../../constants/ChangeSource'; +import { cloneModel } from '../../publicApi/model/cloneModel'; import { createDomToModelContextForSanitizing } from '../createDomToModelContextForSanitizing'; import { domToContentModel } from 'roosterjs-content-model-dom'; import { getSegmentTextFormat } from '../../publicApi/domUtils/getSegmentTextFormat'; import { getSelectedSegments } from '../../publicApi/selection/collectSelections'; import { mergeModel } from '../../publicApi/model/mergeModel'; - import type { MergeModelOption } from '../../publicApi/model/mergeModel'; import type { BeforePasteEvent, ClipboardData, + CloneModelOptions, ContentModelDocument, ContentModelSegmentFormat, - EditorCore, + IEditor, } from 'roosterjs-content-model-types'; const EmptySegmentFormat: Required = { @@ -27,25 +28,32 @@ const EmptySegmentFormat: Required = { textColor: '', underline: false, }; +const CloneOption: CloneModelOptions = { + includeCachedElement: (node, type) => (type == 'cache' ? undefined : node), +}; /** * @internal */ export function mergePasteContent( - core: EditorCore, + editor: IEditor, eventResult: BeforePasteEvent, clipboardData: ClipboardData ) { const { fragment, domToModelOption, customizedMerge, pasteType } = eventResult; - core.api.formatContentModel( - core, + editor.formatContentModel( (model, context) => { + if (clipboardData.modelBeforePaste) { + const clonedModel = cloneModel(clipboardData.modelBeforePaste, CloneOption); + model.blocks = clonedModel.blocks; + } + const selectedSegment = getSelectedSegments(model, true /*includeFormatHolder*/)[0]; const domToModelContext = createDomToModelContextForSanitizing( - core.physicalRoot.ownerDocument, + editor.getDocument(), undefined /*defaultFormat*/, - core.domToModelSettings.customized, + editor.getEnvironment().domToModelSettings.customized, domToModelOption ); diff --git a/packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts b/packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts index 6e1cc6748f7..5e64ec1f315 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/createContentModelTest.ts @@ -44,7 +44,9 @@ describe('createContentModel', () => { cachedModel: mockedCachedMode, }, lifecycle: {}, - domToModelSettings: {}, + environment: { + domToModelSettings: {}, + }, } as any) as EditorCore; }); @@ -107,7 +109,9 @@ describe('createContentModel with selection', () => { createEditorContext: createEditorContextSpy, }, cache: {}, - domToModelSettings: {}, + environment: { + domToModelSettings: {}, + }, }; }); diff --git a/packages/roosterjs-content-model-core/test/coreApi/focusTest.ts b/packages/roosterjs-content-model-core/test/coreApi/focusTest.ts index ac792875dcd..f443b6d716a 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/focusTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/focusTest.ts @@ -28,9 +28,11 @@ describe('focus', () => { logicalRoot: div, lifecycle: {}, api: { - hasFocus: hasFocusSpy, setDOMSelection: setDOMSelectionSpy, }, + domHelper: { + hasFocus: hasFocusSpy, + }, selection: {}, } as any; }); diff --git a/packages/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts b/packages/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts index 884709a8055..33999ea7dfe 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/formatContentModelTest.ts @@ -49,13 +49,15 @@ describe('formatContentModel', () => { getFocusedPosition, triggerEvent, getDOMSelection, - hasFocus, }, lifecycle: {}, cache: {}, undo: { snapshotsManager: {}, }, + domHelper: { + hasFocus, + }, } as any) as EditorCore; }); diff --git a/packages/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts b/packages/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts index db3fccce6c2..6703f8c2afe 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/getDOMSelectionTest.ts @@ -26,7 +26,7 @@ describe('getDOMSelection', () => { logicalRoot: contentDiv, lifecycle: {}, selection: {}, - api: { + domHelper: { hasFocus: hasFocusSpy, }, } as any; diff --git a/packages/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts b/packages/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts deleted file mode 100644 index a1e5c7d9a35..00000000000 --- a/packages/roosterjs-content-model-core/test/coreApi/hasFocusTest.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { EditorCore } from 'roosterjs-content-model-types'; -import { hasFocus } from '../../lib/coreApi/hasFocus'; - -describe('hasFocus', () => { - let core: EditorCore; - let containsSpy: jasmine.Spy; - let mockedElement = 'ELEMENT' as any; - - beforeEach(() => { - containsSpy = jasmine.createSpy('contains'); - - const mockedRoot = { - ownerDocument: {}, - contains: containsSpy, - }; - - core = { - physicalRoot: mockedRoot, - logicalRoot: mockedRoot, - } as any; - }); - - afterEach(() => { - core = null; - }); - - it('Has active element inside editor', () => { - (core.physicalRoot.ownerDocument as any).activeElement = mockedElement; - containsSpy.and.returnValue(true); - - let result = hasFocus(core); - - expect(result).toBe(true); - }); - - it('Has active element outside editor', () => { - (core.physicalRoot.ownerDocument as any).activeElement = mockedElement; - containsSpy.and.returnValue(false); - - let result = hasFocus(core); - - expect(result).toBe(false); - }); - - it('No active element', () => { - let result = hasFocus(core); - - expect(result).toBe(false); - }); -}); diff --git a/packages/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts b/packages/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts index cb3a22b2456..af1d6ae755b 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/setContentModelTest.ts @@ -45,8 +45,10 @@ describe('setContentModel', () => { }, lifecycle: {}, cache: {}, - modelToDomSettings: { - calculated: mockedConfig, + environment: { + modelToDomSettings: { + calculated: mockedConfig, + }, }, } as any) as EditorCore; }); @@ -107,7 +109,7 @@ describe('setContentModel', () => { contentModelToDomSpy.and.returnValue(mockedRange); - core.modelToDomSettings.builtIn = defaultOption; + core.environment.modelToDomSettings.builtIn = defaultOption; setContentModel(core, mockedModel, additionalOption); expect(createModelToDomContextSpy).toHaveBeenCalledWith( diff --git a/packages/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts b/packages/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts index 65a4ba02f49..d7859196dd8 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts +++ b/packages/roosterjs-content-model-core/test/coreApi/setDOMSelectionTest.ts @@ -52,9 +52,11 @@ describe('setDOMSelection', () => { selectionStyleNode: mockedStyleNode, }, api: { - hasFocus: hasFocusSpy, triggerEvent: triggerEventSpy, }, + domHelper: { + hasFocus: hasFocusSpy, + }, } as any; }); diff --git a/packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts index 66eb559940a..f72dbb692eb 100644 --- a/packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts +++ b/packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts @@ -5,6 +5,7 @@ import * as deleteSelectionsFile from '../../lib/publicApi/selection/deleteSelec import * as extractClipboardItemsFile from '../../lib/utils/extractClipboardItems'; import * as iterateSelectionsFile from '../../lib/publicApi/selection/iterateSelections'; import * as normalizeContentModel from 'roosterjs-content-model-dom/lib/modelApi/common/normalizeContentModel'; +import * as paste from '../../lib/publicApi/paste/paste'; import { createModelToDomContext, createTable, createTableCell } from 'roosterjs-content-model-dom'; import { createRange } from 'roosterjs-content-model-dom/test/testUtils'; import { setEntityElementClasses } from 'roosterjs-content-model-dom/test/domUtils/entityUtilTest'; @@ -19,7 +20,6 @@ import { CopyPastePluginState, PluginWithState, DarkColorHandler, - PasteType, } from 'roosterjs-content-model-types'; import { adjustSelectionForCopyCut, @@ -28,8 +28,14 @@ import { preprocessTable, } from '../../lib/corePlugin/CopyPastePlugin'; -const modelValue = 'model' as any; -const pasteModelValue = 'pasteModelValue' as any; +const modelValue = { + blocks: [], + name: 'model1', +} as any; +const pasteModelValue = { + blocks: [], + name: 'model2', +} as any; const insertPointValue = 'insertPoint' as any; const deleteResultValue = 'deleteResult' as any; @@ -100,7 +106,6 @@ describe('CopyPastePlugin |', () => { pasteSpy = jasmine.createSpy('paste_'); isDisposed = jasmine.createSpy('isDisposed'); getVisibleViewportSpy = jasmine.createSpy('getVisibleViewport'); - mockedDarkColorHandler = 'DARKCOLORHANDLER' as any; formatContentModelSpy = jasmine .createSpy('formatContentModel') @@ -139,6 +144,7 @@ describe('CopyPastePlugin |', () => { getDocument() { return { createRange: () => document.createRange(), + createDocumentFragment: () => document.createDocumentFragment(), defaultView: { requestAnimationFrame: (func: Function) => { func(); @@ -149,15 +155,19 @@ describe('CopyPastePlugin |', () => { isDarkMode: () => { return false; }, - pasteFromClipboard: (ar1: any, pasteType?: PasteType) => { - pasteSpy(ar1, pasteType); - }, getColorManager: () => mockedDarkColorHandler, isDisposed, getVisibleViewport: getVisibleViewportSpy, formatContentModel: formatContentModelSpy, + getTrustedHTMLHandler: () => (html: string) => html, + getEnvironment: () => + ({ + domToModelSettings: {}, + modelToDomSettings: {}, + } as any), }); + pasteSpy = spyOn(paste, 'paste'); plugin.initialize(editor); }); @@ -556,7 +566,7 @@ describe('CopyPastePlugin |', () => { domEvents.paste.beforeDispatch?.(clipboardEvent); - expect(pasteSpy).toHaveBeenCalledWith(clipboardData, undefined); + expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, undefined); expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( Array.from(clipboardEvent.clipboardData!.items), allowedCustomPasteType @@ -585,7 +595,7 @@ describe('CopyPastePlugin |', () => { domEvents.paste.beforeDispatch?.(clipboardEvent); - expect(pasteSpy).toHaveBeenCalledWith(clipboardData, undefined); + expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, undefined); expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( Array.from(clipboardEvent.clipboardData!.items), allowedCustomPasteType @@ -615,7 +625,7 @@ describe('CopyPastePlugin |', () => { domEvents.paste.beforeDispatch?.(clipboardEvent); - expect(pasteSpy).toHaveBeenCalledWith(clipboardData, 'mergeFormat'); + expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, 'mergeFormat'); expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( Array.from(clipboardEvent.clipboardData!.items), allowedCustomPasteType @@ -645,7 +655,7 @@ describe('CopyPastePlugin |', () => { domEvents.paste.beforeDispatch?.(clipboardEvent); - expect(pasteSpy).toHaveBeenCalledWith(clipboardData, 'asImage'); + expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, 'asImage'); expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( Array.from(clipboardEvent.clipboardData!.items), allowedCustomPasteType @@ -675,7 +685,7 @@ describe('CopyPastePlugin |', () => { domEvents.paste.beforeDispatch?.(clipboardEvent); - expect(pasteSpy).toHaveBeenCalledWith(clipboardData, 'asPlainText'); + expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, 'asPlainText'); expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( Array.from(clipboardEvent.clipboardData!.items), allowedCustomPasteType @@ -705,7 +715,7 @@ describe('CopyPastePlugin |', () => { domEvents.paste.beforeDispatch?.(clipboardEvent); - expect(pasteSpy).toHaveBeenCalledWith(clipboardData, 'normal'); + expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, 'normal'); expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith( Array.from(clipboardEvent.clipboardData!.items), allowedCustomPasteType diff --git a/packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts b/packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts index 71c65b30d4b..67e4da3fcf3 100644 --- a/packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts +++ b/packages/roosterjs-content-model-core/test/editor/DOMHelperImplTest.ts @@ -1,182 +1,242 @@ import { createDOMHelper } from '../../lib/editor/DOMHelperImpl'; +import { DOMHelper } from 'roosterjs-content-model-types'; describe('DOMHelperImpl', () => { - it('isNodeInEditor', () => { - const mockedResult = 'RESULT' as any; - const containsSpy = jasmine.createSpy('contains').and.returnValue(mockedResult); - const mockedDiv = { - contains: containsSpy, - } as any; - const domHelper = createDOMHelper(mockedDiv); - const mockedNode = 'NODE' as any; - - const result = domHelper.isNodeInEditor(mockedNode); - - expect(result).toBe(mockedResult); - expect(containsSpy).toHaveBeenCalledWith(mockedNode); + describe('isNodeInEditor', () => { + it('isNodeInEditor', () => { + const mockedResult = 'RESULT' as any; + const containsSpy = jasmine.createSpy('contains').and.returnValue(mockedResult); + const mockedDiv = { + contains: containsSpy, + } as any; + const domHelper = createDOMHelper(mockedDiv); + const mockedNode = 'NODE' as any; + + const result = domHelper.isNodeInEditor(mockedNode); + + expect(result).toBe(mockedResult); + expect(containsSpy).toHaveBeenCalledWith(mockedNode); + }); }); - it('queryElements', () => { - const mockedResult = ['RESULT'] as any; - const querySelectorAllSpy = jasmine - .createSpy('querySelectorAll') - .and.returnValue(mockedResult); - const mockedDiv: HTMLElement = { - querySelectorAll: querySelectorAllSpy, - } as any; - const mockedSelector = 'SELECTOR'; - const domHelper = createDOMHelper(mockedDiv); - - const result = domHelper.queryElements(mockedSelector); - - expect(result).toEqual(mockedResult); - expect(querySelectorAllSpy).toHaveBeenCalledWith(mockedSelector); + describe('queryElements', () => { + it('queryElements', () => { + const mockedResult = ['RESULT'] as any; + const querySelectorAllSpy = jasmine + .createSpy('querySelectorAll') + .and.returnValue(mockedResult); + const mockedDiv: HTMLElement = { + querySelectorAll: querySelectorAllSpy, + } as any; + const mockedSelector = 'SELECTOR'; + const domHelper = createDOMHelper(mockedDiv); + + const result = domHelper.queryElements(mockedSelector); + + expect(result).toEqual(mockedResult); + expect(querySelectorAllSpy).toHaveBeenCalledWith(mockedSelector); + }); }); - it('getTextContent', () => { - const mockedTextContent = 'TEXT'; - const mockedDiv: HTMLDivElement = { - textContent: mockedTextContent, - } as any; - const domHelper = createDOMHelper(mockedDiv); + describe('getTextContent', () => { + it('getTextContent', () => { + const mockedTextContent = 'TEXT'; + const mockedDiv: HTMLDivElement = { + textContent: mockedTextContent, + } as any; + const domHelper = createDOMHelper(mockedDiv); - const result = domHelper.getTextContent(); + const result = domHelper.getTextContent(); - expect(result).toBe(mockedTextContent); + expect(result).toBe(mockedTextContent); + }); }); - it('calculateZoomScale 1', () => { - const mockedDiv = { - getBoundingClientRect: () => ({ - width: 1, - }), - offsetWidth: 2, - } as any; - const domHelper = createDOMHelper(mockedDiv); + describe('calculateZoomScale', () => { + it('calculateZoomScale 1', () => { + const mockedDiv = { + getBoundingClientRect: () => ({ + width: 1, + }), + offsetWidth: 2, + } as any; + const domHelper = createDOMHelper(mockedDiv); + + const zoomScale = domHelper.calculateZoomScale(); + + expect(zoomScale).toBe(0.5); + }); + + it('calculateZoomScale 2', () => { + const mockedDiv = { + getBoundingClientRect: () => ({ + width: 1, + }), + offsetWidth: 0, // Wrong number, should return 1 as fallback + } as any; + const domHelper = createDOMHelper(mockedDiv); + + const zoomScale = domHelper.calculateZoomScale(); + + expect(zoomScale).toBe(1); + }); + }); - const zoomScale = domHelper.calculateZoomScale(); + describe('getDomAttribute', () => { + it('getDomAttribute', () => { + const mockedAttr = 'ATTR'; + const mockedValue = 'VALUE'; + const getAttributeSpy = jasmine.createSpy('getAttribute').and.returnValue(mockedValue); + const mockedDiv = { + getAttribute: getAttributeSpy, + } as any; + + const domHelper = createDOMHelper(mockedDiv); + const result = domHelper.getDomAttribute(mockedAttr); + + expect(result).toBe(mockedValue); + expect(getAttributeSpy).toHaveBeenCalledWith(mockedAttr); + }); + }); - expect(zoomScale).toBe(0.5); + describe('setDomAttribute', () => { + it('setDomAttribute', () => { + const mockedAttr1 = 'ATTR1'; + const mockedAttr2 = 'ATTR2'; + const mockedValue = 'VALUE'; + const setAttributeSpy = jasmine.createSpy('setAttribute'); + const removeAttributeSpy = jasmine.createSpy('removeAttribute'); + const mockedDiv = { + setAttribute: setAttributeSpy, + removeAttribute: removeAttributeSpy, + } as any; + + const domHelper = createDOMHelper(mockedDiv); + domHelper.setDomAttribute(mockedAttr1, mockedValue); + + expect(setAttributeSpy).toHaveBeenCalledWith(mockedAttr1, mockedValue); + expect(removeAttributeSpy).not.toHaveBeenCalled(); + + domHelper.setDomAttribute(mockedAttr2, null); + expect(removeAttributeSpy).toHaveBeenCalledWith(mockedAttr2); + }); }); - it('calculateZoomScale 2', () => { - const mockedDiv = { - getBoundingClientRect: () => ({ - width: 1, - }), - offsetWidth: 0, // Wrong number, should return 1 as fallback - } as any; - const domHelper = createDOMHelper(mockedDiv); + describe('getDomStyle', () => { + it('getDomStyle', () => { + const mockedValue = 'COLOR' as any; + const styleName: keyof CSSStyleDeclaration = 'backgroundColor'; + const styleSpy = jasmine.createSpyObj('style', [styleName]); + styleSpy[styleName] = mockedValue; + const mockedDiv = { + style: styleSpy, + } as any; + + const domHelper = createDOMHelper(mockedDiv); + const result = domHelper.getDomStyle(styleName); + + expect(result).toBe(mockedValue); + }); + }); - const zoomScale = domHelper.calculateZoomScale(); + describe('findClosestElementAncestor', () => { + it('findClosestElementAncestor - text node, no parent, no selector', () => { + const startNode = document.createTextNode('test'); + const container = document.createElement('div'); - expect(zoomScale).toBe(1); - }); + container.appendChild(startNode); - it('getDomAttribute', () => { - const mockedAttr = 'ATTR'; - const mockedValue = 'VALUE'; - const getAttributeSpy = jasmine.createSpy('getAttribute').and.returnValue(mockedValue); - const mockedDiv = { - getAttribute: getAttributeSpy, - } as any; + const domHelper = createDOMHelper(container); + const result = domHelper.findClosestElementAncestor(startNode); - const domHelper = createDOMHelper(mockedDiv); - const result = domHelper.getDomAttribute(mockedAttr); + expect(result).toBeNull(); + }); - expect(result).toBe(mockedValue); - expect(getAttributeSpy).toHaveBeenCalledWith(mockedAttr); - }); + it('findClosestElementAncestor - text node, has parent, no selector', () => { + const startNode = document.createTextNode('test'); + const parent = document.createElement('span'); + const container = document.createElement('div'); - it('setDomAttribute', () => { - const mockedAttr1 = 'ATTR1'; - const mockedAttr2 = 'ATTR2'; - const mockedValue = 'VALUE'; - const setAttributeSpy = jasmine.createSpy('setAttribute'); - const removeAttributeSpy = jasmine.createSpy('removeAttribute'); - const mockedDiv = { - setAttribute: setAttributeSpy, - removeAttribute: removeAttributeSpy, - } as any; - - const domHelper = createDOMHelper(mockedDiv); - domHelper.setDomAttribute(mockedAttr1, mockedValue); - - expect(setAttributeSpy).toHaveBeenCalledWith(mockedAttr1, mockedValue); - expect(removeAttributeSpy).not.toHaveBeenCalled(); - - domHelper.setDomAttribute(mockedAttr2, null); - expect(removeAttributeSpy).toHaveBeenCalledWith(mockedAttr2); - }); + parent.appendChild(startNode); + container.appendChild(parent); - it('getDomStyle', () => { - const mockedValue = 'COLOR' as any; - const styleName: keyof CSSStyleDeclaration = 'backgroundColor'; - const styleSpy = jasmine.createSpyObj('style', [styleName]); - styleSpy[styleName] = mockedValue; - const mockedDiv = { - style: styleSpy, - } as any; + const domHelper = createDOMHelper(container); + const result = domHelper.findClosestElementAncestor(startNode); - const domHelper = createDOMHelper(mockedDiv); - const result = domHelper.getDomStyle(styleName); + expect(result).toBe(parent); + }); - expect(result).toBe(mockedValue); - }); + it('findClosestElementAncestor - element node, no selector', () => { + const startNode = document.createElement('span'); + const container = document.createElement('div'); - it('findClosestElementAncestor - text node, no parent, no selector', () => { - const startNode = document.createTextNode('test'); - const container = document.createElement('div'); + container.appendChild(startNode); - container.appendChild(startNode); + const domHelper = createDOMHelper(container); + const result = domHelper.findClosestElementAncestor(startNode); - const domHelper = createDOMHelper(container); - const result = domHelper.findClosestElementAncestor(startNode); + expect(result).toBe(startNode); + }); - expect(result).toBeNull(); - }); + it('findClosestElementAncestor - has selector', () => { + const startNode = document.createElement('span'); + const parent1 = document.createElement('div'); + const parent2 = document.createElement('div'); + const container = document.createElement('div'); - it('findClosestElementAncestor - text node, has parent, no selector', () => { - const startNode = document.createTextNode('test'); - const parent = document.createElement('span'); - const container = document.createElement('div'); + parent2.className = 'testClass'; - parent.appendChild(startNode); - container.appendChild(parent); + parent1.appendChild(startNode); + parent2.appendChild(parent1); + container.appendChild(parent2); - const domHelper = createDOMHelper(container); - const result = domHelper.findClosestElementAncestor(startNode); + const domHelper = createDOMHelper(container); + const result = domHelper.findClosestElementAncestor(startNode, '.testClass'); - expect(result).toBe(parent); + expect(result).toBe(parent2); + }); }); - it('findClosestElementAncestor - element node, no selector', () => { - const startNode = document.createElement('span'); - const container = document.createElement('div'); + describe('hasFocus', () => { + let containsSpy: jasmine.Spy; + let mockedElement = 'ELEMENT' as any; + let mockedRoot: HTMLElement; + let domHelper: DOMHelper; - container.appendChild(startNode); + beforeEach(() => { + containsSpy = jasmine.createSpy('contains'); + mockedRoot = { + ownerDocument: { + activeElement: mockedElement, + }, + contains: containsSpy, + } as any; - const domHelper = createDOMHelper(container); - const result = domHelper.findClosestElementAncestor(startNode); + domHelper = createDOMHelper(mockedRoot); + }); - expect(result).toBe(startNode); - }); + it('Has active element inside editor', () => { + containsSpy.and.returnValue(true); + + let result = domHelper.hasFocus(); + + expect(result).toBe(true); + }); + + it('Has active element outside editor', () => { + containsSpy.and.returnValue(false); - it('findClosestElementAncestor - has selector', () => { - const startNode = document.createElement('span'); - const parent1 = document.createElement('div'); - const parent2 = document.createElement('div'); - const container = document.createElement('div'); + let result = domHelper.hasFocus(); - parent2.className = 'testClass'; + expect(result).toBe(false); + }); - parent1.appendChild(startNode); - parent2.appendChild(parent1); - container.appendChild(parent2); + it('No active element', () => { + (mockedRoot.ownerDocument as any).activeElement = null; - const domHelper = createDOMHelper(container); - const result = domHelper.findClosestElementAncestor(startNode, '.testClass'); + let result = domHelper.hasFocus(); - expect(result).toBe(parent2); + expect(result).toBe(false); + }); }); }); diff --git a/packages/roosterjs-content-model-core/test/editor/EditorTest.ts b/packages/roosterjs-content-model-core/test/editor/EditorTest.ts index 02b1d9fb141..e92935f9527 100644 --- a/packages/roosterjs-content-model-core/test/editor/EditorTest.ts +++ b/packages/roosterjs-content-model-core/test/editor/EditorTest.ts @@ -2,9 +2,9 @@ import * as cloneModel from '../../lib/publicApi/model/cloneModel'; import * as createEditorCore from '../../lib/editor/createEditorCore'; import * as createEmptyModel from 'roosterjs-content-model-dom/lib/modelApi/creators/createEmptyModel'; import * as transformColor from '../../lib/publicApi/color/transformColor'; +import { CachedElementHandler, EditorCore, Rect } from 'roosterjs-content-model-types'; import { ChangeSource } from '../../lib/constants/ChangeSource'; import { Editor } from '../../lib/editor/Editor'; -import { EditorCore, Rect } from 'roosterjs-content-model-types'; import { reducedModelChildProcessor } from '../../lib/override/reducedModelChildProcessor'; import { tableProcessor } from 'roosterjs-content-model-dom'; @@ -189,7 +189,7 @@ describe('Editor', () => { const transformColorSpy = spyOn(transformColor, 'transformColor'); const onClone = cloneModelSpy.calls.argsFor(0)[1]! - .includeCachedElement as cloneModel.CachedElementHandler; + .includeCachedElement as CachedElementHandler; const clonedNode = { style: { @@ -567,9 +567,11 @@ describe('Editor', () => { reset: resetSpy, }, api: { - hasFocus: hasFocusSpy, setContentModel: setContentModelSpy, }, + domHelper: { + hasFocus: hasFocusSpy, + }, } as any; createEditorCoreSpy.and.returnValue(mockedCore); @@ -579,7 +581,7 @@ describe('Editor', () => { const result = editor.hasFocus(); expect(result).toBe(mockedResult); - expect(hasFocusSpy).toHaveBeenCalledWith(mockedCore); + expect(hasFocusSpy).toHaveBeenCalledWith(); editor.dispose(); expect(resetSpy).toHaveBeenCalledWith(); @@ -744,42 +746,6 @@ describe('Editor', () => { expect(() => editor.stopShadowEdit()).toThrow(); }); - it('pasteFromClipboard', () => { - const div = document.createElement('div'); - const pasteSpy = jasmine.createSpy('paste'); - const resetSpy = jasmine.createSpy('reset'); - const mockedCore = { - plugins: [], - darkColorHandler: { - updateKnownColor: updateKnownColorSpy, - reset: resetSpy, - }, - api: { - paste: pasteSpy, - setContentModel: setContentModelSpy, - }, - } as any; - - createEditorCoreSpy.and.returnValue(mockedCore); - - const mockedClipboardData = 'ClipboardData' as any; - const mockedPasteType = 'PASTETYPE' as any; - - const editor = new Editor(div); - - editor.pasteFromClipboard(mockedClipboardData); - - expect(pasteSpy).toHaveBeenCalledWith(mockedCore, mockedClipboardData, 'normal'); - - editor.pasteFromClipboard(mockedClipboardData, mockedPasteType); - - expect(pasteSpy).toHaveBeenCalledWith(mockedCore, mockedClipboardData, mockedPasteType); - - editor.dispose(); - expect(resetSpy).toHaveBeenCalledWith(); - expect(() => editor.pasteFromClipboard(mockedClipboardData)).toThrow(); - }); - it('getColorManager', () => { const div = document.createElement('div'); const resetSpy = jasmine.createSpy('reset'); diff --git a/packages/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts b/packages/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts index b14ba8ffc01..1c7479efafd 100644 --- a/packages/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts +++ b/packages/roosterjs-content-model-core/test/editor/createEditorCoreTest.ts @@ -84,11 +84,11 @@ describe('createEditorCore', () => { isAndroid: false, isSafari: false, isMobileOrTablet: false, + domToModelSettings: mockedDomToModelSettings, + modelToDomSettings: mockedModelToDomSettings, }, darkColorHandler: mockedDarkColorHandler, trustedHTMLHandler: defaultTrustHtmlHandler, - domToModelSettings: mockedDomToModelSettings, - modelToDomSettings: mockedModelToDomSettings, cache: 'cache' as any, format: 'format' as any, copyPaste: 'copyPaste' as any, @@ -205,6 +205,8 @@ describe('createEditorCore', () => { isAndroid: true, isSafari: false, isMobileOrTablet: true, + domToModelSettings: mockedDomToModelSettings, + modelToDomSettings: mockedModelToDomSettings, }, }); @@ -236,6 +238,8 @@ describe('createEditorCore', () => { isAndroid: true, isSafari: false, isMobileOrTablet: true, + domToModelSettings: mockedDomToModelSettings, + modelToDomSettings: mockedModelToDomSettings, }, }); @@ -267,6 +271,8 @@ describe('createEditorCore', () => { isAndroid: false, isSafari: false, isMobileOrTablet: false, + domToModelSettings: mockedDomToModelSettings, + modelToDomSettings: mockedModelToDomSettings, }, }); @@ -298,6 +304,8 @@ describe('createEditorCore', () => { isAndroid: false, isSafari: true, isMobileOrTablet: false, + domToModelSettings: mockedDomToModelSettings, + modelToDomSettings: mockedModelToDomSettings, }, }); @@ -329,6 +337,8 @@ describe('createEditorCore', () => { isAndroid: false, isSafari: false, isMobileOrTablet: false, + domToModelSettings: mockedDomToModelSettings, + modelToDomSettings: mockedModelToDomSettings, }, }); diff --git a/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts b/packages/roosterjs-content-model-core/test/publicApi/paste/pasteTest.ts similarity index 93% rename from packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts rename to packages/roosterjs-content-model-core/test/publicApi/paste/pasteTest.ts index 780535f1d82..f0c1115ff7c 100644 --- a/packages/roosterjs-content-model-core/test/coreApi/pasteTest.ts +++ b/packages/roosterjs-content-model-core/test/publicApi/paste/pasteTest.ts @@ -1,16 +1,16 @@ import * as addParserF from 'roosterjs-content-model-plugins/lib/paste/utils/addParser'; -import * as cloneModel from '../../lib/publicApi/model/cloneModel'; import * as domToContentModel from 'roosterjs-content-model-dom/lib/domToModel/domToContentModel'; import * as ExcelF from 'roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel'; import * as getPasteSourceF from 'roosterjs-content-model-plugins/lib/paste/pasteSourceValidations/getPasteSource'; -import * as getSelectedSegmentsF from '../../lib/publicApi/selection/collectSelections'; -import * as mergeModelFile from '../../lib/publicApi/model/mergeModel'; +import * as getSelectedSegmentsF from '../../../lib/publicApi/selection/collectSelections'; +import * as mergeModelFile from '../../../lib/publicApi/model/mergeModel'; import * as PPT from 'roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint'; import * as setProcessorF from 'roosterjs-content-model-plugins/lib/paste/utils/setProcessor'; import * as WacComponents from 'roosterjs-content-model-plugins/lib/paste/WacComponents/processPastedContentWacComponents'; import * as WordDesktopFile from 'roosterjs-content-model-plugins/lib/paste/WordDesktop/processPastedContentFromWordDesktop'; -import { Editor } from '../../lib/editor/Editor'; +import { Editor } from '../../../lib/editor/Editor'; import { expectEqual, initEditor } from 'roosterjs-content-model-plugins/test/paste/e2e/testUtils'; +import { paste } from '../../../lib/publicApi/paste/paste'; import { PastePlugin } from 'roosterjs-content-model-plugins/lib/paste/PastePlugin'; import { ClipboardData, @@ -32,13 +32,10 @@ describe('Paste ', () => { let mockedMergeModel: ContentModelDocument; let getVisibleViewport: jasmine.Spy; - const mockedCloneModel = 'CloneModel' as any; - let div: HTMLDivElement; beforeEach(() => { spyOn(domToContentModel, 'domToContentModel').and.callThrough(); - spyOn(cloneModel, 'cloneModel').and.returnValue(mockedCloneModel); clipboardData = { types: ['image/png', 'text/html'], text: '', @@ -78,7 +75,6 @@ describe('Paste ', () => { focus, createContentModel, getVisibleViewport, - // formatContentModel, }, }); @@ -92,13 +88,13 @@ describe('Paste ', () => { }); it('Execute', () => { - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); expect(mockedModel).toEqual(mockedMergeModel); }); it('Execute | As plain text', () => { - editor.pasteFromClipboard(clipboardData, 'asPlainText'); + paste(editor, clipboardData, 'asPlainText'); expect(mockedModel).toEqual(mockedMergeModel); }); @@ -137,7 +133,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('wordDesktop'); spyOn(WordDesktopFile, 'processPastedContentFromWordDesktop').and.callThrough(); - editor?.pasteFromClipboard(clipboardData); + paste(editor!, clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(1); expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 4); @@ -148,7 +144,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('wacComponents'); spyOn(WacComponents, 'processPastedContentWacComponents').and.callThrough(); - editor?.pasteFromClipboard(clipboardData); + paste(editor!, clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(2); expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 6); @@ -159,7 +155,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('excelOnline'); spyOn(ExcelF, 'processPastedContentFromExcel').and.callThrough(); - editor?.pasteFromClipboard(clipboardData); + paste(editor!, clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(1); expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); @@ -170,7 +166,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('excelDesktop'); spyOn(ExcelF, 'processPastedContentFromExcel').and.callThrough(); - editor?.pasteFromClipboard(clipboardData); + paste(editor!, clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(1); expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED + 1); @@ -181,7 +177,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('powerPointDesktop'); spyOn(PPT, 'processPastedContentFromPowerPoint').and.callThrough(); - editor?.pasteFromClipboard(clipboardData); + paste(editor!, clipboardData); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); expect(addParserF.addParser).toHaveBeenCalledTimes(DEFAULT_TIMES_ADD_PARSER_CALLED); @@ -193,7 +189,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('wordDesktop'); spyOn(WordDesktopFile, 'processPastedContentFromWordDesktop').and.callThrough(); - editor?.pasteFromClipboard(clipboardData, 'asPlainText'); + paste(editor!, clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); expect(addParserF.addParser).toHaveBeenCalledTimes(0); @@ -204,7 +200,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('wacComponents'); spyOn(WacComponents, 'processPastedContentWacComponents').and.callThrough(); - editor?.pasteFromClipboard(clipboardData, 'asPlainText'); + paste(editor!, clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); expect(addParserF.addParser).toHaveBeenCalledTimes(0); @@ -215,7 +211,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('excelOnline'); spyOn(ExcelF, 'processPastedContentFromExcel').and.callThrough(); - editor?.pasteFromClipboard(clipboardData, 'asPlainText'); + paste(editor!, clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); expect(addParserF.addParser).toHaveBeenCalledTimes(0); @@ -226,7 +222,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('excelDesktop'); spyOn(ExcelF, 'processPastedContentFromExcel').and.callThrough(); - editor?.pasteFromClipboard(clipboardData, 'asPlainText'); + paste(editor!, clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); expect(addParserF.addParser).toHaveBeenCalledTimes(0); @@ -237,7 +233,7 @@ describe('paste with content model & paste plugin', () => { spyOn(getPasteSourceF, 'getPasteSource').and.returnValue('powerPointDesktop'); spyOn(PPT, 'processPastedContentFromPowerPoint').and.callThrough(); - editor?.pasteFromClipboard(clipboardData, 'asPlainText'); + paste(editor!, clipboardData, 'asPlainText'); expect(setProcessorF.setProcessor).toHaveBeenCalledTimes(0); expect(addParserF.addParser).toHaveBeenCalledTimes(0); @@ -273,7 +269,7 @@ describe('paste with content model & paste plugin', () => { ], }); - editor?.pasteFromClipboard(clipboardData); + paste(editor!, clipboardData); expect(eventChecker?.clipboardData).toEqual(clipboardData); expect(eventChecker?.htmlBefore).toBeTruthy(); @@ -309,7 +305,7 @@ describe('Paste with clipboardData', () => { clipboardData.rawHtml = '

Test

'; - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); const model = editor.getContentModelCopy('connected'); @@ -362,7 +358,7 @@ describe('Paste with clipboardData', () => { clipboardData.rawHtml = 'Link'; - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); const model = editor.getContentModelCopy('connected'); @@ -402,7 +398,7 @@ describe('Paste with clipboardData', () => { clipboardData.rawHtml = 'Link'; - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); const model = editor.getContentModelCopy('connected'); diff --git a/packages/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts b/packages/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts index 0ab1e87e81d..5656ee53198 100644 --- a/packages/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts +++ b/packages/roosterjs-content-model-core/test/utils/paste/generatePasteOptionFromPluginsTest.ts @@ -1,8 +1,8 @@ -import { EditorCore } from 'roosterjs-content-model-types'; import { generatePasteOptionFromPlugins } from '../../../lib/utils/paste/generatePasteOptionFromPlugins'; +import { IEditor } from 'roosterjs-content-model-types'; describe('generatePasteOptionFromPlugins', () => { - let core: EditorCore; + let editor: IEditor; let triggerPluginEventSpy: jasmine.Spy; const mockedClipboardData = 'CLIPBOARDDATA' as any; @@ -19,10 +19,8 @@ describe('generatePasteOptionFromPlugins', () => { beforeEach(() => { triggerPluginEventSpy = jasmine.createSpy('triggerEvent'); - core = { - api: { - triggerEvent: triggerPluginEventSpy, - }, + editor = { + triggerEvent: triggerPluginEventSpy, } as any; }); @@ -32,9 +30,11 @@ describe('generatePasteOptionFromPlugins', () => { triggerPluginEventSpy.and.callFake((core, event) => { originalEvent = { ...event }; Object.assign(event, mockedResult); + + return event; }); const result = generatePasteOptionFromPlugins( - core, + editor, mockedClipboardData, mockedFragment, { @@ -76,7 +76,7 @@ describe('generatePasteOptionFromPlugins', () => { }, }); expect(triggerPluginEventSpy).toHaveBeenCalledWith( - core, + 'beforePaste', { eventType: 'beforePaste', clipboardData: mockedClipboardData, @@ -99,10 +99,12 @@ describe('generatePasteOptionFromPlugins', () => { event.domToModelOption = 'OptionResult'; event.pasteType = 'TypeResult'; event.customizedMerge = mockedCustomizedMerge; + + return event; }); const result = generatePasteOptionFromPlugins( - core, + editor, mockedClipboardData, mockedFragment, { @@ -127,7 +129,7 @@ describe('generatePasteOptionFromPlugins', () => { } as any); expect(triggerPluginEventSpy).toHaveBeenCalledTimes(1); expect(triggerPluginEventSpy).toHaveBeenCalledWith( - core, + 'beforePaste', { eventType: 'beforePaste', clipboardData: mockedClipboardData, @@ -149,9 +151,11 @@ describe('generatePasteOptionFromPlugins', () => { triggerPluginEventSpy.and.callFake((core, event) => { originalEvent = { ...event }; Object.assign(event, mockedResult); + + return event; }); const result = generatePasteOptionFromPlugins( - core, + editor, mockedClipboardData, mockedFragment, { @@ -173,7 +177,7 @@ describe('generatePasteOptionFromPlugins', () => { } as any); expect(triggerPluginEventSpy).toHaveBeenCalledTimes(1); expect(triggerPluginEventSpy).toHaveBeenCalledWith( - core, + 'beforePaste', { eventType: 'beforePaste', clipboardData: mockedClipboardData, @@ -211,7 +215,7 @@ describe('generatePasteOptionFromPlugins', () => { Object.assign(event, mockedResult); }); const result = generatePasteOptionFromPlugins( - core, + editor, mockedClipboardData, mockedFragment, { diff --git a/packages/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts b/packages/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts index 7a28b5aea9b..acc68ec2c35 100644 --- a/packages/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts +++ b/packages/roosterjs-content-model-core/test/utils/paste/mergePasteContentTest.ts @@ -10,7 +10,7 @@ import { FormatContentModelContext, FormatContentModelOptions, InsertPoint, - EditorCore, + IEditor, } from 'roosterjs-content-model-types'; describe('mergePasteContent', () => { @@ -18,7 +18,7 @@ describe('mergePasteContent', () => { let context: FormatContentModelContext | undefined; let formatContentModel: jasmine.Spy; let sourceModel: ContentModelDocument; - let core: EditorCore; + let editor: IEditor; const mockedClipboard = 'CLIPBOARD' as any; beforeEach(() => { @@ -27,33 +27,25 @@ describe('mergePasteContent', () => { formatContentModel = jasmine .createSpy('formatContentModel') - .and.callFake( - ( - core: any, - callback: ContentModelFormatter, - options: FormatContentModelOptions - ) => { - context = { - newEntities: [], - deletedEntities: [], - newImages: [], - }; - formatResult = callback(sourceModel, context); - - const changedData = options.getChangeData!(); - - expect(changedData).toBe(mockedClipboard); - } - ); - - core = { - api: { - formatContentModel, - }, - domToModelSettings: {}, - physicalRoot: { - ownerDocument: document, - }, + .and.callFake((callback: ContentModelFormatter, options: FormatContentModelOptions) => { + context = { + newEntities: [], + deletedEntities: [], + newImages: [], + }; + formatResult = callback(sourceModel, context); + + const changedData = options.getChangeData!(); + + expect(changedData).toBe(mockedClipboard); + }); + + editor = { + formatContentModel, + getEnvironment: () => ({ + domToModelSettings: {}, + }), + getDocument: () => document, } as any; }); @@ -163,7 +155,7 @@ describe('mergePasteContent', () => { domToModelOption: { additionalAllowedTags: [] }, } as any; - mergePasteContent(core, eventResult, mockedClipboard); + mergePasteContent(editor, eventResult, mockedClipboard); expect(formatContentModel).toHaveBeenCalledTimes(1); expect(formatResult).toBeTrue(); @@ -268,7 +260,7 @@ describe('mergePasteContent', () => { customizedMerge, } as any; - mergePasteContent(core, eventResult, mockedClipboard); + mergePasteContent(editor, eventResult, mockedClipboard); expect(formatContentModel).toHaveBeenCalledTimes(1); expect(formatResult).toBeTrue(); @@ -289,7 +281,7 @@ describe('mergePasteContent', () => { domToModelOption: { additionalAllowedTags: [] }, } as any; - mergePasteContent(core, eventResult, mockedClipboard); + mergePasteContent(editor, eventResult, mockedClipboard); expect(formatContentModel).toHaveBeenCalledTimes(1); expect(formatResult).toBeTrue(); @@ -363,12 +355,14 @@ describe('mergePasteContent', () => { const mockedDefaultDomToModelOptions = 'OPTIONS3' as any; const mockedFragment = 'FRAGMENT' as any; - (core as any).domToModelSettings = { - customized: mockedDomToModelOptions, - }; + (editor as any).getEnvironment = () => ({ + domToModelSettings: { + customized: mockedDomToModelOptions, + }, + }); mergePasteContent( - core, + editor, { fragment: mockedFragment, domToModelOption: mockedDefaultDomToModelOptions, @@ -429,7 +423,7 @@ describe('mergePasteContent', () => { domToModelOption: { additionalAllowedTags: [] }, } as any; - mergePasteContent(core, eventResult, mockedClipboard); + mergePasteContent(editor, eventResult, mockedClipboard); expect(formatContentModel).toHaveBeenCalledTimes(1); expect(formatResult).toBeTrue(); diff --git a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts index 1e955d6f64a..846a6583fa2 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelOnlineTest.ts @@ -1,5 +1,6 @@ import * as processPastedContentFromExcel from '../../../lib/paste/Excel/processPastedContentFromExcel'; import { expectEqual, initEditor } from './testUtils'; +import { paste } from 'roosterjs-content-model-core'; import type { ClipboardData, IEditor } from 'roosterjs-content-model-types'; const ID = 'CM_Paste_From_ExcelOnline_E2E'; @@ -31,7 +32,7 @@ describe(ID, () => { it('E2E', () => { spyOn(processPastedContentFromExcel, 'processPastedContentFromExcel').and.callThrough(); - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); editor.getContentModelCopy('connected'); expect(processPastedContentFromExcel.processPastedContentFromExcel).toHaveBeenCalled(); @@ -51,7 +52,7 @@ describe(ID, () => { snapshotBeforePaste: '

', }); - editor.pasteFromClipboard(CD); + paste(editor, CD); const model = editor.getContentModelCopy('connected'); diff --git a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts index 75592382d73..e4377fa949c 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromExcelTest.ts @@ -1,28 +1,29 @@ import * as processPastedContentFromExcel from '../../../lib/paste/Excel/processPastedContentFromExcel'; import { expectEqual, initEditor } from './testUtils'; import { itChromeOnly } from 'roosterjs-content-model-dom/test/testUtils'; +import { paste } from 'roosterjs-content-model-core'; import type { ClipboardData, IEditor } from 'roosterjs-content-model-types'; const ID = 'CM_Paste_From_Excel_E2E'; -const clipboardData = ({ - types: ['image/png', 'text/plain', 'text/html'], - text: 'Test\tTest\r\n', - image: {}, - files: [], - rawHtml: '\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
Position {`${x},${y}`}
\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n
TestTest
\r\n\r\n\r\n\r\n\r\n'.replace( - '\r\n', - '' - ), - customValues: {}, - imageDataUri: 'https://github.com/microsoft/roosterjs', - snapshotBeforePaste: '

', -}); +let clipboardData: ClipboardData; describe(ID, () => { let editor: IEditor = undefined!; beforeEach(() => { editor = initEditor(ID); + clipboardData = { + types: ['image/png', 'text/plain', 'text/html'], + text: 'Test\tTest\r\n', + image: {} as any, + files: [], + rawHtml: '\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n
TestTest
\r\n\r\n\r\n\r\n\r\n'.replace( + '\r\n', + '' + ), + customValues: {}, + imageDataUri: 'https://github.com/microsoft/roosterjs', + }; }); afterEach(() => { @@ -32,7 +33,7 @@ describe(ID, () => { it('E2E', () => { spyOn(processPastedContentFromExcel, 'processPastedContentFromExcel').and.callThrough(); - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); editor.getContentModelCopy('connected'); expect(processPastedContentFromExcel.processPastedContentFromExcel).toHaveBeenCalled(); @@ -41,7 +42,7 @@ describe(ID, () => { it('E2E paste as image', () => { spyOn(processPastedContentFromExcel, 'processPastedContentFromExcel').and.callThrough(); - editor.pasteFromClipboard(clipboardData, 'asImage'); + paste(editor, clipboardData, 'asImage'); const model = editor.getContentModelCopy('connected'); @@ -100,7 +101,7 @@ describe(ID, () => { snapshotBeforePaste: '
', }); - editor.pasteFromClipboard(CD); + paste(editor, CD); const model = editor.getContentModelCopy('connected'); diff --git a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts index 3836607d3b5..4e4df8022e7 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWacTest.ts @@ -1,6 +1,7 @@ import * as processPastedContentWacComponents from '../../../lib/paste/WacComponents/processPastedContentWacComponents'; import { ClipboardData, IEditor } from 'roosterjs-content-model-types'; import { expectEqual, initEditor } from './testUtils'; +import { paste } from 'roosterjs-content-model-core'; const ID = 'CM_Paste_From_WORD_Online_E2E'; @@ -36,7 +37,7 @@ describe(ID, () => { 'processPastedContentWacComponents' ).and.callThrough(); - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); editor.getContentModelCopy('connected'); expect( @@ -48,7 +49,7 @@ describe(ID, () => { clipboardData.rawHtml = '

Test Table 

Test Table 

 

'; - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); const model = editor.getContentModelCopy('connected'); diff --git a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts index 35c259ccc68..1efd18b7a6f 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteFromWordTest.ts @@ -1,6 +1,6 @@ import * as wordFile from '../../../lib/paste/WordDesktop/processPastedContentFromWordDesktop'; import { ClipboardData, IEditor } from 'roosterjs-content-model-types'; -import { cloneModel } from 'roosterjs-content-model-core'; +import { cloneModel, paste } from 'roosterjs-content-model-core'; import { expectEqual, initEditor } from './testUtils'; import { itChromeOnly } from 'roosterjs-content-model-dom/test/testUtils'; @@ -33,7 +33,7 @@ describe(ID, () => { itChromeOnly('E2E', () => { clipboardData.rawHtml = '\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n

Test

\r\n\r\n

asdsad

\r\n\r\n\r\n\r\n'; - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); const model = cloneModel(editor.getContentModelCopy('connected'), { includeCachedElement: false, @@ -106,7 +106,7 @@ describe(ID, () => { clipboardData.rawHtml = '

Asdasdsad

asdadasd

 

asdsadasdasdsadasdsadsad

 

'; - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); const model = editor.getContentModelCopy('connected'); diff --git a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts index d0e38ab9dda..2bf61310dd1 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/e2e/cmPasteTest.ts @@ -2,6 +2,7 @@ import * as wordFile from '../../../lib/paste/WordDesktop/processPastedContentFr import { ClipboardData, IEditor } from 'roosterjs-content-model-types'; import { expectEqual, initEditor } from './testUtils'; import { itChromeOnly } from 'roosterjs-content-model-dom/test/testUtils'; +import { paste } from 'roosterjs-content-model-core'; const ID = 'CM_Paste_E2E'; @@ -33,7 +34,7 @@ describe(ID, () => { '
No.
ID
Work Item Type

', }); - editor.pasteFromClipboard(clipboardData); + paste(editor, clipboardData); const model = editor.getContentModelCopy('connected'); diff --git a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts index 52d8b47f744..25d6cdf39eb 100644 --- a/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/shortcut/ShortcutPluginTest.ts @@ -34,7 +34,7 @@ describe('ShortcutPlugin', () => { beforeEach(() => { preventDefaultSpy = jasmine.createSpy('preventDefault'); - mockedEnvironment = {}; + mockedEnvironment = {} as any; mockedEditor = { getEnvironment: () => mockedEnvironment, } as any; @@ -322,7 +322,7 @@ describe('ShortcutPlugin', () => { describe('Mac', () => { beforeEach(() => { - mockedEnvironment.isMac = true; + (mockedEnvironment as any).isMac = true; }); it('not a shortcut', () => { diff --git a/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts b/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts index bab1cc85a38..56169d4b2d6 100644 --- a/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts +++ b/packages/roosterjs-content-model-types/lib/editor/EditorCore.ts @@ -2,8 +2,6 @@ import type { DOMHelper } from '../parameter/DOMHelper'; import type { PluginEvent } from '../event/PluginEvent'; import type { PluginState } from '../pluginState/PluginState'; import type { EditorPlugin } from './EditorPlugin'; -import type { ClipboardData } from '../parameter/ClipboardData'; -import type { PasteType } from '../enum/PasteType'; import type { DOMEventRecord } from '../parameter/DOMEventRecord'; import type { Snapshot } from '../parameter/Snapshot'; import type { EntityState } from '../parameter/FormatContentModelContext'; @@ -11,11 +9,10 @@ import type { DarkColorHandler } from '../context/DarkColorHandler'; import type { ContentModelDocument } from '../group/ContentModelDocument'; import type { DOMSelection } from '../selection/DOMSelection'; import type { DomToModelOption } from '../context/DomToModelOption'; -import type { DomToModelSettings } from '../context/DomToModelSettings'; import type { EditorContext } from '../context/EditorContext'; import type { EditorEnvironment } from '../parameter/EditorEnvironment'; import type { ModelToDomOption } from '../context/ModelToDomOption'; -import type { ModelToDomSettings, OnNodeCreated } from '../context/ModelToDomSettings'; +import type { OnNodeCreated } from '../context/ModelToDomSettings'; import type { TrustedHTMLHandler } from '../parameter/TrustedHTMLHandler'; import type { Rect } from '../parameter/Rect'; import type { @@ -125,13 +122,6 @@ export type AddUndoSnapshot = ( */ export type GetVisibleViewport = (core: EditorCore) => Rect | null; -/** - * Check if the editor has focus now - * @param core The EditorCore object - * @returns True if the editor has focus, otherwise false - */ -export type HasFocus = (core: EditorCore) => boolean; - /** * Focus to editor. If there is a cached selection range, use it as current selection * @param core The EditorCore object @@ -155,14 +145,6 @@ export type AttachDomEvent = ( */ export type RestoreUndoSnapshot = (core: EditorCore, snapshot: Snapshot) => void; -/** - * Paste into editor using a clipboardData object - * @param core The EditorCore object. - * @param clipboardData Clipboard data retrieved from clipboard - * @param pasteType Type of content to paste. @default normal - */ -export type Paste = (core: EditorCore, clipboardData: ClipboardData, pasteType: PasteType) => void; - /** * The interface for the map of core API for Editor. * Editor can call call API from this map under EditorCore object @@ -230,13 +212,6 @@ export interface CoreApiMap { */ getVisibleViewport: GetVisibleViewport; - /** - * Check if the editor has focus now - * @param core The EditorCore object - * @returns True if the editor has focus, otherwise false - */ - hasFocus: HasFocus; - /** * Focus to editor. If there is a cached selection range, use it as current selection * @param core The EditorCore object @@ -274,14 +249,6 @@ export interface CoreApiMap { * @param broadcast Set to true to skip the shouldHandleEventExclusively check */ triggerEvent: TriggerEvent; - - /** - * Paste into editor using a clipboardData object - * @param editor The editor to paste content into - * @param clipboardData Clipboard data retrieved from clipboard - * @param pasteType Type of content to paste. @default normal - */ - paste: Paste; } /** @@ -315,16 +282,6 @@ export interface EditorCore extends PluginState { */ readonly plugins: EditorPlugin[]; - /** - * Settings used by DOM to Content Model conversion - */ - readonly domToModelSettings: ContentModelSettings; - - /** - * Settings used by Content Model to DOM conversion - */ - readonly modelToDomSettings: ContentModelSettings; - /** * Editor running environment */ @@ -355,25 +312,3 @@ export interface EditorCore extends PluginState { */ readonly disposeErrorHandler?: (plugin: EditorPlugin, error: Error) => void; } - -/** - * Default DOM and Content Model conversion settings for an editor - */ -export interface ContentModelSettings { - /** - * Built in options used by editor - */ - builtIn: OptionType; - - /** - * Customize options passed in from Editor Options, used for overwrite default option. - * This will also be used by copy/paste - */ - customized: OptionType; - - /** - * Configuration calculated from default and customized options. - * This is a cached object so that we don't need to cache it every time when we use Content Model - */ - calculated: ConfigType; -} diff --git a/packages/roosterjs-content-model-types/lib/editor/IEditor.ts b/packages/roosterjs-content-model-types/lib/editor/IEditor.ts index 733470dd42c..53ed1df5f2f 100644 --- a/packages/roosterjs-content-model-types/lib/editor/IEditor.ts +++ b/packages/roosterjs-content-model-types/lib/editor/IEditor.ts @@ -1,8 +1,6 @@ import type { DOMHelper } from '../parameter/DOMHelper'; import type { PluginEventData, PluginEventFromType } from '../event/PluginEventData'; import type { PluginEventType } from '../event/PluginEventType'; -import type { PasteType } from '../enum/PasteType'; -import type { ClipboardData } from '../parameter/ClipboardData'; import type { DOMEventRecord } from '../parameter/DOMEventRecord'; import type { SnapshotsManager } from '../parameter/SnapshotsManager'; import type { Snapshot } from '../parameter/Snapshot'; @@ -166,13 +164,6 @@ export interface IEditor { */ stopShadowEdit(): void; - /** - * Paste into editor using a clipboardData object - * @param clipboardData Clipboard data retrieved from clipboard - * @param pasteType Type of paste - */ - pasteFromClipboard(clipboardData: ClipboardData, pasteType?: PasteType): void; - /** * Get a darkColorHandler object for this editor. */ diff --git a/packages/roosterjs-content-model-types/lib/index.ts b/packages/roosterjs-content-model-types/lib/index.ts index c5077e1fd12..015c491a883 100644 --- a/packages/roosterjs-content-model-types/lib/index.ts +++ b/packages/roosterjs-content-model-types/lib/index.ts @@ -212,16 +212,13 @@ export { FormatContentModel, CoreApiMap, EditorCore, - ContentModelSettings, SwitchShadowEdit, TriggerEvent, AddUndoSnapshot, - HasFocus, Focus, AttachDomEvent, RestoreUndoSnapshot, GetVisibleViewport, - Paste, } from './editor/EditorCore'; export { EditorCorePlugins } from './editor/EditorCorePlugins'; export { EditorPlugin } from './editor/EditorPlugin'; @@ -246,7 +243,7 @@ export { } from './pluginState/PluginState'; export { ContextMenuPluginState } from './pluginState/ContextMenuPluginState'; -export { EditorEnvironment } from './parameter/EditorEnvironment'; +export { EditorEnvironment, ContentModelSettings } from './parameter/EditorEnvironment'; export { EntityState, DeletedEntity, @@ -285,6 +282,7 @@ export { Rect } from './parameter/Rect'; export { ValueSanitizer } from './parameter/ValueSanitizer'; export { DOMHelper } from './parameter/DOMHelper'; export { ImageEditOperation, ImageEditor } from './parameter/ImageEditor'; +export { CachedElementHandler, CloneModelOptions } from './parameter/CloneModelOptions'; export { LinkData } from './parameter/LinkData'; export { BasePluginEvent, BasePluginDomEvent } from './event/BasePluginEvent'; diff --git a/packages/roosterjs-content-model-types/lib/parameter/CloneModelOptions.ts b/packages/roosterjs-content-model-types/lib/parameter/CloneModelOptions.ts new file mode 100644 index 00000000000..1660357d050 --- /dev/null +++ b/packages/roosterjs-content-model-types/lib/parameter/CloneModelOptions.ts @@ -0,0 +1,27 @@ +/** + * Function type used for cloneModel API to specify how to handle cached element when clone a model + * @param node The cached node + * @param type Type of the node, it can be + * - general: DOM element of ContentModelGeneralSegment or ContentModelGeneralBlock + * - entity: Wrapper element in ContentModelEntity + * - cache: Cached node in other model element that supports cache + */ +export type CachedElementHandler = ( + node: HTMLElement, + type: 'general' | 'entity' | 'cache' +) => HTMLElement | undefined; + +/** + * + * Options for cloneModel API + */ +export interface CloneModelOptions { + /** + * Specify how to deal with cached element, including cached block element, element in General Model, and wrapper element in Entity + * - True: Cloned model will have the same reference to the cached element + * - False/Not passed: For cached block element, cached element will be undefined. For General Model and Entity, the element will have deep clone and assign to the cloned model + * - A callback: invoke the callback with the source cached element and a string to specify model type, let the callback return the expected value of cached element. + * For General Model and Entity, the callback must return a valid element, otherwise there will be exception thrown. + */ + includeCachedElement?: boolean | CachedElementHandler; +} diff --git a/packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts b/packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts index 216a13af4a5..0e09215976b 100644 --- a/packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts +++ b/packages/roosterjs-content-model-types/lib/parameter/DOMHelper.ts @@ -63,4 +63,10 @@ export interface DOMHelper { * returns the given node */ findClosestElementAncestor(node: Node, selector?: string): HTMLElement | null; + + /** + * Check if the editor has focus now + * @returns True if the editor has focus, otherwise false + */ + hasFocus(): boolean; } diff --git a/packages/roosterjs-content-model-types/lib/parameter/EditorEnvironment.ts b/packages/roosterjs-content-model-types/lib/parameter/EditorEnvironment.ts index e5d9966272b..2b1f64a28db 100644 --- a/packages/roosterjs-content-model-types/lib/parameter/EditorEnvironment.ts +++ b/packages/roosterjs-content-model-types/lib/parameter/EditorEnvironment.ts @@ -1,3 +1,30 @@ +import type { DomToModelOption } from '../context/DomToModelOption'; +import type { DomToModelSettings } from '../context/DomToModelSettings'; +import type { ModelToDomOption } from '../context/ModelToDomOption'; +import type { ModelToDomSettings } from '../context/ModelToDomSettings'; + +/** + * Default DOM and Content Model conversion settings for an editor + */ +export interface ContentModelSettings { + /** + * Built in options used by editor + */ + builtIn: OptionType; + + /** + * Customize options passed in from Editor Options, used for overwrite default option. + * This will also be used by copy/paste + */ + customized: OptionType; + + /** + * Configuration calculated from default and customized options. + * This is a cached object so that we don't need to cache it every time when we use Content Model + */ + calculated: ConfigType; +} + /** * Current running environment */ @@ -5,20 +32,30 @@ export interface EditorEnvironment { /** * Whether editor is running on Mac */ - isMac?: boolean; + readonly isMac?: boolean; /** * Whether editor is running on Android */ - isAndroid?: boolean; + readonly isAndroid?: boolean; /** * Whether editor is running on Safari browser */ - isSafari?: boolean; + readonly isSafari?: boolean; /** * Whether current browser is on mobile or a tablet */ - isMobileOrTablet?: boolean; + readonly isMobileOrTablet?: boolean; + + /** + * Settings used by DOM to Content Model conversion + */ + readonly domToModelSettings: ContentModelSettings; + + /** + * Settings used by Content Model to DOM conversion + */ + readonly modelToDomSettings: ContentModelSettings; } diff --git a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts index 47cdc2a1a4e..ca895b8b0b4 100644 --- a/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts +++ b/packages/roosterjs-editor-adapter/lib/editor/EditorAdapter.ts @@ -17,6 +17,7 @@ import { Editor, transformColor, undo, + paste, } from 'roosterjs-content-model-core'; import { ChangeSource, @@ -351,7 +352,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { return exportContent( this, GetContentModeMap[mode], - this.getCore().modelToDomSettings.customized + this.getCore().environment.modelToDomSettings.customized ); } @@ -375,7 +376,7 @@ export class EditorAdapter extends Editor implements ILegacyEditor { const newModel = createModelFromHtml( content, - core.domToModelSettings.customized, + core.environment.domToModelSettings.customized, trustedHTMLHandler, core.format.defaultFormat ); @@ -453,7 +454,8 @@ export class EditorAdapter extends Editor implements ILegacyEditor { applyCurrentFormat: boolean = false, pasteAsImage: boolean = false ) { - this.pasteFromClipboard( + paste( + this, clipboardData, pasteAsText ? 'asPlainText' From 562cc9ce19d5eae4427907095e396a9191384126 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Fri, 8 Mar 2024 12:27:31 -0800 Subject: [PATCH 76/80] Fix dark mode button in demo site (#2496) --- demo/scripts/controlsV2/demoButtons/darkModeButton.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/scripts/controlsV2/demoButtons/darkModeButton.ts b/demo/scripts/controlsV2/demoButtons/darkModeButton.ts index c8272e472eb..32f773e8039 100644 --- a/demo/scripts/controlsV2/demoButtons/darkModeButton.ts +++ b/demo/scripts/controlsV2/demoButtons/darkModeButton.ts @@ -1,4 +1,4 @@ -import MainPaneBase from '../../controls/MainPaneBase'; +import { MainPane } from '../mainPane/MainPane'; import type { RibbonButton } from '../roosterjsReact/ribbon'; /** @@ -18,7 +18,7 @@ export const darkModeButton: RibbonButton = { editor.focus(); // Let main pane know this state change so that it can be persisted when pop out/pop in - MainPaneBase.getInstance().toggleDarkMode(); + MainPane.getInstance().toggleDarkMode(); return true; }, }; From 9e8af0f3d0096f41d913f655dda5a7ffa4ebe684 Mon Sep 17 00:00:00 2001 From: Andres-CT98 <107568016+Andres-CT98@users.noreply.github.com> Date: Fri, 8 Mar 2024 22:23:23 -0600 Subject: [PATCH 77/80] Fix Table edit plugin anchorContainerSelector query (#2499) * add getDocument * add tests for anchorContainer --- .../lib/tableEdit/TableEditPlugin.ts | 5 +- .../test/TestHelper.ts | 13 +++- .../test/tableEdit/TableEditTestHelper.ts | 36 ++++++++--- .../test/tableEdit/tableEditPluginTest.ts | 63 ++++++++++++++++++- 4 files changed, 104 insertions(+), 13 deletions(-) diff --git a/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts b/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts index 7f872b4f575..8c81918e290 100644 --- a/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts +++ b/packages/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts @@ -130,15 +130,16 @@ export class TableEditPlugin implements EditorPlugin { } if (!this.tableEditor && table && this.editor && table.rows.length > 0) { + // anchorContainerSelector is used to specify the container to host the plugin, which can be outside of the editor's div const container = this.anchorContainerSelector - ? this.editor.getDOMHelper().queryElements(this.anchorContainerSelector)[0] + ? this.editor.getDocument().querySelector(this.anchorContainerSelector) : undefined; this.tableEditor = new TableEditor( this.editor, table, this.invalidateTableRects, - isNodeOfType(container as Node, 'ELEMENT_NODE') ? container : undefined, + isNodeOfType(container, 'ELEMENT_NODE') ? container : undefined, event?.currentTarget ); } diff --git a/packages/roosterjs-content-model-plugins/test/TestHelper.ts b/packages/roosterjs-content-model-plugins/test/TestHelper.ts index b9260f1468c..4f1619b506b 100644 --- a/packages/roosterjs-content-model-plugins/test/TestHelper.ts +++ b/packages/roosterjs-content-model-plugins/test/TestHelper.ts @@ -5,11 +5,20 @@ export function initEditor( id: string, plugins?: EditorPlugin[], initialModel?: ContentModelDocument, - coreApiOverride?: Partial + coreApiOverride?: Partial, + anchorContainerSelector?: string ) { let node = document.createElement('div'); node.id = id; - document.body.insertBefore(node, document.body.childNodes[0]); + + if (anchorContainerSelector) { + let anchorContainer = document.createElement('div'); + anchorContainer.className = anchorContainerSelector; + document.body.insertBefore(anchorContainer, document.body.childNodes[0]); + anchorContainer.insertBefore(node, anchorContainer.childNodes[0]); + } else { + document.body.insertBefore(node, document.body.childNodes[0]); + } return new Editor(node, { plugins, diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts index 1294e7f4569..2660c682057 100644 --- a/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/TableEditTestHelper.ts @@ -13,10 +13,11 @@ import { /** * Function to be called before each Table Edit test * @param TEST_ID The id of the editor div + * @param anchorContainerSelector The selector for the anchor container * @returns The editor, plugin, and handler to be used in the test */ -export function beforeTableTest(TEST_ID: string) { - const plugin = new TableEditPlugin(); +export function beforeTableTest(TEST_ID: string, anchorContainerSelector?: string) { + const plugin = new TableEditPlugin('.' + anchorContainerSelector); let handler: Record = {}; const attachDomEvent = jasmine @@ -38,7 +39,13 @@ export function beforeTableTest(TEST_ID: string) { const coreApiOverride = { attachDomEvent, }; - const editor = TestHelper.initEditor(TEST_ID, [plugin], undefined, coreApiOverride); + const editor = TestHelper.initEditor( + TEST_ID, + [plugin], + undefined, + coreApiOverride, + anchorContainerSelector + ); plugin.initialize(editor); @@ -179,11 +186,7 @@ export function moveAndResize( } // Move mouse to show resizer - const mouseMoveEvent = new MouseEvent('mousemove', { - clientX: mouseStart.x, - clientY: mouseStart.y, - }); - handler.mousemove(mouseMoveEvent); + mouseToPoint(mouseStart, handler); let resizer = editor.getDocument().getElementById(resizerId); if (!!resizer && editorDiv) { @@ -214,6 +217,23 @@ export function moveAndResize( } } +/** + * Function to move the mouse to a point + * @param mouseStart The starting position of the mouse + * @param handler The handler to handle the mouse events + */ +export function mouseToPoint( + mouseStart: Position, + handler: Record +) { + // Move mouse to point + const mouseMoveEvent = new MouseEvent('mousemove', { + clientX: mouseStart.x, + clientY: mouseStart.y, + }); + handler.mousemove(mouseMoveEvent); +} + /** * Function to ckeck if the table rects are the same * @param tableRectSet1 The first set of table rects diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts index f9981981a22..93e5b98bd9d 100644 --- a/packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/tableEditPluginTest.ts @@ -1,8 +1,15 @@ import * as TestHelper from '../TestHelper'; import { createElement } from '../../lib/pluginUtils/CreateElement/createElement'; +import { DOMEventHandlerFunction, IEditor } from 'roosterjs-editor-types'; import { getModelTable } from './tableData'; -import { IEditor } from 'roosterjs-content-model-types'; import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; +import { + afterTableTest, + beforeTableTest, + getCellRect, + initialize, + mouseToPoint, +} from './TableEditTestHelper'; describe('TableEditPlugin', () => { let editor: IEditor; @@ -227,3 +234,57 @@ describe('TableEditPlugin', () => { expect(pluginName).toBe(expectedName); }); }); + +describe('anchorContainer', () => { + let editor: IEditor; + let plugin: TableEditPlugin; + const TEST_ID = 'cellResizerTest'; + const ANCHOR_CLASS = 'anchor_' + TEST_ID; + let handler: Record; + + beforeEach(() => {}); + + afterEach(() => { + afterTableTest(editor, plugin, TEST_ID); + }); + + it('Table editor features, resizer and mover, inserted on anchor', () => { + // Create editor, plugin, and table + const setup = beforeTableTest(TEST_ID, ANCHOR_CLASS); + editor = setup.editor; + plugin = setup.plugin; + handler = setup.handler; + initialize(editor, getModelTable()); + + // Move mouse to the first cell + const cellRect = getCellRect(editor, 0, 0); + mouseToPoint({ x: cellRect.left, y: cellRect.bottom }, handler); + + // Look for table mover and resizer on the anchor + const anchor = editor.getDocument().getElementsByClassName(ANCHOR_CLASS)[0]; + const mover = anchor?.querySelector('#_Table_Mover'); + const resizer = anchor?.querySelector('#_Table_Resizer'); + expect(!!mover).toBe(true); + expect(!!resizer).toBe(true); + }); + + it('Table editor features, resizer and mover, not inserted on anchor', () => { + // Create editor, plugin, and table + const setup = beforeTableTest(TEST_ID); + editor = setup.editor; + plugin = setup.plugin; + handler = setup.handler; + initialize(editor, getModelTable()); + + // Move mouse to the first cell + const cellRect = getCellRect(editor, 0, 0); + mouseToPoint({ x: cellRect.left, y: cellRect.bottom }, handler); + + // Look for table mover and resizer on the anchor + const anchor = editor.getDocument().getElementsByClassName(ANCHOR_CLASS)[0]; + const mover = anchor?.querySelector('#_Table_Mover'); + const resizer = anchor?.querySelector('#_Table_Resizer'); + expect(!!mover).toBe(false); + expect(!!resizer).toBe(false); + }); +}); From c073faf625aa80f343a1ac5101f0cf8f138662d0 Mon Sep 17 00:00:00 2001 From: vhuseinova-msft <98852890+vhuseinova-msft@users.noreply.github.com> Date: Fri, 15 Mar 2024 10:11:38 -0700 Subject: [PATCH 78/80] Vhuseinova/watermark plugin update (#2507) * Add update function for Watermark plugin * Code formatting update --- .../lib/plugins/Watermark/Watermark.ts | 298 ++++++++++-------- 1 file changed, 160 insertions(+), 138 deletions(-) diff --git a/packages/roosterjs-editor-plugins/lib/plugins/Watermark/Watermark.ts b/packages/roosterjs-editor-plugins/lib/plugins/Watermark/Watermark.ts index 1dff254aec0..ee361bac04a 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/Watermark/Watermark.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/Watermark/Watermark.ts @@ -1,138 +1,160 @@ -import { applyFormat, getEntitySelector, getTagOfNode } from 'roosterjs-editor-dom'; -import { ContentPosition, EntityOperation, PluginEventType } from 'roosterjs-editor-types'; -import { insertEntity } from 'roosterjs-editor-api'; -import type { - DefaultFormat, - EditorPlugin, - Entity, - IEditor, - PluginEvent, -} from 'roosterjs-editor-types'; - -const ENTITY_TYPE = 'WATERMARK_WRAPPER'; - -/** - * A watermark plugin to manage watermark string for roosterjs - */ -export default class Watermark implements EditorPlugin { - private editor: IEditor | null = null; - private disposer: (() => void) | null = null; - private format: DefaultFormat; - - /** - * Create an instance of Watermark plugin - * @param watermark The watermark string - */ - constructor(private watermark: string, format?: DefaultFormat, private customClass?: string) { - this.format = format || { - fontSize: '14px', - textColors: { - lightModeColor: '#AAAAAA', - darkModeColor: '#6B6B6B', - }, - }; - } - - /** - * Get a friendly name of this plugin - */ - getName() { - return 'Watermark'; - } - - /** - * Initialize this plugin. This should only be called from Editor - * @param editor Editor instance - */ - initialize(editor: IEditor) { - this.editor = editor; - this.disposer = this.editor.addDomEventHandler({ - focus: this.showHideWatermark, - blur: this.showHideWatermark, - }); - } - - /** - * Dispose this plugin - */ - dispose() { - this.disposer?.(); - this.disposer = null; - this.editor = null; - } - - /** - * Handle events triggered from editor - * @param event PluginEvent object - */ - onPluginEvent(event: PluginEvent) { - if ( - event.eventType == PluginEventType.EditorReady || - (event.eventType == PluginEventType.ContentChanged && - (event.data)?.type != ENTITY_TYPE) - ) { - this.showHideWatermark(); - } else if ( - event.eventType == PluginEventType.EntityOperation && - event.entity.type == ENTITY_TYPE && - this.editor - ) { - const { - operation, - entity: { wrapper }, - } = event; - if (operation == EntityOperation.ReplaceTemporaryContent) { - this.removeWatermark(wrapper); - } else if (event.operation == EntityOperation.NewEntity) { - applyFormat( - wrapper, - this.format, - this.editor.isDarkMode(), - this.editor.getDarkColorHandler() - ); - wrapper.spellcheck = false; - } - } - } - - private showHideWatermark = () => { - if (!this.editor) { - return; - } - const hasFocus = this.editor.hasFocus(); - const watermarks = this.editor.queryElements(getEntitySelector(ENTITY_TYPE)); - const isShowing = watermarks.length > 0; - - if (hasFocus && isShowing) { - watermarks.forEach(this.removeWatermark); - this.editor.focus(); - } else if (!hasFocus && !isShowing && this.editor.isEmpty()) { - const newEntity = insertEntity( - this.editor, - ENTITY_TYPE, - this.editor.getDocument().createTextNode(this.watermark), - false /*isBlock*/, - false /*isReadonly*/, - ContentPosition.Begin - ); - if (this.customClass) { - newEntity.wrapper.classList.add(this.customClass); - } - } - }; - - private removeWatermark = (wrapper: HTMLElement) => { - const parentNode = wrapper.parentNode; - parentNode?.removeChild(wrapper); - - // After remove watermark node, if it leaves an empty DIV, append a BR node into it to make it a regular empty line - if ( - parentNode && - this.editor?.contains(parentNode) && - getTagOfNode(parentNode) == 'DIV' && - !parentNode.firstChild - ) { - parentNode.appendChild(this.editor.getDocument().createElement('BR')); - } - }; -} +import { applyFormat, getEntitySelector, getTagOfNode } from 'roosterjs-editor-dom'; +import { ContentPosition, EntityOperation, PluginEventType } from 'roosterjs-editor-types'; +import { insertEntity } from 'roosterjs-editor-api'; +import type { + DefaultFormat, + EditorPlugin, + Entity, + IEditor, + PluginEvent, +} from 'roosterjs-editor-types'; + +const ENTITY_TYPE = 'WATERMARK_WRAPPER'; + +/** + * A watermark plugin to manage watermark string for roosterjs + */ +export default class Watermark implements EditorPlugin { + private editor: IEditor | null = null; + private disposer: (() => void) | null = null; + private format: DefaultFormat; + + /** + * Create an instance of Watermark plugin + * @param watermark The watermark string + */ + constructor(private watermark: string, format?: DefaultFormat, private customClass?: string) { + this.format = format || { + fontSize: '14px', + textColors: { + lightModeColor: '#AAAAAA', + darkModeColor: '#6B6B6B', + }, + }; + } + + /** + * Updates the watermark text. + * @param watermark - The new watermark text. + */ + updateWatermark(watermark: string) { + this.watermark = watermark; + + if (!this.editor) { + return; + } + const watermarks = this.editor.queryElements(getEntitySelector(ENTITY_TYPE)); + const isShowing = watermarks.length > 0; + // re-render watermark only if it's already displayed + if (isShowing) { + // hide watermark + const watermarks = this.editor.queryElements(getEntitySelector(ENTITY_TYPE)); + watermarks.forEach(this.removeWatermark); + // show watermark + this.showHideWatermark(); + } + } + + /** + * Get a friendly name of this plugin + */ + getName() { + return 'Watermark'; + } + + /** + * Initialize this plugin. This should only be called from Editor + * @param editor Editor instance + */ + initialize(editor: IEditor) { + this.editor = editor; + this.disposer = this.editor.addDomEventHandler({ + focus: this.showHideWatermark, + blur: this.showHideWatermark, + }); + } + + /** + * Dispose this plugin + */ + dispose() { + this.disposer?.(); + this.disposer = null; + this.editor = null; + } + + /** + * Handle events triggered from editor + * @param event PluginEvent object + */ + onPluginEvent(event: PluginEvent) { + if ( + event.eventType == PluginEventType.EditorReady || + (event.eventType == PluginEventType.ContentChanged && + (event.data)?.type != ENTITY_TYPE) + ) { + this.showHideWatermark(); + } else if ( + event.eventType == PluginEventType.EntityOperation && + event.entity.type == ENTITY_TYPE && + this.editor + ) { + const { + operation, + entity: { wrapper }, + } = event; + if (operation == EntityOperation.ReplaceTemporaryContent) { + this.removeWatermark(wrapper); + } else if (event.operation == EntityOperation.NewEntity) { + applyFormat( + wrapper, + this.format, + this.editor.isDarkMode(), + this.editor.getDarkColorHandler() + ); + wrapper.spellcheck = false; + } + } + } + + private showHideWatermark = () => { + if (!this.editor) { + return; + } + const hasFocus = this.editor.hasFocus(); + const watermarks = this.editor.queryElements(getEntitySelector(ENTITY_TYPE)); + const isShowing = watermarks.length > 0; + + if (hasFocus && isShowing) { + watermarks.forEach(this.removeWatermark); + this.editor.focus(); + } else if (!hasFocus && !isShowing && this.editor.isEmpty()) { + const newEntity = insertEntity( + this.editor, + ENTITY_TYPE, + this.editor.getDocument().createTextNode(this.watermark), + false /*isBlock*/, + false /*isReadonly*/, + ContentPosition.Begin + ); + if (this.customClass) { + newEntity.wrapper.classList.add(this.customClass); + } + } + }; + + private removeWatermark = (wrapper: HTMLElement) => { + const parentNode = wrapper.parentNode; + parentNode?.removeChild(wrapper); + + // After remove watermark node, if it leaves an empty DIV, append a BR node into it to make it a regular empty line + if ( + parentNode && + this.editor?.contains(parentNode) && + getTagOfNode(parentNode) == 'DIV' && + !parentNode.firstChild + ) { + parentNode.appendChild(this.editor.getDocument().createElement('BR')); + } + }; +} From 7c99f50650755937036c2173077fee44543d4457 Mon Sep 17 00:00:00 2001 From: Vi Nguyen <74168693+vinguyen12@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:19:33 -0700 Subject: [PATCH 79/80] resolve conflicts --- demo/scripts/controls/BuildInPluginState.ts | 1 + .../editorOptions/EditorOptionsPlugin.ts | 1 + .../contentModel/ContentModelPanePlugin.ts | 2 +- .../lib/edit/tabUtils/handleTabOnList.ts | 41 -- .../lib/edit/tabUtils/handleTabOnParagraph.ts | 90 ---- .../lib/shortcut/ShortcutCommand.ts | 50 --- .../lib/shortcut/ShortcutPlugin.ts | 150 ------- .../lib/shortcut/shortcuts.ts | 195 --------- .../lib/tableEdit/TableEditPlugin.ts | 175 -------- .../lib/tableEdit/editors/TableEditor.ts | 385 ------------------ .../tableEdit/editors/features/CellResizer.ts | 244 ----------- .../editors/features/TableEditorFeature.ts | 22 - .../editors/features/TableInserter.ts | 173 -------- .../tableEdit/editors/features/TableMover.ts | 136 ------- .../editors/features/TableResizer.ts | 249 ----------- .../test/corePlugin/CopyPastePluginTest.ts | 1 - .../setShortcutIndentationCommandTest.ts | 224 ++++++++++ .../test/tableEdit/tableResizerTest.ts | 198 +++++++++ 18 files changed, 425 insertions(+), 1912 deletions(-) delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts delete mode 100644 packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts create mode 100644 packages/roosterjs-content-model-plugins/test/shortcut/utils/setShortcutIndentationCommandTest.ts create mode 100644 packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts diff --git a/demo/scripts/controls/BuildInPluginState.ts b/demo/scripts/controls/BuildInPluginState.ts index 919c41da93b..fb61f825b38 100644 --- a/demo/scripts/controls/BuildInPluginState.ts +++ b/demo/scripts/controls/BuildInPluginState.ts @@ -15,6 +15,7 @@ export interface BuildInPluginList { imageEdit: boolean; cutPasteListChain: boolean; tableCellSelection: boolean; + tableResize: boolean; customReplace: boolean; listEditMenu: boolean; imageEditMenu: boolean; diff --git a/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts b/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts index 47528ae642d..01c06bc44d0 100644 --- a/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts +++ b/demo/scripts/controls/sidePane/editorOptions/EditorOptionsPlugin.ts @@ -13,6 +13,7 @@ const initialState: BuildInPluginState = { imageEdit: true, cutPasteListChain: true, tableCellSelection: true, + tableResize: true, customReplace: true, listEditMenu: true, imageEditMenu: true, diff --git a/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPanePlugin.ts b/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPanePlugin.ts index 79fcc880ee1..c56b67835bd 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPanePlugin.ts +++ b/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPanePlugin.ts @@ -16,7 +16,7 @@ export class ContentModelPanePlugin extends SidePanePluginImpl< this.contentModelRibbon = createRibbonPlugin(); } - initialize(editor: ILegacyEditor): void { + initialize(editor: IEditor): void { super.initialize(editor); this.contentModelRibbon.initialize(editor); diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts deleted file mode 100644 index 1f2cbf130ff..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnList.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { handleTabOnParagraph } from './handleTabOnParagraph'; -import { setModelIndentation } from 'roosterjs-content-model-api'; -import type { ContentModelDocument, ContentModelListItem } from 'roosterjs-content-model-types'; - -/** - * 1. When the selection is collapsed and the cursor is at start of a list item, call setModelIndentation. - * 2. Otherwise call handleTabOnParagraph. - * @internal - */ -export function handleTabOnList( - model: ContentModelDocument, - listItem: ContentModelListItem, - rawEvent: KeyboardEvent -) { - const selectedParagraph = findSelectedParagraph(listItem); - if ( - !isMarkerAtStartOfBlock(listItem) && - selectedParagraph.length == 1 && - selectedParagraph[0].blockType === 'Paragraph' - ) { - return handleTabOnParagraph(model, selectedParagraph[0], rawEvent); - } else { - setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent'); - rawEvent.preventDefault(); - return true; - } -} - -function isMarkerAtStartOfBlock(listItem: ContentModelListItem) { - return ( - listItem.blocks[0].blockType == 'Paragraph' && - listItem.blocks[0].segments[0].segmentType == 'SelectionMarker' - ); -} - -function findSelectedParagraph(listItem: ContentModelListItem) { - return listItem.blocks.filter( - block => - block.blockType == 'Paragraph' && block.segments.some(segment => segment.isSelected) - ); -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts b/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts deleted file mode 100644 index 684ef48e70e..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/edit/tabUtils/handleTabOnParagraph.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { createSelectionMarker, createText } from 'roosterjs-content-model-dom'; -import { setModelIndentation } from 'roosterjs-content-model-api'; -import type { ContentModelDocument, ContentModelParagraph } from 'roosterjs-content-model-types'; - -const tabSpaces = '    '; -const space = ' '; - -/** - * @internal - The handleTabOnParagraph function will handle the tab key in following scenarios: - * 1. When the selection is collapsed and the cursor is at the end of a paragraph, add 4 spaces. - * 2. When the selection is collapsed and the cursor is at the start of a paragraph, call setModelIndention function to indent the whole paragraph - * 3. When the selection is collapsed and the cursor is at the middle of a paragraph, add 4 spaces. - * 4. When the selection is not collapsed, replace the selected range with a single space. - * 5. When the selection is not collapsed, but all segments are selected, call setModelIndention function to indent the whole paragraph - The handleTabOnParagraph function will handle the shift + tab key in a indented paragraph in following scenarios: - * 1. When the selection is collapsed and the cursor is at the end of a paragraph, remove 4 spaces. - * 2. When the selection is collapsed and the cursor is at the start of a paragraph, call setModelIndention function to outdent the whole paragraph - * 3. When the selection is collapsed and the cursor is at the middle of a paragraph, remove 4 spaces. - * 4. When the selection is not collapsed, replace the selected range with a 4 space. - * 5. When the selection is not collapsed, but all segments are selected, call setModelIndention function to outdent the whole paragraph - */ -export function handleTabOnParagraph( - model: ContentModelDocument, - paragraph: ContentModelParagraph, - rawEvent: KeyboardEvent -) { - const selectedSegments = paragraph.segments.filter(segment => segment.isSelected); - const isCollapsed = - selectedSegments.length === 1 && selectedSegments[0].segmentType === 'SelectionMarker'; - const isAllSelected = paragraph.segments.every(segment => segment.isSelected); - if ((paragraph.segments[0].segmentType === 'SelectionMarker' && isCollapsed) || isAllSelected) { - setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent'); - } else { - if (!isCollapsed) { - let firstSelectedSegmentIndex: number | undefined = undefined; - let lastSelectedSegmentIndex: number | undefined = undefined; - - paragraph.segments.forEach((segment, index) => { - if (segment.isSelected) { - if (!firstSelectedSegmentIndex) { - firstSelectedSegmentIndex = index; - } - lastSelectedSegmentIndex = index; - } - }); - if (firstSelectedSegmentIndex && lastSelectedSegmentIndex) { - const firstSelectedSegment = paragraph.segments[firstSelectedSegmentIndex]; - const spaceText = createText( - rawEvent.shiftKey ? tabSpaces : space, - firstSelectedSegment.format - ); - const marker = createSelectionMarker(firstSelectedSegment.format); - paragraph.segments.splice( - firstSelectedSegmentIndex, - lastSelectedSegmentIndex - firstSelectedSegmentIndex + 1, - spaceText, - marker - ); - } else { - return false; - } - } else { - const markerIndex = paragraph.segments.findIndex( - segment => segment.segmentType === 'SelectionMarker' - ); - if (!rawEvent.shiftKey) { - const markerFormat = paragraph.segments[markerIndex].format; - const tabText = createText(tabSpaces, markerFormat); - paragraph.segments.splice(markerIndex, 0, tabText); - } else { - const tabText = paragraph.segments[markerIndex - 1]; - const tabSpacesLength = tabSpaces.length; - if (tabText.segmentType == 'Text') { - const tabSpaceTextLength = tabText.text.length - tabSpacesLength; - if (tabText.text === tabSpaces) { - paragraph.segments.splice(markerIndex - 1, 1); - } else if (tabText.text.substring(tabSpaceTextLength) === tabSpaces) { - tabText.text = tabText.text.substring(0, tabSpaceTextLength); - } else { - return false; - } - } - } - } - } - - rawEvent.preventDefault(); - return true; -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts b/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts deleted file mode 100644 index 2c2e4416712..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutCommand.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { IEditor } from 'roosterjs-content-model-types'; - -/** - * Definition of the shortcut key - */ -export interface ShortcutKeyDefinition { - /** - * Modifier key for this shortcut, allowed values are: - * ctrl: Ctrl key (or Meta key on MacOS) - * alt: Alt key - */ - modifierKey: 'ctrl' | 'alt'; - - /** - * Whether ALT key is required for this shortcut - */ - shiftKey: boolean; - - /** - * Key code for this shortcut. The value should be the value of KeyboardEvent.which - * We are still using key code here rather than key name (event.key) although event.which is deprecated because of globalization. - * For example, on US keyboard, Shift+Comma="<" but on Spanish keyboard it is ":" - * And we still want the shortcut key to be registered on the same key, in that case key name is different but key code keeps the same. - */ - which: number; -} - -/** - * Represents a command for shortcut - */ -export interface ShortcutCommand { - /** - * Definition of the shortcut key - */ - shortcutKey: ShortcutKeyDefinition; - - /** - * @optional Required environment for this command - * all: (Default) This feature is available for all environments - * mac: This feature is available on MacOS only - * nonMac: This feature is available on OS other than MacOS - */ - environment?: 'all' | 'mac' | 'nonMac'; - - /** - * The callback function to invoke when this shortcut is triggered - * @param editor The editor object - */ - onClick: (editor: IEditor) => void; -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts b/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts deleted file mode 100644 index 9b7fe7a3f60..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/ShortcutPlugin.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { cacheGetEventData } from 'roosterjs-content-model-core'; -import type { ShortcutCommand, ShortcutKeyDefinition } from './ShortcutCommand'; -import { - ShortcutBold, - ShortcutBullet, - ShortcutClearFormat, - ShortcutDecreaseFont, - ShortcutIncreaseFont, - ShortcutItalic, - ShortcutNumbering, - ShortcutRedo, - ShortcutRedoMacOS, - ShortcutUnderline, - ShortcutUndo, - ShortcutUndo2, -} from './shortcuts'; -import type { - EditorPlugin, - IEditor, - KeyDownEvent, - PluginEvent, -} from 'roosterjs-content-model-types'; - -const defaultShortcuts: ShortcutCommand[] = [ - ShortcutBold, - ShortcutItalic, - ShortcutUnderline, - ShortcutClearFormat, - ShortcutUndo, - ShortcutUndo2, - ShortcutRedo, - ShortcutRedoMacOS, - ShortcutBullet, - ShortcutNumbering, - ShortcutIncreaseFont, - ShortcutDecreaseFont, -]; -const CommandCacheKey = '__ShortcutCommandCache'; - -/** - * Shortcut plugin hook on the specified shortcut keys and trigger related format API - */ -export class ShortcutPlugin implements EditorPlugin { - private editor: IEditor | null = null; - private isMac = false; - - /** - * Create a new instance of ShortcutPlugin - * @param [shortcuts=defaultShortcuts] Allowed commands - */ - constructor(private shortcuts: ShortcutCommand[] = defaultShortcuts) {} - - /** - * Get name of this plugin - */ - getName() { - return 'Shortcut'; - } - - /** - * The first method that editor will call to a plugin when editor is initializing. - * It will pass in the editor instance, plugin should take this chance to save the - * editor reference so that it can call to any editor method or format API later. - * @param editor The editor object - */ - initialize(editor: IEditor) { - this.editor = editor; - this.isMac = !!this.editor.getEnvironment().isMac; - } - - /** - * The last method that editor will call to a plugin before it is disposed. - * Plugin can take this chance to clear the reference to editor. After this method is - * called, plugin should not call to any editor method since it will result in error. - */ - dispose() { - this.editor = null; - } - - /** - * Check if the plugin should handle the given event exclusively. - * Handle an event exclusively means other plugin will not receive this event in - * onPluginEvent method. - * If two plugins will return true in willHandleEventExclusively() for the same event, - * the final result depends on the order of the plugins are added into editor - * @param event The event to check: - */ - willHandleEventExclusively(event: PluginEvent) { - return ( - event.eventType == 'keyDown' && - (event.rawEvent.ctrlKey || event.rawEvent.altKey || event.rawEvent.metaKey) && - !!this.cacheGetCommand(event) - ); - } - - /** - * Core method for a plugin. Once an event happens in editor, editor will call this - * method of each plugin to handle the event as long as the event is not handled - * exclusively by another plugin. - * @param event The event to handle: - */ - onPluginEvent(event: PluginEvent) { - if (this.editor && event.eventType == 'keyDown') { - const command = this.cacheGetCommand(event); - - if (command) { - command.onClick(this.editor); - event.rawEvent.preventDefault(); - } - } - } - - private cacheGetCommand(event: KeyDownEvent) { - return cacheGetEventData(event, CommandCacheKey, event => { - const editor = this.editor; - - return ( - editor && - this.shortcuts.filter( - command => - this.matchOS(command.environment) && - this.matchShortcut(command.shortcutKey, event.rawEvent) - )[0] - ); - }); - } - - private matchOS(environment?: 'all' | 'mac' | 'nonMac') { - switch (environment) { - case 'mac': - return this.isMac; - - case 'nonMac': - return !this.isMac; - - default: - return true; - } - } - - private matchShortcut(shortcutKey: ShortcutKeyDefinition, event: KeyboardEvent) { - const { ctrlKey, altKey, shiftKey, which, metaKey } = event; - const ctrlOrMeta = this.isMac ? metaKey : ctrlKey; - const matchModifier = - (shortcutKey.modifierKey == 'ctrl' && ctrlOrMeta && !altKey) || - (shortcutKey.modifierKey == 'alt' && altKey && !ctrlOrMeta); - - return matchModifier && shiftKey == shortcutKey.shiftKey && shortcutKey.which == which; - } -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts b/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts deleted file mode 100644 index 8c2520485c4..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/shortcut/shortcuts.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { redo, undo } from 'roosterjs-content-model-core'; -import { - changeFontSize, - clearFormat, - toggleBold, - toggleBullet, - toggleItalic, - toggleNumbering, - toggleUnderline, -} from 'roosterjs-content-model-api'; -import type { ShortcutCommand } from './ShortcutCommand'; - -const enum Keys { - BACKSPACE = 8, - SPACE = 32, - B = 66, - I = 73, - U = 85, - Y = 89, - Z = 90, - COMMA = 188, - PERIOD = 190, - FORWARD_SLASH = 191, -} - -/** - * Shortcut command for Bold - * Windows: Ctrl + B - * MacOS: Meta + B - */ -export const ShortcutBold: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: false, - which: Keys.B, - }, - onClick: editor => toggleBold(editor), -}; - -/** - * Shortcut command for Italic - * Windows: Ctrl + I - * MacOS: Meta + I - */ -export const ShortcutItalic: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: false, - which: Keys.I, - }, - onClick: editor => toggleItalic(editor), -}; - -/** - * Shortcut command for Underline - * Windows: Ctrl + U - * MacOS: Meta + U - */ -export const ShortcutUnderline: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: false, - which: Keys.U, - }, - onClick: editor => toggleUnderline(editor), -}; - -/** - * Shortcut command for Clear Format - * Windows: Ctrl + Space - * MacOS: Meta + Space - */ -export const ShortcutClearFormat: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: false, - which: Keys.SPACE, - }, - onClick: editor => clearFormat(editor), -}; - -/** - * Shortcut command for Undo 1 - * Windows: Ctrl + Z - * MacOS: Meta + Z - */ -export const ShortcutUndo: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: false, - which: Keys.Z, - }, - onClick: editor => undo(editor), -}; - -/** - * Shortcut command for Undo 2 - * Windows: Alt + Backspace - * MacOS: N/A - */ -export const ShortcutUndo2: ShortcutCommand = { - shortcutKey: { - modifierKey: 'alt', - shiftKey: false, - which: Keys.BACKSPACE, - }, - onClick: editor => undo(editor), - environment: 'nonMac', -}; - -/** - * Shortcut command for Redo 1 - * Windows: Ctrl + Y - * MacOS: N/A - */ -export const ShortcutRedo: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: false, - which: Keys.Y, - }, - onClick: editor => redo(editor), - environment: 'nonMac', -}; - -/** - * Shortcut command for Redo 2 - * Windows: N/A - * MacOS: Meta + Shift + Z - */ -export const ShortcutRedoMacOS: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: true, - which: Keys.Z, - }, - onClick: editor => redo(editor), - environment: 'mac', -}; - -/** - * Shortcut command for Bullet List - * Windows: Ctrl + . (Period) - * MacOS: Meta + . (Period) - */ -export const ShortcutBullet: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: false, - which: Keys.PERIOD, - }, - onClick: editor => toggleBullet(editor), -}; - -/** - * Shortcut command for Numbering List - * Windows: Ctrl + / (Forward slash) - * MacOS: Meta + / (Forward slash) - */ -export const ShortcutNumbering: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: false, - which: Keys.FORWARD_SLASH, - }, - onClick: editor => toggleNumbering(editor), -}; - -/** - * Shortcut command for Increase Font - * Windows: Ctrl + Shift + . (Period) - * MacOS: Meta + Shift + . (Period) - */ -export const ShortcutIncreaseFont: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: true, - which: Keys.PERIOD, - }, - onClick: editor => changeFontSize(editor, 'increase'), -}; - -/** - * Shortcut command for Decrease Font - * Windows: Ctrl + Shift + , (Comma) - * MacOS: Meta + Shift + , (Comma) - */ -export const ShortcutDecreaseFont: ShortcutCommand = { - shortcutKey: { - modifierKey: 'ctrl', - shiftKey: true, - which: Keys.COMMA, - }, - onClick: editor => changeFontSize(editor, 'decrease'), -}; diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts deleted file mode 100644 index f2b0c1b9649..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/TableEditPlugin.ts +++ /dev/null @@ -1,175 +0,0 @@ -import normalizeRect from '../pluginUtils/Rect/normalizeRect'; -import TableEditor from './editors/TableEditor'; -import { isNodeOfType } from 'roosterjs-content-model-dom'; -import type { EditorPlugin, IEditor, PluginEvent, Rect } from 'roosterjs-content-model-types'; - -const TABLE_RESIZER_LENGTH = 12; - -/** - * TableEdit plugin, provides the ability to resize a table by drag-and-drop - */ -export class TableEditPlugin implements EditorPlugin { - private editor: IEditor | null = null; - private onMouseMoveDisposer: (() => void) | null = null; - private tableRectMap: { table: HTMLTableElement; rect: Rect }[] | null = null; - private tableEditor: TableEditor | null = null; - - /** - * Construct a new instance of TableResize plugin - * @param anchorContainerSelector An optional selector string to specify the container to host the plugin. - * The container must not be affected by transform: scale(), otherwise the position calculation will be wrong. - * If not specified, the plugin will be inserted in document.body - */ - constructor(private anchorContainerSelector?: string) {} - - /** - * Get a friendly name of this plugin - */ - getName() { - return 'TableEdit'; - } - - /** - * Initialize this plugin. This should only be called from Editor - * @param editor Editor instance - */ - initialize(editor: IEditor) { - this.editor = editor; - this.onMouseMoveDisposer = this.editor.attachDomEvent({ - mousemove: { beforeDispatch: this.onMouseMove }, - }); - const scrollContainer = this.editor.getScrollContainer(); - scrollContainer.addEventListener('mouseout', this.onMouseOut); - } - - private onMouseOut = ({ relatedTarget, currentTarget }: MouseEvent) => { - const relatedTargetNode = relatedTarget as Node; - const currentTargetNode = currentTarget as Node; - if ( - isNodeOfType(relatedTargetNode, 'ELEMENT_NODE') && - isNodeOfType(currentTargetNode, 'ELEMENT_NODE') && - this.tableEditor && - !this.tableEditor.isOwnedElement(relatedTargetNode) && - !currentTargetNode.contains(relatedTargetNode) - ) { - this.setTableEditor(null); - } - }; - - /** - * Dispose this plugin - */ - dispose() { - const scrollContainer = this.editor?.getScrollContainer(); - scrollContainer?.removeEventListener('mouseout', this.onMouseOut); - this.onMouseMoveDisposer?.(); - this.invalidateTableRects(); - this.disposeTableEditor(); - this.editor = null; - this.onMouseMoveDisposer = null; - } - - /** - * Handle events triggered from editor - * @param event PluginEvent object - */ - onPluginEvent(e: PluginEvent) { - switch (e.eventType) { - case 'input': - case 'contentChanged': - case 'scroll': - case 'zoomChanged': - this.setTableEditor(null); - this.invalidateTableRects(); - break; - } - } - - private onMouseMove = (event: Event) => { - const e = event as MouseEvent; - - if (e.buttons > 0 || !this.editor) { - return; - } - - this.ensureTableRects(); - - const editorWindow = this.editor.getDocument().defaultView || window; - const x = e.pageX - editorWindow.scrollX; - const y = e.pageY - editorWindow.scrollY; - let currentTable: HTMLTableElement | null = null; - - //Find table in range of mouse - if (this.tableRectMap) { - for (let i = this.tableRectMap.length - 1; i >= 0; i--) { - const { table, rect } = this.tableRectMap[i]; - - if ( - x >= rect.left - TABLE_RESIZER_LENGTH && - x <= rect.right + TABLE_RESIZER_LENGTH && - y >= rect.top - TABLE_RESIZER_LENGTH && - y <= rect.bottom + TABLE_RESIZER_LENGTH - ) { - currentTable = table; - break; - } - } - } - - this.setTableEditor(currentTable, e); - this.tableEditor?.onMouseMove(x, y); - }; - - /** - * @internal Public only for unit test - * @param table Table to use when setting the Editors - * @param event (Optional) Mouse event - */ - public setTableEditor(table: HTMLTableElement | null, event?: MouseEvent) { - if (this.tableEditor && !this.tableEditor.isEditing() && table != this.tableEditor.table) { - this.disposeTableEditor(); - } - - if (!this.tableEditor && table && this.editor && table.rows.length > 0) { - const container = this.anchorContainerSelector - ? this.editor.getDOMHelper().queryElements(this.anchorContainerSelector)[0] - : undefined; - - this.tableEditor = new TableEditor( - this.editor, - table, - this.invalidateTableRects, - isNodeOfType(container as Node, 'ELEMENT_NODE') ? container : undefined, - event?.currentTarget - ); - } - } - - private invalidateTableRects = () => { - this.tableRectMap = null; - }; - - private disposeTableEditor() { - this.tableEditor?.dispose(); - this.tableEditor = null; - } - - private ensureTableRects() { - if (!this.tableRectMap && this.editor) { - this.tableRectMap = []; - - const tables = this.editor.getDOMHelper().queryElements('table'); - tables.forEach(table => { - if (table.isContentEditable) { - const rect = normalizeRect(table.getBoundingClientRect()); - if (rect && this.tableRectMap) { - this.tableRectMap.push({ - table, - rect, - }); - } - } - }); - } - } -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts deleted file mode 100644 index 96cedfe6d22..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/TableEditor.ts +++ /dev/null @@ -1,385 +0,0 @@ -import createCellResizer from './features/CellResizer'; -import createTableInserter from './features/TableInserter'; -import createTableMover from './features/TableMover'; -import createTableResizer from './features/TableResizer'; -import normalizeRect from '../../pluginUtils/Rect/normalizeRect'; -import { disposeTableEditFeature } from './features/TableEditorFeature'; -import { isNodeOfType } from 'roosterjs-content-model-dom'; -import type TableEditFeature from './features/TableEditorFeature'; -import type { IEditor, TableSelection } from 'roosterjs-content-model-types'; - -const INSERTER_HOVER_OFFSET = 6; -const enum TOP_OR_SIDE { - top = 0, - side = 1, -} -/** - * @internal - * - * A table has 6 hot areas to be resized/edited (take LTR example): - * - * [6] [ ] - * +[ 1 ]+--------------------+ - * |[ ]| | - * [ ] [ ] | - * [ ] [ ] | - * [2] [3] | - * [ ] [ ] | - * [ ][ 4 ]| | - * +------------------+--------------------+ - * | | | - * | | | - * | | | - * +------------------+--------------------+ - * [5] - * - * 1 - Hover area to show insert column button - * 2 - Hover area to show insert row button - * 3 - Hover area to show vertical resizing bar - * 4 - Hover area to show horizontal resizing bar - * 5 - Hover area to show whole table resize handle - * 6 - Hover area to show whole table mover handle - * - * When set a different current table or change current TD, we need to update these areas - */ -export default class TableEditor { - // 1, 2 - Insert a column or a row - private horizontalInserter: TableEditFeature | null = null; - private verticalInserter: TableEditFeature | null = null; - - // 3, 4 - Resize a column or a row from a cell - private horizontalResizer: TableEditFeature | null = null; - private verticalResizer: TableEditFeature | null = null; - - // 5 - Resize whole table - private tableResizer: TableEditFeature | null = null; - - // 6 - Move as well as select whole table - private tableMover: TableEditFeature | null = null; - - private isRTL: boolean; - private range: Range | null = null; - private isCurrentlyEditing: boolean; - - constructor( - private editor: IEditor, - public readonly table: HTMLTableElement, - private onChanged: () => void, - private anchorContainer?: HTMLElement, - private contentDiv?: EventTarget | null - ) { - this.isRTL = editor.getDocument().defaultView?.getComputedStyle(table).direction == 'rtl'; - this.setEditorFeatures(); - this.isCurrentlyEditing = false; - } - - dispose() { - this.disposeTableResizer(); - this.disposeCellResizers(); - this.disposeTableInserter(); - this.disposeTableMover(); - } - - isEditing(): boolean { - return this.isCurrentlyEditing; - } - - isOwnedElement(node: Node) { - return [ - this.tableResizer, - this.tableMover, - this.horizontalInserter, - this.verticalInserter, - this.horizontalResizer, - this.verticalResizer, - ] - .filter(feature => !!feature?.div) - .some(feature => feature?.div == node); - } - - onMouseMove(x: number, y: number) { - // Get whole table rect - const tableRect = normalizeRect(this.table.getBoundingClientRect()); - - //console.log('>>>tableRect', tableRect); - if (!tableRect) { - return; - } - - // Determine if cursor is on top or side - const topOrSide = - y <= tableRect.top + INSERTER_HOVER_OFFSET - ? TOP_OR_SIDE.top - : this.isRTL - ? x >= tableRect.right - INSERTER_HOVER_OFFSET - ? TOP_OR_SIDE.side - : undefined - : x <= tableRect.left + INSERTER_HOVER_OFFSET - ? TOP_OR_SIDE.side - : undefined; - const topOrSideBinary = topOrSide ? 1 : 0; - - // i is row index, j is column index - for (let i = 0; i < this.table.rows.length; i++) { - const tr = this.table.rows[i]; - let j = 0; - for (; j < tr.cells.length; j++) { - const td = tr.cells[j]; - const tdRect = normalizeRect(td.getBoundingClientRect()); - - if (!tdRect || !tableRect) { - continue; - } - - // Determine the cell the cursor is in range of - // Offset is only used for first row and column - const lessThanBottom = y <= tdRect.bottom; - const lessThanRight = this.isRTL - ? x <= tdRect.right + INSERTER_HOVER_OFFSET * topOrSideBinary - : x <= tdRect.right; - const moreThanLeft = this.isRTL - ? x >= tdRect.left - : x >= tdRect.left - INSERTER_HOVER_OFFSET * topOrSideBinary; - - if (lessThanBottom && lessThanRight && moreThanLeft) { - if (i === 0 && topOrSide == TOP_OR_SIDE.top) { - const center = (tdRect.left + tdRect.right) / 2; - const isOnRightHalf = this.isRTL ? x < center : x > center; - this.setInserterTd( - isOnRightHalf ? td : tr.cells[j - 1], - false /*isHorizontal*/ - ); - } else if (j === 0 && topOrSide == TOP_OR_SIDE.side) { - const tdAbove = this.table.rows[i - 1]?.cells[0]; - const tdAboveRect = tdAbove - ? normalizeRect(tdAbove.getBoundingClientRect()) - : null; - - const isTdNotAboveMerged = !tdAboveRect - ? null - : this.isRTL - ? tdAboveRect.right === tdRect.right - : tdAboveRect.left === tdRect.left; - - this.setInserterTd( - y < (tdRect.top + tdRect.bottom) / 2 && isTdNotAboveMerged - ? tdAbove - : td, - true /*isHorizontal*/ - ); - } else { - this.setInserterTd(null); - } - - this.setResizingTd(td); - - //Cell found - break; - } - } - - if (j < tr.cells.length) { - break; - } - } - - // Create Mover and Resizer - this.setEditorFeatures(); - } - - private setEditorFeatures() { - if (!this.tableMover) { - this.tableMover = createTableMover( - this.table, - this.editor, - this.isRTL, - this.onSelect, - this.getOnMouseOut, - this.contentDiv, - this.anchorContainer - ); - } - - if (!this.tableResizer) { - this.tableResizer = createTableResizer( - this.table, - this.editor, - this.isRTL, - this.onStartTableResize, - this.onFinishEditing, - this.contentDiv, - this.anchorContainer - ); - } - } - - private setResizingTd(td: HTMLTableCellElement) { - if (this.horizontalResizer && this.horizontalResizer.node != td) { - this.disposeCellResizers(); - } - - if (!this.horizontalResizer && td) { - this.horizontalResizer = createCellResizer( - this.editor, - td, - this.table, - this.isRTL, - true /*isHorizontal*/, - this.onStartCellResize, - this.onFinishEditing, - this.anchorContainer - ); - this.verticalResizer = createCellResizer( - this.editor, - td, - this.table, - this.isRTL, - false /*isHorizontal*/, - this.onStartCellResize, - this.onFinishEditing, - this.anchorContainer - ); - } - } - - /** - * create or remove TableInserter - * @param td td to attach to, set this to null to remove inserters (both horizontal and vertical) - */ - private setInserterTd(td: HTMLTableCellElement | null, isHorizontal?: boolean) { - const inserter = isHorizontal ? this.horizontalInserter : this.verticalInserter; - if (td === null || (inserter && inserter.node != td)) { - this.disposeTableInserter(); - } - - if (!this.horizontalInserter && !this.verticalInserter && td) { - const newInserter = createTableInserter( - this.editor, - td, - this.table, - this.isRTL, - !!isHorizontal, - this.onInserted, - this.getOnMouseOut, - this.anchorContainer - ); - if (isHorizontal) { - this.horizontalInserter = newInserter; - } else { - this.verticalInserter = newInserter; - } - } - } - - private disposeTableResizer() { - if (this.tableResizer) { - disposeTableEditFeature(this.tableResizer); - this.tableResizer = null; - } - } - - private disposeTableInserter() { - if (this.horizontalInserter) { - disposeTableEditFeature(this.horizontalInserter); - this.horizontalInserter = null; - } - if (this.verticalInserter) { - disposeTableEditFeature(this.verticalInserter); - this.verticalInserter = null; - } - } - - private disposeCellResizers() { - if (this.horizontalResizer) { - disposeTableEditFeature(this.horizontalResizer); - this.horizontalResizer = null; - } - if (this.verticalResizer) { - disposeTableEditFeature(this.verticalResizer); - this.verticalResizer = null; - } - } - - private disposeTableMover() { - if (this.tableMover) { - disposeTableEditFeature(this.tableMover); - this.tableMover = null; - } - } - - private onFinishEditing = (): false => { - this.editor.focus(); - - if (this.range) { - this.editor.setDOMSelection({ type: 'range', range: this.range, isReverted: false }); - } - - this.editor.takeSnapshot(); // Pass in an empty callback to make sure ContentChangedEvent is triggered - this.onChanged(); - this.isCurrentlyEditing = false; - - return false; - }; - - private onStartTableResize = () => { - this.isCurrentlyEditing = true; - this.onStartResize(); - }; - - private onStartCellResize = () => { - this.isCurrentlyEditing = true; - this.disposeTableResizer(); - this.onStartResize(); - }; - - private onStartResize() { - this.isCurrentlyEditing = true; - const range = this.editor.getDOMSelection(); - - if (range && range.type == 'range') { - this.range = range.range; - } - - this.editor.takeSnapshot(); - } - - private onInserted = () => { - this.disposeTableResizer(); - this.onFinishEditing(); - }; - - /** - * Public only for testing purposes - * @param table the table to select - */ - public onSelect = (table: HTMLTableElement) => { - this.editor.focus(); - - if (table) { - const selection: TableSelection = { - table: table, - firstRow: 0, - firstColumn: 0, - lastRow: table.rows.length - 1, - lastColumn: table.rows[table.rows.length - 1].cells.length - 1, - type: 'table', - }; - - this.editor.setDOMSelection(selection); - } - }; - - private getOnMouseOut = (feature: HTMLElement) => { - return (ev: MouseEvent) => { - if ( - feature && - ev.relatedTarget != feature && - isNodeOfType(this.contentDiv as Node, 'ELEMENT_NODE') && - isNodeOfType(ev.relatedTarget as Node, 'ELEMENT_NODE') && - !(this.contentDiv == ev.relatedTarget) - ) { - this.dispose(); - } - }; - }; -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts deleted file mode 100644 index 799879858be..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/CellResizer.ts +++ /dev/null @@ -1,244 +0,0 @@ -import createElement from '../../../pluginUtils/CreateElement/createElement'; -import DragAndDropHelper from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; -import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; -import { isElementOfType } from 'roosterjs-content-model-dom'; -import { - getFirstSelectedTable, - MIN_ALLOWED_TABLE_CELL_WIDTH, - normalizeTable, -} from 'roosterjs-content-model-core'; -import type DragAndDropHandler from '../../../pluginUtils/DragAndDrop/DragAndDropHandler'; -import type { ContentModelTable, IEditor } from 'roosterjs-content-model-types'; -import type TableEditFeature from './TableEditorFeature'; - -const CELL_RESIZER_WIDTH = 4; - -/** - * @internal - */ -export default function createCellResizer( - editor: IEditor, - td: HTMLTableCellElement, - table: HTMLTableElement, - isRTL: boolean, - isHorizontal: boolean, - onStart: () => void, - onEnd: () => false, - anchorContainer?: HTMLElement -): TableEditFeature | null { - const document = td.ownerDocument; - const createElementData = { - tag: 'div', - style: `position: fixed; cursor: ${isHorizontal ? 'row' : 'col'}-resize; user-select: none`, - }; - const zoomScale = editor.getDOMHelper().calculateZoomScale(); - - const div = createElement(createElementData, document) as HTMLDivElement; - - (anchorContainer || document.body).appendChild(div); - - const context: DragAndDropContext = { editor, td, table, isRTL, zoomScale, onStart }; - const setPosition = isHorizontal ? setHorizontalPosition : setVerticalPosition; - setPosition(context, div); - - const handler: DragAndDropHandler = { - onDragStart, - // Horizontal modifies row height, vertical modifies column width - onDragging: isHorizontal ? onDraggingHorizontal : onDraggingVertical, - onDragEnd: onEnd, - }; - - const featureHandler = new DragAndDropHelper( - div, - context, - setPosition, - handler, - zoomScale, - editor.getEnvironment().isMobileOrTablet - ); - - return { node: td, div, featureHandler }; -} - -interface DragAndDropContext { - editor: IEditor; - td: HTMLTableCellElement; - table: HTMLTableElement; - isRTL: boolean; - zoomScale: number; - onStart: () => void; -} - -interface DragAndDropInitValue { - cmTable: ContentModelTable | undefined; - anchorColumn: number | undefined; - anchorRow: number | undefined; - anchorRowHeight: number; - allWidths: number[]; -} - -function onDragStart(context: DragAndDropContext, event: MouseEvent): DragAndDropInitValue { - const { td, onStart } = context; - const rect = normalizeRect(td.getBoundingClientRect()); - - // Get cell coordinates - const columnIndex = td.cellIndex; - const row = - td.parentElement && isElementOfType(td.parentElement, 'tr') ? td.parentElement : undefined; - const rowIndex = row?.rowIndex; - - if (rowIndex == undefined) { - return { - cmTable: undefined, - anchorColumn: undefined, - anchorRow: undefined, - anchorRowHeight: -1, - allWidths: [], - }; // Just a fallback - } - - const { editor, table } = context; - - // Get current selection - const selection = editor.getDOMSelection(); - - // Select first cell of the table - editor.setDOMSelection({ - type: 'table', - firstColumn: 0, - firstRow: 0, - lastColumn: 0, - lastRow: 0, - table: table, - }); - - // Get the table content model - const cmTable = getFirstSelectedTable(editor.getContentModelCopy('disconnected'))[0]; - - // Restore selection - editor.setDOMSelection(selection); - - if (rect && cmTable) { - onStart(); - - return { - cmTable, - anchorColumn: columnIndex, - anchorRow: rowIndex, - anchorRowHeight: cmTable.rows[rowIndex].height, - allWidths: [...cmTable.widths], - }; - } else { - return { - cmTable, - anchorColumn: undefined, - anchorRow: undefined, - anchorRowHeight: -1, - allWidths: [], - }; // Just a fallback - } -} - -function onDraggingHorizontal( - context: DragAndDropContext, - event: MouseEvent, - initValue: DragAndDropInitValue, - deltaX: number, - deltaY: number -) { - const { table } = context; - const { cmTable, anchorRow, anchorRowHeight } = initValue; - - // Assign new widths and heights to the CM table - if (cmTable && anchorRow != undefined) { - // Modify the CM Table size - cmTable.rows[anchorRow].height = (anchorRowHeight ?? 0) + deltaY; - - // Normalize the table - normalizeTable(cmTable); - - // Writeback CM Table size changes to DOM Table - const tableRow = table.rows[anchorRow]; - for (let col = 0; col < tableRow.cells.length; col++) { - const td = tableRow.cells[col]; - td.style.height = cmTable.rows[anchorRow].height + 'px'; - } - - return true; - } else { - return false; - } -} - -function onDraggingVertical( - context: DragAndDropContext, - event: MouseEvent, - initValue: DragAndDropInitValue, - deltaX: number -) { - const { table, isRTL } = context; - const { cmTable, anchorColumn, allWidths } = initValue; - - // Assign new widths and heights to the CM table - if (cmTable && anchorColumn != undefined) { - // Modify the CM Table size - const lastColumn = anchorColumn == cmTable.widths.length - 1; - const change = deltaX * (isRTL ? -1 : 1); - // This is the last column - if (lastColumn) { - // Only the last column changes - cmTable.widths[anchorColumn] = allWidths[anchorColumn] + change; - } else { - // Any other two columns - const anchorChange = allWidths[anchorColumn] + change; - const nextAnchorChange = allWidths[anchorColumn + 1] - change; - if ( - anchorChange < MIN_ALLOWED_TABLE_CELL_WIDTH || - nextAnchorChange < MIN_ALLOWED_TABLE_CELL_WIDTH - ) { - return false; - } - cmTable.widths[anchorColumn] = anchorChange; - cmTable.widths[anchorColumn + 1] = nextAnchorChange; - } - - // Normalize the table - normalizeTable(cmTable); - - // Writeback CM Table size changes to DOM Table - for (let row = 0; row < table.rows.length; row++) { - const tableRow = table.rows[row]; - for (let col = 0; col < tableRow.cells.length; col++) { - tableRow.cells[col].style.width = cmTable.widths[col] + 'px'; - } - } - - return true; - } else { - return false; - } -} - -function setHorizontalPosition(context: DragAndDropContext, trigger: HTMLElement) { - const { td } = context; - const rect = normalizeRect(td.getBoundingClientRect()); - if (rect) { - trigger.id = 'horizontalResizer'; - trigger.style.top = rect.bottom - CELL_RESIZER_WIDTH + 'px'; - trigger.style.left = rect.left + 'px'; - trigger.style.width = rect.right - rect.left + 'px'; - trigger.style.height = CELL_RESIZER_WIDTH + 'px'; - } -} - -function setVerticalPosition(context: DragAndDropContext, trigger: HTMLElement) { - const { td, isRTL } = context; - const rect = normalizeRect(td.getBoundingClientRect()); - if (rect) { - trigger.id = 'verticalResizer'; - trigger.style.top = rect.top + 'px'; - trigger.style.left = (isRTL ? rect.left : rect.right) - CELL_RESIZER_WIDTH + 1 + 'px'; - trigger.style.width = CELL_RESIZER_WIDTH + 'px'; - trigger.style.height = rect.bottom - rect.top + 'px'; - } -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts deleted file mode 100644 index f244e2bd39d..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableEditorFeature.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type Disposable from '../../../pluginUtils/Disposable'; - -/** - * @internal - */ -export default interface TableEditFeature { - node: Node; - div: HTMLDivElement | null; - featureHandler: Disposable | null; -} - -/** - * @internal - */ -export function disposeTableEditFeature(resizer: TableEditFeature | null) { - if (resizer) { - resizer.div?.parentNode?.removeChild(resizer.div); - resizer.div = null; - resizer.featureHandler?.dispose(); - resizer.featureHandler = null; - } -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts deleted file mode 100644 index 9a6d45eec5d..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableInserter.ts +++ /dev/null @@ -1,173 +0,0 @@ -import createElement from '../../../pluginUtils/CreateElement/createElement'; -import getIntersectedRect from '../../../pluginUtils/Rect/getIntersectedRect'; -import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; -import { isElementOfType } from 'roosterjs-content-model-dom'; -import { - formatTableWithContentModel, - insertTableColumn, - insertTableRow, -} from 'roosterjs-content-model-api'; -import type CreateElementData from '../../../pluginUtils/CreateElement/CreateElementData'; -import type Disposable from '../../../pluginUtils/Disposable'; -import type TableEditFeature from './TableEditorFeature'; -import type { IEditor } from 'roosterjs-content-model-types'; - -const INSERTER_COLOR = '#4A4A4A'; -const INSERTER_COLOR_DARK_MODE = 'white'; -const INSERTER_SIDE_LENGTH = 12; -const INSERTER_BORDER_SIZE = 1; - -/** - * @internal - */ -export default function createTableInserter( - editor: IEditor, - td: HTMLTableCellElement, - table: HTMLTableElement, - isRTL: boolean, - isHorizontal: boolean, - onInsert: () => void, - getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void, - anchorContainer?: HTMLElement -): TableEditFeature | null { - const tdRect = normalizeRect(td.getBoundingClientRect()); - const viewPort = editor.getVisibleViewport(); - const tableRect = table && viewPort ? getIntersectedRect([table], [viewPort]) : null; - - // set inserter position - if (tdRect && tableRect) { - const document = td.ownerDocument; - const createElementData = getInsertElementData( - isHorizontal, - editor.isDarkMode(), - isRTL, - editor.getDOMHelper().getDomStyle('backgroundColor') || 'white' - ); - - const div = createElement(createElementData, document) as HTMLDivElement; - - if (isHorizontal) { - // tableRect.left/right is used because the Inserter is always intended to be on the side - div.id = 'horizontalInserter'; - div.style.left = `${ - isRTL - ? tableRect.right - : tableRect.left - (INSERTER_SIDE_LENGTH - 1 + 2 * INSERTER_BORDER_SIZE) - }px`; - div.style.top = `${tdRect.bottom - 8}px`; - (div.firstChild as HTMLElement).style.width = `${tableRect.right - tableRect.left}px`; - } else { - div.id = 'verticalInserter'; - div.style.left = `${isRTL ? tdRect.left - 8 : tdRect.right - 8}px`; - // tableRect.top is used because the Inserter is always intended to be on top - div.style.top = `${ - tableRect.top - (INSERTER_SIDE_LENGTH - 1 + 2 * INSERTER_BORDER_SIZE) - }px`; - (div.firstChild as HTMLElement).style.height = `${tableRect.bottom - tableRect.top}px`; - } - - (anchorContainer || document.body).appendChild(div); - - const handler = new TableInsertHandler( - div, - td, - table, - isHorizontal, - editor, - onInsert, - getOnMouseOut - ); - - return { div, featureHandler: handler, node: td }; - } - - return null; -} - -class TableInsertHandler implements Disposable { - private onMouseOutEvent: null | ((ev: MouseEvent) => void); - constructor( - private div: HTMLDivElement, - private td: HTMLTableCellElement, - private table: HTMLTableElement, - private isHorizontal: boolean, - private editor: IEditor, - private onInsert: () => void, - getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void - ) { - this.div.addEventListener('click', this.insertTd); - this.onMouseOutEvent = getOnMouseOut(div); - this.div.addEventListener('mouseout', this.onMouseOutEvent); - } - - dispose() { - this.div.removeEventListener('click', this.insertTd); - - if (this.onMouseOutEvent) { - this.div.removeEventListener('mouseout', this.onMouseOutEvent); - } - - this.onMouseOutEvent = null; - } - - private insertTd = () => { - // Get cell coordinates - const columnIndex = this.td.cellIndex; - const row = - this.td.parentElement && isElementOfType(this.td.parentElement, 'tr') - ? this.td.parentElement - : undefined; - const rowIndex = row && row.rowIndex; - - if (row?.cells == undefined || rowIndex == undefined) { - return; - } - - // Insert row or column - formatTableWithContentModel( - this.editor, - 'editTablePlugin', - tableModel => { - this.isHorizontal - ? insertTableRow(tableModel, 'insertBelow') - : insertTableColumn(tableModel, 'insertRight'); - }, // Select cell to make insertion - { - type: 'table', - firstColumn: columnIndex, - firstRow: rowIndex, - lastColumn: columnIndex, - lastRow: rowIndex, - table: this.table, - } - ); - - this.onInsert(); - }; -} - -function getInsertElementData( - isHorizontal: boolean, - isDark: boolean, - isRTL: boolean, - backgroundColor: string -): CreateElementData { - const inserterColor = isDark ? INSERTER_COLOR_DARK_MODE : INSERTER_COLOR; - const outerDivStyle = `position: fixed; width: ${INSERTER_SIDE_LENGTH}px; height: ${INSERTER_SIDE_LENGTH}px; font-size: 16px; color: black; line-height: 8px; vertical-align: middle; text-align: center; cursor: pointer; border: solid ${INSERTER_BORDER_SIZE}px ${inserterColor}; border-radius: 50%; background-color: ${backgroundColor}`; - const leftOrRight = isRTL ? 'right' : 'left'; - const childBaseStyles = `position: absolute; box-sizing: border-box; background-color: ${backgroundColor};`; - const childInfo: CreateElementData = { - tag: 'div', - style: - childBaseStyles + - (isHorizontal - ? `${leftOrRight}: 12px; top: 5px; height: 3px; border-top: 1px solid ${inserterColor}; border-bottom: 1px solid ${inserterColor}; border-right: 1px solid ${inserterColor}; border-left: 0px;` - : `left: 5px; top: 12px; width: 3px; border-left: 1px solid ${inserterColor}; border-right: 1px solid ${inserterColor}; border-bottom: 1px solid ${inserterColor}; border-top: 0px;`), - }; - - return { - tag: 'div', - style: outerDivStyle, - children: [childInfo, '+'], - }; -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts deleted file mode 100644 index d4dd46a94c0..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableMover.ts +++ /dev/null @@ -1,136 +0,0 @@ -import createElement from '../../../pluginUtils/CreateElement/createElement'; -import DragAndDropHelper from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; -import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; -import { isNodeOfType } from 'roosterjs-content-model-dom'; -import type DragAndDropHandler from '../../../pluginUtils/DragAndDrop/DragAndDropHandler'; -import type { IEditor, Rect } from 'roosterjs-content-model-types'; -import type TableEditorFeature from './TableEditorFeature'; - -const TABLE_MOVER_LENGTH = 12; -const TABLE_MOVER_ID = '_Table_Mover'; - -/** - * @internal - * Contains the function to select whole table - * Moving behavior not implemented yet - */ -export default function createTableMover( - table: HTMLTableElement, - editor: IEditor, - isRTL: boolean, - onFinishDragging: (table: HTMLTableElement) => void, - getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void, - contentDiv?: EventTarget | null, - anchorContainer?: HTMLElement -): TableEditorFeature | null { - const rect = normalizeRect(table.getBoundingClientRect()); - - if (!isTableTopVisible(editor, rect, contentDiv as Node)) { - return null; - } - - const zoomScale = editor.getDOMHelper().calculateZoomScale(); - const document = table.ownerDocument; - const createElementData = { - tag: 'div', - style: 'position: fixed; cursor: all-scroll; user-select: none; border: 1px solid #808080', - }; - - const div = createElement(createElementData, document) as HTMLDivElement; - - div.id = TABLE_MOVER_ID; - div.style.width = `${TABLE_MOVER_LENGTH}px`; - div.style.height = `${TABLE_MOVER_LENGTH}px`; - - (anchorContainer || document.body).appendChild(div); - - const context: TableMoverContext = { - table, - zoomScale, - rect, - isRTL, - }; - - setDivPosition(context, div); - - const onDragEnd = (context: TableMoverContext, event: MouseEvent): false => { - if (event.target == div) { - onFinishDragging(context.table); - } - return false; - }; - - const featureHandler = new TableMoverFeature( - div, - context, - setDivPosition, - { - onDragEnd, - }, - context.zoomScale, - getOnMouseOut - ); - - return { div, featureHandler, node: table }; -} - -interface TableMoverContext { - table: HTMLTableElement; - zoomScale: number; - rect: Rect | null; - isRTL: boolean; -} - -interface TableMoverInitValue { - event: MouseEvent; -} - -class TableMoverFeature extends DragAndDropHelper { - private onMouseOut: ((ev: MouseEvent) => void) | null; - - constructor( - private div: HTMLElement, - context: TableMoverContext, - onSubmit: ( - context: TableMoverContext, - trigger: HTMLElement, - container?: HTMLElement - ) => void, - handler: DragAndDropHandler, - zoomScale: number, - getOnMouseOut: (feature: HTMLElement) => (ev: MouseEvent) => void, - forceMobile?: boolean | undefined, - container?: HTMLElement - ) { - super(div, context, onSubmit, handler, zoomScale, forceMobile); - this.onMouseOut = getOnMouseOut(div); - div.addEventListener('mouseout', this.onMouseOut); - } - - dispose(): void { - super.dispose(); - if (this.onMouseOut) { - this.div.removeEventListener('mouseout', this.onMouseOut); - } - this.onMouseOut = null; - } -} - -function setDivPosition(context: TableMoverContext, trigger: HTMLElement) { - const { rect } = context; - if (rect) { - trigger.style.top = `${rect.top - TABLE_MOVER_LENGTH}px`; - trigger.style.left = `${rect.left - TABLE_MOVER_LENGTH - 2}px`; - } -} - -function isTableTopVisible(editor: IEditor, rect: Rect | null, contentDiv?: Node | null): boolean { - const visibleViewport = editor.getVisibleViewport(); - if (isNodeOfType(contentDiv, 'ELEMENT_NODE') && visibleViewport && rect) { - const containerRect = normalizeRect(contentDiv.getBoundingClientRect()); - - return !!containerRect && containerRect.top <= rect.top && visibleViewport.top <= rect.top; - } - - return true; -} diff --git a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts b/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts deleted file mode 100644 index f455c9f83a3..00000000000 --- a/packages-content-model/roosterjs-content-model-plugins/lib/tableEdit/editors/features/TableResizer.ts +++ /dev/null @@ -1,249 +0,0 @@ -import createElement from '../../../pluginUtils/CreateElement/createElement'; -import DragAndDropHelper from '../../../pluginUtils/DragAndDrop/DragAndDropHelper'; -import normalizeRect from '../../../pluginUtils/Rect/normalizeRect'; -import { getFirstSelectedTable, normalizeTable } from 'roosterjs-content-model-core'; -import { isNodeOfType } from 'roosterjs-content-model-dom'; -import type { ContentModelTable, IEditor, Rect } from 'roosterjs-content-model-types'; -import type TableEditFeature from './TableEditorFeature'; - -const TABLE_RESIZER_LENGTH = 12; -const TABLE_RESIZER_ID = '_Table_Resizer'; - -/** - * @internal - */ -export default function createTableResizer( - table: HTMLTableElement, - editor: IEditor, - isRTL: boolean, - onStart: () => void, - onEnd: () => false, - contentDiv?: EventTarget | null, - anchorContainer?: HTMLElement -): TableEditFeature | null { - const rect = normalizeRect(table.getBoundingClientRect()); - - if (!isTableBottomVisible(editor, rect, contentDiv as Node)) { - return null; - } - - const document = table.ownerDocument; - const zoomScale = editor.getDOMHelper().calculateZoomScale(); - const createElementData = { - tag: 'div', - style: `position: fixed; cursor: ${ - isRTL ? 'ne' : 'nw' - }-resize; user-select: none; border: 1px solid #808080`, - }; - - const div = createElement(createElementData, document) as HTMLDivElement; - - div.id = TABLE_RESIZER_ID; - div.style.width = `${TABLE_RESIZER_LENGTH}px`; - div.style.height = `${TABLE_RESIZER_LENGTH}px`; - - (anchorContainer || document.body).appendChild(div); - - const context: DragAndDropContext = { - isRTL, - table, - zoomScale, - onStart, - onEnd, - div, - editor, - contentDiv, - }; - - setDivPosition(context, div); - - const featureHandler = new DragAndDropHelper( - div, - context, - hideResizer, // Resizer is hidden while dragging only - { - onDragStart, - onDragging, - onDragEnd, - }, - zoomScale, - editor.getEnvironment().isMobileOrTablet - ); - - return { node: table, div, featureHandler }; -} - -interface DragAndDropContext { - table: HTMLTableElement; - isRTL: boolean; - zoomScale: number; - onStart: () => void; - onEnd: () => false; - div: HTMLDivElement; - editor: IEditor; - contentDiv?: EventTarget | null; -} - -interface DragAndDropInitValue { - originalRect: DOMRect; - originalHeights: number[]; - originalWidths: number[]; - cmTable: ContentModelTable | undefined; -} - -function onDragStart(context: DragAndDropContext, event: MouseEvent) { - context.onStart(); - - const { editor, table } = context; - - // Get current selection - const selection = editor.getDOMSelection(); - - // Select first cell of the table - editor.setDOMSelection({ - type: 'table', - firstColumn: 0, - firstRow: 0, - lastColumn: 0, - lastRow: 0, - table: table, - }); - - // Get the table content model - const cmTable = getFirstSelectedTable(editor.getContentModelCopy('disconnected'))[0]; - - // Restore selection - editor.setDOMSelection(selection); - - // Save original widths and heights - const heights: number[] = []; - cmTable?.rows.forEach(row => { - heights.push(row.height); - }); - const widths: number[] = []; - cmTable?.widths.forEach(width => { - widths.push(width); - }); - - return { - originalRect: table.getBoundingClientRect(), - cmTable, - originalHeights: heights ?? [], - originalWidths: widths ?? [], - }; -} - -function onDragging( - context: DragAndDropContext, - event: MouseEvent, - initValue: DragAndDropInitValue, - deltaX: number, - deltaY: number -) { - const { isRTL, zoomScale, table } = context; - const { originalRect, originalHeights, originalWidths, cmTable } = initValue; - - const ratioX = 1.0 + (deltaX / originalRect.width) * zoomScale * (isRTL ? -1 : 1); - const ratioY = 1.0 + (deltaY / originalRect.height) * zoomScale; - const shouldResizeX = Math.abs(ratioX - 1.0) > 1e-3; - const shouldResizeY = Math.abs(ratioY - 1.0) > 1e-3; - - // If the width of some external table is fixed, we need to make it resizable - table.style.setProperty('width', null); - // If the height of some external table is fixed, we need to make it resizable - table.style.setProperty('height', null); - - // Assign new widths and heights to the CM table - if (cmTable && cmTable.rows && (shouldResizeX || shouldResizeY)) { - // Modify the CM Table size - for (let i = 0; i < cmTable.rows.length; i++) { - for (let j = 0; j < cmTable.rows[i].cells.length; j++) { - const cell = cmTable.rows[i].cells[j]; - if (cell) { - if (shouldResizeX && i == 0) { - cmTable.widths[j] = (originalWidths[j] ?? 0) * ratioX; - } - if (shouldResizeY && j == 0) { - cmTable.rows[i].height = (originalHeights[i] ?? 0) * ratioY; - } - } - } - } - - // Normalize the table - normalizeTable(cmTable); - - // Writeback CM Table size changes to DOM Table - for (let row = 0; row < table.rows.length; row++) { - const tableRow = table.rows[row]; - - if (tableRow.cells.length == 0) { - // Skip empty row - continue; - } - - for (let col = 0; col < tableRow.cells.length; col++) { - const td = tableRow.cells[col]; - td.style.width = cmTable.widths[col] + 'px'; - td.style.height = cmTable.rows[row].height + 'px'; - } - } - return true; - } else { - return false; - } -} - -function onDragEnd( - context: DragAndDropContext, - event: MouseEvent, - initValue: DragAndDropInitValue | undefined -) { - if ( - isTableBottomVisible( - context.editor, - normalizeRect(context.table.getBoundingClientRect()), - context.contentDiv as Node - ) - ) { - context.div.style.visibility = 'visible'; - setDivPosition(context, context.div); - } - context.onEnd(); - return false; -} - -function setDivPosition(context: DragAndDropContext, trigger: HTMLElement) { - const { table, isRTL } = context; - const rect = normalizeRect(table.getBoundingClientRect()); - - if (rect) { - trigger.style.top = `${rect.bottom}px`; - trigger.style.left = isRTL - ? `${rect.left - TABLE_RESIZER_LENGTH - 2}px` - : `${rect.right}px`; - } -} - -function hideResizer(context: DragAndDropContext, trigger: HTMLElement) { - trigger.style.visibility = 'hidden'; -} - -function isTableBottomVisible( - editor: IEditor, - rect: Rect | null, - contentDiv?: Node | null -): boolean { - const visibleViewport = editor.getVisibleViewport(); - if (isNodeOfType(contentDiv, 'ELEMENT_NODE') && visibleViewport && rect) { - const containerRect = normalizeRect(contentDiv.getBoundingClientRect()); - - return ( - !!containerRect && - containerRect.bottom >= rect.bottom && - visibleViewport.bottom >= rect.bottom - ); - } - - return true; -} diff --git a/packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts index 7076030775d..f72dbb692eb 100644 --- a/packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts +++ b/packages/roosterjs-content-model-core/test/corePlugin/CopyPastePluginTest.ts @@ -20,7 +20,6 @@ import { CopyPastePluginState, PluginWithState, DarkColorHandler, - PasteType, } from 'roosterjs-content-model-types'; import { adjustSelectionForCopyCut, diff --git a/packages/roosterjs-content-model-plugins/test/shortcut/utils/setShortcutIndentationCommandTest.ts b/packages/roosterjs-content-model-plugins/test/shortcut/utils/setShortcutIndentationCommandTest.ts new file mode 100644 index 00000000000..f683e28415e --- /dev/null +++ b/packages/roosterjs-content-model-plugins/test/shortcut/utils/setShortcutIndentationCommandTest.ts @@ -0,0 +1,224 @@ +import * as getFirstSelectedListItem from 'roosterjs-content-model-core/lib/publicApi/selection/collectSelections'; +import * as setModelIndentation from 'roosterjs-content-model-api/lib/modelApi/block/setModelIndentation'; +import { setShortcutIndentationCommand } from '../../../lib/shortcut/utils/setShortcutIndentationCommand'; +import type { + ContentModelDocument, + ContentModelFormatter, + ContentModelListItem, + FormatContentModelContext, + IEditor, +} from 'roosterjs-content-model-types'; + +describe('setShortcutIndentationCommand', () => { + let editor: IEditor; + let formatContentModelSpy: jasmine.Spy; + let context: FormatContentModelContext; + let setModelIndentationSpy: jasmine.Spy; + + beforeEach(() => { + setModelIndentationSpy = spyOn(setModelIndentation, 'setModelIndentation'); + }); + + function runTest( + model: ContentModelDocument, + listItem: ContentModelListItem, + shouldIndent: boolean, + operation: 'indent' | 'outdent' + ) { + context = undefined!; + formatContentModelSpy = jasmine + .createSpy('formatContentModel') + .and.callFake((callback: ContentModelFormatter) => { + context = { + newEntities: [], + newImages: [], + deletedEntities: [], + }; + callback(model, context); + }); + + spyOn(getFirstSelectedListItem, 'getFirstSelectedListItem').and.returnValue(listItem); + + editor = ({ + formatContentModel: formatContentModelSpy, + focus: jasmine.createSpy('focus'), + getPendingFormat: () => null as any, + } as any) as IEditor; + + setShortcutIndentationCommand(editor, operation); + expect(formatContentModelSpy).toHaveBeenCalledTimes(1); + if (shouldIndent) { + expect(setModelIndentationSpy).toHaveBeenCalledTimes(1); + expect(setModelIndentationSpy).toHaveBeenCalledWith(model, operation); + } else { + expect(setModelIndentationSpy).not.toHaveBeenCalled(); + } + } + + it('indent', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + ], + format: {}, + }; + + runTest(model, model.blocks[0] as ContentModelListItem, true, 'indent'); + }); + + it('should not indent', () => { + const model: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + levels: [ + { + listType: 'OL', + format: { + listStyleType: 'decimal', + }, + dataset: { + editingInfo: '{"orderedStyleType":1}', + }, + }, + ], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + format: {}, + }, + ], + format: {}, + }; + runTest(model, model.blocks[1] as ContentModelListItem, false, 'indent'); + }); +}); diff --git a/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts b/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts new file mode 100644 index 00000000000..9a32065e85a --- /dev/null +++ b/packages/roosterjs-content-model-plugins/test/tableEdit/tableResizerTest.ts @@ -0,0 +1,198 @@ +import * as TestHelper from '../TestHelper'; +import { getModelTable } from './tableData'; +import { TableEditPlugin } from '../../lib/tableEdit/TableEditPlugin'; +import { + ContentModelTable, + DOMEventHandlerFunction, + IEditor, + PluginEvent, +} from 'roosterjs-content-model-types'; +import { + Position, + afterTableTest, + beforeTableTest, + getCellRect, + getCurrentTable, + getTableRectSet, + initialize, + moveAndResize, + resizeDirection, +} from './TableEditTestHelper'; + +const TABLE_RESIZER_ID = '_Table_Resizer'; + +xdescribe('Table Resizer tests', () => { + let editor: IEditor; + let plugin: TableEditPlugin; + const TEST_ID = 'resizerTest'; + let handler: Record; + + beforeEach(() => { + const setup = beforeTableTest(TEST_ID); + editor = setup.editor; + plugin = setup.plugin; + handler = setup.handler; + }); + + afterEach(() => { + afterTableTest(editor, plugin, TEST_ID); + }); + + /************************** Resizer removing tests **************************/ + + function removeResizerTest(pluginEvent: PluginEvent) { + let resizer: HTMLElement | null = null; + plugin.initialize(editor); + initialize(editor, getModelTable()); + const cellRect = getCellRect(editor, 0, 0); + handler.mousemove( + new MouseEvent('mousemove', { clientX: cellRect?.right, clientY: cellRect?.bottom }) + ); + resizer = editor.getDocument().getElementById(TABLE_RESIZER_ID); + expect(!!resizer).toBe(true); + plugin.onPluginEvent(pluginEvent); + resizer = editor.getDocument().getElementById(TABLE_RESIZER_ID); + expect(!!resizer).toBe(false); + } + + it('removes table resizer on input', () => { + const pluginEvent: PluginEvent = { + eventType: 'input', + rawEvent: null, + }; + removeResizerTest(pluginEvent); + }); + + it('removes table resizer on content change', () => { + const pluginEvent: PluginEvent = { + eventType: 'contentChanged', + source: null, + }; + removeResizerTest(pluginEvent); + }); + + it('removes table resizer on scrolling', () => { + const pluginEvent: PluginEvent = { + eventType: 'scroll', + scrollContainer: editor.getScrollContainer(), + rawEvent: null, + }; + removeResizerTest(pluginEvent); + }); + + /************************ Resizing table related tests ************************/ + + function resizeWholeTableTest( + table: ContentModelTable, + growth: number, + direction: resizeDirection + ) { + const delta = 20 * growth; + const tableRect = initialize(editor, table); + const mouseStart = { x: tableRect.right + 3, y: tableRect.bottom + 3 }; + let mouseEnd: Position = { x: 0, y: 0 }; + switch (direction) { + case 'horizontal': + mouseEnd = { x: tableRect.right + 3 + delta, y: tableRect.bottom + 3 }; + break; + case 'vertical': + mouseEnd = { x: tableRect.right + 3, y: tableRect.bottom + 3 + delta }; + break; + case 'both': + mouseEnd = { x: tableRect.right + 3 + delta, y: tableRect.bottom + 3 + delta }; + break; + } + const beforeSize = getTableRectSet(getCurrentTable(editor)); + moveAndResize(mouseStart, mouseEnd, 'both', editor, handler, TEST_ID); + const afterSize = getTableRectSet(getCurrentTable(editor)); + compareTableRects(beforeSize, afterSize, growth, direction); + } + + function verifyTableRectChange( + rect1: DOMRect, + rect2: DOMRect, + growth: number, + direction: resizeDirection + ): boolean { + switch (direction) { + case 'horizontal': + return growth > 0 ? rect1.width < rect2.width : rect1.width > rect2.width; + case 'vertical': + return growth > 0 ? rect1.height < rect2.height : rect1.height > rect2.height; + case 'both': + return growth > 0 + ? rect1.width < rect2.width && rect1.height < rect2.height + : rect1.width > rect2.width && rect1.height > rect2.height; + } + } + + function verifyCellRectChange( + rect1: DOMRect, + rect2: DOMRect, + growth: number, + direction: resizeDirection + ): boolean { + switch (direction) { + case 'horizontal': + return rect1.top == rect2.top && rect1.bottom == rect2.bottom && growth > 0 + ? rect1.left <= rect2.left && rect1.right <= rect2.right + : rect1.left >= rect2.left && rect1.right >= rect2.right; + case 'vertical': + return rect1.left == rect2.left && rect1.right == rect2.right && growth > 0 + ? rect1.top <= rect2.top && rect1.bottom <= rect2.bottom + : rect1.top >= rect2.top && rect1.bottom >= rect2.bottom; + case 'both': + return growth > 0 + ? rect1.left <= rect2.left && + rect1.right <= rect2.right && + rect1.top <= rect2.top && + rect1.bottom <= rect2.bottom + : rect1.left >= rect2.left && + rect1.right >= rect2.right && + rect1.top >= rect2.top && + rect1.bottom >= rect2.bottom; + } + } + + function compareTableRects( + beforeTableRectSet1: DOMRect[], + afterTableRectSet2: DOMRect[], + growth: number, + direction: resizeDirection + ) { + expect(beforeTableRectSet1.length).toBe(afterTableRectSet2.length); + beforeTableRectSet1.forEach((rect, i) => { + i == 0 + ? expect( + verifyTableRectChange(rect, afterTableRectSet2[i], growth, direction) + ).toBe(true) // Verify a change to whole table size + : expect(verifyCellRectChange(rect, afterTableRectSet2[i], growth, direction)).toBe( + true // Verify a change to each cell size + ); + }); + } + + it('increases the width of the table', () => { + resizeWholeTableTest(getModelTable(), 1, 'horizontal'); + }); + + it('increases the height of the table', () => { + resizeWholeTableTest(getModelTable(), 1, 'vertical'); + }); + + it('increases the width and height of the table', () => { + resizeWholeTableTest(getModelTable(), 1, 'both'); + }); + + it('decreases the width of the table', () => { + resizeWholeTableTest(getModelTable(), -1, 'horizontal'); + }); + + it('decreases the height of the table', () => { + resizeWholeTableTest(getModelTable(), -1, 'vertical'); + }); + + it('decreases the width and height of the table', () => { + resizeWholeTableTest(getModelTable(), -1, 'both'); + }); +}); From 7a8729f2b7bca370f746eb7cf0a6cdde3c7b8df4 Mon Sep 17 00:00:00 2001 From: Vi Nguyen <74168693+vinguyen12@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:20:30 -0700 Subject: [PATCH 80/80] updated version --- versions.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/versions.json b/versions.json index f9e5564a6fc..9bed8dabc95 100644 --- a/versions.json +++ b/versions.json @@ -1,7 +1,7 @@ { - "legacy": "0.0.0", - "react": "0.0.0", - "main": "0.0.0", - "legacyAdapter": "0.0.0", + "legacy": "8.61.0", + "react": "8.55.0", + "main": "0.28.0", + "legacyAdapter": "0.28.0", "overrides": {} }