diff --git a/packages/roosterjs-content-model-dom/lib/domUtils/selection/pruneUnselectedModel.ts b/packages/roosterjs-content-model-dom/lib/domUtils/selection/pruneUnselectedModel.ts index 8d32f9474b29..7ebe3f4200f4 100644 --- a/packages/roosterjs-content-model-dom/lib/domUtils/selection/pruneUnselectedModel.ts +++ b/packages/roosterjs-content-model-dom/lib/domUtils/selection/pruneUnselectedModel.ts @@ -47,12 +47,18 @@ function pruneUnselectedModelInternal( case 'Paragraph': const newSegments: ContentModelSegment[] = []; for (const segment of block.segments) { + const isSelectedOrUndeletable = + segment.isSelected || segment.link?.format.undeletable; + if (segment.segmentType == 'General') { pruneUnselectedModel(segment); - if (segment.blocks.length > 0 || segment.isSelected) { + if (segment.blocks.length > 0 || isSelectedOrUndeletable) { newSegments.push(segment); } - } else if (segment.isSelected && segment.segmentType != 'SelectionMarker') { + } else if ( + isSelectedOrUndeletable && + segment.segmentType != 'SelectionMarker' + ) { newSegments.push(segment); } } diff --git a/packages/roosterjs-content-model-dom/test/domUtils/selection/pruneUnselectedModelTest.ts b/packages/roosterjs-content-model-dom/test/domUtils/selection/pruneUnselectedModelTest.ts index f1370d81ea55..25a268736929 100644 --- a/packages/roosterjs-content-model-dom/test/domUtils/selection/pruneUnselectedModelTest.ts +++ b/packages/roosterjs-content-model-dom/test/domUtils/selection/pruneUnselectedModelTest.ts @@ -1474,4 +1474,212 @@ describe('pruneUnselectedModel', () => { ], }); }); + + it('retains unselected segment with undeletable link', () => { + const group = createContentModelDocument(); + const para = createParagraph(); + const text1 = createText('undeletable link'); + const text2 = createText('normal text'); + + text1.link = { + format: { href: 'http://example.com', undeletable: true }, + dataset: {}, + }; + + para.segments.push(text1); + para.segments.push(text2); + group.blocks.push(para); + + pruneUnselectedModel(group); + + expect(group).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'undeletable link', + format: {}, + link: { + format: { href: 'http://example.com', undeletable: true }, + dataset: {}, + }, + }, + ], + format: {}, + isImplicit: true, + }, + ], + }); + }); + + it('retains both selected segment and unselected undeletable link segment', () => { + const group = createContentModelDocument(); + const para = createParagraph(); + const text1 = createText('selected'); + const text2 = createText('undeletable link'); + const text3 = createText('normal'); + + text1.isSelected = true; + text2.link = { + format: { href: 'http://example.com', undeletable: true }, + dataset: {}, + }; + + para.segments.push(text1); + para.segments.push(text2); + para.segments.push(text3); + group.blocks.push(para); + + pruneUnselectedModel(group); + + expect(group).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'selected', + format: {}, + isSelected: true, + }, + { + segmentType: 'Text', + text: 'undeletable link', + format: {}, + link: { + format: { href: 'http://example.com', undeletable: true }, + dataset: {}, + }, + }, + ], + format: {}, + isImplicit: true, + }, + ], + }); + }); + + it('does not retain unselected segment with link that is not undeletable', () => { + const group = createContentModelDocument(); + const para = createParagraph(); + const text1 = createText('selected'); + const text2 = createText('deletable link'); + + text1.isSelected = true; + text2.link = { + format: { href: 'http://example.com' }, + dataset: {}, + }; + + para.segments.push(text1); + para.segments.push(text2); + group.blocks.push(para); + + pruneUnselectedModel(group); + + expect(group).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'selected', + format: {}, + isSelected: true, + }, + ], + format: {}, + isImplicit: true, + }, + ], + }); + }); + + it('retains unselected general segment with undeletable link', () => { + const group = createContentModelDocument(); + const para = createParagraph(); + const generalSpan = createGeneralSegment(document.createElement('span')); + const text = createText('normal text'); + + generalSpan.link = { + format: { href: 'http://example.com', undeletable: true }, + dataset: {}, + }; + + para.segments.push(generalSpan); + para.segments.push(text); + group.blocks.push(para); + + pruneUnselectedModel(group); + + expect(group).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + blockType: 'BlockGroup', + blockGroupType: 'General', + segmentType: 'General', + format: {}, + blocks: [], + element: jasmine.anything(), + link: { + format: { href: 'http://example.com', undeletable: true }, + dataset: {}, + }, + }, + ], + format: {}, + isImplicit: true, + }, + ], + }); + }); + + it('does not retain selection marker with undeletable link', () => { + const group = createContentModelDocument(); + const para = createParagraph(); + const text = createText('selected'); + const marker = createSelectionMarker(); + + text.isSelected = true; + marker.link = { + format: { href: 'http://example.com', undeletable: true }, + dataset: {}, + }; + + para.segments.push(text); + para.segments.push(marker); + group.blocks.push(para); + + pruneUnselectedModel(group); + + expect(group).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'selected', + format: {}, + isSelected: true, + }, + ], + format: {}, + isImplicit: true, + }, + ], + }); + }); });