diff --git a/packages/roosterjs-content-model-core/lib/corePlugin/format/FormatPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/format/FormatPlugin.ts index 973bc685c52..e0417b83b45 100644 --- a/packages/roosterjs-content-model-core/lib/corePlugin/format/FormatPlugin.ts +++ b/packages/roosterjs-content-model-core/lib/corePlugin/format/FormatPlugin.ts @@ -21,6 +21,8 @@ import type { // During IME input, KeyDown event will have "Process" as key const ProcessKey = 'Process'; +// For some Android IME, KeyDown event will have "Unidentified" as key +const UnidentifiedKey = 'Unidentified'; const DefaultStyleKeyMap: Record< keyof (FontFamilyFormat & FontSizeFormat & TextColorFormat & BackgroundColorFormat), keyof CSSStyleDeclaration @@ -116,12 +118,16 @@ class FormatPlugin implements PluginWithState { break; case 'keyDown': + const isAndroidIME = + this.editor.getEnvironment().isAndroid && event.rawEvent.key == UnidentifiedKey; if (isCursorMovingKey(event.rawEvent)) { this.clearPendingFormat(); this.lastCheckedNode = null; } else if ( this.defaultFormatKeys.size > 0 && - (isCharacterValue(event.rawEvent) || event.rawEvent.key == ProcessKey) && + (isAndroidIME || + isCharacterValue(event.rawEvent) || + event.rawEvent.key == ProcessKey) && this.shouldApplyDefaultFormat(this.editor) ) { applyDefaultFormat(this.editor, this.state.defaultFormat); diff --git a/packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts b/packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts index d2aaf74b2be..a4ef7d713aa 100644 --- a/packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts +++ b/packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts @@ -467,6 +467,13 @@ class SelectionPlugin implements PluginWithState { key == Up ? td.childNodes.length : 0, this.editor ); + } else if (!td && (lastCo.row == -1 || lastCo.row <= parsedTable.length)) { + this.selectBeforeOrAfterElement( + this.editor, + table, + change == 1 /* after */, + change != 1 /* setSelectionInNextSiblingElement */ + ); } } else if (key == 'TabLeft' || key == 'TabRight') { const reverse = key == 'TabLeft'; @@ -568,15 +575,30 @@ class SelectionPlugin implements PluginWithState { } } - private selectBeforeOrAfterElement(editor: IEditor, element: HTMLElement, after?: boolean) { + private selectBeforeOrAfterElement( + editor: IEditor, + element: HTMLElement, + after?: boolean, + setSelectionInNextSiblingElement?: boolean + ) { const doc = editor.getDocument(); const parent = element.parentNode; const index = parent && toArray(parent.childNodes).indexOf(element); + let sibling: Element | undefined | null; if (parent && index !== null && index >= 0) { const range = doc.createRange(); - range.setStart(parent, index + (after ? 1 : 0)); - range.collapse(); + if ( + setSelectionInNextSiblingElement && + (sibling = after ? element.nextElementSibling : element.previousElementSibling) && + isNodeOfType(sibling, 'ELEMENT_NODE') + ) { + range.selectNodeContents(sibling); + range.collapse(false /* toStart */); + } else { + range.setStart(parent, index + (after ? 1 : 0)); + range.collapse(); + } this.setDOMSelection( { diff --git a/packages/roosterjs-content-model-core/test/corePlugin/format/FormatPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/format/FormatPluginTest.ts index 8b5b73aa3b6..b947a73cffa 100644 --- a/packages/roosterjs-content-model-core/test/corePlugin/format/FormatPluginTest.ts +++ b/packages/roosterjs-content-model-core/test/corePlugin/format/FormatPluginTest.ts @@ -18,6 +18,7 @@ describe('FormatPlugin', () => { const editor = ({ cacheContentModel: () => {}, isDarkMode: () => false, + getEnvironment: () => ({}), } as any) as IEditor; const plugin = createFormatPlugin({}); plugin.initialize(editor); @@ -101,6 +102,7 @@ describe('FormatPlugin', () => { const editor = ({ createContentModel: () => model, cacheContentModel: () => {}, + getEnvironment: () => ({}), } as any) as IEditor; const plugin = createFormatPlugin({}); @@ -243,6 +245,7 @@ describe('FormatPlugin for default format', () => { cacheContentModel: cacheContentModelSpy, takeSnapshot: takeSnapshotSpy, formatContentModel: formatContentModelSpy, + getEnvironment: () => ({}), } as any) as IEditor; }); diff --git a/packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts index e2584338b5d..d03704d44d7 100644 --- a/packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts +++ b/packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts @@ -1788,6 +1788,205 @@ describe('SelectionPlugin handle table selection', () => { }); }); + it('From Range, Press Down in the last row and move focus outside of table.', () => { + getDOMSelectionSpy.and.returnValue({ + type: 'range', + range: { + startContainer: td3, + startOffset: 0, + endContainer: td3, + endOffset: 0, + commonAncestorContainer: tr2, + }, + isReverted: false, + }); + + requestAnimationFrameSpy.and.callFake((func: Function) => { + getDOMSelectionSpy.and.returnValue({ + type: 'range', + range: { + startContainer: td4, + startOffset: 0, + endContainer: td4, + endOffset: 0, + commonAncestorContainer: tr2, + collapsed: true, + }, + isReverted: false, + }); + + func(); + }); + + const setStartSpy = jasmine.createSpy('setStart'); + const collapseSpy = jasmine.createSpy('collapse'); + const mockedRange = { + setStart: setStartSpy, + collapse: collapseSpy, + } as any; + + createRangeSpy.and.returnValue(mockedRange); + + plugin.onPluginEvent!({ + eventType: 'keyDown', + rawEvent: { + key: 'ArrowDown', + } as any, + }); + + expect(requestAnimationFrameSpy).toHaveBeenCalledTimes(1); + expect(plugin.getState()).toEqual({ + selection: null, + tableSelection: null, + imageSelectionBorderColor: DEFAULT_SELECTION_BORDER_COLOR, + imageSelectionBorderColorDark: DEFAULT_SELECTION_BORDER_COLOR, + tableCellSelectionBackgroundColor: DEFAULT_TABLE_CELL_SELECTION_BACKGROUND_COLOR, + tableCellSelectionBackgroundColorDark: DEFAULT_TABLE_CELL_SELECTION_BACKGROUND_COLOR, + }); + expect(setDOMSelectionSpy).toHaveBeenCalledTimes(1); + expect(setDOMSelectionSpy).toHaveBeenCalledWith({ + type: 'range', + range: mockedRange, + isReverted: false, + }); + expect(setStartSpy).toHaveBeenCalledWith(table.parentElement, 1); + }); + + it('From Range, Press Up in the first row and move focus outside of table, select before table as there are no elements before table.', () => { + getDOMSelectionSpy.and.returnValue({ + type: 'range', + range: { + startContainer: td2, + startOffset: 0, + endContainer: td2, + endOffset: 0, + commonAncestorContainer: tr1, + }, + isReverted: false, + }); + + requestAnimationFrameSpy.and.callFake((func: Function) => { + getDOMSelectionSpy.and.returnValue({ + type: 'range', + range: { + startContainer: td1, + startOffset: 0, + endContainer: td1, + endOffset: 0, + commonAncestorContainer: tr1, + collapsed: true, + }, + isReverted: false, + }); + + func(); + }); + + const setStartSpy = jasmine.createSpy('setStart'); + const collapseSpy = jasmine.createSpy('collapse'); + const mockedRange = { + setStart: setStartSpy, + collapse: collapseSpy, + } as any; + + createRangeSpy.and.returnValue(mockedRange); + + plugin.onPluginEvent!({ + eventType: 'keyDown', + rawEvent: { + key: 'ArrowUp', + } as any, + }); + + expect(requestAnimationFrameSpy).toHaveBeenCalledTimes(1); + expect(plugin.getState()).toEqual({ + selection: null, + tableSelection: null, + imageSelectionBorderColor: DEFAULT_SELECTION_BORDER_COLOR, + imageSelectionBorderColorDark: DEFAULT_SELECTION_BORDER_COLOR, + tableCellSelectionBackgroundColor: DEFAULT_TABLE_CELL_SELECTION_BACKGROUND_COLOR, + tableCellSelectionBackgroundColorDark: DEFAULT_TABLE_CELL_SELECTION_BACKGROUND_COLOR, + }); + expect(setDOMSelectionSpy).toHaveBeenCalledTimes(1); + expect(setDOMSelectionSpy).toHaveBeenCalledWith({ + type: 'range', + range: mockedRange, + isReverted: false, + }); + expect(setStartSpy).toHaveBeenCalledWith(table.parentElement, 0); + }); + + it('From Range, Press Up in the first row and move focus outside of table, select before table as there are no elements before table.', () => { + getDOMSelectionSpy.and.returnValue({ + type: 'range', + range: { + startContainer: td2, + startOffset: 0, + endContainer: td2, + endOffset: 0, + commonAncestorContainer: tr1, + }, + isReverted: false, + }); + + requestAnimationFrameSpy.and.callFake((func: Function) => { + getDOMSelectionSpy.and.returnValue({ + type: 'range', + range: { + startContainer: td1, + startOffset: 0, + endContainer: td1, + endOffset: 0, + commonAncestorContainer: tr1, + collapsed: true, + }, + isReverted: false, + }); + + func(); + }); + + const setStartSpy = jasmine.createSpy('setStart'); + const collapseSpy = jasmine.createSpy('collapse'); + const selectNodeContentsSpy = jasmine.createSpy('selectNodeContents'); + + const mockedRange = { + setStart: setStartSpy, + collapse: collapseSpy, + selectNodeContents: selectNodeContentsSpy, + } as any; + + const div = document.createElement('div'); + table.parentElement?.insertBefore(div, table); + createRangeSpy.and.returnValue(mockedRange); + + plugin.onPluginEvent!({ + eventType: 'keyDown', + rawEvent: { + key: 'ArrowUp', + } as any, + }); + + expect(requestAnimationFrameSpy).toHaveBeenCalledTimes(1); + expect(plugin.getState()).toEqual({ + selection: null, + tableSelection: null, + imageSelectionBorderColor: DEFAULT_SELECTION_BORDER_COLOR, + imageSelectionBorderColorDark: DEFAULT_SELECTION_BORDER_COLOR, + tableCellSelectionBackgroundColor: DEFAULT_TABLE_CELL_SELECTION_BACKGROUND_COLOR, + tableCellSelectionBackgroundColorDark: DEFAULT_TABLE_CELL_SELECTION_BACKGROUND_COLOR, + }); + expect(setDOMSelectionSpy).toHaveBeenCalledTimes(1); + expect(setDOMSelectionSpy).toHaveBeenCalledWith({ + type: 'range', + range: mockedRange, + isReverted: false, + }); + expect(setStartSpy).not.toHaveBeenCalledWith(table.parentElement, 0); + expect(selectNodeContentsSpy).toHaveBeenCalledWith(div); + expect(collapseSpy).toHaveBeenCalledWith(false); + }); + it('From Range, Press Shift+Up', () => { getDOMSelectionSpy.and.returnValue({ type: 'range', diff --git a/packages/roosterjs-content-model-dom/lib/formatHandlers/list/listLevelThreadFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/list/listLevelThreadFormatHandler.ts index 91d7c739f59..5029aea159c 100644 --- a/packages/roosterjs-content-model-dom/lib/formatHandlers/list/listLevelThreadFormatHandler.ts +++ b/packages/roosterjs-content-model-dom/lib/formatHandlers/list/listLevelThreadFormatHandler.ts @@ -13,8 +13,9 @@ export const listLevelThreadFormatHandler: FormatHandler = { const depth = levels.length; if ( - typeof threadItemCounts[depth] === 'number' && - element.start != threadItemCounts[depth] + 1 + element.start == 1 || + (typeof threadItemCounts[depth] === 'number' && + element.start != threadItemCounts[depth] + 1) ) { format.startNumberOverride = element.start; } diff --git a/packages/roosterjs-content-model-dom/test/domToModel/processors/childProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/childProcessorTest.ts index dd4f8301fc7..5074f28b276 100644 --- a/packages/roosterjs-content-model-dom/test/domToModel/processors/childProcessorTest.ts +++ b/packages/roosterjs-content-model-dom/test/domToModel/processors/childProcessorTest.ts @@ -453,7 +453,15 @@ describe('childProcessor', () => { { blockType: 'BlockGroup', blockGroupType: 'ListItem', - levels: [{ listType: 'OL', format: {}, dataset: {} }], + levels: [ + { + listType: 'OL', + format: { + startNumberOverride: 1, + }, + dataset: {}, + }, + ], formatHolder: { segmentType: 'SelectionMarker', isSelected: false, format: {} }, blocks: [ { diff --git a/packages/roosterjs-content-model-dom/test/domToModel/processors/listProcessorTest.ts b/packages/roosterjs-content-model-dom/test/domToModel/processors/listProcessorTest.ts index 63bd2a1de78..db3fd845b16 100644 --- a/packages/roosterjs-content-model-dom/test/domToModel/processors/listProcessorTest.ts +++ b/packages/roosterjs-content-model-dom/test/domToModel/processors/listProcessorTest.ts @@ -56,7 +56,7 @@ describe('listProcessor', () => { childProcessor.and.callFake((group, parent, context) => { expect(context.listFormat.listParent).toBe(group); expect(context.listFormat.levels).toEqual([ - { listType: 'OL', format: {}, dataset: {} }, + { listType: 'OL', format: { startNumberOverride: 1 }, dataset: {} }, ]); expect(context.listFormat.threadItemCounts).toEqual([0]); expect(context.segmentFormat).toEqual({}); @@ -88,7 +88,13 @@ describe('listProcessor', () => { childProcessor.and.callFake((group, parent, context) => { expect(context.listFormat.listParent).toBe(group); expect(context.listFormat.levels).toEqual([ - { listType: 'OL', format: {}, dataset: {} }, + { + listType: 'OL', + format: { + startNumberOverride: 1, + }, + dataset: {}, + }, ]); expect(context.listFormat.threadItemCounts).toEqual([0]); expect(context.segmentFormat).toEqual({ @@ -212,6 +218,7 @@ describe('listProcessor', () => { paddingBottom: '2px', paddingLeft: '2px', listStylePosition: 'inside', + startNumberOverride: 1, }, dataset: {}, }, @@ -254,6 +261,7 @@ describe('listProcessor', () => { marginBottom: '0px', marginLeft: '0px', marginRight: '0px', + startNumberOverride: 1, }, dataset: {}, }, @@ -468,7 +476,7 @@ describe('listProcessor process metadata', () => { expect(context.listFormat.levels).toEqual([ { listType: 'OL', - format: {}, + format: { startNumberOverride: 1 }, dataset: {}, }, ]); @@ -492,7 +500,7 @@ describe('listProcessor process metadata', () => { expect(context.listFormat.levels).toEqual([ { listType: 'OL', - format: {}, + format: { startNumberOverride: 1 }, dataset: { editingInfo: JSON.stringify({ orderedStyleType: 1, unorderedStyleType: 2 }), }, @@ -520,7 +528,7 @@ describe('listProcessor process metadata', () => { expect(context.listFormat.levels).toEqual([ { listType: 'OL', - format: {}, + format: { startNumberOverride: 1 }, dataset: { editingInfo: metadata }, }, ]); @@ -544,7 +552,7 @@ describe('listProcessor process metadata', () => { expect(context.listFormat.levels).toEqual([ { listType: 'OL', - format: {}, + format: { startNumberOverride: 1 }, dataset: { editingInfo: JSON.stringify({ orderedStyleType: NumberingListType.Max, @@ -576,7 +584,7 @@ describe('listProcessor process metadata', () => { { listType: 'OL', dataset: { editingInfo }, - format: {}, + format: { startNumberOverride: 1 }, }, ]); }); @@ -601,6 +609,7 @@ describe('listProcessor process metadata', () => { listType: 'OL', format: { listStyleType: 'decimal', + startNumberOverride: 1, }, dataset: { editingInfo: JSON.stringify({ orderedStyleType: NumberingListType.Max }), @@ -640,6 +649,7 @@ describe('listProcessor process metadata', () => { dataset: {}, format: { direction: 'rtl', + startNumberOverride: 1, }, }, ], diff --git a/packages/roosterjs-content-model-dom/test/endToEndTest.ts b/packages/roosterjs-content-model-dom/test/endToEndTest.ts index be9b8fc582d..d282506eea8 100644 --- a/packages/roosterjs-content-model-dom/test/endToEndTest.ts +++ b/packages/roosterjs-content-model-dom/test/endToEndTest.ts @@ -207,7 +207,9 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { isImplicit: true, }, ], - levels: [{ listType: 'OL', format: {}, dataset: {} }], + levels: [ + { listType: 'OL', format: { startNumberOverride: 1 }, dataset: {} }, + ], formatHolder: { segmentType: 'SelectionMarker', isSelected: false, @@ -228,7 +230,7 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { ], levels: [ { listType: 'OL', format: {}, dataset: {} }, - { listType: 'OL', format: {}, dataset: {} }, + { listType: 'OL', format: { startNumberOverride: 1 }, dataset: {} }, ], formatHolder: { segmentType: 'SelectionMarker', @@ -2139,12 +2141,12 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { levels: [ { listType: 'OL', - format: {}, + format: { startNumberOverride: 1 }, dataset: {}, }, { listType: 'OL', - format: { listStyleType: '"1) "' }, + format: { listStyleType: '"1) "', startNumberOverride: 1 }, dataset: {}, }, ], diff --git a/packages/roosterjs-content-model-dom/test/formatHandlers/list/listLevelThreadFormatHandlerTest.ts b/packages/roosterjs-content-model-dom/test/formatHandlers/list/listLevelThreadFormatHandlerTest.ts index 210d5281342..ee7c4e606b1 100644 --- a/packages/roosterjs-content-model-dom/test/formatHandlers/list/listLevelThreadFormatHandlerTest.ts +++ b/packages/roosterjs-content-model-dom/test/formatHandlers/list/listLevelThreadFormatHandlerTest.ts @@ -22,7 +22,7 @@ describe('listLevelThreadFormatHandler.parse', () => { listLevelThreadFormatHandler.parse(format, ol, context, {}); - expect(format.startNumberOverride).toBeUndefined(); + expect(format.startNumberOverride).toBe(1); expect(context.listFormat).toEqual({ threadItemCounts: [0], levels: [], @@ -38,7 +38,7 @@ describe('listLevelThreadFormatHandler.parse', () => { listLevelThreadFormatHandler.parse(format, ol, context, {}); - expect(format.startNumberOverride).toBeUndefined(); + expect(format.startNumberOverride).toBe(1); expect(context.listFormat).toEqual({ threadItemCounts: [0], levels: [], @@ -91,7 +91,7 @@ describe('listLevelThreadFormatHandler.parse', () => { listLevelThreadFormatHandler.parse(format, ol, context, {}); - expect(format.startNumberOverride).toBeUndefined(); + expect(format.startNumberOverride).toBe(1); expect(context.listFormat).toEqual({ threadItemCounts: [2, 0], levels: [ diff --git a/packages/roosterjs-content-model-plugins/lib/autoFormat/numbers/transformOrdinals.ts b/packages/roosterjs-content-model-plugins/lib/autoFormat/numbers/transformOrdinals.ts index 49da2c899be..7726b5592b3 100644 --- a/packages/roosterjs-content-model-plugins/lib/autoFormat/numbers/transformOrdinals.ts +++ b/packages/roosterjs-content-model-plugins/lib/autoFormat/numbers/transformOrdinals.ts @@ -15,18 +15,22 @@ const getOrdinal = (value: number) => { }; /** - * @internal + * The two last characters of ordinal number (st, nd, rd, th) */ -export function transformOrdinals( +const ORDINAL_LENGTH = 2; + +/** + * @internal + */ export function transformOrdinals( previousSegment: ContentModelText, paragraph: ShallowMutableContentModelParagraph, context: FormatContentModelContext ): boolean { const value = previousSegment.text.split(' ').pop()?.trim(); if (value) { - const ordinal = value.substring(value.length - 2); - const ordinalValue = parseInt(value); - if (ordinalValue && getOrdinal(ordinalValue) === ordinal) { + const ordinal = value.substring(value.length - ORDINAL_LENGTH); // This value is equal st, nd, rd, th + const numericValue = getNumericValue(value); //This is the numeric part. Ex: 10th, numeric value = 10 + if (numericValue && getOrdinal(numericValue) === ordinal) { const ordinalSegment = splitTextSegment( previousSegment, paragraph, @@ -41,3 +45,12 @@ export function transformOrdinals( } return false; } + +function getNumericValue(text: string) { + const number = text.substring(0, text.length - ORDINAL_LENGTH); + const isNumber = /^-?\d+$/.test(number); + if (isNumber) { + return parseInt(text); + } + return null; +} diff --git a/packages/roosterjs-content-model-plugins/lib/markdown/utils/setFormat.ts b/packages/roosterjs-content-model-plugins/lib/markdown/utils/setFormat.ts index 3205c5ce8b0..600f7e369e4 100644 --- a/packages/roosterjs-content-model-plugins/lib/markdown/utils/setFormat.ts +++ b/packages/roosterjs-content-model-plugins/lib/markdown/utils/setFormat.ts @@ -21,7 +21,8 @@ export function setFormat( editor, (_model, previousSegment, paragraph, markerFormat, context) => { if (previousSegment.text[previousSegment.text.length - 1] == character) { - const textBeforeMarker = previousSegment.text.slice(0, -1); + const textSegment = previousSegment.text; + const textBeforeMarker = textSegment.slice(0, -1); context.newPendingFormat = { ...markerFormat, strikethrough: !!markerFormat.strikethrough, @@ -29,11 +30,15 @@ export function setFormat( fontWeight: markerFormat?.fontWeight ? 'bold' : undefined, }; if (textBeforeMarker.indexOf(character) > -1) { - const lastCharIndex = previousSegment.text.length; - const firstCharIndex = previousSegment.text + const lastCharIndex = textSegment.length; + const firstCharIndex = textSegment .substring(0, lastCharIndex - 1) .lastIndexOf(character); - if (lastCharIndex - firstCharIndex > 2) { + + if ( + hasSpaceBeforeFirstCharacter(textSegment, firstCharIndex) && + lastCharIndex - firstCharIndex > 2 + ) { const formattedText = splitTextSegment( previousSegment, paragraph, @@ -61,3 +66,12 @@ export function setFormat( } ); } + +/** + * The markdown should not be trigger inside a word, then check if exist a space before the trigger character + * Should trigger markdown example: _one two_ + * Should not trigger markdown example: one_two_ + */ +function hasSpaceBeforeFirstCharacter(text: string, index: number) { + return !text[index - 1] || text[index - 1].trim().length == 0; +} diff --git a/packages/roosterjs-content-model-plugins/test/autoFormat/numbers/transformOrdinalsTest.ts b/packages/roosterjs-content-model-plugins/test/autoFormat/numbers/transformOrdinalsTest.ts index 74ffb14f773..f5ffeda194d 100644 --- a/packages/roosterjs-content-model-plugins/test/autoFormat/numbers/transformOrdinalsTest.ts +++ b/packages/roosterjs-content-model-plugins/test/autoFormat/numbers/transformOrdinalsTest.ts @@ -127,4 +127,60 @@ describe('transformOrdinals', () => { }; runTest(segment, paragraph, { canUndoByBackspace: true } as any, false); }); + + it('word and th', () => { + const segment: ContentModelText = { + segmentType: 'Text', + text: '12-month', + format: {}, + }; + const paragraph: ContentModelParagraph = { + blockType: 'Paragraph', + segments: [segment], + format: {}, + }; + runTest(segment, paragraph, { canUndoByBackspace: true } as any, false); + }); + + it('word and rd', () => { + const segment: ContentModelText = { + segmentType: 'Text', + text: '13-rd', + format: {}, + }; + const paragraph: ContentModelParagraph = { + blockType: 'Paragraph', + segments: [segment], + format: {}, + }; + runTest(segment, paragraph, { canUndoByBackspace: true } as any, false); + }); + + it('word and nd', () => { + const segment: ContentModelText = { + segmentType: 'Text', + text: '14-second', + format: {}, + }; + const paragraph: ContentModelParagraph = { + blockType: 'Paragraph', + segments: [segment], + format: {}, + }; + runTest(segment, paragraph, { canUndoByBackspace: true } as any, false); + }); + + it('large number', () => { + const segment: ContentModelText = { + segmentType: 'Text', + text: '145th', + format: {}, + }; + const paragraph: ContentModelParagraph = { + blockType: 'Paragraph', + segments: [segment], + format: {}, + }; + runTest(segment, paragraph, { canUndoByBackspace: true } as any, true); + }); }); diff --git a/packages/roosterjs-content-model-plugins/test/markdown/utils/setFormatTest.ts b/packages/roosterjs-content-model-plugins/test/markdown/utils/setFormatTest.ts index fd7ed0add02..21b7dcf1d4b 100644 --- a/packages/roosterjs-content-model-plugins/test/markdown/utils/setFormatTest.ts +++ b/packages/roosterjs-content-model-plugins/test/markdown/utils/setFormatTest.ts @@ -492,4 +492,31 @@ describe('setFormat', () => { runTest(input, '*', { fontWeight: 'bold' }, input, false); }); + + it('should not set italic - one_two_', () => { + const input: ContentModelDocument = { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'one_two_', + format: {}, + }, + { + segmentType: 'SelectionMarker', + format: {}, + isSelected: true, + }, + ], + format: {}, + }, + ], + format: {}, + }; + + runTest(input, '_', { italic: true }, input, false); + }); }); diff --git a/versions.json b/versions.json index 148574cb665..c4a78d6cd0d 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { "react": "9.0.0", - "main": "9.9.0", + "main": "9.9.1", "legacyAdapter": "8.62.1" }