onChange improvements (#1678)

* onChange improvements

* Return modifications observer module

* Fix lint

* Fix tests
This commit is contained in:
George Berezhnoy 2021-05-26 18:59:32 +03:00 committed by GitHub
parent 51d94e1a11
commit 4e7b33c2b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 259 additions and 205 deletions

View file

@ -1,5 +1,9 @@
# Changelog
### 2.22.0
- `New` - `onChange` callback now receive Block API object of affected block
### 2.21.0
- `New` - Blocks now have unique ids [#873](https://github.com/codex-team/editor.js/issues/873)

View file

@ -193,7 +193,7 @@ var editor = new EditorJS({
/**
* onChange callback
*/
onChange: () => {console.log('Now I know that Editor\'s content changed!')}
onChange: (editorAPI, affectedBlockAPI) => {console.log('Now I know that Editor\'s content changed!')}
});
```

View file

@ -319,8 +319,8 @@
onReady: function(){
saveButton.click();
},
onChange: function() {
console.log('something changed');
onChange: function(api, block) {
console.log('something changed', block);
},
});

View file

@ -281,8 +281,8 @@
onReady: function(){
saveButton.click();
},
onChange: function() {
console.log('something changed');
onChange: function(api, block) {
console.log('something changed', block);
}
});

View file

@ -18,6 +18,7 @@ import BlockTool from '../tools/block';
import BlockTune from '../tools/tune';
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
import ToolsCollection from '../tools/collection';
import EventsDispatcher from '../utils/events';
/**
* Interface describes Block class constructor argument
@ -79,6 +80,11 @@ export enum BlockToolAPI {
ON_PASTE = 'onPaste',
}
/**
* Names of events supported by Block class
*/
type BlockEvents = 'didMutated';
/**
* @classdesc Abstract Block class that contains Block information, Tool name and Tool class instance
*
@ -86,7 +92,7 @@ export enum BlockToolAPI {
* @property {HTMLElement} holder - Div element that wraps block content with Tool's content. Has `ce-block` CSS class
* @property {HTMLElement} pluginsContent - HTML content that returns by Tool's render function
*/
export default class Block {
export default class Block extends EventsDispatcher<BlockEvents> {
/**
* CSS classes for the Block
*
@ -207,6 +213,8 @@ export default class Block {
this.updateCurrentInput();
this.call(BlockToolAPI.UPDATED);
this.emit('didMutated', this);
}, this.modificationDebounceTimer);
/**
@ -230,6 +238,8 @@ export default class Block {
readOnly,
tunesData,
}: BlockConstructorOptions) {
super();
this.name = tool.name;
this.id = id;
this.settings = tool.settings;
@ -680,6 +690,8 @@ export default class Block {
* Call Tool instance destroy method
*/
public destroy(): void {
super.destroy();
if (_.isFunction(this.toolInstance.destroy)) {
this.toolInstance.destroy();
}
@ -777,6 +789,13 @@ export default class Block {
private addInputEvents(): void {
this.inputs.forEach(input => {
input.addEventListener('focus', this.handleFocus);
/**
* If input is native input add oninput listener to observe changes
*/
if ($.isNativeInput(input)) {
input.addEventListener('input', this.didMutated);
}
});
}
@ -786,6 +805,10 @@ export default class Block {
private removeInputEvents(): void {
this.inputs.forEach(input => {
input.removeEventListener('focus', this.handleFocus);
if ($.isNativeInput(input)) {
input.removeEventListener('input', this.didMutated);
}
});
}
}

View file

@ -11,7 +11,7 @@ export default class SanitizerAPI extends Module {
/**
* Available methods
*
* @returns {Sanitizer}
* @returns {SanitizerConfig}
*/
public get methods(): ISanitizer {
return {

View file

@ -13,6 +13,7 @@ import * as _ from '../utils';
import Blocks from '../blocks';
import { BlockToolData, PasteEvent } from '../../../types';
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
import BlockAPI from '../block/api';
/**
* @typedef {BlockManager} BlockManager
@ -290,6 +291,11 @@ export default class BlockManager extends Module {
this._blocks.insert(newIndex, block, replace);
/**
* Force call of didMutated event on Block insertion
*/
this.blockDidMutated(block);
if (needToFocus) {
this.currentBlockIndex = newIndex;
} else if (newIndex <= this.currentBlockIndex) {
@ -361,6 +367,11 @@ export default class BlockManager extends Module {
this._blocks[index] = block;
/**
* Force call of didMutated event on Block insertion
*/
this.blockDidMutated(block);
if (needToFocus) {
this.currentBlockIndex = index;
} else if (index <= this.currentBlockIndex) {
@ -426,8 +437,15 @@ export default class BlockManager extends Module {
throw new Error('Can\'t find a Block to remove');
}
const blockToRemove = this._blocks[index];
this._blocks.remove(index);
/**
* Force call of didMutated event on Block removal
*/
this.blockDidMutated(blockToRemove);
if (this.currentBlockIndex >= index) {
this.currentBlockIndex--;
}
@ -689,6 +707,11 @@ export default class BlockManager extends Module {
/** Now actual block moved so that current block index changed */
this.currentBlockIndex = toIndex;
/**
* Force call of didMutated event on Block movement
*/
this.blockDidMutated(this.currentBlock);
}
/**
@ -754,6 +777,8 @@ export default class BlockManager extends Module {
this.readOnlyMutableListeners.on(block.holder, 'dragleave', (event: DragEvent) => {
BlockEvents.dragLeave(event);
});
block.on('didMutated', (affectedBlock: Block) => this.blockDidMutated(affectedBlock));
}
/**
@ -789,4 +814,15 @@ export default class BlockManager extends Module {
private validateIndex(index: number): boolean {
return !(index < 0 || index >= this._blocks.length);
}
/**
* Block mutation callback
*
* @param block - mutated block
*/
private blockDidMutated(block: Block): Block {
this.Editor.ModificationsObserver.onChange(new BlockAPI(block));
return block;
}
}

View file

@ -1,195 +1,40 @@
/**
* @module ModificationsObserver
*
* Handles any mutations
* and gives opportunity to handle outside
*/
import Module from '../__module';
import { BlockAPI } from '../../../types';
import * as _ from '../utils';
import Block from '../block';
/**
*
* Single entry point for Block mutation events
*/
export default class ModificationsObserver extends Module {
/**
* Debounce Timer
*
* @type {number}
*/
public static readonly DebounceTimer = 450;
/**
* MutationObserver instance
*/
private observer: MutationObserver;
/**
* Allows to temporary disable mutations handling
* Flag shows onChange event is disabled
*/
private disabled = false;
/**
* Used to prevent several mutation callback execution
*
* @type {Function}
*/
private mutationDebouncer = _.debounce(() => {
this.updateNativeInputs();
if (_.isFunction(this.config.onChange)) {
this.config.onChange(this.Editor.API.methods);
}
}, ModificationsObserver.DebounceTimer);
/**
* Array of native inputs in Blocks.
* Changes in native inputs are not handled by modification observer, so we need to set change event listeners on them
*/
private nativeInputs: HTMLElement[] = [];
/**
* Clear timeout and set null to mutationDebouncer property
*/
public destroy(): void {
this.mutationDebouncer = null;
if (this.observer) {
this.observer.disconnect();
}
this.observer = null;
this.nativeInputs.forEach((input) => this.listeners.off(input, 'input', this.mutationDebouncer));
this.mutationDebouncer = null;
}
/**
* Set read-only state
*
* @param {boolean} readOnlyEnabled - read only flag value
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
if (readOnlyEnabled) {
this.disableModule();
} else {
this.enableModule();
}
}
/**
* Allows to disable observer,
* for example when Editor wants to stealthy mutate DOM
*/
public disable(): void {
this.disabled = true;
}
/**
* Enables mutation handling
* Should be called after .disable()
* Enables onChange event
*/
public enable(): void {
this.disabled = false;
}
/**
* setObserver
*
* sets 'DOMSubtreeModified' listener on Editor's UI.nodes.redactor
* so that User can handle outside from API
* Disables onChange event
*/
private setObserver(): void {
const { UI } = this.Editor;
const observerOptions = {
childList: true,
attributes: true,
subtree: true,
characterData: true,
characterDataOldValue: true,
};
this.observer = new MutationObserver((mutationList, observer) => {
this.mutationHandler(mutationList, observer);
});
this.observer.observe(UI.nodes.redactor, observerOptions);
public disable(): void {
this.disabled = true;
}
/**
* MutationObserver events handler
* Call onChange event passed to Editor.js configuration
*
* @param {MutationRecord[]} mutationList - list of mutations
* @param {MutationObserver} observer - observer instance
* @param block - changed Block
*/
private mutationHandler(mutationList: MutationRecord[], observer: MutationObserver): void {
/**
* Skip mutations in stealth mode
*/
if (this.disabled) {
public onChange(block: BlockAPI): void {
if (this.disabled || !_.isFunction(this.config.onChange)) {
return;
}
/**
* We divide two Mutation types:
* 1) mutations that concerns client changes: settings changes, symbol added, deletion, insertions and so on
* 2) functional changes. On each client actions we set functional identifiers to interact with user
*/
let contentMutated = false;
mutationList.forEach((mutation) => {
switch (mutation.type) {
case 'childList':
case 'characterData':
contentMutated = true;
break;
case 'attributes':
/**
* Changes on Element.ce-block usually is functional
*/
if (!(mutation.target as Element).classList.contains(Block.CSS.wrapper)) {
contentMutated = true;
}
break;
}
});
/** call once */
if (contentMutated) {
this.mutationDebouncer();
}
}
/**
* Gets native inputs and set oninput event handler
*/
private updateNativeInputs(): void {
if (this.nativeInputs) {
this.nativeInputs.forEach((input) => {
this.listeners.off(input, 'input');
});
}
this.nativeInputs = Array.from(this.Editor.UI.nodes.redactor.querySelectorAll('textarea, input, select'));
this.nativeInputs.forEach((input) => this.listeners.on(input, 'input', this.mutationDebouncer));
}
/**
* Sets observer and enables it
*/
private enableModule(): void {
/**
* wait till Browser render Editor's Blocks
*/
window.setTimeout(() => {
this.setObserver();
this.updateNativeInputs();
this.enable();
}, 1000);
}
/**
* Disables observer
*/
private disableModule(): void {
this.disable();
this.config.onChange(this.Editor.API.methods, block);
}
}

View file

@ -639,7 +639,7 @@ export default class Paste extends Module {
* @param {PasteData} dataToInsert - data of Block to insert
*/
private async processInlinePaste(dataToInsert: PasteData): Promise<void> {
const { BlockManager, Caret, Tools } = this.Editor;
const { BlockManager, Caret } = this.Editor;
const { content } = dataToInsert;
const currentBlockIsDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault;

View file

@ -48,8 +48,15 @@ export default class Renderer extends Module {
public async render(blocks: OutputBlockData[]): Promise<void> {
const chainData = blocks.map((block) => ({ function: (): Promise<void> => this.insertBlock(block) }));
/**
* Disable onChange callback on render to not to spam those events
*/
this.Editor.ModificationsObserver.disable();
const sequence = await _.sequence(chainData as _.ChainData[]);
this.Editor.ModificationsObserver.enable();
this.Editor.UI.checkEmptiness();
return sequence;

View file

@ -7,7 +7,7 @@
*/
import Module from '../__module';
import { OutputData } from '../../../types';
import { ValidatedData } from '../../../types/data-formats';
import { SavedData, ValidatedData } from '../../../types/data-formats';
import Block from '../block';
import * as _ from '../utils';
import { sanitizeBlocks } from '../utils/sanitizer';
@ -28,26 +28,28 @@ export default class Saver extends Module {
* @returns {OutputData}
*/
public async save(): Promise<OutputData> {
const { BlockManager, ModificationsObserver, Tools } = this.Editor;
const { BlockManager, Tools, ModificationsObserver } = this.Editor;
const blocks = BlockManager.blocks,
chainData = [];
/**
* Disable modifications observe while saving
*/
ModificationsObserver.disable();
try {
/**
* Disable onChange callback on save to not to spam those events
*/
ModificationsObserver.disable();
blocks.forEach((block: Block) => {
chainData.push(this.getSavedData(block));
});
const extractedData = await Promise.all(chainData);
const extractedData = await Promise.all(chainData) as Array<Pick<SavedData, 'data' | 'tool'>>;
const sanitizedData = await sanitizeBlocks(extractedData, (name) => {
return Tools.blockTools.get(name).sanitizeConfig;
});
return this.makeOutput(sanitizedData);
} catch (e) {
_.logLabeled(`Saving failed due to the Error %o`, 'error', e);
} finally {
ModificationsObserver.enable();
}

View file

@ -9,10 +9,8 @@ import { I18nInternalNS } from '../../i18n/namespace-internal';
import Shortcuts from '../../utils/shortcuts';
import Tooltip from '../../utils/tooltip';
import { ModuleConfig } from '../../../types-internal/module-config';
import EventsDispatcher from '../../utils/events';
import InlineTool from '../../tools/inline';
import { CommonInternalSettings } from '../../tools/base';
import BlockTool from '../../tools/block';
/**
* Inline Toolbar elements

View file

@ -660,7 +660,7 @@ export function deprecationAssert(condition: boolean, oldProperty: string, newPr
* @param propertyKey - method or accessor name
* @param descriptor - property descriptor
*/
export function cacheable<Target, Value, Arguments extends any[] = any[]>(
export function cacheable<Target, Value, Arguments extends unknown[] = unknown[]>(
target: Target,
propertyKey: string,
descriptor: PropertyDescriptor
@ -672,7 +672,7 @@ export function cacheable<Target, Value, Arguments extends any[] = any[]>(
/**
* Override get or value descriptor property to cache return value
*
* @param args
* @param args - method args
*/
descriptor[propertyToOverride] = function (...args: Arguments): Value {
/**
@ -688,12 +688,12 @@ export function cacheable<Target, Value, Arguments extends any[] = any[]>(
/**
* If get accessor has been overridden, we need to override set accessor to clear cache
*
* @param value
* @param value - value to set
*/
if (propertyToOverride === 'get' && descriptor.set) {
const originalSet = descriptor.set;
descriptor.set = function (value: any): void {
descriptor.set = function (value: unknown): void {
delete target[cacheKey];
originalSet.apply(this, value);

View file

@ -11,7 +11,7 @@
* @typedef {Events} Events
* @property {object} subscribers - all subscribers grouped by event name
*/
export default class EventsDispatcher {
export default class EventsDispatcher<Events extends string = string> {
/**
* Object with events` names as key and array of callback functions as value
*
@ -25,7 +25,7 @@ export default class EventsDispatcher {
* @param {string} eventName - event name
* @param {Function} callback - subscriber
*/
public on(eventName: string, callback: (data: object) => object): void {
public on(eventName: Events, callback: (data: object) => object): void {
if (!(eventName in this.subscribers)) {
this.subscribers[eventName] = [];
}
@ -40,7 +40,7 @@ export default class EventsDispatcher {
* @param {string} eventName - event name
* @param {Function} callback - subscriber
*/
public once(eventName: string, callback: (data: object) => object): void {
public once(eventName: Events, callback: (data: object) => object): void {
if (!(eventName in this.subscribers)) {
this.subscribers[eventName] = [];
}
@ -67,7 +67,7 @@ export default class EventsDispatcher {
* @param {string} eventName - event name
* @param {object} data - subscribers get this data when they were fired
*/
public emit(eventName: string, data?: object): void {
public emit(eventName: Events, data?: object): void {
if (!this.subscribers[eventName]) {
return;
}
@ -85,7 +85,7 @@ export default class EventsDispatcher {
* @param {string} eventName - event name
* @param {Function} callback - event handler
*/
public off(eventName: string, callback: (data: object) => object): void {
public off(eventName: Events, callback: (data: object) => object): void {
for (let i = 0; i < this.subscribers[eventName].length; i++) {
if (this.subscribers[eventName][i] === callback) {
delete this.subscribers[eventName][i];

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
/**
* CodeX Sanitizer
*

View file

@ -6,7 +6,6 @@ import Toolbox from '../components/modules/toolbar/toolbox';
import BlockSettings from '../components/modules/toolbar/blockSettings';
import Paste from '../components/modules/paste';
import DragNDrop from '../components/modules/dragNDrop';
import ModificationsObserver from '../components/modules/modificationsObserver';
import Renderer from '../components/modules/renderer';
import Tools from '../components/modules/tools';
import API from '../components/modules/api/index';
@ -32,6 +31,7 @@ import TooltipAPI from '../components/modules/api/tooltip';
import ReadOnly from '../components/modules/readonly';
import ReadOnlyAPI from '../components/modules/api/readonly';
import I18nAPI from '../components/modules/api/i18n';
import ModificationsObserver from '../components/modules/modificationsObserver';
export interface EditorModules {
UI: UI;
@ -45,7 +45,6 @@ export interface EditorModules {
ConversionToolbar: ConversionToolbar;
Paste: Paste;
DragNDrop: DragNDrop;
ModificationsObserver: ModificationsObserver;
Renderer: Renderer;
Tools: Tools;
API: API;
@ -68,4 +67,5 @@ export interface EditorModules {
ReadOnly: ReadOnly;
ReadOnlyAPI: ReadOnlyAPI;
I18nAPI: I18nAPI;
ModificationsObserver: ModificationsObserver;
}

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* tslint:disable:no-var-requires */
/**
* This file contains connection of Cypres plugins
@ -6,7 +7,7 @@ const webpackConfig = require('../../../webpack.config.js');
const preprocessor = require('@cypress/webpack-preprocessor');
const codeCoverageTask = require('@cypress/code-coverage/task');
module.exports = (on, config): any => {
module.exports = (on, config): unknown => {
/**
* Add Cypress task to get code coverage
*/

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* This file contains custom commands for Cypress.
* Also it can override the existing commands.
@ -40,7 +41,7 @@ Cypress.Commands.add('createEditor', (editorConfig: EditorConfig = {}): Chainabl
/**
* Paste command to dispatch paste event
*
* @usage
* Usage
* cy.get('div').paste({'text/plain': 'Text', 'text/html': '<b>Text</b>'})
*
* @param data - map with MIME type as a key and data as value
@ -66,7 +67,7 @@ Cypress.Commands.add('paste', {
/**
* Copy command to dispatch copy event on subject
*
* @usage
* Usage:
* cy.get('div').copy().then(data => {})
*/
Cypress.Commands.add('copy', { prevSubject: true }, async (subject) => {
@ -92,7 +93,7 @@ Cypress.Commands.add('copy', { prevSubject: true }, async (subject) => {
/**
* Cut command to dispatch cut event on subject
*
* @usage
* Usage:
* cy.get('div').cut().then(data => {})
*/
Cypress.Commands.add('cut', { prevSubject: true }, async (subject) => {

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* There will be described test cases of 'blocks.*' API
*/

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Header from '../../../example/tools/header';
import { nanoid } from 'nanoid';

View file

@ -1,5 +1,5 @@
/* tslint:disable:max-classes-per-file */
/* eslint-disable @typescript-eslint/ban-ts-ignore */
/* eslint-disable @typescript-eslint/ban-ts-ignore,@typescript-eslint/no-explicit-any,jsdoc/require-jsdoc */
import Tools from '../../../../src/components/modules/tools';
import { EditorConfig } from '../../../../types';
import BlockTool from '../../../../src/components/tools/block';
@ -86,8 +86,8 @@ describe('Tools module', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
public static prepare(): void {}
} as any,
inlineToolbar: ['inlineTool2'],
tunes: ['blockTune2']
inlineToolbar: [ 'inlineTool2' ],
tunes: [ 'blockTune2' ],
},
withFailedPrepare: class {
public static prepare(): void {

View file

@ -0,0 +1,127 @@
import Header from '../../../example/tools/header';
/**
* @todo Add checks that correct block API object is passed to onChange
* @todo Add cases for native inputs changes
*/
describe('onChange callback', () => {
const config = {
tools: {
header: Header,
},
onChange: (): void => {
console.log('something changed');
},
};
beforeEach(() => {
if (this && this.editorInstance) {
this.editorInstance.destroy();
} else {
cy.spy(config, 'onChange').as('onChange');
cy.createEditor(config).as('editorInstance');
}
});
it('should fire onChange callback on block insertion', () => {
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click()
.type('{enter}');
cy.get('@onChange').should('be.called');
});
it('should fire onChange callback on typing into block', () => {
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click()
.type('some text');
cy.get('@onChange').should('be.called');
});
it('should fire onChange callback on block replacement', () => {
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click();
cy.get('[data-cy=editorjs]')
.get('div.ce-toolbar__plus')
.click();
cy.get('[data-cy=editorjs]')
.get('li.ce-toolbox__button[data-tool=header]')
.click();
cy.get('@onChange').should('be.calledWithMatch', Cypress.sinon.match.any, Cypress.sinon.match({ name: 'header' }));
});
it('should fire onChange callback on tune modifier', () => {
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click();
cy.get('[data-cy=editorjs]')
.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]')
.get('div.ce-block')
.click();
cy.get('[data-cy=editorjs]')
.get('span.ce-toolbar__settings-btn')
.click();
cy.get('[data-cy=editorjs]')
.get('span.cdx-settings-button[data-level=1]')
.click();
cy.get('@onChange').should('be.calledWithMatch', Cypress.sinon.match.any, Cypress.sinon.match({ name: 'header' }));
});
it('should fire onChange callback when block is removed', () => {
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click();
cy.get('[data-cy=editorjs]')
.get('span.ce-toolbar__settings-btn')
.click();
cy.get('[data-cy=editorjs]')
.get('div.ce-settings__button--delete')
.click()
.click();
cy.get('@onChange').should('be.called');
});
it('should fire onChange callback when block is moved', () => {
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click()
.type('{enter}');
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.last()
.click();
cy.get('[data-cy=editorjs]')
.get('span.ce-toolbar__settings-btn')
.click();
cy.get('[data-cy=editorjs]')
.get('div.ce-tune-move-up')
.click();
cy.get('@onChange').should('be.called');
});
});

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
describe('Output sanitisation', () => {
beforeEach(() => {
if (this && this.editorInstance) {

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* tslint:disable:max-classes-per-file */
import { BlockToolData, ToolSettings } from '../../../../types';
import { ToolType } from '../../../../src/components/tools/base';

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* tslint:disable:max-classes-per-file */
import { ToolSettings } from '../../../../types';
import { ToolType } from '../../../../src/components/tools/base';

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* tslint:disable:max-classes-per-file */
import { ToolSettings } from '../../../../types';
import { ToolType } from '../../../../src/components/tools/base';

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ToolsCollection from '../../../../src/components/tools/collection';
import BlockTool from '../../../../src/components/tools/block';
import InlineTool from '../../../../src/components/tools/inline';

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import LinkInlineTool from '../../../../src/components/inline-tools/inline-tool-link';
import MoveUpTune from '../../../../src/components/block-tunes/block-tune-move-up';
import ToolsFactory from '../../../../src/components/tools/factory';

View file

@ -1,5 +1,5 @@
import {ToolConstructable, ToolSettings} from '../tools';
import {API, LogLevels, OutputData} from '../index';
import {API, BlockAPI, LogLevels, OutputData} from '../index';
import {SanitizerConfig} from './sanitizer-config';
import {I18nConfig} from './i18n-config';
@ -88,8 +88,9 @@ export interface EditorConfig {
/**
* Fires when something changed in DOM
* @param {API} api - editor.js api
* @param block - changed block API
*/
onChange?(api: API): void;
onChange?(api: API, block: BlockAPI): void;
/**
* Defines default toolbar for all tools.