mirror of
https://github.com/codex-team/editor.js
synced 2024-05-19 06:47:16 +02:00
fix(block-tunes): enter keydown problems (#2650)
* debug enter press * fix sync set caret * fix enter keydown problems + tests addedd * Update search-input.ts * add changelog * add useful log to cypress custom comand * Update commands.ts
This commit is contained in:
parent
e9b4c30407
commit
ee6433201d
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,3 +17,4 @@ dist/
|
||||||
|
|
||||||
coverage/
|
coverage/
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
|
.vscode/launch.json
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
### 2.30.0
|
### 2.30.0
|
||||||
|
|
||||||
- `Fix` — `onChange` will be called when removing the entire text within a descendant element of a block.
|
- `Fix` — `onChange` will be called when removing the entire text within a descendant element of a block.
|
||||||
|
- `Fix` - Unexpected new line on Enter press with selected block without caret
|
||||||
|
- `Fix` - Search input autofocus loosing after Block Tunes opening
|
||||||
|
- `Fix` - Block removing while Enter press on Block Tunes
|
||||||
|
|
||||||
### 2.29.1
|
### 2.29.1
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@editorjs/editorjs",
|
"name": "@editorjs/editorjs",
|
||||||
"version": "2.30.0-rc.0",
|
"version": "2.30.0-rc.1",
|
||||||
"description": "Editor.js — Native JS, based on API and Open Source",
|
"description": "Editor.js — Native JS, based on API and Open Source",
|
||||||
"main": "dist/editorjs.umd.js",
|
"main": "dist/editorjs.umd.js",
|
||||||
"module": "dist/editorjs.mjs",
|
"module": "dist/editorjs.mjs",
|
||||||
|
|
|
@ -738,6 +738,10 @@ export default class Block extends EventsDispatcher<BlockEvents> {
|
||||||
contentNode = $.make('div', Block.CSS.content),
|
contentNode = $.make('div', Block.CSS.content),
|
||||||
pluginsContent = this.toolInstance.render();
|
pluginsContent = this.toolInstance.render();
|
||||||
|
|
||||||
|
if (import.meta.env.MODE === 'test') {
|
||||||
|
wrapper.setAttribute('data-cy', 'block-wrapper');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export id to the DOM three
|
* Export id to the DOM three
|
||||||
* Useful for standalone modules development. For example, allows to identify Block by some child node. Or scroll to a particular Block by id.
|
* Useful for standalone modules development. For example, allows to identify Block by some child node. Or scroll to a particular Block by id.
|
||||||
|
|
5
src/components/constants.ts
Normal file
5
src/components/constants.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/**
|
||||||
|
* Debounce timeout for selection change event
|
||||||
|
* {@link modules/ui.ts}
|
||||||
|
*/
|
||||||
|
export const selectionChangeDebounceTimeout = 180;
|
|
@ -48,11 +48,11 @@ export default class CrossBlockSelection extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return boolean is cross block selection started
|
* Return boolean is cross block selection started:
|
||||||
|
* there should be at least 2 selected blocks
|
||||||
*/
|
*/
|
||||||
public get isCrossBlockSelectionStarted(): boolean {
|
public get isCrossBlockSelectionStarted(): boolean {
|
||||||
return !!this.firstSelectedBlock &&
|
return !!this.firstSelectedBlock && !!this.lastSelectedBlock && this.firstSelectedBlock !== this.lastSelectedBlock;
|
||||||
!!this.lastSelectedBlock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { mobileScreenBreakpoint } from '../utils';
|
||||||
|
|
||||||
import styles from '../../styles/main.css?inline';
|
import styles from '../../styles/main.css?inline';
|
||||||
import { BlockHovered } from '../events/BlockHovered';
|
import { BlockHovered } from '../events/BlockHovered';
|
||||||
|
import { selectionChangeDebounceTimeout } from '../constants';
|
||||||
/**
|
/**
|
||||||
* HTML Elements used for UI
|
* HTML Elements used for UI
|
||||||
*/
|
*/
|
||||||
|
@ -350,7 +351,6 @@ export default class UI extends Module<UINodes> {
|
||||||
/**
|
/**
|
||||||
* Handle selection change to manipulate Inline Toolbar appearance
|
* Handle selection change to manipulate Inline Toolbar appearance
|
||||||
*/
|
*/
|
||||||
const selectionChangeDebounceTimeout = 180;
|
|
||||||
const selectionChangeDebounced = _.debounce(() => {
|
const selectionChangeDebounced = _.debounce(() => {
|
||||||
this.selectionChanged();
|
this.selectionChanged();
|
||||||
}, selectionChangeDebounceTimeout);
|
}, selectionChangeDebounceTimeout);
|
||||||
|
@ -556,6 +556,11 @@ export default class UI extends Module<UINodes> {
|
||||||
*/
|
*/
|
||||||
private enterPressed(event: KeyboardEvent): void {
|
private enterPressed(event: KeyboardEvent): void {
|
||||||
const { BlockManager, BlockSelection } = this.Editor;
|
const { BlockManager, BlockSelection } = this.Editor;
|
||||||
|
|
||||||
|
if (this.someToolbarOpened) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const hasPointerToBlock = BlockManager.currentBlockIndex >= 0;
|
const hasPointerToBlock = BlockManager.currentBlockIndex >= 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -591,6 +596,10 @@ export default class UI extends Module<UINodes> {
|
||||||
*/
|
*/
|
||||||
const newBlock = this.Editor.BlockManager.insert();
|
const newBlock = this.Editor.BlockManager.insert();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent default enter behaviour to prevent adding a new line (<div><br></div>) to the inserted block
|
||||||
|
*/
|
||||||
|
event.preventDefault();
|
||||||
this.Editor.Caret.setToBlock(newBlock);
|
this.Editor.Caret.setToBlock(newBlock);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -237,9 +237,7 @@ export default class Popover extends EventsDispatcher<PopoverEventMap> {
|
||||||
this.flipper.activate(this.flippableElements);
|
this.flipper.activate(this.flippableElements);
|
||||||
|
|
||||||
if (this.search !== undefined) {
|
if (this.search !== undefined) {
|
||||||
requestAnimationFrame(() => {
|
this.search?.focus();
|
||||||
this.search?.focus();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMobileScreen()) {
|
if (isMobileScreen()) {
|
||||||
|
|
|
@ -234,3 +234,30 @@ Cypress.Commands.add('getLineWrapPositions', {
|
||||||
|
|
||||||
return cy.wrap(lineWraps);
|
return cy.wrap(lineWraps);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches keydown event on subject
|
||||||
|
* Uses the correct KeyboardEvent object to make it work with our code (see below)
|
||||||
|
*/
|
||||||
|
Cypress.Commands.add('keydown', {
|
||||||
|
prevSubject: true,
|
||||||
|
}, (subject, keyCode: number) => {
|
||||||
|
cy.log('Dispatching KeyboardEvent with keyCode: ' + keyCode);
|
||||||
|
/**
|
||||||
|
* We use the "reason instanceof KeyboardEvent" statement in blockSelection.ts
|
||||||
|
* but by default cypress' KeyboardEvent is not an instance of the native KeyboardEvent,
|
||||||
|
* so real-world and Cypress behaviour were different.
|
||||||
|
*
|
||||||
|
* To make it work we need to trigger Cypress event with "eventConstructor: 'KeyboardEvent'",
|
||||||
|
*
|
||||||
|
* @see https://github.com/cypress-io/cypress/issues/5650
|
||||||
|
* @see https://github.com/cypress-io/cypress/pull/8305/files
|
||||||
|
*/
|
||||||
|
subject.trigger('keydown', {
|
||||||
|
eventConstructor: 'KeyboardEvent',
|
||||||
|
keyCode,
|
||||||
|
bubbles: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return cy.wrap(subject);
|
||||||
|
});
|
||||||
|
|
8
test/cypress/support/index.d.ts
vendored
8
test/cypress/support/index.d.ts
vendored
|
@ -85,6 +85,14 @@ declare global {
|
||||||
* @returns number[] - array of line wrap positions
|
* @returns number[] - array of line wrap positions
|
||||||
*/
|
*/
|
||||||
getLineWrapPositions(): Chainable<number[]>;
|
getLineWrapPositions(): Chainable<number[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches keydown event on subject
|
||||||
|
* Uses the correct KeyboardEvent object to make it work with our code (see below)
|
||||||
|
*
|
||||||
|
* @param keyCode - key code to dispatch
|
||||||
|
*/
|
||||||
|
keydown(keyCode: number): Chainable<Subject>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApplicationWindow {
|
interface ApplicationWindow {
|
||||||
|
|
107
test/cypress/tests/ui/BlockTunes.cy.ts
Normal file
107
test/cypress/tests/ui/BlockTunes.cy.ts
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import { selectionChangeDebounceTimeout } from '../../../../src/components/constants';
|
||||||
|
|
||||||
|
describe('BlockTunes', function () {
|
||||||
|
describe('Search', () => {
|
||||||
|
it('should be focused after popover opened', () => {
|
||||||
|
cy.createEditor({
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'Some text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-paragraph')
|
||||||
|
.click()
|
||||||
|
.type('{cmd}/')
|
||||||
|
.wait(selectionChangeDebounceTimeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caret is set to the search input
|
||||||
|
*/
|
||||||
|
cy.window()
|
||||||
|
.then((window) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
|
||||||
|
expect(selection.rangeCount).to.be.equal(1);
|
||||||
|
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('[data-cy="block-tunes"] .cdx-search-field')
|
||||||
|
.should(($block) => {
|
||||||
|
expect($block[0].contains(range.startContainer)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Keyboard only', function () {
|
||||||
|
it('should not delete the currently selected block when Enter pressed on a search input (or any block tune)', function () {
|
||||||
|
const ENTER_KEY_CODE = 13;
|
||||||
|
|
||||||
|
cy.createEditor({
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'Some text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-paragraph')
|
||||||
|
.click()
|
||||||
|
.type('{cmd}/')
|
||||||
|
.wait(selectionChangeDebounceTimeout)
|
||||||
|
.keydown(ENTER_KEY_CODE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block should have same text
|
||||||
|
*/
|
||||||
|
cy.get('[data-cy="block-wrapper"')
|
||||||
|
.should('have.text', 'Some text');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not unselect currently selected block when Enter pressed on a block tune', function () {
|
||||||
|
const ENTER_KEY_CODE = 13;
|
||||||
|
|
||||||
|
cy.createEditor({
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'Some text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-paragraph')
|
||||||
|
.click()
|
||||||
|
.type('{cmd}/')
|
||||||
|
.wait(selectionChangeDebounceTimeout)
|
||||||
|
.keydown(ENTER_KEY_CODE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block should not be selected
|
||||||
|
*/
|
||||||
|
cy.get('[data-cy="block-wrapper"')
|
||||||
|
.first()
|
||||||
|
.should('have.class', 'ce-block--selected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue