Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image Edit Selection change #2687

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getPath } from './getPath';
import { isElementOfType, isNodeOfType, moveChildNodes } from 'roosterjs-content-model-dom';
import type { EditorCore, SnapshotSelection } from 'roosterjs-content-model-types';
import { getPath } from './getPath';

/**
* @internal
Expand Down Expand Up @@ -30,6 +30,7 @@ export function createSnapshotSelection(core: EditorCore): SnapshotSelection {
range: newRange,
isReverted: !!selection.isReverted,
},
selection,
true /*skipSelectionChangedEvent*/
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ export const focus: Focus = core => {
const { api, domHelper, selection } = core;

if (!domHelper.hasFocus() && selection.selection?.type == 'range') {
api.setDOMSelection(core, selection.selection, true /*skipSelectionChangedEvent*/);
api.setDOMSelection(
core,
selection.selection,
undefined /* previousSelection */,
true /*skipSelectionChangedEvent*/
);
}

// fallback, in case editor still have no focus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ const SELECTION_SELECTOR = '*::selection';
/**
* @internal
*/
export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionChangedEvent) => {
export const setDOMSelection: SetDOMSelection = (
core,
selection,
previousSelection,
skipSelectionChangedEvent
) => {
// We are applying a new selection, so we don't need to apply cached selection in DOMEventPlugin.
// Set skipReselectOnFocus to skip this behavior
const skipReselectOnFocus = core.selection.skipReselectOnFocus;
Expand Down Expand Up @@ -150,6 +155,7 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC
const eventData: SelectionChangedEvent = {
eventType: 'selectionChanged',
newSelection: selection,
previousSelection: previousSelection,
};

core.api.triggerEvent(core, eventData, true /*broadcast*/);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,12 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
this.selectImageWithRange(image, rawEvent);
return;
} else if (selection?.type == 'image' && selection.image !== rawEvent.target) {
this.selectBeforeOrAfterElement(editor, selection.image);
this.selectBeforeOrAfterElement(
editor,
selection.image,
undefined /* after */,
selection
);
return;
}

Expand Down Expand Up @@ -523,7 +528,12 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}
}

