diff --git a/demo/scripts/controls/editor/ExperimentalContentModelEditor.ts b/demo/scripts/controls/editor/ExperimentalContentModelEditor.ts index d3480815f52..87af8988362 100644 --- a/demo/scripts/controls/editor/ExperimentalContentModelEditor.ts +++ b/demo/scripts/controls/editor/ExperimentalContentModelEditor.ts @@ -1,19 +1,20 @@ import { Editor } from 'roosterjs-editor-core'; import { EditorOptions, SelectionRangeTypes } from 'roosterjs-editor-types'; import { - getComputedStyles, - Position, - restoreContentWithEntityPlaceholder, -} from 'roosterjs-editor-dom'; -import { - EditorContext, ContentModelDocument, + ContentModelSegmentFormat, contentModelToDom, domToContentModel, DomToModelOption, + EditorContext, IExperimentalContentModelEditor, ModelToDomOption, } from 'roosterjs-content-model'; +import { + getComputedStyles, + Position, + restoreContentWithEntityPlaceholder, +} from 'roosterjs-editor-dom'; /** * !!! This is a temporary interface and will be removed in the future !!! @@ -23,6 +24,7 @@ import { export default class ExperimentalContentModelEditor extends Editor implements IExperimentalContentModelEditor { private getDarkColor: ((lightColor: string) => string) | undefined; + private pendingFormat: ContentModelSegmentFormat | null = null; /** * Creates an instance of ExperimentalContentModelEditor @@ -88,4 +90,20 @@ export default class ExperimentalContentModelEditor extends Editor this.select(range); } } + + /** + * Get current pending format if any. A pending format is a format that user set when selection is collapsed, + * it will be applied when next time user input something + */ + getPendingFormat(): ContentModelSegmentFormat | null { + return this.pendingFormat; + } + + /** + * Set current pending format if any. A pending format is a format that user set when selection is collapsed, + * it will be applied when next time user input something + */ + setPendingFormat(format: ContentModelSegmentFormat | null) { + this.pendingFormat = format; + } } diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/changeFontSize.ts b/packages/roosterjs-content-model/lib/publicApi/segment/changeFontSize.ts index 61f2bd3242a..6cf4eb033db 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/changeFontSize.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/changeFontSize.ts @@ -1,4 +1,4 @@ -import { ContentModelSegment } from '../../publicTypes/segment/ContentModelSegment'; +import { ContentModelSegmentFormat } from '../../publicTypes/format/ContentModelSegmentFormat'; import { FontSizeFormat } from '../../publicTypes/format/formatParts/FontSizeFormat'; import { FormatParser } from '../../publicTypes/context/DomToModelSettings'; import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel'; @@ -26,7 +26,7 @@ export default function changeFontSize( formatSegmentWithContentModel( editor, 'changeFontSize', - segment => changeFontSizeInternal(segment, change), + format => changeFontSizeInternal(format, change), undefined /* segmentHasStyleCallback*/, true /*includingFormatHandler*/, { @@ -45,14 +45,17 @@ const fontSizeHandler: FormatParser = (format, element, context, } }; -function changeFontSizeInternal(segment: ContentModelSegment, change: 'increase' | 'decrease') { - if (segment.format.fontSize) { - let sizeNumber = parseFloat(segment.format.fontSize); +function changeFontSizeInternal( + format: ContentModelSegmentFormat, + change: 'increase' | 'decrease' +) { + if (format.fontSize) { + let sizeNumber = parseFloat(format.fontSize); if (sizeNumber > 0) { const newSize = getNewFontSize(sizeNumber, change == 'increase' ? 1 : -1, FONT_SIZES); - segment.format.fontSize = newSize + 'pt'; + format.fontSize = newSize + 'pt'; } } } diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/setBackgroundColor.ts b/packages/roosterjs-content-model/lib/publicApi/segment/setBackgroundColor.ts index d13256e1af2..488cd2519f9 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/setBackgroundColor.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/setBackgroundColor.ts @@ -10,7 +10,7 @@ export default function setBackgroundColor( editor: IExperimentalContentModelEditor, backgroundColor: string ) { - formatSegmentWithContentModel(editor, 'setBackgroundColor', segment => { - segment.format.backgroundColor = backgroundColor; + formatSegmentWithContentModel(editor, 'setBackgroundColor', format => { + format.backgroundColor = backgroundColor; }); } diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/setFontName.ts b/packages/roosterjs-content-model/lib/publicApi/segment/setFontName.ts index b594915bc7b..c02f31790bc 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/setFontName.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/setFontName.ts @@ -10,8 +10,8 @@ export default function setFontName(editor: IExperimentalContentModelEditor, fon formatSegmentWithContentModel( editor, 'setFontName', - segment => { - segment.format.fontFamily = fontName; + format => { + format.fontFamily = fontName; }, undefined /* segmentHasStyleCallback*/, true /*includingFormatHandler*/ diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/setFontSize.ts b/packages/roosterjs-content-model/lib/publicApi/segment/setFontSize.ts index f746522af50..4d03a3b8aa0 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/setFontSize.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/setFontSize.ts @@ -10,8 +10,8 @@ export default function setFontSize(editor: IExperimentalContentModelEditor, fon formatSegmentWithContentModel( editor, 'setFontSize', - segment => { - segment.format.fontSize = fontSize; + format => { + format.fontSize = fontSize; }, undefined /* segmentHasStyleCallback*/, true /*includingFormatHandler*/ diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/setTextColor.ts b/packages/roosterjs-content-model/lib/publicApi/segment/setTextColor.ts index d0c32cb7dda..c8b5124ee7c 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/setTextColor.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/setTextColor.ts @@ -10,8 +10,8 @@ export default function setTextColor(editor: IExperimentalContentModelEditor, te formatSegmentWithContentModel( editor, 'setTextColor', - segment => { - segment.format.textColor = textColor; + format => { + format.textColor = textColor; }, undefined /* segmentHasStyleCallback*/, true /*includingFormatHandler*/ diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/toggleBold.ts b/packages/roosterjs-content-model/lib/publicApi/segment/toggleBold.ts index 1f0778fdc3b..ba2f7bc3702 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/toggleBold.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/toggleBold.ts @@ -9,14 +9,17 @@ export default function toggleBold(editor: IExperimentalContentModelEditor) { formatSegmentWithContentModel( editor, 'toggleBold', - (segment, isTurningOn) => { - segment.format.fontWeight = isTurningOn ? 'bold' : undefined; + (format, isTurningOn) => { + format.fontWeight = isTurningOn ? 'bold' : undefined; }, - segment => isBold(segment.format.fontWeight) + format => isBold(format.fontWeight) ); } -function isBold(boldStyle?: string): boolean { +/** + * @internal + */ +export function isBold(boldStyle?: string): boolean { return ( !!boldStyle && (boldStyle == 'bold' || boldStyle == 'bolder' || parseInt(boldStyle) >= 600) ); diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/toggleItalic.ts b/packages/roosterjs-content-model/lib/publicApi/segment/toggleItalic.ts index 477cc358fb4..6c5b8ad2b43 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/toggleItalic.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/toggleItalic.ts @@ -9,9 +9,9 @@ export default function toggleItalic(editor: IExperimentalContentModelEditor) { formatSegmentWithContentModel( editor, 'toggleItalic', - (segment, isTurningOn) => { - segment.format.italic = !!isTurningOn; + (format, isTurningOn) => { + format.italic = !!isTurningOn; }, - segment => !!segment.format.italic + format => !!format.italic ); } diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/toggleStrikethrough.ts b/packages/roosterjs-content-model/lib/publicApi/segment/toggleStrikethrough.ts index 2a67e92b9c5..29b4645431b 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/toggleStrikethrough.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/toggleStrikethrough.ts @@ -9,9 +9,9 @@ export default function toggleStrikethrough(editor: IExperimentalContentModelEdi formatSegmentWithContentModel( editor, 'toggleStrikethrough', - (segment, isTurningOn) => { - segment.format.strikethrough = !!isTurningOn; + (format, isTurningOn) => { + format.strikethrough = !!isTurningOn; }, - segment => !!segment.format.strikethrough + format => !!format.strikethrough ); } diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/toggleSubscript.ts b/packages/roosterjs-content-model/lib/publicApi/segment/toggleSubscript.ts index 4f770018ca5..fe138169b7f 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/toggleSubscript.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/toggleSubscript.ts @@ -9,9 +9,9 @@ export default function toggleSubscript(editor: IExperimentalContentModelEditor) formatSegmentWithContentModel( editor, 'toggleSubscript', - (segment, isTurningOn) => { - segment.format.superOrSubScriptSequence = isTurningOn ? 'sub' : ''; + (format, isTurningOn) => { + format.superOrSubScriptSequence = isTurningOn ? 'sub' : ''; }, - segment => segment.format.superOrSubScriptSequence?.split(' ').pop() == 'sub' + format => format.superOrSubScriptSequence?.split(' ').pop() == 'sub' ); } diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/toggleSuperscript.ts b/packages/roosterjs-content-model/lib/publicApi/segment/toggleSuperscript.ts index 3211021f581..093c8c2d79f 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/toggleSuperscript.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/toggleSuperscript.ts @@ -9,9 +9,9 @@ export default function toggleSuperscript(editor: IExperimentalContentModelEdito formatSegmentWithContentModel( editor, 'toggleSuperscript', - (segment, isTurningOn) => { - segment.format.superOrSubScriptSequence = isTurningOn ? 'super' : ''; + (format, isTurningOn) => { + format.superOrSubScriptSequence = isTurningOn ? 'super' : ''; }, - segment => segment.format.superOrSubScriptSequence?.split(' ').pop() == 'super' + format => format.superOrSubScriptSequence?.split(' ').pop() == 'super' ); } diff --git a/packages/roosterjs-content-model/lib/publicApi/segment/toggleUnderline.ts b/packages/roosterjs-content-model/lib/publicApi/segment/toggleUnderline.ts index ebbe7c1f472..640103f8e86 100644 --- a/packages/roosterjs-content-model/lib/publicApi/segment/toggleUnderline.ts +++ b/packages/roosterjs-content-model/lib/publicApi/segment/toggleUnderline.ts @@ -9,9 +9,9 @@ export default function toggleUnderline(editor: IExperimentalContentModelEditor) formatSegmentWithContentModel( editor, 'toggleUnderline', - (segment, isTurningOn) => { - segment.format.underline = !!isTurningOn; + (format, isTurningOn) => { + format.underline = !!isTurningOn; }, - segment => !!segment.format.underline + format => !!format.underline ); } diff --git a/packages/roosterjs-content-model/lib/publicApi/utils/formatSegmentWithContentModel.ts b/packages/roosterjs-content-model/lib/publicApi/utils/formatSegmentWithContentModel.ts index 9972c646d59..d70f8336856 100644 --- a/packages/roosterjs-content-model/lib/publicApi/utils/formatSegmentWithContentModel.ts +++ b/packages/roosterjs-content-model/lib/publicApi/utils/formatSegmentWithContentModel.ts @@ -1,4 +1,4 @@ -import { ContentModelSegment } from '../../publicTypes/segment/ContentModelSegment'; +import { ContentModelSegmentFormat } from '../../publicTypes/format/ContentModelSegmentFormat'; import { formatWithContentModel } from './formatWithContentModel'; import { getSelectedSegments } from '../../modelApi/selection/collectSelections'; import { @@ -12,8 +12,8 @@ import { export function formatSegmentWithContentModel( editor: IExperimentalContentModelEditor, apiName: string, - toggleStyleCallback: (segment: ContentModelSegment, isTuringOn: boolean) => void, - segmentHasStyleCallback?: (segment: ContentModelSegment) => boolean, + toggleStyleCallback: (format: ContentModelSegmentFormat, isTuringOn: boolean) => void, + segmentHasStyleCallback?: (format: ContentModelSegmentFormat) => boolean, includingFormatHolder?: boolean, domToModelOptions?: DomToModelOption ) { @@ -22,17 +22,30 @@ export function formatSegmentWithContentModel( apiName, model => { const segments = getSelectedSegments(model, !!includingFormatHolder); + const pendingFormat = editor.getPendingFormat(); + const formats = pendingFormat + ? [pendingFormat] + : segments.map(segment => segment.format); const isTurningOff = segmentHasStyleCallback - ? segments.every(segmentHasStyleCallback) + ? formats.every(format => segmentHasStyleCallback(format)) : false; - segments.forEach(segment => toggleStyleCallback(segment, !isTurningOff)); + formats.forEach(format => toggleStyleCallback(format, !isTurningOff)); - return ( - segments.length > 1 || - (!!segments[0] && segments[0].segmentType != 'SelectionMarker') - ); + const isCollapsedSelection = + segments.length == 1 && segments[0].segmentType == 'SelectionMarker'; + + if (!pendingFormat && isCollapsedSelection) { + editor.setPendingFormat(segments[0].format); + } + + if (isCollapsedSelection) { + editor.focus(); + return false; + } else { + return formats.length > 0; + } }, domToModelOptions ); diff --git a/packages/roosterjs-content-model/lib/publicTypes/IExperimentalContentModelEditor.ts b/packages/roosterjs-content-model/lib/publicTypes/IExperimentalContentModelEditor.ts index e68b02e443b..4bc05a1d116 100644 --- a/packages/roosterjs-content-model/lib/publicTypes/IExperimentalContentModelEditor.ts +++ b/packages/roosterjs-content-model/lib/publicTypes/IExperimentalContentModelEditor.ts @@ -1,4 +1,5 @@ import { ContentModelDocument } from './group/ContentModelDocument'; +import { ContentModelSegmentFormat } from './format/ContentModelSegmentFormat'; import { EditorContext } from './context/EditorContext'; import { IEditor, SelectionRangeEx } from 'roosterjs-editor-types'; import { @@ -123,4 +124,16 @@ export interface IExperimentalContentModelEditor extends IEditor { * @param option Additional options to customize the behavior of Content Model to DOM conversion */ setContentModel(model: ContentModelDocument, option?: ModelToDomOption): void; + + /** + * Get current pending format if any. A pending format is a format that user set when selection is collapsed, + * it will be applied when next time user input something + */ + getPendingFormat(): ContentModelSegmentFormat | null; + + /** + * Set current pending format if any. A pending format is a format that user set when selection is collapsed, + * it will be applied when next time user input something + */ + setPendingFormat(format: ContentModelSegmentFormat | null): void; } diff --git a/packages/roosterjs-content-model/test/publicApi/block/paragraphTestCommon.ts b/packages/roosterjs-content-model/test/publicApi/block/paragraphTestCommon.ts index ae804e305eb..aaba220c1f6 100644 --- a/packages/roosterjs-content-model/test/publicApi/block/paragraphTestCommon.ts +++ b/packages/roosterjs-content-model/test/publicApi/block/paragraphTestCommon.ts @@ -1,4 +1,5 @@ import { ContentModelDocument } from '../../../lib/publicTypes/group/ContentModelDocument'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { IExperimentalContentModelEditor } from '../../../lib/publicTypes/IExperimentalContentModelEditor'; export function paragraphTestCommon( @@ -23,6 +24,8 @@ export function paragraphTestCommon( addUndoSnapshot, focus: jasmine.createSpy(), setContentModel, + getPendingFormat: (): ContentModelSegmentFormat | null => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; executionCallback(editor); diff --git a/packages/roosterjs-content-model/test/publicApi/block/setIndentationTest.ts b/packages/roosterjs-content-model/test/publicApi/block/setIndentationTest.ts index 5d9c88e0df8..71e1d938343 100644 --- a/packages/roosterjs-content-model/test/publicApi/block/setIndentationTest.ts +++ b/packages/roosterjs-content-model/test/publicApi/block/setIndentationTest.ts @@ -1,6 +1,7 @@ import * as formatWithContentModel from '../../../lib/publicApi/utils/formatWithContentModel'; import * as setModelIndentation from '../../../lib/modelApi/block/setModelIndentation'; import setIndentation from '../../../lib/publicApi/block/setIndentation'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { IExperimentalContentModelEditor } from '../../../lib/publicTypes/IExperimentalContentModelEditor'; describe('setIndentation', () => { @@ -10,6 +11,8 @@ describe('setIndentation', () => { beforeEach(() => { editor = ({ createContentModel: () => fakeModel, + getPendingFormat: (): ContentModelSegmentFormat | null => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; }); diff --git a/packages/roosterjs-content-model/test/publicApi/block/toggleBlockQuoteTest.ts b/packages/roosterjs-content-model/test/publicApi/block/toggleBlockQuoteTest.ts index c1bb6179582..7e3e5aef496 100644 --- a/packages/roosterjs-content-model/test/publicApi/block/toggleBlockQuoteTest.ts +++ b/packages/roosterjs-content-model/test/publicApi/block/toggleBlockQuoteTest.ts @@ -1,6 +1,7 @@ import * as formatWithContentModel from '../../../lib/publicApi/utils/formatWithContentModel'; import * as toggleModelBlockQuote from '../../../lib/modelApi/block/toggleModelBlockQuote'; import toggleBlockQuote from '../../../lib/publicApi/block/toggleBlockQuote'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { IExperimentalContentModelEditor } from '../../../lib/publicTypes/IExperimentalContentModelEditor'; describe('toggleBlockQuote', () => { @@ -10,6 +11,8 @@ describe('toggleBlockQuote', () => { beforeEach(() => { editor = ({ createContentModel: () => fakeModel, + getPendingFormat: (): ContentModelSegmentFormat | null => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; }); diff --git a/packages/roosterjs-content-model/test/publicApi/insert/insertImageTest.ts b/packages/roosterjs-content-model/test/publicApi/insert/insertImageTest.ts index fcbc14dab32..eafe2295e18 100644 --- a/packages/roosterjs-content-model/test/publicApi/insert/insertImageTest.ts +++ b/packages/roosterjs-content-model/test/publicApi/insert/insertImageTest.ts @@ -2,6 +2,7 @@ import * as readFile from 'roosterjs-editor-dom/lib/utils/readFile'; import insertImage from '../../../lib/publicApi/insert/insertImage'; import { addSegment } from '../../../lib/modelApi/common/addSegment'; import { ContentModelDocument } from '../../../lib/publicTypes/group/ContentModelDocument'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { createContentModelDocument } from '../../../lib/modelApi/creators/createContentModelDocument'; import { createSelectionMarker } from '../../../lib/modelApi/creators/createSelectionMarker'; import { IExperimentalContentModelEditor } from '../../../lib/publicTypes/IExperimentalContentModelEditor'; @@ -35,6 +36,8 @@ describe('insertImage', () => { setContentModel, isDisposed: () => false, getDocument: () => document, + getPendingFormat: (): ContentModelSegmentFormat | null => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; executionCallback(editor); diff --git a/packages/roosterjs-content-model/test/publicApi/list/toggleBulletTest.ts b/packages/roosterjs-content-model/test/publicApi/list/toggleBulletTest.ts index def21f3af0a..8f0264a0985 100644 --- a/packages/roosterjs-content-model/test/publicApi/list/toggleBulletTest.ts +++ b/packages/roosterjs-content-model/test/publicApi/list/toggleBulletTest.ts @@ -1,6 +1,7 @@ import * as setListType from '../../../lib/modelApi/list/setListType'; import toggleBullet from '../../../lib/publicApi/list/toggleBullet'; import { ContentModelDocument } from '../../../lib/publicTypes/group/ContentModelDocument'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { IExperimentalContentModelEditor } from '../../../lib/publicTypes/IExperimentalContentModelEditor'; describe('toggleBullet', () => { @@ -24,6 +25,8 @@ describe('toggleBullet', () => { addUndoSnapshot, createContentModel, setContentModel, + getPendingFormat: (): ContentModelSegmentFormat | null => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; spyOn(setListType, 'setListType').and.returnValue(true); diff --git a/packages/roosterjs-content-model/test/publicApi/list/toggleNumberingTest.ts b/packages/roosterjs-content-model/test/publicApi/list/toggleNumberingTest.ts index d1bd9899437..783544c5187 100644 --- a/packages/roosterjs-content-model/test/publicApi/list/toggleNumberingTest.ts +++ b/packages/roosterjs-content-model/test/publicApi/list/toggleNumberingTest.ts @@ -1,6 +1,7 @@ import * as setListType from '../../../lib/modelApi/list/setListType'; import toggleNumbering from '../../../lib/publicApi/list/toggleNumbering'; import { ContentModelDocument } from '../../../lib/publicTypes/group/ContentModelDocument'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { IExperimentalContentModelEditor } from '../../../lib/publicTypes/IExperimentalContentModelEditor'; describe('toggleNumbering', () => { @@ -24,6 +25,8 @@ describe('toggleNumbering', () => { addUndoSnapshot, createContentModel, setContentModel, + getPendingFormat: (): ContentModelSegmentFormat | null => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; spyOn(setListType, 'setListType').and.returnValue(true); diff --git a/packages/roosterjs-content-model/test/publicApi/segment/changeFontSizeTest.ts b/packages/roosterjs-content-model/test/publicApi/segment/changeFontSizeTest.ts index 4f38a5c7c81..b6466f36dc1 100644 --- a/packages/roosterjs-content-model/test/publicApi/segment/changeFontSizeTest.ts +++ b/packages/roosterjs-content-model/test/publicApi/segment/changeFontSizeTest.ts @@ -307,6 +307,8 @@ describe('changeFontSize', () => { addUndoSnapshot, focus: jasmine.createSpy(), setContentModel, + getPendingFormat: () => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; spyOn(getComputedStyles, 'getComputedStyle').and.callFake( diff --git a/packages/roosterjs-content-model/test/publicApi/segment/segmentTestCommon.ts b/packages/roosterjs-content-model/test/publicApi/segment/segmentTestCommon.ts index c332f01d37e..d4d9be74c91 100644 --- a/packages/roosterjs-content-model/test/publicApi/segment/segmentTestCommon.ts +++ b/packages/roosterjs-content-model/test/publicApi/segment/segmentTestCommon.ts @@ -1,4 +1,5 @@ import { ContentModelDocument } from '../../../lib/publicTypes/group/ContentModelDocument'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { IExperimentalContentModelEditor } from '../../../lib/publicTypes/IExperimentalContentModelEditor'; export function segmentTestCommon( @@ -23,6 +24,8 @@ export function segmentTestCommon( addUndoSnapshot, focus: jasmine.createSpy(), setContentModel, + getPendingFormat: (): ContentModelSegmentFormat | null => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; executionCallback(editor); diff --git a/packages/roosterjs-content-model/test/publicApi/utils/formatParagraphWithContentModelTest.ts b/packages/roosterjs-content-model/test/publicApi/utils/formatParagraphWithContentModelTest.ts index 6176705838a..637265c289a 100644 --- a/packages/roosterjs-content-model/test/publicApi/utils/formatParagraphWithContentModelTest.ts +++ b/packages/roosterjs-content-model/test/publicApi/utils/formatParagraphWithContentModelTest.ts @@ -1,4 +1,5 @@ import { ContentModelDocument } from '../../../lib/publicTypes/group/ContentModelDocument'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { createContentModelDocument } from '../../../lib/modelApi/creators/createContentModelDocument'; import { createParagraph } from '../../../lib/modelApi/creators/createParagraph'; import { createText } from '../../../lib/modelApi/creators/createText'; @@ -24,6 +25,8 @@ describe('formatParagraphWithContentModel', () => { addUndoSnapshot, createContentModel: () => model, setContentModel, + getPendingFormat: (): ContentModelSegmentFormat | null => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; }); diff --git a/packages/roosterjs-content-model/test/publicApi/utils/formatSegmentWithContentModelTest.ts b/packages/roosterjs-content-model/test/publicApi/utils/formatSegmentWithContentModelTest.ts index 3915bd9ea90..1bb23de68c0 100644 --- a/packages/roosterjs-content-model/test/publicApi/utils/formatSegmentWithContentModelTest.ts +++ b/packages/roosterjs-content-model/test/publicApi/utils/formatSegmentWithContentModelTest.ts @@ -1,6 +1,8 @@ import { ContentModelDocument } from '../../../lib/publicTypes/group/ContentModelDocument'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { createContentModelDocument } from '../../../lib/modelApi/creators/createContentModelDocument'; import { createParagraph } from '../../../lib/modelApi/creators/createParagraph'; +import { createSelectionMarker } from '../../../lib/modelApi/creators/createSelectionMarker'; import { createText } from '../../../lib/modelApi/creators/createText'; import { formatSegmentWithContentModel } from '../../../lib/publicApi/utils/formatSegmentWithContentModel'; import { IExperimentalContentModelEditor } from '../../../lib/publicTypes/IExperimentalContentModelEditor'; @@ -11,6 +13,8 @@ describe('formatSegmentWithContentModel', () => { let setContentModel: jasmine.Spy; let focus: jasmine.Spy; let model: ContentModelDocument; + let getPendingFormat: jasmine.Spy; + let setPendingFormat: jasmine.Spy; const apiName = 'mockedApi'; @@ -18,29 +22,31 @@ describe('formatSegmentWithContentModel', () => { addUndoSnapshot = jasmine.createSpy('addUndoSnapshot').and.callFake(callback => callback()); setContentModel = jasmine.createSpy('setContentModel'); focus = jasmine.createSpy('focus'); + getPendingFormat = jasmine.createSpy('getPendingFormat'); + setPendingFormat = jasmine.createSpy('setPendingFormat'); editor = ({ focus, addUndoSnapshot, createContentModel: () => model, setContentModel, + getPendingFormat: getPendingFormat, + setPendingFormat: setPendingFormat, } as any) as IExperimentalContentModelEditor; }); it('empty doc', () => { model = createContentModelDocument(); - formatSegmentWithContentModel( - editor, - apiName, - segment => (segment.format.fontFamily = 'test') - ); + formatSegmentWithContentModel(editor, apiName, format => (format.fontFamily = 'test')); expect(model).toEqual({ blockGroupType: 'Document', blocks: [], }); expect(addUndoSnapshot).not.toHaveBeenCalled(); + expect(getPendingFormat).toHaveBeenCalledTimes(1); + expect(setPendingFormat).toHaveBeenCalledTimes(0); }); it('doc with selection', () => { @@ -53,11 +59,7 @@ describe('formatSegmentWithContentModel', () => { para.segments.push(text); model.blocks.push(para); - formatSegmentWithContentModel( - editor, - apiName, - segment => (segment.format.fontFamily = 'test') - ); + formatSegmentWithContentModel(editor, apiName, format => (format.fontFamily = 'test')); expect(model).toEqual({ blockGroupType: 'Document', blocks: [ @@ -78,6 +80,8 @@ describe('formatSegmentWithContentModel', () => { ], }); expect(addUndoSnapshot).toHaveBeenCalledTimes(1); + expect(getPendingFormat).toHaveBeenCalledTimes(1); + expect(setPendingFormat).toHaveBeenCalledTimes(0); }); it('doc with selection, all segments are already in expected state', () => { @@ -93,7 +97,7 @@ describe('formatSegmentWithContentModel', () => { const segmentHasStyleCallback = jasmine.createSpy().and.returnValue(true); const toggleStyleCallback = jasmine .createSpy() - .and.callFake(segment => (segment.format.fontFamily = 'test')); + .and.callFake(format => (format.fontFamily = 'test')); formatSegmentWithContentModel( editor, @@ -123,9 +127,11 @@ describe('formatSegmentWithContentModel', () => { }); expect(addUndoSnapshot).toHaveBeenCalledTimes(1); expect(segmentHasStyleCallback).toHaveBeenCalledTimes(1); - expect(segmentHasStyleCallback).toHaveBeenCalledWith(text, 0, [text]); + expect(segmentHasStyleCallback).toHaveBeenCalledWith(text.format); expect(toggleStyleCallback).toHaveBeenCalledTimes(1); - expect(toggleStyleCallback).toHaveBeenCalledWith(text, false); + expect(toggleStyleCallback).toHaveBeenCalledWith(text.format, false); + expect(getPendingFormat).toHaveBeenCalledTimes(1); + expect(setPendingFormat).toHaveBeenCalledTimes(0); }); it('doc with selection, some segments are in expected state', () => { @@ -145,10 +151,10 @@ describe('formatSegmentWithContentModel', () => { const segmentHasStyleCallback = jasmine .createSpy() - .and.callFake(segment => segment == text1); + .and.callFake(format => format == text1.format); const toggleStyleCallback = jasmine .createSpy() - .and.callFake(segment => (segment.format.fontFamily = 'test')); + .and.callFake(format => (format.fontFamily = 'test')); formatSegmentWithContentModel( editor, @@ -191,10 +197,92 @@ describe('formatSegmentWithContentModel', () => { }); expect(addUndoSnapshot).toHaveBeenCalledTimes(1); expect(segmentHasStyleCallback).toHaveBeenCalledTimes(2); - expect(segmentHasStyleCallback).toHaveBeenCalledWith(text1, 0, [text1, text3]); - expect(segmentHasStyleCallback).toHaveBeenCalledWith(text3, 1, [text1, text3]); + expect(segmentHasStyleCallback).toHaveBeenCalledWith(text1.format); + expect(segmentHasStyleCallback).toHaveBeenCalledWith(text3.format); expect(toggleStyleCallback).toHaveBeenCalledTimes(2); - expect(toggleStyleCallback).toHaveBeenCalledWith(text1, true); - expect(toggleStyleCallback).toHaveBeenCalledWith(text3, true); + expect(toggleStyleCallback).toHaveBeenCalledWith(text1.format, true); + expect(toggleStyleCallback).toHaveBeenCalledWith(text3.format, true); + expect(getPendingFormat).toHaveBeenCalledTimes(1); + expect(setPendingFormat).toHaveBeenCalledTimes(0); + }); + + it('Collapsed selection', () => { + model = createContentModelDocument(); + const para = createParagraph(); + const format: ContentModelSegmentFormat = { + fontSize: '10px', + }; + const marker = createSelectionMarker(format); + + para.segments.push(marker); + model.blocks.push(para); + + formatSegmentWithContentModel(editor, apiName, format => (format.fontFamily = 'test')); + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + format: {}, + segments: [ + { + segmentType: 'SelectionMarker', + isSelected: true, + format: { + fontSize: '10px', + fontFamily: 'test', + }, + }, + ], + }, + ], + }); + expect(addUndoSnapshot).toHaveBeenCalledTimes(0); + expect(getPendingFormat).toHaveBeenCalledTimes(1); + expect(setPendingFormat).toHaveBeenCalledTimes(1); + expect(setPendingFormat).toHaveBeenCalledWith({ + fontSize: '10px', + fontFamily: 'test', + }); + }); + + it('With pending format', () => { + model = createContentModelDocument(); + const para = createParagraph(); + const text = createText('test'); + + para.segments.push(text); + model.blocks.push(para); + + const pendingFormat: ContentModelSegmentFormat = { + fontSize: '10px', + }; + + getPendingFormat.and.returnValue(pendingFormat); + + formatSegmentWithContentModel(editor, apiName, format => (format.fontFamily = 'test')); + expect(model).toEqual({ + blockGroupType: 'Document', + blocks: [ + { + blockType: 'Paragraph', + format: {}, + segments: [ + { + segmentType: 'Text', + text: 'test', + format: {}, + }, + ], + }, + ], + }); + expect(addUndoSnapshot).toHaveBeenCalledTimes(1); + expect(pendingFormat).toEqual({ + fontSize: '10px', + fontFamily: 'test', + }); + expect(getPendingFormat).toHaveBeenCalledTimes(1); + expect(setPendingFormat).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/roosterjs-content-model/test/publicApi/utils/formatWithContentModelTest.ts b/packages/roosterjs-content-model/test/publicApi/utils/formatWithContentModelTest.ts index 4e9c5ab09bc..1ce41cfb659 100644 --- a/packages/roosterjs-content-model/test/publicApi/utils/formatWithContentModelTest.ts +++ b/packages/roosterjs-content-model/test/publicApi/utils/formatWithContentModelTest.ts @@ -1,5 +1,6 @@ import { ChangeSource } from 'roosterjs-editor-types'; import { ContentModelDocument } from '../../../lib/publicTypes/group/ContentModelDocument'; +import { ContentModelSegmentFormat } from '../../../lib/publicTypes/format/ContentModelSegmentFormat'; import { formatWithContentModel } from '../../../lib/publicApi/utils/formatWithContentModel'; import { IExperimentalContentModelEditor } from '../../../lib/publicTypes/IExperimentalContentModelEditor'; @@ -26,6 +27,8 @@ describe('formatWithContentModel', () => { addUndoSnapshot, createContentModel, setContentModel, + getPendingFormat: (): ContentModelSegmentFormat | null => null, + setPendingFormat: () => {}, } as any) as IExperimentalContentModelEditor; });