Skip to content

Commit

Permalink
Merge branch 'master' into u/xiameng/expose-split-text-api
Browse files Browse the repository at this point in the history
  • Loading branch information
FrancisMengx authored Jun 26, 2024
2 parents a66addd + a69e21a commit 8a2ed84
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {
} from 'roosterjs-content-model-types';

const MouseLeftButton = 0;
const MouseRightButton = 2;
const Up = 'ArrowUp';
const Down = 'ArrowDown';
const Left = 'ArrowLeft';
Expand Down Expand Up @@ -163,12 +164,17 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
let image: HTMLImageElement | null;

// Image selection
if (selection?.type == 'image' && rawEvent.button == MouseLeftButton) {
if (
selection?.type == 'image' &&
(rawEvent.button == MouseLeftButton ||
(rawEvent.button == MouseRightButton &&
!this.getClickingImage(rawEvent) &&
!this.getContainedTargetImage(rawEvent, selection)))
) {
this.setDOMSelection(null /*domSelection*/, null /*tableSelection*/);
}

if (
rawEvent.button === MouseLeftButton &&
(image =
this.getClickingImage(rawEvent) ??
this.getContainedTargetImage(rawEvent, selection)) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,38 @@ describe('SelectionPlugin handle image selection', () => {
expect(setDOMSelectionSpy).toHaveBeenCalledWith(null);
});

it('Image selection, mouse down with right click to div', () => {
const mockedImage = {
parentNode: { childNodes: [] },
} as any;

mockedImage.parentNode.childNodes.push(mockedImage);

const mockedRange = {
setStart: jasmine.createSpy('setStart'),
collapse: jasmine.createSpy('collapse'),
};

getDOMSelectionSpy.and.returnValue({
type: 'image',
image: mockedImage,
});

createRangeSpy.and.returnValue(mockedRange);

const node = document.createElement('div');
plugin.onPluginEvent!({
eventType: 'mouseDown',
rawEvent: {
target: node,
button: 2,
} as any,
});

expect(setDOMSelectionSpy).toHaveBeenCalledTimes(1);
expect(setDOMSelectionSpy).toHaveBeenCalledWith(null);
});

it('Image selection, mouse down to div, no parent of image', () => {
const mockedImage = {
parentNode: { childNodes: [] },
Expand Down
28 changes: 27 additions & 1 deletion packages/roosterjs-content-model-plugins/lib/edit/EditPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,26 @@ import type {
PluginEvent,
} from 'roosterjs-content-model-types';

/**
* Options to customize the keyboard handling behavior of Edit plugin
*/
export type EditOptions = {
/**
* Whether to handle Tab key in keyboard. @default true
*/
handleTabKey?: boolean;
}

const BACKSPACE_KEY = 8;
const DELETE_KEY = 46;

/**
* @internal
*/
const DefaultOptions: Partial<EditOptions> = {
handleTabKey: true,
};

/**
* Edit plugins helps editor to do editing operation on top of content model.
* This includes:
Expand All @@ -28,6 +45,12 @@ export class EditPlugin implements EditorPlugin {
private selectionAfterDelete: DOMSelection | null = null;
private handleNormalEnter = false;

/**
* @param options An optional parameter that takes in an object of type EditOptions, which includes the following properties:
* handleTabKey: A boolean that enables or disables Tab key handling. Defaults to true.
*/
constructor(private options: EditOptions = DefaultOptions) {}

/**
* Get name of this plugin
*/
Expand Down Expand Up @@ -98,6 +121,7 @@ export class EditPlugin implements EditorPlugin {
willHandleEventExclusively(event: PluginEvent) {
if (
this.editor &&
this.options.handleTabKey &&
event.eventType == 'keyDown' &&
event.rawEvent.key == 'Tab' &&
!event.rawEvent.shiftKey
Expand Down Expand Up @@ -148,7 +172,9 @@ export class EditPlugin implements EditorPlugin {
break;

case 'Tab':
keyboardTab(editor, rawEvent);
if (this.options.handleTabKey) {
keyboardTab(editor, rawEvent);
}
break;
case 'Unidentified':
if (editor.getEnvironment().isAndroid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import type { ImageHtmlOptions } from './types/ImageHtmlOptions';
import type { ImageEditOptions } from './types/ImageEditOptions';
import type {
ContentModelImage,
DOMSelection,
EditorPlugin,
IEditor,
ImageEditOperation,
Expand All @@ -49,6 +48,8 @@ const DefaultOptions: Partial<ImageEditOptions> = {
onSelectState: ['resize', 'rotate'],
};

const MouseRightButton = 2;

/**
* ImageEdit plugin handles the following image editing features:
* - Resize image
Expand Down Expand Up @@ -143,14 +144,26 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
}
}

private isImageSelection(target: Node) {
return (
isNodeOfType(target, 'ELEMENT_NODE') &&
(isElementOfType(target, 'img') ||
!!(
isElementOfType(target, 'span') &&
target.firstElementChild &&
isNodeOfType(target.firstElementChild, 'ELEMENT_NODE') &&
isElementOfType(target.firstElementChild, 'img')
))
);
}

private mouseUpHandler(editor: IEditor, event: MouseUpEvent) {
const selection = editor.getDOMSelection();
if ((selection && selection.type == 'image') || this.isEditing) {
this.applyFormatWithContentModel(
editor,
this.isCropMode,
false /* shouldSelectImage */
);
const shouldSelectImage =
this.isImageSelection(event.rawEvent.target as Node) &&
event.rawEvent.button === MouseRightButton;
this.applyFormatWithContentModel(editor, this.isCropMode, shouldSelectImage);
}
}

Expand Down Expand Up @@ -188,7 +201,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
this.applyFormatWithContentModel(
editor,
this.isCropMode,
isModifierKey(event.rawEvent) && isImageSelection //if it's a modifier key over a image, the image should select the image
(isModifierKey(event.rawEvent) || event.rawEvent.shiftKey) && isImageSelection //if it's a modifier key over a image, the image should select the image
);
}
}
Expand All @@ -197,24 +210,28 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
private applyFormatWithContentModel(
editor: IEditor,
isCropMode: boolean,
shouldSelectImage: boolean
shouldSelectImage: boolean,
isApiOperation?: boolean
) {
let editingImageModel: ContentModelImage | undefined;
const selection = editor.getDOMSelection();
editor.formatContentModel(
model => {
const previousSelectedImage = findEditingImage(model);
const editingImage = getSelectedImage(model);
const previousSelectedImage = isApiOperation
? editingImage
: findEditingImage(model);

let result = false;
if (
shouldSelectImage ||
previousSelectedImage?.image != editingImage?.image ||
previousSelectedImage?.image.dataset.isEditing
previousSelectedImage?.image.dataset.isEditing ||
isApiOperation
) {
const { lastSrc, selectedImage, imageEditInfo, clonedImage } = this;
if (
this.isEditing &&
(this.isEditing || isApiOperation) &&
previousSelectedImage &&
lastSrc &&
selectedImage &&
Expand All @@ -240,12 +257,18 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
}
);
this.cleanInfo();
this.isEditing = false;
this.isCropMode = false;
result = true;
}

if (editingImage && selection?.type == 'image' && !shouldSelectImage) {
this.isEditing = false;
this.isCropMode = false;

if (
editingImage &&
selection?.type == 'image' &&
!shouldSelectImage &&
!isApiOperation
) {
this.isEditing = true;
this.isCropMode = isCropMode;
mutateSegment(editingImage.paragraph, editingImage.image, image => {
Expand All @@ -263,6 +286,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
{
onNodeCreated: (model, node) => {
if (
!isApiOperation &&
editingImageModel &&
editingImageModel == model &&
editingImageModel.dataset.isEditing &&
Expand Down Expand Up @@ -372,8 +396,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
this.options,
this.selectedImage,
this.clonedImage,
this.wrapper,
this.rotators
this.wrapper
);
this.updateRotateHandleState(
editor,
Expand Down Expand Up @@ -516,12 +539,8 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
editor: IEditor,
image: HTMLImageElement,
apiOperation: ImageEditOperation[],
selection: DOMSelection | null,
operation: (imageEditInfo: ImageMetadataFormat) => void
) {
if (this.wrapper && this.selectedImage && this.shadowSpan) {
image = this.removeImageWrapper() ?? image;
}
this.startEditing(editor, image, apiOperation);
if (!this.selectedImage || !this.imageEditInfo || !this.wrapper || !this.clonedImage) {
return;
Expand All @@ -537,7 +556,12 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
this.wrapper
);

this.applyFormatWithContentModel(editor, false /* isCrop */, true /* shouldSelect*/);
this.applyFormatWithContentModel(
editor,
false /* isCrop */,
true /* shouldSelect*/,
true /* isApiOperation */
);
}

private cleanInfo() {
Expand Down Expand Up @@ -583,7 +607,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
}
const image = selection.image;
if (this.editor) {
this.editImage(this.editor, image, ['flip'], selection, imageEditInfo => {
this.editImage(this.editor, image, ['flip'], imageEditInfo => {
const angleRad = imageEditInfo.angleRad || 0;
const isInVerticalPostion =
(angleRad >= Math.PI / 2 && angleRad < (3 * Math.PI) / 4) ||
Expand Down Expand Up @@ -612,7 +636,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
}
const image = selection.image;
if (this.editor) {
this.editImage(this.editor, image, [], selection, imageEditInfo => {
this.editImage(this.editor, image, [], imageEditInfo => {
imageEditInfo.angleRad = (imageEditInfo.angleRad || 0) + angleRad;
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/roosterjs-content-model-plugins/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { TableEditPlugin } from './tableEdit/TableEditPlugin';
export { OnTableEditorCreatedCallback } from './tableEdit/OnTableEditorCreatedCallback';
export { TableEditFeatureName } from './tableEdit/editors/features/TableEditFeatureName';
export { PastePlugin } from './paste/PastePlugin';
export { EditPlugin } from './edit/EditPlugin';
export { EditPlugin, EditOptions } from './edit/EditPlugin';
export { AutoFormatPlugin, AutoFormatOptions } from './autoFormat/AutoFormatPlugin';

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,10 @@ export class TableEditor {
this.editor.takeSnapshot();
}

private onEndTableMove = () => {
this.disposeTableMover();
private onEndTableMove = (disposeHandler: boolean) => {
if (disposeHandler) {
this.disposeTableMover();
}
return this.onFinishEditing();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function createTableMover(
isRTL: boolean,
onFinishDragging: (table: HTMLTableElement) => void,
onStart: () => void,
onEnd: () => void,
onEnd: (disposeHandler: boolean) => void,
contentDiv?: EventTarget | null,
anchorContainer?: HTMLElement,
onTableEditorCreated?: OnTableEditorCreatedCallback,
Expand Down Expand Up @@ -118,7 +118,7 @@ export interface TableMoverContext {
div: HTMLElement;
onFinishDragging: (table: HTMLTableElement) => void;
onStart: () => void;
onEnd: () => void;
onEnd: (disposeHandler: boolean) => void;
disableMovement?: boolean;
}

Expand Down Expand Up @@ -298,9 +298,9 @@ export function onDragEnd(
setTableMoverCursor(editor, false);

if (element == context.div) {
// Table mover was only clicked, select whole table
// Table mover was only clicked, select whole table and do not dismiss the handler element.
selectWholeTable(table);
context.onEnd();
context.onEnd(false /* disposeHandler */);
return true;
} else {
// Check if table was dragged on itself, element is not in editor, or movement is disabled
Expand All @@ -310,7 +310,7 @@ export function onDragEnd(
disableMovement
) {
editor.setDOMSelection(initValue?.initialSelection ?? null);
context.onEnd();
context.onEnd(true /* disposeHandler */);
return false;
}

Expand Down Expand Up @@ -376,7 +376,7 @@ export function onDragEnd(
// No movement, restore initial selection
editor.setDOMSelection(initValue?.initialSelection ?? null);
}
context.onEnd();
context.onEnd(true /* disposeHandler */);
return insertionSuccess;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,23 @@ describe('EditPlugin', () => {
expect(keyboardEnterSpy).not.toHaveBeenCalled();
});

it('Tab, Tab handling not enabled', () => {
plugin = new EditPlugin({ handleTabKey: false });
const rawEvent = { key: 'Tab' } as any;

plugin.initialize(editor);

plugin.onPluginEvent({
eventType: 'keyDown',
rawEvent,
});

expect(keyboardTabSpy).not.toHaveBeenCalled();
expect(keyboardInputSpy).not.toHaveBeenCalled();
expect(keyboardDeleteSpy).not.toHaveBeenCalled();
expect(keyboardEnterSpy).not.toHaveBeenCalled();
});

it('Enter, normal enter not enabled', () => {
plugin = new EditPlugin();
const rawEvent = { which: 13, key: 'Enter' } as any;
Expand Down
Loading

0 comments on commit 8a2ed84

Please sign in to comment.