diff --git a/packages/roosterjs-content-model/lib/modelApi/common/mergeModel.ts b/packages/roosterjs-content-model/lib/modelApi/common/mergeModel.ts index 6b9c31b86e7..3eabd30018c 100644 --- a/packages/roosterjs-content-model/lib/modelApi/common/mergeModel.ts +++ b/packages/roosterjs-content-model/lib/modelApi/common/mergeModel.ts @@ -25,6 +25,12 @@ export interface MergeModelOption { * @default false */ mergeTable?: boolean; + + /** + * Use this insert position to merge instead of querying selection from target model + * @default undefined + */ + insertPosition?: InsertPosition; } /** @@ -35,7 +41,7 @@ export function mergeModel( source: ContentModelDocument, options?: MergeModelOption ) { - const insertPosition = deleteSelection(target); + const insertPosition = options?.insertPosition ?? deleteSelection(target); if (insertPosition) { for (let i = 0; i < source.blocks.length; i++) { @@ -140,7 +146,7 @@ function mergeTable( } } - normalizeTable(table); + normalizeTable(table, markerPosition.marker.format); applyTableFormat(table, undefined /*newFormat*/, true /*keepCellShade*/); } else { insertBlock(markerPosition, newTable); diff --git a/packages/roosterjs-content-model/lib/modelApi/table/normalizeTable.ts b/packages/roosterjs-content-model/lib/modelApi/table/normalizeTable.ts index 055b11521cc..fa295e65664 100644 --- a/packages/roosterjs-content-model/lib/modelApi/table/normalizeTable.ts +++ b/packages/roosterjs-content-model/lib/modelApi/table/normalizeTable.ts @@ -1,6 +1,7 @@ import { addSegment } from '../common/addSegment'; import { arrayPush } from 'roosterjs-editor-dom'; import { ContentModelSegment } from '../../publicTypes/segment/ContentModelSegment'; +import { ContentModelSegmentFormat } from '../../publicTypes/format/ContentModelSegmentFormat'; import { ContentModelTable } from '../../publicTypes/block/ContentModelTable'; import { ContentModelTableCell } from '../../publicTypes/group/ContentModelTableCell'; import { createBr } from '../creators/createBr'; @@ -10,7 +11,10 @@ const MIN_HEIGHT = 22; /** * @internal */ -export function normalizeTable(table: ContentModelTable) { +export function normalizeTable( + table: ContentModelTable, + defaultSegmentFormat?: ContentModelSegmentFormat +) { // Always collapse border and use border box for table in roosterjs to make layout simpler table.format.borderCollapse = true; table.format.useBorderBox = true; @@ -21,7 +25,7 @@ export function normalizeTable(table: ContentModelTable) { table.cells.forEach((row, rowIndex) => { row.forEach((cell, colIndex) => { if (cell.blocks.length == 0) { - addSegment(cell, createBr()); + addSegment(cell, createBr(defaultSegmentFormat)); } if (rowIndex == 0) { diff --git a/packages/roosterjs-content-model/lib/publicApi/table/insertTable.ts b/packages/roosterjs-content-model/lib/publicApi/table/insertTable.ts index 3c9b793e7dc..1b7b0deaf9c 100644 --- a/packages/roosterjs-content-model/lib/publicApi/table/insertTable.ts +++ b/packages/roosterjs-content-model/lib/publicApi/table/insertTable.ts @@ -2,6 +2,7 @@ import { applyTableFormat } from '../../modelApi/table/applyTableFormat'; import { createContentModelDocument } from '../../modelApi/creators/createContentModelDocument'; import { createSelectionMarker } from '../../modelApi/creators/createSelectionMarker'; import { createTableStructure } from '../../modelApi/table/createTableStructure'; +import { deleteSelection } from '../../modelApi/selection/deleteSelections'; import { formatWithContentModel } from '../utils/formatWithContentModel'; import { IContentModelEditor } from '../../publicTypes/IContentModelEditor'; import { mergeModel } from '../../modelApi/common/mergeModel'; @@ -25,21 +26,29 @@ export default function insertTable( format?: TableMetadataFormat ) { formatWithContentModel(editor, 'insertTable', model => { - const doc = createContentModelDocument(); - const table = createTableStructure(doc, columns, rows); + const insertPosition = deleteSelection(model); - normalizeTable(table); - applyTableFormat(table, format); - mergeModel(model, doc); + if (insertPosition) { + const doc = createContentModelDocument(); + const table = createTableStructure(doc, columns, rows); - const firstBlock = table.cells[0]?.[0]?.blocks[0]; + normalizeTable(table, insertPosition.marker.format); + applyTableFormat(table, format); + mergeModel(model, doc, { + insertPosition, + }); - if (firstBlock?.blockType == 'Paragraph') { - const marker = createSelectionMarker(firstBlock.segments[0]?.format); - firstBlock.segments.unshift(marker); - setSelection(model, marker); - } + const firstBlock = table.cells[0]?.[0]?.blocks[0]; + + if (firstBlock?.blockType == 'Paragraph') { + const marker = createSelectionMarker(firstBlock.segments[0]?.format); + firstBlock.segments.unshift(marker); + setSelection(model, marker); + } - return true; + return true; + } else { + return false; + } }); } diff --git a/packages/roosterjs-content-model/test/modelApi/common/mergeModelTest.ts b/packages/roosterjs-content-model/test/modelApi/common/mergeModelTest.ts index e14e4258a8a..bbd55c17e2c 100644 --- a/packages/roosterjs-content-model/test/modelApi/common/mergeModelTest.ts +++ b/packages/roosterjs-content-model/test/modelApi/common/mergeModelTest.ts @@ -1258,4 +1258,74 @@ describe('mergeModel', () => { ], }); }); + + it('Use customized insert position', () => { + const majorModel = createContentModelDocument(); + const sourceModel = createContentModelDocument(); + const para1 = createParagraph(); + const text1 = createText('test1'); + const text2 = createText('test2'); + const marker1 = createSelectionMarker(); + const marker2 = createSelectionMarker(); + const marker3 = createSelectionMarker(); + + para1.segments.push(marker1, text1, marker2, text2, marker3); + majorModel.blocks.push(para1); + + const newPara = createParagraph(); + const newText = createText('new text'); + + newPara.segments.push(newText); + sourceModel.blocks.push(newPara); + + mergeModel(majorModel, sourceModel, { + insertPosition: { + marker: marker2, + paragraph: para1, + path: [majorModel], + }, + }); + + expect(majorModel).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test1', + format: {}, + }, + { + segmentType: 'Text', + text: 'new text', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + { + segmentType: 'Text', + text: 'test2', + format: {}, + }, + { + segmentType: 'SelectionMarker', + isSelected: true, + format: {}, + }, + ], + format: {}, + }, + ], + }); + }); }); diff --git a/packages/roosterjs-content-model/test/modelApi/table/normalizeTableTest.ts b/packages/roosterjs-content-model/test/modelApi/table/normalizeTableTest.ts index 25ca386b055..ed66fdc747e 100644 --- a/packages/roosterjs-content-model/test/modelApi/table/normalizeTableTest.ts +++ b/packages/roosterjs-content-model/test/modelApi/table/normalizeTableTest.ts @@ -1,3 +1,4 @@ +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { createParagraph } from '../../../lib/modelApi/creators/createParagraph'; import { createTable } from '../../../lib/modelApi/creators/createTable'; import { createTableCell } from '../../../lib/modelApi/creators/createTableCell'; @@ -571,4 +572,53 @@ describe('normalizeTable', () => { dataset: {}, }); }); + + it('Normalize a table with format', () => { + const table = createTable(1); + const format: ContentModelSegmentFormat = { + fontSize: '10px', + }; + + table.cells[0].push(createTableCell(1, 1, false)); + + normalizeTable(table, format); + + expect(table).toEqual({ + blockType: 'Table', + cells: [ + [ + { + blockGroupType: 'TableCell', + spanAbove: false, + spanLeft: false, + isHeader: false, + format: { useBorderBox: true }, + blocks: [ + { + blockType: 'Paragraph', + isImplicit: true, + segments: [ + { + segmentType: 'Br', + format: { + fontSize: '10px', + }, + }, + ], + format: {}, + }, + ], + dataset: {}, + }, + ], + ], + format: { + borderCollapse: true, + useBorderBox: true, + }, + widths: [120], + heights: [22], + dataset: {}, + }); + }); });