diff --git a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createText.ts b/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createText.ts index e6d1afc2762..3e4f1f63b0f 100644 --- a/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createText.ts +++ b/packages-content-model/roosterjs-content-model-dom/lib/modelApi/creators/createText.ts @@ -1,14 +1,37 @@ -import { ContentModelSegmentFormat, ContentModelText } from 'roosterjs-content-model-types'; +import { addCode, addLink } from '../common/addDecorators'; +import { + ContentModelCode, + ContentModelLink, + ContentModelSegmentFormat, + ContentModelText, +} from 'roosterjs-content-model-types'; /** * Create a ContentModelText model * @param text Text of this model * @param format @optional The format of this model + * @param link @optional The link decorator + * @param code @option The code decorator */ -export function createText(text: string, format?: ContentModelSegmentFormat): ContentModelText { - return { +export function createText( + text: string, + format?: ContentModelSegmentFormat, + link?: ContentModelLink, + code?: ContentModelCode +): ContentModelText { + const result: ContentModelText = { segmentType: 'Text', text: text, format: format ? { ...format } : {}, }; + + if (link) { + addLink(result, link); + } + + if (code) { + addCode(result, code); + } + + return result; } diff --git a/packages-content-model/roosterjs-content-model-dom/test/modelApi/creators/creatorsTest.ts b/packages-content-model/roosterjs-content-model-dom/test/modelApi/creators/creatorsTest.ts index b1d91fdcb3b..42f484fae4c 100644 --- a/packages-content-model/roosterjs-content-model-dom/test/modelApi/creators/creatorsTest.ts +++ b/packages-content-model/roosterjs-content-model-dom/test/modelApi/creators/creatorsTest.ts @@ -15,6 +15,8 @@ import { createTable } from '../../../lib/modelApi/creators/createTable'; import { createTableCell } from '../../../lib/modelApi/creators/createTableCell'; import { createText } from '../../../lib/modelApi/creators/createText'; import { + ContentModelCode, + ContentModelLink, ContentModelListLevel, ContentModelSegmentFormat, ContentModelTableCellFormat, @@ -188,6 +190,47 @@ describe('Creators', () => { expect(format).toEqual({ a: 1 }); }); + it('createText with decorators', () => { + const format = { a: 1 } as any; + const text = 'test'; + const link: ContentModelLink = { + dataset: {}, + format: { + href: 'test', + }, + }; + const code: ContentModelCode = { + format: { fontFamily: 'test' }, + }; + const result = createText(text, format, link, code); + + expect(result).toEqual({ + segmentType: 'Text', + format: { a: 1 } as any, + text: text, + link, + code, + }); + expect(result.link).not.toBe(link); + expect(result.code).not.toBe(code); + + result.link!.dataset.a = 'b'; + result.link!.format.href = 'test2'; + + expect(link).toEqual({ + dataset: {}, + format: { + href: 'test', + }, + }); + + result.code!.format.fontFamily = 'test2'; + + expect(code).toEqual({ + format: { fontFamily: 'test' }, + }); + }); + it('createTable', () => { const tableModel = createTable(2); diff --git a/packages-content-model/roosterjs-content-model-editor/lib/modelApi/selection/adjustWordSelection.ts b/packages-content-model/roosterjs-content-model-editor/lib/modelApi/selection/adjustWordSelection.ts index 450c4387902..4db23ddadf2 100644 --- a/packages-content-model/roosterjs-content-model-editor/lib/modelApi/selection/adjustWordSelection.ts +++ b/packages-content-model/roosterjs-content-model-editor/lib/modelApi/selection/adjustWordSelection.ts @@ -25,28 +25,34 @@ export function adjustWordSelection( return true; }); - if (markerBlock) { + const tempSegments = markerBlock ? [...markerBlock.segments] : undefined; + + if (tempSegments && markerBlock) { const segments: ContentModelSegment[] = []; - let markerSelectionIndex = markerBlock.segments.indexOf(marker); + let markerSelectionIndex = tempSegments.indexOf(marker); for (let i = markerSelectionIndex - 1; i >= 0; i--) { - const currentSegment = markerBlock.segments[i]; + const currentSegment = tempSegments[i]; if (currentSegment.segmentType == 'Text') { const found = findDelimiter(currentSegment, false /*moveRightward*/); if (found > -1) { if (found == currentSegment.text.length) { break; } - splitTextSegment(markerBlock.segments, currentSegment, i, found); - segments.push(markerBlock.segments[i + 1]); + + splitTextSegment(tempSegments, currentSegment, i, found); + + segments.push(tempSegments[i + 1]); + break; } else { - segments.push(markerBlock.segments[i]); + segments.push(tempSegments[i]); } } else { break; } } - markerSelectionIndex = markerBlock.segments.indexOf(marker); + + markerSelectionIndex = tempSegments.indexOf(marker); segments.push(marker); // Marker is at start of word @@ -54,19 +60,19 @@ export function adjustWordSelection( return segments; } - for (let i = markerSelectionIndex + 1; i < markerBlock.segments.length; i++) { - const currentSegment = markerBlock.segments[i]; + for (let i = markerSelectionIndex + 1; i < tempSegments.length; i++) { + const currentSegment = tempSegments[i]; if (currentSegment.segmentType == 'Text') { const found = findDelimiter(currentSegment, true /*moveRightward*/); if (found > -1) { if (found == 0) { break; } - splitTextSegment(markerBlock.segments, currentSegment, i, found); - segments.push(markerBlock.segments[i]); + splitTextSegment(tempSegments, currentSegment, i, found); + segments.push(tempSegments[i]); break; } else { - segments.push(markerBlock.segments[i]); + segments.push(tempSegments[i]); } } else { break; @@ -78,6 +84,7 @@ export function adjustWordSelection( return [marker]; } + markerBlock.segments = tempSegments; return segments; } else { return [marker]; @@ -123,26 +130,22 @@ function findDelimiter(segment: ContentModelText, moveRightward: boolean): numbe function splitTextSegment( segments: ContentModelSegment[], - textSegment: ContentModelText, + textSegment: Readonly, index: number, found: number ) { const text = textSegment.text; - const newSegment = createText(text.substring(0, found), segments[index].format); - - if (textSegment.code) { - newSegment.code = { - format: { ...textSegment.code.format }, - }; - } - - if (textSegment.link) { - newSegment.link = { - format: { ...textSegment.link.format }, - dataset: { ...textSegment.link.dataset }, - }; - } - - textSegment.text = text.substring(found, text.length); - segments.splice(index, 0, newSegment); + const newSegmentLeft = createText( + text.substring(0, found), + textSegment.format, + textSegment.link, + textSegment.code + ); + const newSegmentRight = createText( + text.substring(found, text.length), + textSegment.format, + textSegment.link, + textSegment.code + ); + segments.splice(index, 1, newSegmentLeft, newSegmentRight); } diff --git a/packages-content-model/roosterjs-content-model-editor/test/modelApi/selection/adjustWordSelectionTest.ts b/packages-content-model/roosterjs-content-model-editor/test/modelApi/selection/adjustWordSelectionTest.ts index 62aa185f584..23517eb65c5 100644 --- a/packages-content-model/roosterjs-content-model-editor/test/modelApi/selection/adjustWordSelectionTest.ts +++ b/packages-content-model/roosterjs-content-model-editor/test/modelApi/selection/adjustWordSelectionTest.ts @@ -1,4 +1,10 @@ import { adjustWordSelection } from '../../../lib/modelApi/selection/adjustWordSelection'; +import { + createContentModelDocument, + createParagraph, + createSelectionMarker, + createText, +} from 'roosterjs-content-model-dom'; import { ContentModelBlock, ContentModelDocument, @@ -885,4 +891,84 @@ describe('adjustWordSelection', () => { ); }); }); + + it('Do not modify segments array if no word is selected: marker before text', () => { + const text = createText('Word1 Word2'); + const marker = createSelectionMarker(); + const paragraph = createParagraph(); + const model = createContentModelDocument(); + + paragraph.segments.push(marker, text); + model.blocks.push(paragraph); + + adjustWordSelection(model, marker); + + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [marker, text], + format: {}, + }, + ], + }); + expect(model.blocks[0]).toBe(paragraph); + expect(paragraph.segments[0]).toBe(marker); + expect(paragraph.segments[1]).toBe(text); + }); + + it('Do not modify segments array if no word is selected: marker after text', () => { + const text = createText('Word1 Word2'); + const marker = createSelectionMarker(); + const paragraph = createParagraph(); + const model = createContentModelDocument(); + + paragraph.segments.push(text, marker); + model.blocks.push(paragraph); + + adjustWordSelection(model, marker); + + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [text, marker], + format: {}, + }, + ], + }); + expect(model.blocks[0]).toBe(paragraph); + expect(paragraph.segments[0]).toBe(text); + expect(paragraph.segments[1]).toBe(marker); + }); + + it('Do not modify segments array if no word is selected: marker between text', () => { + const text1 = createText('Word1 '); + const text2 = createText(' Word2'); + const marker = createSelectionMarker(); + const paragraph = createParagraph(); + const model = createContentModelDocument(); + + paragraph.segments.push(text1, marker, text2); + model.blocks.push(paragraph); + + adjustWordSelection(model, marker); + + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [text1, marker, text2], + format: {}, + }, + ], + }); + expect(model.blocks[0]).toBe(paragraph); + expect(paragraph.segments[0]).toBe(text1); + expect(paragraph.segments[1]).toBe(marker); + expect(paragraph.segments[2]).toBe(text2); + }); });