private selectBeforeOrAfterElement(editor: IEditor, element: HTMLElement, after?: boolean) {
private selectBeforeOrAfterElement(
editor: IEditor,
element: HTMLElement,
after?: boolean,
previousSelection?: DOMSelection
) {
const doc = editor.getDocument();
const parent = element.parentNode;
const index = parent && toArray(parent.childNodes).indexOf(element);
Expand All @@ -539,7 +549,8 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
range: range,
isReverted: false,
},
null /*tableSelection*/
null /*tableSelection*/,
previousSelection
);
}
}
Expand Down Expand Up @@ -686,9 +697,10 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {

private setDOMSelection(
selection: DOMSelection | null,
tableSelection: TableSelectionInfo | null
tableSelection: TableSelectionInfo | null,
previousSelection?: DOMSelection
) {
this.editor?.setDOMSelection(selection);
this.editor?.setDOMSelection(selection, previousSelection);
this.state.tableSelection = tableSelection;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/roosterjs-content-model-core/lib/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ export class Editor implements IEditor {
* Set DOMSelection into editor content.
* @param selection The selection to set
*/
setDOMSelection(selection: DOMSelection | null) {
setDOMSelection(selection: DOMSelection | null, previousSelection?: DOMSelection) {
const core = this.getCore();

core.api.setDOMSelection(core, selection);
core.api.setDOMSelection(core, selection, previousSelection);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { canRegenerateImage } from './utils/canRegenerateImage';
import { checkIfImageWasResized, isASmallImage } from './utils/imageEditUtils';
import { createImageWrapper } from './utils/createImageWrapper';
import { Cropper } from './Cropper/cropperContext';
import { formatInsertPointWithContentModel } from 'roosterjs-content-model-api/lib';
import { getDropAndDragHelpers } from './utils/getDropAndDragHelpers';
import { getHTMLImageOptions } from './utils/getHTMLImageOptions';
import { getSelectedImageMetadata } from './utils/updateImageEditInfo';
Expand All @@ -23,11 +24,14 @@ import type { DragAndDropContext } from './types/DragAndDropContext';
import type { ImageHtmlOptions } from './types/ImageHtmlOptions';
import type { ImageEditOptions } from './types/ImageEditOptions';
import type {
DOMInsertPoint,
DOMSelection,
EditorPlugin,
IEditor,
ImageEditOperation,
ImageEditor,
ImageMetadataFormat,
ImageSelection,
PluginEvent,
} from 'roosterjs-content-model-types';

Expand Down Expand Up @@ -118,15 +122,38 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
* exclusively by another plugin.
* @param event The event to handle:
*/
onPluginEvent(_event: PluginEvent) {}
onPluginEvent(event: PluginEvent) {
if (event.eventType == 'selectionChanged' && this.editor) {
const selection = event.newSelection;
const previousSelection = event.previousSelection;

if (previousSelection?.type == 'image' && selection && this.shadowSpan) {
const previousImage = previousSelection.image;
if (previousImage) {
this.formatImageWhenSelectionChange(this.editor, selection, previousSelection);
}
}
if (selection && selection.type == 'image') {
const image = selection.image;
if (image) {
this.startRotateAndResize(this.editor, image);
}
}
}
}

private startEditing(
editor: IEditor,
image: HTMLImageElement,
apiOperation?: ImageEditOperation
) {
const imageSpan = image.parentElement;
if (!imageSpan || (imageSpan && !isElementOfType(imageSpan, 'span'))) {
if (
!imageSpan ||
(imageSpan && !isElementOfType(imageSpan, 'span')) ||
image.width <= 0 ||
image.height <= 0
) {
return;
}
this.imageEditInfo = getSelectedImageMetadata(editor, image);
Expand Down Expand Up @@ -171,6 +198,7 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
if (this.wrapper && this.selectedImage && this.shadowSpan) {
this.removeImageWrapper();
}

this.startEditing(editor, image, apiOperation);
if (this.selectedImage && this.imageEditInfo && this.wrapper && this.clonedImage) {
this.dndHelpers = [
Expand Down Expand Up @@ -454,6 +482,97 @@ export class ImageEditPlugin implements ImageEditor, EditorPlugin {
}
}

private createInsertPoint(selection: DOMSelection): DOMInsertPoint | null {
if (selection.type === 'image' && selection.image) {
return {
node: selection.image,
offset: selection.image.offsetLeft,
};
} else if (
selection.type === 'range' &&
isNodeOfType(selection.range.startContainer, 'ELEMENT_NODE')
) {
return {
node: selection.range.startContainer,
offset: selection.range.startContainer.offsetLeft,
};
}
return null;
}

private formatImageWhenSelectionChange(
editor: IEditor,
newSelection: DOMSelection,
previousSelection: ImageSelection
) {
const insertPoint = this.createInsertPoint(newSelection);
if (
insertPoint &&
this.lastSrc &&
this.selectedImage &&
this.imageEditInfo &&
this.clonedImage &&
this.shadowSpan
) {
formatInsertPointWithContentModel(
editor,
insertPoint,
(model, _, insertPoint) => {
const selectedSegmentsAndParagraphs = getSelectedSegmentsAndParagraphs(
model,
false
);
if (!selectedSegmentsAndParagraphs[0]) {
return false;
}

const segment = selectedSegmentsAndParagraphs[0][0];
const paragraph = selectedSegmentsAndParagraphs[0][1];

if (paragraph && segment.segmentType == 'Image') {
mutateSegment(paragraph, segment, image => {
if (
this.lastSrc &&
this.selectedImage &&
this.imageEditInfo &&
this.clonedImage
) {
applyChange(
editor,
this.selectedImage,
image,
this.imageEditInfo,
this.lastSrc,
this.wasImageResized || this.isCropMode,
this.clonedImage
);
if (insertPoint) {
insertPoint.marker.isSelected = true;
image.isSelected = false;
image.isSelectedAsImageSelection = false;
}
}
});

return true;
}

return false;
},
{
changeSource: IMAGE_EDIT_CHANGE_SOURCE,
onNodeCreated: () => {
this.cleanInfo();
},
selectionOverride: {
type: 'image',
image: previousSelection.image,
},
}
);
}
}

private removeImageWrapper() {
let image: HTMLImageElement | null = null;
if (this.shadowSpan && this.shadowSpan.parentElement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,10 @@ const cloneImage = (image: HTMLImageElement, editInfo: ImageMetadataFormat) => {
imageClone.removeAttribute('id');
imageClone.style.removeProperty('max-width');
imageClone.style.removeProperty('max-height');
imageClone.style.width = editInfo.widthPx + 'px';
imageClone.style.height = editInfo.heightPx + 'px';
if (editInfo.widthPx && editInfo.heightPx) {
imageClone.style.width = editInfo.widthPx + 'px';
imageClone.style.height = editInfo.heightPx + 'px';
}
}
return imageClone;
};
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export type SetContentModel = (
export type SetDOMSelection = (
core: EditorCore,
selection: DOMSelection | null,
previousSelection?: DOMSelection,
skipSelectionChangedEvent?: boolean
) => void;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export interface IEditor {
* This is the replacement of IEditor.select.
* @param selection The selection to set
*/
setDOMSelection(selection: DOMSelection | null): void;
setDOMSelection(selection: DOMSelection | null, previousSelection?: DOMSelection): void;

/**
* Set a new logical root (most likely due to focus change)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ export interface SelectionChangedEvent extends BasePluginEvent<'selectionChanged
* The new selection after change
*/
newSelection: DOMSelection | null;

/**
* Previous selection before change
*/
previousSelection?: DOMSelection;
}
Loading