mirror of
https://github.com/codex-team/editor.js
synced 2024-06-15 20:25:15 +02:00
feat(onchange): callback now accepts custom event (#1791)
* feat(onchange): callback now accepts custom event * Delete block-added.ts * testd updated, changelog added * Update example-dev.html * indexes added to all events * block-removed dispatching on block replacing * Update example-dev.html
This commit is contained in:
parent
1433c1a7dc
commit
4f15bbc0cb
|
@ -1,5 +1,9 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### 2.23.0
|
||||||
|
|
||||||
|
- `Improvement` — The `onChange` callback now accepts two arguments: EditorJS API and the CustomEvent with `type` and `detail` allowing to determine what happened with a Block
|
||||||
|
|
||||||
### 2.22.3
|
### 2.22.3
|
||||||
|
|
||||||
- `Fix` — Tool config is passed to `prepare` method [editor-js/embed#68](https://github.com/editor-js/embed/issues/68)
|
- `Fix` — Tool config is passed to `prepare` method [editor-js/embed#68](https://github.com/editor-js/embed/issues/68)
|
||||||
|
|
|
@ -192,8 +192,9 @@ var editor = new EditorJS({
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* onChange callback
|
* onChange callback
|
||||||
|
* Accepts CustomEvent describing what happened
|
||||||
*/
|
*/
|
||||||
onChange: (editorAPI, affectedBlockAPI) => {console.log('Now I know that Editor\'s content changed!')}
|
onChange: (editorAPI, event) => {console.log('Now I know that Editor\'s content changed!')}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -318,8 +318,8 @@
|
||||||
onReady: function(){
|
onReady: function(){
|
||||||
saveButton.click();
|
saveButton.click();
|
||||||
},
|
},
|
||||||
onChange: function(api, block) {
|
onChange: function(api, event) {
|
||||||
console.log('something changed', block);
|
console.log('something changed', event);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -281,8 +281,8 @@
|
||||||
onReady: function(){
|
onReady: function(){
|
||||||
saveButton.click();
|
saveButton.click();
|
||||||
},
|
},
|
||||||
onChange: function(api, block) {
|
onChange: function(api, event) {
|
||||||
console.log('something changed', block);
|
console.log('something changed', event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@editorjs/editorjs",
|
"name": "@editorjs/editorjs",
|
||||||
"version": "2.22.3",
|
"version": "2.23.0",
|
||||||
"description": "Editor.js — Native JS, based on API and Open Source",
|
"description": "Editor.js — Native JS, based on API and Open Source",
|
||||||
"main": "dist/editor.js",
|
"main": "dist/editor.js",
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
|
@ -29,7 +29,8 @@
|
||||||
"_tools:build": "git submodule foreach yarn build",
|
"_tools:build": "git submodule foreach yarn build",
|
||||||
"_tools:make": "yarn _tools:yarn && yarn _tools:build",
|
"_tools:make": "yarn _tools:yarn && yarn _tools:build",
|
||||||
"tools:update": "yarn _tools:checkout && yarn _tools:pull && yarn _tools:make",
|
"tools:update": "yarn _tools:checkout && yarn _tools:pull && yarn _tools:make",
|
||||||
"test:e2e": "yarn build && cypress run"
|
"test:e2e": "yarn build && cypress run",
|
||||||
|
"test:e2e:open": "yarn build && cypress open"
|
||||||
},
|
},
|
||||||
"author": "CodeX",
|
"author": "CodeX",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
|
@ -14,6 +14,7 @@ import Blocks from '../blocks';
|
||||||
import { BlockToolData, PasteEvent } from '../../../types';
|
import { BlockToolData, PasteEvent } from '../../../types';
|
||||||
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
|
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
|
||||||
import BlockAPI from '../block/api';
|
import BlockAPI from '../block/api';
|
||||||
|
import { BlockMutationType } from '../../../types/events/block/mutation-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {BlockManager} BlockManager
|
* @typedef {BlockManager} BlockManager
|
||||||
|
@ -289,12 +290,24 @@ export default class BlockManager extends Module {
|
||||||
tunes,
|
tunes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In case of block replacing (Converting OR from Toolbox or Shortcut on empty block OR on-paste to empty block)
|
||||||
|
* we need to dispatch the 'block-removing' event for the replacing block
|
||||||
|
*/
|
||||||
|
if (replace) {
|
||||||
|
this.blockDidMutated(BlockMutationType.Removed, this.getBlockByIndex(newIndex), {
|
||||||
|
index: newIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this._blocks.insert(newIndex, block, replace);
|
this._blocks.insert(newIndex, block, replace);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force call of didMutated event on Block insertion
|
* Force call of didMutated event on Block insertion
|
||||||
*/
|
*/
|
||||||
this.blockDidMutated(block);
|
this.blockDidMutated(BlockMutationType.Added, block, {
|
||||||
|
index: newIndex,
|
||||||
|
});
|
||||||
|
|
||||||
if (needToFocus) {
|
if (needToFocus) {
|
||||||
this.currentBlockIndex = newIndex;
|
this.currentBlockIndex = newIndex;
|
||||||
|
@ -370,7 +383,9 @@ export default class BlockManager extends Module {
|
||||||
/**
|
/**
|
||||||
* Force call of didMutated event on Block insertion
|
* Force call of didMutated event on Block insertion
|
||||||
*/
|
*/
|
||||||
this.blockDidMutated(block);
|
this.blockDidMutated(BlockMutationType.Added, block, {
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
|
||||||
if (needToFocus) {
|
if (needToFocus) {
|
||||||
this.currentBlockIndex = index;
|
this.currentBlockIndex = index;
|
||||||
|
@ -445,7 +460,9 @@ export default class BlockManager extends Module {
|
||||||
/**
|
/**
|
||||||
* Force call of didMutated event on Block removal
|
* Force call of didMutated event on Block removal
|
||||||
*/
|
*/
|
||||||
this.blockDidMutated(blockToRemove);
|
this.blockDidMutated(BlockMutationType.Removed, blockToRemove, {
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
|
||||||
if (this.currentBlockIndex >= index) {
|
if (this.currentBlockIndex >= index) {
|
||||||
this.currentBlockIndex--;
|
this.currentBlockIndex--;
|
||||||
|
@ -721,7 +738,10 @@ export default class BlockManager extends Module {
|
||||||
/**
|
/**
|
||||||
* Force call of didMutated event on Block movement
|
* Force call of didMutated event on Block movement
|
||||||
*/
|
*/
|
||||||
this.blockDidMutated(this.currentBlock);
|
this.blockDidMutated(BlockMutationType.Moved, this.currentBlock, {
|
||||||
|
fromIndex,
|
||||||
|
toIndex,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -788,7 +808,11 @@ export default class BlockManager extends Module {
|
||||||
BlockEvents.dragLeave(event);
|
BlockEvents.dragLeave(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
block.on('didMutated', (affectedBlock: Block) => this.blockDidMutated(affectedBlock));
|
block.on('didMutated', (affectedBlock: Block) => {
|
||||||
|
return this.blockDidMutated(BlockMutationType.Changed, affectedBlock, {
|
||||||
|
index: this.getBlockIndex(affectedBlock),
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -828,10 +852,19 @@ export default class BlockManager extends Module {
|
||||||
/**
|
/**
|
||||||
* Block mutation callback
|
* Block mutation callback
|
||||||
*
|
*
|
||||||
|
* @param mutationType - what happened with block
|
||||||
* @param block - mutated block
|
* @param block - mutated block
|
||||||
|
* @param details - additional data to pass with change event
|
||||||
*/
|
*/
|
||||||
private blockDidMutated(block: Block): Block {
|
private blockDidMutated(mutationType: BlockMutationType, block: Block, details: Record<string, unknown> = {}): Block {
|
||||||
this.Editor.ModificationsObserver.onChange(new BlockAPI(block));
|
const event = new CustomEvent(mutationType, {
|
||||||
|
detail: {
|
||||||
|
target: new BlockAPI(block),
|
||||||
|
...details,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.Editor.ModificationsObserver.onChange(event);
|
||||||
|
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import Module from '../__module';
|
import Module from '../__module';
|
||||||
import { BlockAPI } from '../../../types';
|
|
||||||
import * as _ from '../utils';
|
import * as _ from '../utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,13 +27,13 @@ export default class ModificationsObserver extends Module {
|
||||||
/**
|
/**
|
||||||
* Call onChange event passed to Editor.js configuration
|
* Call onChange event passed to Editor.js configuration
|
||||||
*
|
*
|
||||||
* @param block - changed Block
|
* @param event - some of our custom change events
|
||||||
*/
|
*/
|
||||||
public onChange(block: BlockAPI): void {
|
public onChange(event: CustomEvent): void {
|
||||||
if (this.disabled || !_.isFunction(this.config.onChange)) {
|
if (this.disabled || !_.isFunction(this.config.onChange)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.config.onChange(this.Editor.API.methods, block);
|
this.config.onChange(this.Editor.API.methods, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,98 @@
|
||||||
import Header from '../../../example/tools/header';
|
import Header from '../../../example/tools/header';
|
||||||
|
import { BlockMutationType } from '../../../types/events/block/mutation-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Add checks that correct block API object is passed to onChange
|
* @todo Add checks that correct block API object is passed to onChange
|
||||||
* @todo Add cases for native inputs changes
|
* @todo Add cases for native inputs changes
|
||||||
|
* @todo debug onChange firing on Block Tune toggling (see below)
|
||||||
*/
|
*/
|
||||||
describe('onChange callback', () => {
|
describe('onChange callback', () => {
|
||||||
|
/**
|
||||||
|
* Creates Editor instance
|
||||||
|
*
|
||||||
|
* @param blocks - list of blocks to prefill the editor
|
||||||
|
*/
|
||||||
|
function createEditor(blocks = null): void {
|
||||||
const config = {
|
const config = {
|
||||||
tools: {
|
tools: {
|
||||||
header: Header,
|
header: Header,
|
||||||
},
|
},
|
||||||
onChange: (): void => {
|
onChange: (api, event): void => {
|
||||||
console.log('something changed');
|
console.log('something changed', api, event);
|
||||||
},
|
},
|
||||||
|
data: blocks ? {
|
||||||
|
blocks,
|
||||||
|
} : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
if (this && this.editorInstance) {
|
|
||||||
this.editorInstance.destroy();
|
|
||||||
} else {
|
|
||||||
cy.spy(config, 'onChange').as('onChange');
|
cy.spy(config, 'onChange').as('onChange');
|
||||||
|
|
||||||
cy.createEditor(config).as('editorInstance');
|
cy.createEditor(config).as('editorInstance');
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
it('should fire onChange callback on block insertion', () => {
|
/**
|
||||||
|
* EditorJS API is passed as the first parameter of the onChange callback
|
||||||
|
*/
|
||||||
|
const EditorJSApiMock = Cypress.sinon.match.any;
|
||||||
|
|
||||||
|
it('should fire onChange callback with correct index on block insertion above the current (by pressing Enter at the start)', () => {
|
||||||
|
createEditor();
|
||||||
|
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
.get('div.ce-block')
|
.get('div.ce-block')
|
||||||
.click()
|
.click()
|
||||||
.type('{enter}');
|
.type('{enter}');
|
||||||
|
|
||||||
cy.get('@onChange').should('be.called');
|
cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({
|
||||||
|
type: BlockMutationType.Added,
|
||||||
|
detail: {
|
||||||
|
target: {
|
||||||
|
name: 'paragraph'
|
||||||
|
},
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire onChange callback with correct index on block insertion below the current (by pressing enter at the end)', () => {
|
||||||
|
createEditor();
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('div.ce-block')
|
||||||
|
.click()
|
||||||
|
.type('some text')
|
||||||
|
.type('{enter}');
|
||||||
|
|
||||||
|
cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({
|
||||||
|
type: BlockMutationType.Added,
|
||||||
|
detail: {
|
||||||
|
target: {
|
||||||
|
name: 'paragraph'
|
||||||
|
},
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fire onChange callback on typing into block', () => {
|
it('should fire onChange callback on typing into block', () => {
|
||||||
|
createEditor();
|
||||||
|
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
.get('div.ce-block')
|
.get('div.ce-block')
|
||||||
.click()
|
.click()
|
||||||
.type('some text');
|
.type('some text');
|
||||||
|
|
||||||
cy.get('@onChange').should('be.called');
|
cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({
|
||||||
|
type: BlockMutationType.Changed,
|
||||||
|
detail: {
|
||||||
|
index: 0
|
||||||
|
},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fire onChange callback on block replacement', () => {
|
it('should fire onChange callback on block replacement for both of blocks', () => {
|
||||||
|
createEditor();
|
||||||
|
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
.get('div.ce-block')
|
.get('div.ce-block')
|
||||||
.click();
|
.click();
|
||||||
|
@ -55,21 +105,36 @@ describe('onChange callback', () => {
|
||||||
.get('li.ce-toolbox__button[data-tool=header]')
|
.get('li.ce-toolbox__button[data-tool=header]')
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
cy.get('@onChange').should('be.calledWithMatch', Cypress.sinon.match.any, Cypress.sinon.match({ name: 'header' }));
|
cy.get('@onChange').should('be.calledTwice');
|
||||||
|
cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({
|
||||||
|
type: BlockMutationType.Removed,
|
||||||
|
detail: {
|
||||||
|
index: 0,
|
||||||
|
target: {
|
||||||
|
name: 'paragraph',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({
|
||||||
|
type: BlockMutationType.Added,
|
||||||
|
detail: {
|
||||||
|
index: 0,
|
||||||
|
target: {
|
||||||
|
name: 'header',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fire onChange callback on tune modifier', () => {
|
it('should fire onChange callback on tune modifying', () => {
|
||||||
cy.get('[data-cy=editorjs]')
|
createEditor([
|
||||||
.get('div.ce-block')
|
{
|
||||||
.click();
|
type: 'header',
|
||||||
|
data: {
|
||||||
cy.get('[data-cy=editorjs]')
|
text: 'Header block',
|
||||||
.get('div.ce-toolbar__plus')
|
},
|
||||||
.click();
|
},
|
||||||
|
]);
|
||||||
cy.get('[data-cy=editorjs]')
|
|
||||||
.get('li.ce-toolbox__button[data-tool=header]')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
.get('div.ce-block')
|
.get('div.ce-block')
|
||||||
|
@ -80,13 +145,30 @@ describe('onChange callback', () => {
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
.get('span.cdx-settings-button[data-level=1]')
|
.get('span.cdx-settings-button[data-level=4]')
|
||||||
|
.click()
|
||||||
|
/**
|
||||||
|
* For some reason, the first click fires the mutation of removeFakeCursor only, so we need to click again.
|
||||||
|
* Reproduced only in Cypress.
|
||||||
|
*
|
||||||
|
* @todo debug it later
|
||||||
|
*/
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
cy.get('@onChange').should('be.calledWithMatch', Cypress.sinon.match.any, Cypress.sinon.match({ name: 'header' }));
|
cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({
|
||||||
|
type: BlockMutationType.Changed,
|
||||||
|
detail: {
|
||||||
|
index: 0,
|
||||||
|
target: {
|
||||||
|
name: 'header',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fire onChange callback when block is removed', () => {
|
it('should fire onChange callback when block is removed', () => {
|
||||||
|
createEditor();
|
||||||
|
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
.get('div.ce-block')
|
.get('div.ce-block')
|
||||||
.click();
|
.click();
|
||||||
|
@ -100,10 +182,17 @@ describe('onChange callback', () => {
|
||||||
.click()
|
.click()
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
cy.get('@onChange').should('be.called');
|
cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({
|
||||||
|
type: BlockMutationType.Removed,
|
||||||
|
detail: {
|
||||||
|
index: 0
|
||||||
|
},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fire onChange callback when block is moved', () => {
|
it('should fire onChange callback when block is moved', () => {
|
||||||
|
createEditor();
|
||||||
|
|
||||||
cy.get('[data-cy=editorjs]')
|
cy.get('[data-cy=editorjs]')
|
||||||
.get('div.ce-block')
|
.get('div.ce-block')
|
||||||
.click()
|
.click()
|
||||||
|
@ -122,6 +211,12 @@ describe('onChange callback', () => {
|
||||||
.get('div.ce-tune-move-up')
|
.get('div.ce-tune-move-up')
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
cy.get('@onChange').should('be.called');
|
cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({
|
||||||
|
type: BlockMutationType.Moved,
|
||||||
|
detail: {
|
||||||
|
fromIndex: 1,
|
||||||
|
toIndex: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
4
types/configs/editor-config.d.ts
vendored
4
types/configs/editor-config.d.ts
vendored
|
@ -90,9 +90,9 @@ export interface EditorConfig {
|
||||||
/**
|
/**
|
||||||
* Fires when something changed in DOM
|
* Fires when something changed in DOM
|
||||||
* @param {API} api - editor.js api
|
* @param {API} api - editor.js api
|
||||||
* @param block - changed block API
|
* @param event - custom event describing mutation
|
||||||
*/
|
*/
|
||||||
onChange?(api: API, block: BlockAPI): void;
|
onChange?(api: API, event: CustomEvent): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines default toolbar for all tools.
|
* Defines default toolbar for all tools.
|
||||||
|
|
24
types/events/block/mutation-type.ts
Normal file
24
types/events/block/mutation-type.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* What kind of modification happened with the Block
|
||||||
|
*/
|
||||||
|
export enum BlockMutationType {
|
||||||
|
/**
|
||||||
|
* New Block added
|
||||||
|
*/
|
||||||
|
Added = 'block-added',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On Block deletion
|
||||||
|
*/
|
||||||
|
Removed = 'block-removed',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moving of a Block
|
||||||
|
*/
|
||||||
|
Moved = 'block-moved',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any changes inside the Block
|
||||||
|
*/
|
||||||
|
Changed = 'block-changed',
|
||||||
|
}
|
Loading…
Reference in a new issue