Skip to content

Commit

Permalink
Merge branch 'master' into u/jisong/2202_2
Browse files Browse the repository at this point in the history
  • Loading branch information
JiuqingSong authored Feb 29, 2024
2 parents 83c1683 + cb6c80b commit 73e0aab
Show file tree
Hide file tree
Showing 11 changed files with 2,067 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ export const formatContentModel: FormatContentModel = (core, formatter, options)
if (shouldAddSnapshot) {
core.undo.isNested = true;

if (core.undo.snapshotsManager.hasNewContent || entityStates) {
core.api.addUndoSnapshot(core, !!canUndoByBackspace);
}
core.api.addUndoSnapshot(core, !!canUndoByBackspace, entityStates);
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ class SnapshotsManagerImpl implements SnapshotsManager {
currentSnapshot.html == snapshot.html &&
!currentSnapshot.entityStates &&
!snapshot.entityStates;
const addSnapshot = !currentSnapshot || shouldAddSnapshot(currentSnapshot, snapshot);

if (this.snapshots.currentIndex < 0 || !currentSnapshot || !isSameSnapshot) {
if (this.snapshots.currentIndex < 0 || addSnapshot) {
this.clearRedo();
this.snapshots.snapshots.push(snapshot);
this.snapshots.currentIndex++;
Expand Down Expand Up @@ -129,3 +130,13 @@ class SnapshotsManagerImpl implements SnapshotsManager {
export function createSnapshotsManager(snapshots?: Snapshots): SnapshotsManager {
return new SnapshotsManagerImpl(snapshots);
}

function shouldAddSnapshot(currentSnapshot: Snapshot, snapshot: Snapshot) {
return (
currentSnapshot.html !== snapshot.html ||
(currentSnapshot.entityStates &&
snapshot.entityStates &&
currentSnapshot.entityStates !== snapshot.entityStates) ||
(!currentSnapshot.entityStates && snapshot.entityStates)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('formatContentModel', () => {
newImages: [],
});
expect(createContentModel).toHaveBeenCalledTimes(1);
expect(addUndoSnapshot).toHaveBeenCalledTimes(1);
expect(addUndoSnapshot).toHaveBeenCalledTimes(2);
expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, undefined);
expect(setContentModel).toHaveBeenCalledTimes(1);
expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined);
Expand Down Expand Up @@ -725,7 +725,7 @@ describe('formatContentModel', () => {

expect(callback).toHaveBeenCalledTimes(1);
expect(addUndoSnapshot).toHaveBeenCalledTimes(2);
expect(addUndoSnapshot).toHaveBeenCalledWith(core, false);
expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, undefined);
expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, undefined);
expect(setContentModel).toHaveBeenCalledTimes(1);
expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined);
Expand All @@ -750,7 +750,7 @@ describe('formatContentModel', () => {

expect(callback).toHaveBeenCalledTimes(1);
expect(addUndoSnapshot).toHaveBeenCalledTimes(2);
expect(addUndoSnapshot).toHaveBeenCalledWith(core, false);
expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, mockedEntityState);
expect(addUndoSnapshot).toHaveBeenCalledWith(core, false, mockedEntityState);
expect(setContentModel).toHaveBeenCalledTimes(1);
expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined);
Expand All @@ -771,7 +771,7 @@ describe('formatContentModel', () => {
formatContentModel(core, callback);

expect(callback).toHaveBeenCalledTimes(1);
expect(addUndoSnapshot).toHaveBeenCalledTimes(1);
expect(addUndoSnapshot).toHaveBeenCalledTimes(2);
expect(addUndoSnapshot).toHaveBeenCalledWith(core, true, undefined);
expect(setContentModel).toHaveBeenCalledTimes(1);
expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined);
Expand Down Expand Up @@ -800,7 +800,7 @@ describe('formatContentModel', () => {
formatContentModel(core, callback);

expect(callback).toHaveBeenCalledTimes(1);
expect(addUndoSnapshot).toHaveBeenCalledTimes(1);
expect(addUndoSnapshot).toHaveBeenCalledTimes(2);
expect(addUndoSnapshot).toHaveBeenCalledWith(core, true, undefined);
expect(setContentModel).toHaveBeenCalledTimes(1);
expect(setContentModel).toHaveBeenCalledWith(core, mockedModel, undefined, undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,171 @@ describe('SnapshotsManagerImpl.addSnapshot', () => {
]);
});

it('Add snapshot with entity state with equal entity states', () => {
const mockedEntityStates = 'ENTITYSTATES' as any;

service.addSnapshot(
{
html: 'test',
isDarkMode: false,
},
false
);

expect(snapshots.snapshots).toEqual([
{
html: 'test',
isDarkMode: false,
},
]);

service.addSnapshot(
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
false
);

expect(snapshots.snapshots).toEqual([
{
html: 'test',
isDarkMode: false,
},
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
]);

service.addSnapshot(
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
false
);

expect(snapshots.snapshots).toEqual([
{
html: 'test',
isDarkMode: false,
},
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
]);
});

it('Add snapshot with entity state with different entity states', () => {
const mockedEntityStates = 'ENTITYSTATES' as any;
const mockedEntityStates2 = 'ENTITYSTATES2' as any;

service.addSnapshot(
{
html: 'test',
isDarkMode: false,
},
false
);

expect(snapshots.snapshots).toEqual([
{
html: 'test',
isDarkMode: false,
},
]);

service.addSnapshot(
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
false
);

expect(snapshots.snapshots).toEqual([
{
html: 'test',
isDarkMode: false,
},
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
]);

service.addSnapshot(
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates2,
},
false
);

expect(snapshots.snapshots).toEqual([
{
html: 'test',
isDarkMode: false,
},
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates2,
},
]);
});

it('Add snapshot without entity state after a snapshot with empty state', () => {
const mockedEntityStates = 'ENTITYSTATES' as any;

service.addSnapshot(
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
false
);

expect(snapshots.snapshots).toEqual([
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
]);

service.addSnapshot(
{
html: 'test',
isDarkMode: false,
},
false
);

expect(snapshots.snapshots).toEqual([
{
html: 'test',
isDarkMode: false,
entityStates: mockedEntityStates,
},
]);
});

it('Has onChanged', () => {
const onChanged = jasmine.createSpy('onChanged');
snapshots.onChanged = onChanged;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { getOperationalBlocks, isBlockGroupOfType } from 'roosterjs-content-model-core';
import { handleTabOnList } from './tabUtils/handleTabOnList';
import { handleTabOnParagraph } from './tabUtils/handleTabOnParagraph';
import { setModelIndentation } from 'roosterjs-content-model-api';
import type {
ContentModelDocument,
Expand All @@ -13,34 +15,34 @@ export function keyboardTab(editor: IEditor, rawEvent: KeyboardEvent) {
const selection = editor.getDOMSelection();

if (selection?.type == 'range') {
editor.takeSnapshot();

editor.formatContentModel((model, _context) => {
return handleTabOnList(model, rawEvent);
});
editor.formatContentModel(
model => {
return handleTab(model, rawEvent);
},
{
apiName: 'handleTabKey',
}
);

return true;
}
}

function isMarkerAtStartOfBlock(listItem: ContentModelListItem) {
return (
listItem.blocks[0].blockType == 'Paragraph' &&
listItem.blocks[0].segments[0].segmentType == 'SelectionMarker'
);
}

function handleTabOnList(model: ContentModelDocument, rawEvent: KeyboardEvent) {
/**
* If multiple blocks are selected, indent or outdent the selected blocks with setModelIndentation.
* If only one block is selected, call handleTabOnParagraph or handleTabOnList to handle the tab key.
*/
function handleTab(model: ContentModelDocument, rawEvent: KeyboardEvent) {
const blocks = getOperationalBlocks<ContentModelListItem>(model, ['ListItem'], ['TableCell']);
const listItem = blocks[0].block;

if (
isBlockGroupOfType<ContentModelListItem>(listItem, 'ListItem') &&
isMarkerAtStartOfBlock(listItem)
) {
const block = blocks.length > 0 ? blocks[0].block : undefined;
if (blocks.length > 1) {
setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent');
rawEvent.preventDefault();
return true;
} else if (block?.blockType === 'Paragraph') {
return handleTabOnParagraph(model, block, rawEvent);
} else if (isBlockGroupOfType<ContentModelListItem>(block, 'ListItem')) {
return handleTabOnList(model, block, rawEvent);
}
return false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { handleTabOnParagraph } from './handleTabOnParagraph';
import { setModelIndentation } from 'roosterjs-content-model-api';
import type { ContentModelDocument, ContentModelListItem } from 'roosterjs-content-model-types';

/**
* 1. When the selection is collapsed and the cursor is at start of a list item, call setModelIndentation.
* 2. Otherwise call handleTabOnParagraph.
* @internal
*/
export function handleTabOnList(
model: ContentModelDocument,
listItem: ContentModelListItem,
rawEvent: KeyboardEvent
) {
const selectedParagraph = findSelectedParagraph(listItem);
if (
!isMarkerAtStartOfBlock(listItem) &&
selectedParagraph.length == 1 &&
selectedParagraph[0].blockType === 'Paragraph'
) {
return handleTabOnParagraph(model, selectedParagraph[0], rawEvent);
} else {
setModelIndentation(model, rawEvent.shiftKey ? 'outdent' : 'indent');
rawEvent.preventDefault();
return true;
}
}

function isMarkerAtStartOfBlock(listItem: ContentModelListItem) {
return (
listItem.blocks[0].blockType == 'Paragraph' &&
listItem.blocks[0].segments[0].segmentType == 'SelectionMarker'
);
}

function findSelectedParagraph(listItem: ContentModelListItem) {
return listItem.blocks.filter(
block =>
block.blockType == 'Paragraph' && block.segments.some(segment => segment.isSelected)
);
}
Loading

0 comments on commit 73e0aab

Please sign in to comment.