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

Fix selection with ctrl+a #2556

Merged
merged 2 commits into from
Apr 4, 2024
Merged
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
Expand Up @@ -59,9 +59,8 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {

this.isSafari = !!env.isSafari;
this.isMac = !!env.isMac;

document.addEventListener('selectionchange', this.onSelectionChange);
if (this.isSafari) {
document.addEventListener('selectionchange', this.onSelectionChangeSafari);
this.disposer = this.editor.attachDomEvent({
focus: { beforeDispatch: this.onFocus },
drop: { beforeDispatch: this.onDrop },
Expand All @@ -76,9 +75,7 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}

dispose() {
this.editor
?.getDocument()
.removeEventListener('selectionchange', this.onSelectionChangeSafari);
this.editor?.getDocument().removeEventListener('selectionchange', this.onSelectionChange);

if (this.disposer) {
this.disposer();
Expand Down Expand Up @@ -522,13 +519,27 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}
};

private onSelectionChangeSafari = () => {
private onSelectionChange = () => {
if (this.editor?.hasFocus() && !this.editor.isInShadowEdit()) {
BryanValverdeU marked this conversation as resolved.
Show resolved Hide resolved
// Safari has problem to handle onBlur event. When blur, we cannot get the original selection from editor.
// So we always save a selection whenever editor has focus. Then after blur, we can still use this cached selection.
const newSelection = this.editor.getDOMSelection();

if (newSelection?.type == 'range') {
//If am image selection changed to a wider range due a keyboard event, we should update the selection
const selection = this.editor.getDocument().getSelection();
if (
newSelection?.type == 'image' &&
selection &&
selection.containsNode(newSelection.image, false /*partiallyContained*/)
) {
this.editor.setDOMSelection({
type: 'range',
range: selection.getRangeAt(0),
isReverted: false,
});
}

// Safari has problem to handle onBlur event. When blur, we cannot get the original selection from editor.
// So we always save a selection whenever editor has focus. Then after blur, we can still use this cached selection.
if (newSelection?.type == 'range' && this.isSafari) {
BryanValverdeU marked this conversation as resolved.
Show resolved Hide resolved
this.state.selection = newSelection;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createDOMHelper } from '../../../lib/editor/core/DOMHelperImpl';
import { createSelectionPlugin } from '../../../lib/corePlugin/selection/SelectionPlugin';
import {
DOMEventRecord,
DOMSelection,
EditorPlugin,
IEditor,
PluginWithState,
Expand All @@ -14,8 +15,10 @@ describe('SelectionPlugin', () => {
const disposer = jasmine.createSpy('disposer');
const attachDomEvent = jasmine.createSpy('attachDomEvent').and.returnValue(disposer);
const removeEventListenerSpy = jasmine.createSpy('removeEventListener');
const addEventListenerSpy = jasmine.createSpy('addEventListener');
const getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
removeEventListener: removeEventListenerSpy,
addEventListener: addEventListenerSpy,
});
const state = plugin.getState();
const editor = ({
Expand Down Expand Up @@ -46,13 +49,14 @@ describe('SelectionPlugin', () => {
imageSelectionBorderColor: 'red',
});
const state = plugin.getState();

const addEventListenerSpy = jasmine.createSpy('addEventListener');
const attachDomEvent = jasmine
.createSpy('attachDomEvent')
.and.returnValue(jasmine.createSpy('disposer'));
const removeEventListenerSpy = jasmine.createSpy('removeEventListener');
const getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
removeEventListener: removeEventListenerSpy,
addEventListener: addEventListenerSpy,
});

plugin.initialize(<IEditor>(<any>{
Expand Down Expand Up @@ -81,15 +85,18 @@ describe('SelectionPlugin handle onFocus and onBlur event', () => {
let getDocumentSpy: jasmine.Spy;
let setDOMSelectionSpy: jasmine.Spy;
let removeEventListenerSpy: jasmine.Spy;
let addEventListenerSpy: jasmine.Spy;

let editor: IEditor;

beforeEach(() => {
triggerEvent = jasmine.createSpy('triggerEvent');
getElementAtCursorSpy = jasmine.createSpy('getElementAtCursor');
removeEventListenerSpy = jasmine.createSpy('removeEventListener');
addEventListenerSpy = jasmine.createSpy('addEventListener');
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
removeEventListener: removeEventListenerSpy,
addEventListener: addEventListenerSpy,
});
setDOMSelectionSpy = jasmine.createSpy('setDOMSelection');

Expand Down Expand Up @@ -153,13 +160,16 @@ describe('SelectionPlugin handle image selection', () => {
let setDOMSelectionSpy: jasmine.Spy;
let getDocumentSpy: jasmine.Spy;
let createRangeSpy: jasmine.Spy;
let addEventListenerSpy: jasmine.Spy;

beforeEach(() => {
getDOMSelectionSpy = jasmine.createSpy('getDOMSelection');
setDOMSelectionSpy = jasmine.createSpy('setDOMSelection');
createRangeSpy = jasmine.createSpy('createRange');
addEventListenerSpy = jasmine.createSpy('addEventListener');
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
createRange: createRangeSpy,
addEventListener: addEventListenerSpy,
});

editor = {
Expand Down Expand Up @@ -557,6 +567,7 @@ describe('SelectionPlugin handle table selection', () => {
let mouseMoveDisposer: jasmine.Spy;
let requestAnimationFrameSpy: jasmine.Spy;
let getComputedStyleSpy: jasmine.Spy;
let addEventListenerSpy: jasmine.Spy;

beforeEach(() => {
contentDiv = document.createElement('div');
Expand All @@ -565,12 +576,14 @@ describe('SelectionPlugin handle table selection', () => {
createRangeSpy = jasmine.createSpy('createRange');
requestAnimationFrameSpy = jasmine.createSpy('requestAnimationFrame');
getComputedStyleSpy = jasmine.createSpy('getComputedStyle');
addEventListenerSpy = jasmine.createSpy('addEventListener');
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
createRange: createRangeSpy,
defaultView: {
requestAnimationFrame: requestAnimationFrameSpy,
getComputedStyle: getComputedStyleSpy,
},
addEventListener: addEventListenerSpy,
});
focusDisposer = jasmine.createSpy('focus');
mouseMoveDisposer = jasmine.createSpy('mouseMove');
Expand Down Expand Up @@ -1875,19 +1888,22 @@ describe('SelectionPlugin on Safari', () => {
let isInShadowEditSpy: jasmine.Spy;
let getDOMSelectionSpy: jasmine.Spy;
let editor: IEditor;
let getSelectionSpy: jasmine.Spy;

beforeEach(() => {
disposer = jasmine.createSpy('disposer');
appendChildSpy = jasmine.createSpy('appendChild');
attachDomEvent = jasmine.createSpy('attachDomEvent').and.returnValue(disposer);
removeEventListenerSpy = jasmine.createSpy('removeEventListener');
addEventListenerSpy = jasmine.createSpy('addEventListener');
getSelectionSpy = jasmine.createSpy('getSelection');
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
head: {
appendChild: appendChildSpy,
},
addEventListener: addEventListenerSpy,
removeEventListener: removeEventListenerSpy,
getSelection: getSelectionSpy,
});
hasFocusSpy = jasmine.createSpy('hasFocus');
isInShadowEditSpy = jasmine.createSpy('isInShadowEdit');
Expand Down Expand Up @@ -2093,4 +2109,155 @@ describe('SelectionPlugin on Safari', () => {
});
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(0);
});

it('', () => {});
});

describe('SelectionPlugin selectionChange on image selected', () => {
let disposer: jasmine.Spy;
let appendChildSpy: jasmine.Spy;
let attachDomEvent: jasmine.Spy;
let removeEventListenerSpy: jasmine.Spy;
let addEventListenerSpy: jasmine.Spy;
let getDocumentSpy: jasmine.Spy;
let hasFocusSpy: jasmine.Spy;
let isInShadowEditSpy: jasmine.Spy;
let getDOMSelectionSpy: jasmine.Spy;
let editor: IEditor;
let setDOMSelectionSpy: jasmine.Spy;
let containsNodeSpy: jasmine.Spy;
let getRangeAtSpy: jasmine.Spy;
let getSelectionSpy: jasmine.Spy;

beforeEach(() => {
disposer = jasmine.createSpy('disposer');
appendChildSpy = jasmine.createSpy('appendChild');
attachDomEvent = jasmine.createSpy('attachDomEvent').and.returnValue(disposer);
removeEventListenerSpy = jasmine.createSpy('removeEventListener');
addEventListenerSpy = jasmine.createSpy('addEventListener');
containsNodeSpy = jasmine.createSpy('containsNode');
getRangeAtSpy = jasmine.createSpy('getRangeAt');
getSelectionSpy = jasmine.createSpy('getSelection').and.returnValue({
containsNode: containsNodeSpy,
getRangeAt: getRangeAtSpy,
});
getDocumentSpy = jasmine.createSpy('getDocument').and.returnValue({
head: {
appendChild: appendChildSpy,
},
addEventListener: addEventListenerSpy,
removeEventListener: removeEventListenerSpy,
getSelection: getSelectionSpy,
});
hasFocusSpy = jasmine.createSpy('hasFocus');
isInShadowEditSpy = jasmine.createSpy('isInShadowEdit');
getDOMSelectionSpy = jasmine.createSpy('getDOMSelection');
setDOMSelectionSpy = jasmine.createSpy('setDOMSelection');

editor = ({
getDocument: getDocumentSpy,
attachDomEvent,
getEnvironment: () => ({
isSafari: true,
}),
hasFocus: hasFocusSpy,
isInShadowEdit: isInShadowEditSpy,
getDOMSelection: getDOMSelectionSpy,
setDOMSelection: setDOMSelectionSpy,
} as any) as IEditor;
});

it('onSelectionChange on image', () => {
const plugin = createSelectionPlugin({});
const state = plugin.getState();
const mockedOldSelection = {
type: 'image',
image: {} as any,
} as DOMSelection;

state.selection = mockedOldSelection;

plugin.initialize(editor);

const onSelectionChange = addEventListenerSpy.calls.argsFor(0)[1] as Function;
const mockedNewSelection = {
type: 'image',
image: {} as any,
} as any;

hasFocusSpy.and.returnValue(true);
isInShadowEditSpy.and.returnValue(false);
getDOMSelectionSpy.and.returnValue(mockedNewSelection);
containsNodeSpy.and.returnValue(true);
getRangeAtSpy.and.returnValue({ startContainer: {} });

onSelectionChange();

expect(setDOMSelectionSpy).toHaveBeenCalledWith({
type: 'range',
range: { startContainer: {} } as Range,
isReverted: false,
});
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
});

it('onSelectionChange on image', () => {
const plugin = createSelectionPlugin({});
const state = plugin.getState();
const mockedOldSelection = {
type: 'image',
image: {} as any,
} as DOMSelection;

state.selection = mockedOldSelection;

plugin.initialize(editor);

const onSelectionChange = addEventListenerSpy.calls.argsFor(0)[1] as Function;
const mockedNewSelection = {
type: 'image',
image: {} as any,
} as any;

hasFocusSpy.and.returnValue(true);
isInShadowEditSpy.and.returnValue(false);
getDOMSelectionSpy.and.returnValue(mockedNewSelection);
containsNodeSpy.and.returnValue(false);
getRangeAtSpy.and.returnValue({ startContainer: {} });

onSelectionChange();

expect(setDOMSelectionSpy).not.toHaveBeenCalled();
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
});

it('onSelectionChange on image', () => {
const plugin = createSelectionPlugin({});
const state = plugin.getState();
const mockedOldSelection = {
type: 'image',
image: {} as any,
} as DOMSelection;

state.selection = mockedOldSelection;

plugin.initialize(editor);

const onSelectionChange = addEventListenerSpy.calls.argsFor(0)[1] as Function;
const mockedNewSelection = {
type: 'range',
range: {} as any,
} as any;

hasFocusSpy.and.returnValue(true);
isInShadowEditSpy.and.returnValue(false);
getDOMSelectionSpy.and.returnValue(mockedNewSelection);
containsNodeSpy.and.returnValue(true);
getRangeAtSpy.and.returnValue({ startContainer: {} });

onSelectionChange();

expect(setDOMSelectionSpy).not.toHaveBeenCalled();
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
});
});
Loading