Skip to content

Commit

Permalink
#2861 Allow specify pending format for paragraph (#2885)
Browse files Browse the repository at this point in the history
* #2861

* Fix build and test

* add test
  • Loading branch information
JiuqingSong authored Nov 22, 2024
1 parent 9a58a18 commit 261055d
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,19 @@ function handlePendingFormat(
context.newPendingFormat == 'preserve'
? core.format.pendingFormat?.format
: context.newPendingFormat;

if (pendingFormat && selection?.type == 'range' && selection.range.collapsed) {
const pendingParagraphFormat =
context.newPendingParagraphFormat == 'preserve'
? core.format.pendingFormat?.paragraphFormat
: context.newPendingParagraphFormat;

if (
(pendingFormat || pendingParagraphFormat) &&
selection?.type == 'range' &&
selection.range.collapsed
) {
core.format.pendingFormat = {
format: { ...pendingFormat },
format: pendingFormat ? { ...pendingFormat } : undefined,
paragraphFormat: pendingParagraphFormat ? { ...pendingParagraphFormat } : undefined,
insertPoint: {
node: selection.range.startContainer,
offset: selection.range.startOffset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,16 @@ class FormatPlugin implements PluginWithState<FormatPluginState> {
break;

case 'keyDown':
const isAndroidIME = this.editor.getEnvironment().isAndroid && event.rawEvent.key == UnidentifiedKey;
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 &&
(isAndroidIME || 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);
Expand All @@ -145,7 +148,12 @@ class FormatPlugin implements PluginWithState<FormatPluginState> {

private checkAndApplyPendingFormat(data: string | null) {
if (this.editor && data && this.state.pendingFormat) {
applyPendingFormat(this.editor, data, this.state.pendingFormat.format);
applyPendingFormat(
this.editor,
data,
this.state.pendingFormat.format,
this.state.pendingFormat.paragraphFormat
);
this.clearPendingFormat();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import {
createText,
iterateSelections,
mutateBlock,
mutateSegment,
normalizeContentModel,
setParagraphNotImplicit,
} from 'roosterjs-content-model-dom';
import type { ContentModelSegmentFormat, IEditor } from 'roosterjs-content-model-types';
import type {
ContentModelBlockFormat,
ContentModelSegmentFormat,
IEditor,
} from 'roosterjs-content-model-types';

const ANSI_SPACE = '\u0020';
const NON_BREAK_SPACE = '\u00A0';
Expand All @@ -19,7 +24,8 @@ const NON_BREAK_SPACE = '\u00A0';
export function applyPendingFormat(
editor: IEditor,
data: string,
format: ContentModelSegmentFormat
segmentFormat?: ContentModelSegmentFormat,
paragraphFormat?: ContentModelBlockFormat
) {
let isChanged = false;

Expand All @@ -41,24 +47,35 @@ export function applyPendingFormat(

// For space, there can be &#32 (space) or &#160 (&nbsp;), we treat them as the same
if (subStr == data || (data == ANSI_SPACE && subStr == NON_BREAK_SPACE)) {
mutateSegment(block, previousSegment, previousSegment => {
previousSegment.text = text.substring(0, text.length - data.length);
});
if (segmentFormat) {
mutateSegment(block, previousSegment, previousSegment => {
previousSegment.text = text.substring(
0,
text.length - data.length
);
});

mutateSegment(block, marker, (marker, block) => {
marker.format = { ...format };
mutateSegment(block, marker, (marker, block) => {
marker.format = { ...segmentFormat };

const newText = createText(
data == ANSI_SPACE ? NON_BREAK_SPACE : data,
{
...previousSegment.format,
...format,
}
);
const newText = createText(
data == ANSI_SPACE ? NON_BREAK_SPACE : data,
{
...previousSegment.format,
...segmentFormat,
}
);

block.segments.splice(index, 0, newText);
setParagraphNotImplicit(block);
});
block.segments.splice(index, 0, newText);
setParagraphNotImplicit(block);
});
}

if (paragraphFormat) {
const mutableParagraph = mutateBlock(block);

Object.assign(mutableParagraph.format, paragraphFormat);
}

isChanged = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,7 @@ describe('formatContentModel', () => {
it('Has pending format, callback returns true, preserve pending format', () => {
core.format.pendingFormat = {
format: mockedFormat1,
paragraphFormat: mockedFormat2,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
Expand All @@ -671,16 +672,43 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat1,
paragraphFormat: undefined,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
} as any);
});

it('Has pending format, callback returns false, preserve pending format', () => {
it('Has pending format, callback returns true, preserve paragraph pending format', () => {
core.format.pendingFormat = {
format: mockedFormat1,
paragraphFormat: mockedFormat2,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
},
};

formatContentModel(core, (model, context) => {
context.newPendingParagraphFormat = 'preserve';
return true;
});

expect(core.format.pendingFormat).toEqual({
format: undefined,
paragraphFormat: mockedFormat2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
} as any);
});

it('Has pending format, callback returns true, preserve both pending format', () => {
core.format.pendingFormat = {
format: mockedFormat1,
paragraphFormat: mockedFormat2,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
Expand All @@ -689,11 +717,39 @@ describe('formatContentModel', () => {

formatContentModel(core, (model, context) => {
context.newPendingFormat = 'preserve';
context.newPendingParagraphFormat = 'preserve';
return true;
});

expect(core.format.pendingFormat).toEqual({
format: mockedFormat1,
paragraphFormat: mockedFormat2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
} as any);
});

it('Has pending format, callback returns false, preserve both pending format', () => {
core.format.pendingFormat = {
format: mockedFormat1,
paragraphFormat: mockedFormat2,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
},
};

formatContentModel(core, (model, context) => {
context.newPendingFormat = 'preserve';
context.newPendingParagraphFormat = 'preserve';
return false;
});

expect(core.format.pendingFormat).toEqual({
format: mockedFormat1,
paragraphFormat: mockedFormat2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
Expand All @@ -709,6 +765,23 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat2,
paragraphFormat: undefined,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
});
});

it('No pending format, callback returns true, new paragraph format', () => {
formatContentModel(core, (model, context) => {
context.newPendingParagraphFormat = mockedFormat2;
return true;
});

expect(core.format.pendingFormat).toEqual({
format: undefined,
paragraphFormat: mockedFormat2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
Expand All @@ -724,6 +797,7 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat2,
paragraphFormat: undefined,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
Expand All @@ -747,6 +821,7 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat2,
paragraphFormat: undefined,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
Expand All @@ -770,6 +845,31 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat2,
paragraphFormat: undefined,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
});
});

it('Has pending format, callback returns false, new paragraph format', () => {
core.format.pendingFormat = {
paragraphFormat: mockedFormat1,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
},
};

formatContentModel(core, (model, context) => {
context.newPendingParagraphFormat = mockedFormat2;
return false;
});

expect(core.format.pendingFormat).toEqual({
format: undefined,
paragraphFormat: mockedFormat2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ describe('FormatPlugin', () => {
const mockedFormat = {
fontSize: '10px',
};
const mockedFormat2 = {
lineSpace: 2,
};
let applyPendingFormatSpy: jasmine.Spy;

beforeEach(() => {
Expand Down Expand Up @@ -49,6 +52,7 @@ describe('FormatPlugin', () => {

(state.pendingFormat = {
format: mockedFormat,
paragraphFormat: mockedFormat2,
} as any),
plugin.initialize(editor);

Expand All @@ -60,7 +64,12 @@ describe('FormatPlugin', () => {
plugin.dispose();

expect(applyPendingFormatSpy).toHaveBeenCalledTimes(1);
expect(applyPendingFormatSpy).toHaveBeenCalledWith(editor, 'a', mockedFormat);
expect(applyPendingFormatSpy).toHaveBeenCalledWith(
editor,
'a',
mockedFormat,
mockedFormat2
);
expect(state.pendingFormat).toBeNull();
});

Expand Down Expand Up @@ -92,7 +101,7 @@ describe('FormatPlugin', () => {
});
plugin.dispose();

expect(applyPendingFormatSpy).toHaveBeenCalledWith(editor, 'test', mockedFormat);
expect(applyPendingFormatSpy).toHaveBeenCalledWith(editor, 'test', mockedFormat, undefined);
expect(state.pendingFormat).toBeNull();
});

Expand All @@ -111,7 +120,7 @@ describe('FormatPlugin', () => {
const state = plugin.getState();

state.pendingFormat = {
format: mockedFormat,
paragraphFormat: mockedFormat2,
} as any;

plugin.onPluginEvent({
Expand All @@ -122,7 +131,7 @@ describe('FormatPlugin', () => {

expect(applyPendingFormatSpy).not.toHaveBeenCalled();
expect(state.pendingFormat).toEqual({
format: mockedFormat,
paragraphFormat: mockedFormat2,
} as any);
});

Expand Down
Loading

0 comments on commit 261055d

Please sign in to comment.