[Feature] BlockAPI Interface (#1075)

This commit is contained in:
George Berezhnoy 2020-05-27 22:49:19 +03:00 committed by GitHub
parent 7c3bf76050
commit ffe5bbc8fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 544 additions and 226 deletions

2
dist/editor.js vendored

File diff suppressed because one or more lines are too long

View file

@ -3,6 +3,7 @@
### 2.18
- `New` *I18n API* — Ability to provide internalization for Editor.js core and tools. [#751](https://github.com/codex-team/editor.js/issues/751)
- `New` — Block API that allows you to access certain Block properties and methods
- `Improvements` - TSLint (deprecated) replaced with ESLint, old config changed to [CodeX ESLint Config](https://github.com/codex-team/eslint-config).
- `Improvements` - Fix many code-style issues, add missed annotations.
- `Improvements` - Adjusted GitHub action for ESLint.
@ -20,6 +21,9 @@
- `Fix` - Public getter `shortcut` now works for Inline Tools [#1132](https://github.com/codex-team/editor.js/issues/1132)
- `Fix` - `CMD+A` handler removed after Editor.js destroy [#1133](https://github.com/codex-team/editor.js/issues/1133)
> *Breaking changes* `blocks.getBlockByIndex` method now returns BlockAPI object. To access old value, use BlockAPI.holder property
### 2.17
- `Improvements` - Editor's [onchange callback](https://editorjs.io/configuration#editor-modifications-callback) now accepts an API as a parameter

View file

@ -8,9 +8,33 @@ Most actual API described by [this interface](../types/api/index.d.ts).
---
Blocks have access to the public methods provided by Editor.js API Module. Plugin and Tune Developers
Tools have access to the public methods provided by Editor.js API Module. Plugin and Tune Developers
can use Editor\`s API as they want.
## Block API
API for certain Block methods and properties. You can access it through `editor.api.block.getBlockByIndex` method or get it form `block` property of [Tool constructor](../types/tools/block-tool.d.ts) argument.
`name: string` — Block's Tool name (key, specified in `tools` property of initial configuration)
`config: ToolConfig` — Tool config passed on Editor initialization
`holder: HTMLElement` — HTML Element that wraps Tool's HTML content
`isEmpty: boolean``true` if Block has any editable content
`selected: boolean` - `true` if Block is selected with Cross-Block Selection
`set stretched(state: boolean)` — set Block's stretch state
`stretched: boolean``true` if Block is stretched
`call(methodName: string, param?: object): void` — method to call any Tool's instance methods with checks and error handlers under-the-hood. For example, [Block lifecycle hooks](./tools.md#block-lifecycle-hooks)
`save(): Promise<void|SavedData>` — returns data saved from current Block's state, including Tool name and saving exec time
`validate(data: BlockToolData): Promise<boolean>` — calls Tool's validate method if exists
## Api object description
Common API interface.
@ -43,11 +67,11 @@ use 'move' instead)
`getCurrentBlockIndex()` - current Block index
`getBlockByIndex(index: Number)` - returns Block with passed index
`getBlockByIndex(index: Number)` - returns Block API object by passed index
`getBlocksCount()` - returns Blocks count
`stretchBlock(index: number, status: boolean)` - make Block stretched
`stretchBlock(index: number, status: boolean)` - _Deprecated. Use Block API interface instead._ make Block stretched.
`insertNewBlock()` - __Deprecated__ insert new Block after working place

View file

@ -12,11 +12,12 @@ Each Tool should have an installation guide.
Each Tool's instance called with an params object.
| Param | Type | Description |
| ------ | ------------------- | ----------------------------------------------- |
| api | [`IAPI`][iapi-link] | Editor.js's API methods |
| config | `object` | Special configuration params passed in «config» |
| data | `object` | Data to be rendered in this Tool |
| Param | Type | Description |
| ------ | ------------------------------------------------------ | ----------------------------------------------- |
| api | [`IAPI`](../types/index.d.ts) | Editor.js's API methods |
| config | [`ToolConfig`](../types/tools/tool-config.d.ts) | Special configuration params passed in «config» |
| data | [`BlockToolData`](../types/tools/block-tool-data.d.ts) | Data to be rendered in this Tool |
| block | [`BlockAPI`](../types/api/block.d.ts) | Block's API methods |
[iapi-link]: ../src/types-internal/api.ts
@ -228,14 +229,14 @@ onPaste (event) {
### Disable paste handling
If you need to disable paste handling on your Tool for some reason, you can provide `false` as `pasteConfig` value.
If you need to disable paste handling on your Tool for some reason, you can provide `false` as `pasteConfig` value.
That way paste event won't be processed if fired on your Tool:
```javascript
static get pasteConfig {
return false;
}
```
```
## Sanitize <a name="sanitize"></a>
@ -364,7 +365,7 @@ Editor.js has a Conversion Toolbar that allows user to convert one Block to anot
2. You can add ability to convert other Tools to your Tool. Specify «import» property of `conversionConfig`.
Conversion Toolbar will be shown only near Blocks that specified an «export» rule, when user selected almost all block's content.
This Toolbar will contain only Tools that specified an «import» rule.
This Toolbar will contain only Tools that specified an «import» rule.
Example:
@ -391,11 +392,11 @@ class Header {
### Your Tool -> other Tool
The «export» field specifies how to represent your Tool's data as a string to pass it to other tool.
The «export» field specifies how to represent your Tool's data as a string to pass it to other tool.
It can be a `String` or a `Function`.
`String` means a key of your Tool data object that should be used as string to export.
`String` means a key of your Tool data object that should be used as string to export.
`Function` is a method that accepts your Tool data and compose a string to export from it. See example below:
@ -411,7 +412,7 @@ class ListTool {
type: 'ordered'
}
}
static get conversionConfig() {
return {
export: (data) => {
@ -425,11 +426,11 @@ class ListTool {
### Other Tool -> your Tool
The «import» rule specifies how to create your Tool's data object from the string created by original block.
The «import» rule specifies how to create your Tool's data object from the string created by original block.
It can be a `String` or a `Function`.
It can be a `String` or a `Function`.
`String` means the key in tool data that will be filled by an exported string.
`String` means the key in tool data that will be filled by an exported string.
For example, `import: 'text'` means that `constructor` of your block will accept a `data` object with `text` property filled with string composed by original block.
`Function` allows you to specify own logic, how a string should be converted to your tool data. For example:
@ -442,13 +443,13 @@ class ListTool {
type: 'unordered'
}
}
static get conversionConfig() {
return {
// ... export rule
// ... export rule
/**
* In this example, List Tool creates items by splitting original text by a dot symbol.
* In this example, List Tool creates items by splitting original text by a dot symbol.
*/
import: (string) => {
const items = string.split('.');

View file

@ -83,7 +83,8 @@ export default class MoveDownTune implements BlockTune {
return;
}
const nextBlockElement = this.api.blocks.getBlockByIndex(currentBlockIndex + 1);
const nextBlock = this.api.blocks.getBlockByIndex(currentBlockIndex + 1);
const nextBlockElement = nextBlock.holder;
const nextBlockCoords = nextBlockElement.getBoundingClientRect();
let scrollOffset = Math.abs(window.innerHeight - nextBlockElement.offsetHeight);

View file

@ -81,8 +81,10 @@ export default class MoveUpTune implements BlockTune {
return;
}
const currentBlockElement = this.api.blocks.getBlockByIndex(currentBlockIndex);
const previousBlockElement = this.api.blocks.getBlockByIndex(currentBlockIndex - 1);
const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
const currentBlockElement = currentBlock.holder;
const previousBlock = this.api.blocks.getBlockByIndex(currentBlockIndex - 1);
const previousBlockElement = previousBlock.holder;
/**
* Here is two cases:

114
src/components/block/api.ts Normal file
View file

@ -0,0 +1,114 @@
import Block from './index';
import { BlockToolData, ToolConfig } from '../../../types/tools';
import { SavedData } from '../../types-internal/block-data';
import { BlockAPI as BlockAPIInterface } from '../../../types/api';
/**
* Constructs new BlockAPI object
*
* @class
*
* @param {Block} block - Block to expose
*/
function BlockAPI(block: Block): void {
const blockAPI: BlockAPIInterface = {
/**
* Tool name
*
* @returns {string}
*/
get name(): string {
return block.name;
},
/**
* Tool config passed on Editor's initialization
*
* @returns {ToolConfig}
*/
get config(): ToolConfig {
return block.config;
},
/**
* .ce-block element, that wraps plugin contents
*
* @returns {HTMLElement}
*/
get holder(): HTMLElement {
return block.holder;
},
/**
* True if Block content is empty
*
* @returns {boolean}
*/
get isEmpty(): boolean {
return block.isEmpty;
},
/**
* True if Block is selected with Cross-Block selection
*
* @returns {boolean}
*/
get selected(): boolean {
return block.selected;
},
/**
* Set Block's stretch state
*
* @param {boolean} state state to set
*/
set stretched(state: boolean) {
block.stretched = state;
},
/**
* True if Block is stretched
*
* @returns {boolean}
*/
get stretched(): boolean {
return block.stretched;
},
/**
* Call Tool method with errors handler under-the-hood
*
* @param {string} methodName - method to call
* @param {object} param - object with parameters
*
* @returns {void}
*/
call(methodName: string, param?: object): void {
block.call(methodName, param);
},
/**
* Save Block content
*
* @returns {Promise<void|SavedData>}
*/
save(): Promise<void|SavedData> {
return block.save();
},
/**
* Validate Block data
*
* @param {BlockToolData} data - data to validate
*
* @returns {Promise<boolean>}
*/
validate(data: BlockToolData): Promise<boolean> {
return block.validate(data);
},
};
Object.setPrototypeOf(this, blockAPI);
}
export default BlockAPI;

View file

@ -1,23 +1,57 @@
import {
BlockAPI as BlockAPIInterface,
BlockTool,
BlockToolConstructable,
BlockToolData,
BlockTune,
BlockTuneConstructable,
SanitizerConfig,
ToolConfig
} from '../../types';
ToolConfig,
ToolSettings
} from '../../../types';
import { SavedData } from '../../types-internal/block-data';
import $ from '../dom';
import * as _ from '../utils';
import ApiModule from '../modules/api';
import SelectionUtils from '../selection';
import BlockAPI from './api';
import { ToolType } from '../modules/tools';
import { SavedData } from '../types-internal/block-data';
import $ from './dom';
import * as _ from './utils';
import ApiModule from './../components/modules/api';
/** Import default tunes */
import MoveUpTune from './block-tunes/block-tune-move-up';
import DeleteTune from './block-tunes/block-tune-delete';
import MoveDownTune from './block-tunes/block-tune-move-down';
import SelectionUtils from './selection';
import { ToolType } from './modules/tools';
import MoveUpTune from '../block-tunes/block-tune-move-up';
import DeleteTune from '../block-tunes/block-tune-delete';
import MoveDownTune from '../block-tunes/block-tune-move-down';
/**
* Interface describes Block class constructor argument
*/
interface BlockConstructorOptions {
/**
* Tool's name
*/
name: string;
/**
* Initial Block data
*/
data: BlockToolData;
/**
* Tool's class or constructor function
*/
Tool: BlockToolConstructable;
/**
* Tool settings from initial config
*/
settings: ToolSettings;
/**
* Editor's API methods
*/
api: ApiModule;
}
/**
* @class Block
@ -98,6 +132,11 @@ export default class Block {
*/
public tunes: BlockTune[];
/**
* Tool's user configuration
*/
public readonly config: ToolConfig;
/**
* Cached inputs
*
@ -149,29 +188,42 @@ export default class Block {
}, this.modificationDebounceTimer);
/**
* @class
* @param {string} toolName - Tool name that passed on initialization
* @param {object} toolInstance passed Tool`s instance that rendered the Block
* @param {object} toolClass Tool's class
* @param {object} settings - default settings
* @param {ApiModule} apiModule - Editor API module for pass it to the Block Tunes
* Current block API interface
*/
constructor(
toolName: string,
toolInstance: BlockTool,
toolClass: BlockToolConstructable,
settings: ToolConfig,
apiModule: ApiModule
) {
this.name = toolName;
this.tool = toolInstance;
this.class = toolClass;
private readonly blockAPI: BlockAPIInterface;
/**
* @class
* @param {string} tool - Tool name that passed on initialization
* @param {BlockToolData} data - Tool's initial data
* @param {BlockToolConstructable} Tool Tool's class
* @param {ToolSettings} settings - default tool's config
* @param {ApiModule} api - Editor API module for pass it to the Block Tunes
*/
constructor({
name,
data,
Tool,
settings,
api,
}: BlockConstructorOptions) {
this.name = name;
this.class = Tool;
this.settings = settings;
this.api = apiModule;
this.holder = this.compose();
this.config = settings.config || {};
this.api = api;
this.blockAPI = new BlockAPI(this);
this.mutationObserver = new MutationObserver(this.didMutated);
this.tool = new Tool({
data,
config: this.config,
api: this.api.getMethodsForTool(name, ToolType.Block),
block: this.blockAPI,
});
this.holder = this.compose();
/**
* @type {BlockTune[]}
*/
@ -285,37 +337,12 @@ export default class Block {
return this.inputs[this.inputIndex - 1];
}
/**
* Returns Plugins content
*
* @returns {HTMLElement}
*/
public get pluginsContent(): HTMLElement {
const blockContentNodes = this.holder.querySelector(`.${Block.CSS.content}`);
if (blockContentNodes && blockContentNodes.childNodes.length) {
/**
* Editors Block content can contain different Nodes from extensions
* We use DOM isExtensionNode to ignore such Nodes and return first Block that does not match filtering list
*/
for (let child = blockContentNodes.childNodes.length - 1; child >= 0; child--) {
const contentNode = blockContentNodes.childNodes[child];
if (!$.isExtensionNode(contentNode)) {
return contentNode as HTMLElement;
}
}
}
return null;
}
/**
* Get Block's JSON data
*
* @returns {object}
*/
public get data(): BlockToolData {
public get data(): Promise<BlockToolData> {
return this.save().then((savedObject) => {
if (savedObject && !_.isEmpty(savedObject.data)) {
return savedObject.data;
@ -390,6 +417,13 @@ export default class Block {
this.holder.classList.toggle(Block.CSS.focused, state);
}
/**
* Get Block's focused state
*/
public get focused(): boolean {
return this.holder.classList.contains(Block.CSS.focused);
}
/**
* Set selected state
* We don't need to mark Block as Selected when it is empty
@ -422,6 +456,15 @@ export default class Block {
this.holder.classList.toggle(Block.CSS.wrapperStretched, state);
}
/**
* Return Block's stretched state
*
* @returns {boolean}
*/
public get stretched(): boolean {
return this.holder.classList.contains(Block.CSS.wrapperStretched);
}
/**
* Toggle drop target state
*
@ -431,6 +474,31 @@ export default class Block {
this.holder.classList.toggle(Block.CSS.dropTarget, state);
}
/**
* Returns Plugins content
*
* @returns {HTMLElement}
*/
public get pluginsContent(): HTMLElement {
const blockContentNodes = this.holder.querySelector(`.${Block.CSS.content}`);
if (blockContentNodes && blockContentNodes.childNodes.length) {
/**
* Editors Block content can contain different Nodes from extensions
* We use DOM isExtensionNode to ignore such Nodes and return first Block that does not match filtering list
*/
for (let child = blockContentNodes.childNodes.length - 1; child >= 0; child--) {
const contentNode = blockContentNodes.childNodes[child];
if (!$.isExtensionNode(contentNode)) {
return contentNode as HTMLElement;
}
}
}
return null;
}
/**
* Calls Tool's method
*
@ -444,6 +512,14 @@ export default class Block {
* call Tool's method with the instance context
*/
if (this.tool[methodName] && this.tool[methodName] instanceof Function) {
if (methodName === BlockToolAPI.APPEND_CALLBACK) {
_.log(
'`appendCallback` hook is deprecated and will be removed in the next major release. ' +
'Use `rendered` hook instead',
'warn'
);
}
try {
// eslint-disable-next-line no-useless-call
this.tool[methodName].call(this.tool, params);
@ -538,7 +614,7 @@ export default class Block {
return tunesList.map(({ name, Tune }: {name: string; Tune: BlockTuneConstructable}) => {
return new Tune({
api: this.api.getMethodsForTool(name, ToolType.Tune),
settings: this.settings,
settings: this.config,
});
});
}

View file

@ -133,7 +133,8 @@ export default class Core {
if (config.holderId && !config.holder) {
config.holder = config.holderId;
config.holderId = null;
_.log('holderId property will deprecated in next major release, use holder property instead.', 'warn');
_.log('holderId property is deprecated and will be removed in the next major release. ' +
'Use holder property instead.', 'warn');
}
/**

View file

@ -1,8 +1,9 @@
import Module from '../../__module';
import { Blocks } from '../../../../types/api';
import { BlockAPI as BlockAPIInterface, Blocks } from '../../../../types/api';
import { BlockToolData, OutputData, ToolConfig } from '../../../../types';
import * as _ from './../../utils';
import BlockAPI from '../../block/api';
/**
* @class BlocksAPI
@ -22,7 +23,7 @@ export default class BlocksAPI extends Module {
delete: (index?: number): void => this.delete(index),
swap: (fromIndex: number, toIndex: number): void => this.swap(fromIndex, toIndex),
move: (toIndex: number, fromIndex?: number): void => this.move(toIndex, fromIndex),
getBlockByIndex: (index: number): HTMLElement => this.getBlockByIndex(index),
getBlockByIndex: (index: number): BlockAPIInterface => this.getBlockByIndex(index),
getCurrentBlockIndex: (): number => this.getCurrentBlockIndex(),
getBlocksCount: (): number => this.getBlocksCount(),
stretchBlock: (index: number, status = true): void => this.stretchBlock(index, status),
@ -56,10 +57,10 @@ export default class BlocksAPI extends Module {
*
* @returns {HTMLElement}
*/
public getBlockByIndex(index: number): HTMLElement {
public getBlockByIndex(index: number): BlockAPIInterface {
const block = this.Editor.BlockManager.getBlockByIndex(index);
return block.holder;
return new BlockAPI(block);
}
/**
@ -70,6 +71,12 @@ export default class BlocksAPI extends Module {
* @deprecated use 'move' instead
*/
public swap(fromIndex: number, toIndex: number): void {
_.log(
'`blocks.swap()` method is deprecated and will be removed in the next major release. ' +
'Use `block.move()` method instead',
'info'
);
this.Editor.BlockManager.swap(fromIndex, toIndex);
/**
@ -161,8 +168,16 @@ export default class BlocksAPI extends Module {
*
* @param {number} index - index of Block to stretch
* @param {boolean} status - true to enable, false to disable
*
* @deprecated Use BlockAPI interface to stretch Blocks
*/
public stretchBlock(index: number, status = true): void {
_.log(
'`blocks.stretchBlock()` method is deprecated and will be removed in the next major release. ' +
'Use BlockAPI interface instead',
'warn'
);
const block = this.Editor.BlockManager.getBlockByIndex(index);
if (!block) {
@ -188,14 +203,13 @@ export default class BlocksAPI extends Module {
index?: number,
needToFocus?: boolean
): void => {
this.Editor.BlockManager.insert(
type,
this.Editor.BlockManager.insert({
tool: type,
data,
config,
index,
needToFocus
);
};
needToFocus,
});
}
/**
* Insert new Block
@ -206,7 +220,7 @@ export default class BlocksAPI extends Module {
* @deprecated with insert() method
*/
public insertNewBlock(): void {
_.log('Method blocks.insertNewBlock() is deprecated and it will be removed in next major release. ' +
_.log('Method blocks.insertNewBlock() is deprecated and it will be removed in the next major release. ' +
'Use blocks.insert() instead.', 'warn');
this.insert();
}

View file

@ -11,7 +11,7 @@ import Module from '../__module';
import $ from '../dom';
import * as _ from '../utils';
import Blocks from '../blocks';
import { BlockTool, BlockToolConstructable, BlockToolData, PasteEvent, ToolConfig } from '../../../types';
import { BlockToolConstructable, BlockToolData, PasteEvent } from '../../../types';
/**
* @typedef {BlockManager} BlockManager
@ -204,16 +204,21 @@ export default class BlockManager extends Module {
/**
* Creates Block instance by tool name
*
* @param {string} toolName - tools passed in editor config {@link EditorConfig#tools}
* @param {object} data - constructor params
* @param {object} settings - block settings
* @param {string} tool - tools passed in editor config {@link EditorConfig#tools}
* @param {BlockToolData} [data] - constructor params
*
* @returns {Block}
*/
public composeBlock(toolName: string, data: BlockToolData = {}, settings: ToolConfig = {}): Block {
const toolInstance = this.Editor.Tools.construct(toolName, data) as BlockTool;
const toolClass = this.Editor.Tools.available[toolName] as BlockToolConstructable;
const block = new Block(toolName, toolInstance, toolClass, settings, this.Editor.API);
public composeBlock({ tool, data = {} }: {tool: string; data?: BlockToolData}): Block {
const settings = this.Editor.Tools.getToolSettings(tool);
const Tool = this.Editor.Tools.available[tool] as BlockToolConstructable;
const block = new Block({
name: tool,
data,
Tool,
settings,
api: this.Editor.API,
});
this.bindEvents(block);
@ -223,32 +228,63 @@ export default class BlockManager extends Module {
/**
* Insert new block into _blocks
*
* @param {string} toolName plugin name, by default method inserts initial block type
* @param {string} tool plugin name, by default method inserts initial block type
* @param {object} data plugin data
* @param {object} settings - default settings
* @param {number} index - index where to insert new Block
* @param {boolean} needToFocus - flag shows if needed to update current Block index
* @param {boolean} replace - flag shows if block by passed index should be replaced with inserted one
*
* @returns {Block}
*/
public insert(
toolName: string = this.config.initialBlock,
data: BlockToolData = {},
settings: ToolConfig = {},
index: number = this.currentBlockIndex + 1,
needToFocus = true
): Block {
const block = this.composeBlock(toolName, data, settings);
public insert({
tool = this.config.initialBlock,
data = {},
index = this.currentBlockIndex + 1,
needToFocus = true,
replace = false,
}: {
tool?: string;
data?: BlockToolData;
index?: number;
needToFocus?: boolean;
replace?: boolean;
} = {}): Block {
const block = this.composeBlock({
tool,
data,
});
this._blocks[index] = block;
this._blocks.insert(index, block, replace);
if (needToFocus) {
this.currentBlockIndex = index;
} else if (index <= this.currentBlockIndex) {
this.currentBlockIndex++;
}
return block;
}
/**
* Replace current working block
*
* @param {string} tool plugin name
* @param {BlockToolData} data plugin data
*
* @returns {Block}
*/
public replace({
tool = this.config.initialBlock,
data = {},
}): Block {
return this.insert({
tool,
data,
index: this.currentBlockIndex,
replace: true,
});
}
/**
* Insert pasted content. Call onPaste callback after insert.
*
@ -261,13 +297,10 @@ export default class BlockManager extends Module {
pasteEvent: PasteEvent,
replace = false
): Block {
let block;
if (replace) {
block = this.replace(toolName);
} else {
block = this.insert(toolName);
}
const block = this.insert({
tool: toolName,
replace,
});
try {
block.call(BlockToolAPI.ON_PASTE, pasteEvent);
@ -289,7 +322,7 @@ export default class BlockManager extends Module {
* @returns {Block} inserted Block
*/
public insertInitialBlockAtIndex(index: number, needToFocus = false): Block {
const block = this.composeBlock(this.config.initialBlock, {}, {});
const block = this.composeBlock({ tool: this.config.initialBlock });
this._blocks[index] = block;
@ -381,7 +414,7 @@ export default class BlockManager extends Module {
*
* @returns {number|undefined}
*/
public removeSelectedBlocks(): number|undefined {
public removeSelectedBlocks(): number | undefined {
let firstSelectedBlockIndex;
/**
@ -439,28 +472,7 @@ export default class BlockManager extends Module {
*
* @type {Block}
*/
return this.insert(this.config.initialBlock, data);
}
/**
* Replace current working block
*
* @param {string} toolName plugin name
* @param {BlockToolData} data plugin data
* @param {ToolConfig} settings plugin config
*
* @returns {Block}
*/
public replace(
toolName: string = this.config.initialBlock,
data: BlockToolData = {},
settings: ToolConfig = {}
): Block {
const block = this.composeBlock(toolName, data, settings);
this._blocks.insert(this.currentBlockIndex, block, true);
return block;
return this.insert({ data });
}
/**
@ -637,7 +649,7 @@ export default class BlockManager extends Module {
this.dropPointer();
if (needAddInitialBlock) {
this.insert(this.config.initialBlock);
this.insert();
}
/**

View file

@ -4,12 +4,12 @@ import * as _ from '../utils';
import {
BlockTool,
BlockToolConstructable,
BlockToolData,
PasteConfig,
PasteEvent,
PasteEventDetail
} from '../../../types';
import Block from '../block';
import { SavedData } from '../../types-internal/block-data';
/**
* Tag substitute object.
@ -735,16 +735,14 @@ export default class Paste extends Module {
/**
* Insert data passed as application/x-editor-js JSON
*
* @param {object} blocks Blocks' data to insert
* @param {Array} blocks Blocks' data to insert
*
* @returns {void}
*/
private insertEditorJSData(blocks: Array<{tool: string; data: BlockToolData}>): void {
private insertEditorJSData(blocks: Array<Pick<SavedData, 'data' | 'tool'>>): void {
const { BlockManager, Tools } = this.Editor;
blocks.forEach(({ tool, data }, i) => {
const settings = this.Editor.Tools.getToolSettings(tool);
let needToReplaceCurrentBlock = false;
if (i === 0) {
@ -753,11 +751,11 @@ export default class Paste extends Module {
needToReplaceCurrentBlock = isCurrentBlockInitial && BlockManager.currentBlock.isEmpty;
}
if (needToReplaceCurrentBlock) {
BlockManager.replace(tool, data, settings);
} else {
BlockManager.insert(tool, data, settings);
}
BlockManager.insert({
tool,
data,
replace: needToReplaceCurrentBlock,
});
});
}

View file

@ -2,8 +2,7 @@ import Module from '../__module';
/* eslint-disable import/no-duplicates */
import * as _ from '../utils';
import { ChainData } from '../utils';
import { BlockToolData } from '../../../types';
import { BlockToolConstructable } from '../../../types/tools';
import { BlockToolConstructable, OutputBlockData } from '../../../types';
/**
* Editor.js Renderer Module
@ -43,9 +42,9 @@ export default class Renderer extends Module {
/**
* Make plugin blocks from array of plugin`s data
*
* @param {BlockToolData[]} blocks - blocks to render
* @param {OutputBlockData[]} blocks - blocks to render
*/
public async render(blocks: BlockToolData[]): Promise<void> {
public async render(blocks: OutputBlockData[]): Promise<void> {
const chainData = blocks.map((block) => ({ function: (): Promise<void> => this.insertBlock(block) }));
const sequence = await _.sequence(chainData as ChainData[]);
@ -63,15 +62,17 @@ export default class Renderer extends Module {
* @param {object} item - Block data to insert
* @returns {Promise<void>}
*/
public async insertBlock(item): Promise<void> {
public async insertBlock(item: OutputBlockData): Promise<void> {
const { Tools, BlockManager } = this.Editor;
const tool = item.type;
const data = item.data;
const settings = item.settings;
if (tool in Tools.available) {
try {
BlockManager.insert(tool, data, settings);
BlockManager.insert({
tool,
data,
});
} catch (error) {
_.log(`Block «${tool}» skipped because of plugins error`, 'warn', data);
throw Error(error);
@ -93,7 +94,10 @@ export default class Renderer extends Module {
stubData.title = toolToolboxSettings.title || userToolboxSettings.title || stubData.title;
}
const stub = BlockManager.insert(Tools.stubTool, stubData, settings);
const stub = BlockManager.insert({
tool: Tools.stubTool,
data: stubData,
});
stub.stretched = true;

View file

@ -37,6 +37,7 @@ import * as _ from '../utils';
import HTMLJanitor from 'html-janitor';
import { BlockToolData, InlineToolConstructable, SanitizerConfig } from '../../../types';
import { SavedData } from '../../types-internal/block-data';
/**
*
@ -60,8 +61,8 @@ export default class Sanitizer extends Module {
* @param {Array<{tool, data: BlockToolData}>} blocksData - blocks' data to sanitize
*/
public sanitizeBlocks(
blocksData: Array<{tool: string; data: BlockToolData}>
): Array<{tool: string; data: BlockToolData}> {
blocksData: Array<Pick<SavedData, 'data' | 'tool'>>
): Array<Pick<SavedData, 'data' | 'tool'>> {
return blocksData.map((block) => {
const toolConfig = this.composeToolConfig(block.tool);

View file

@ -233,7 +233,10 @@ export default class ConversionToolbar extends Module {
return;
}
this.Editor.BlockManager.replace(replacingToolName, newBlockData);
this.Editor.BlockManager.replace({
tool: replacingToolName,
data: newBlockData,
});
this.Editor.BlockSelection.clearSelection();
this.close();

View file

@ -295,13 +295,10 @@ export default class Toolbox extends Module {
const { BlockManager, Caret } = this.Editor;
const { currentBlock } = BlockManager;
let newBlock;
if (currentBlock.isEmpty) {
newBlock = BlockManager.replace(toolName);
} else {
newBlock = BlockManager.insert(toolName);
}
const newBlock = BlockManager.insert({
tool: toolName,
replace: currentBlock.isEmpty,
});
/**
* Apply callback before inserting html

View file

@ -2,8 +2,8 @@ import Paragraph from '../tools/paragraph/dist/bundle';
import Module from '../__module';
import * as _ from '../utils';
import {
BlockTool,
BlockToolConstructable, BlockToolData, EditorConfig,
BlockToolConstructable,
EditorConfig,
InlineTool,
InlineToolConstructable, Tool,
ToolConfig,
@ -325,36 +325,6 @@ export default class Tools extends Module {
this.toolsUnavailable[data.toolName] = this.toolsClasses[data.toolName];
}
/**
* Return Tool`s instance
*
* @param {string} tool tool name
* @param {object} data initial data
*
* @returns {BlockTool}
*/
public construct(tool: string, data: BlockToolData): BlockTool {
const Plugin = this.toolsClasses[tool] as BlockToolConstructable;
/**
* Configuration to be passed to the Tool's constructor
*/
const config = this.toolsSettings[tool][this.USER_SETTINGS.CONFIG] || {};
// Pass placeholder to initial Block config
if (tool === this.config.initialBlock && !config.placeholder) {
config.placeholder = this.config.placeholder;
}
const constructorOptions = {
api: this.Editor.API.getMethodsForTool(tool),
config,
data,
};
return new Plugin(constructorOptions);
}
/**
* Return Inline Tool's instance
*
@ -393,7 +363,16 @@ export default class Tools extends Module {
* @returns {ToolSettings}
*/
public getToolSettings(toolName): ToolSettings {
return this.toolsSettings[toolName];
const settings = this.toolsSettings[toolName];
const config = settings[this.USER_SETTINGS.CONFIG] || {};
// Pass placeholder to initial Block config
if (toolName === this.config.initialBlock && !config.placeholder) {
config.placeholder = this.config.placeholder;
settings[this.USER_SETTINGS.CONFIG] = config;
}
return settings;
}
/**

View file

@ -1,5 +1,5 @@
import $ from '../../dom';
import { API, BlockTool, BlockToolData } from '../../../../types';
import { API, BlockTool, BlockToolData, BlockToolConstructorOptions } from '../../../../types';
export interface StubData extends BlockToolData{
title: string;
@ -52,7 +52,7 @@ export default class Stub implements BlockTool {
* @param data - stub tool data
* @param api - Editor.js API
*/
constructor({ data, api }: {data: StubData; api: API}) {
constructor({ data, api }: BlockToolConstructorOptions<StubData>) {
this.api = api;
this.title = data.title || this.api.i18n.t('Error');
this.subtitle = this.api.i18n.t('The block can not be displayed correctly.');

65
types/api/block.d.ts vendored Normal file
View file

@ -0,0 +1,65 @@
import {BlockToolData, ToolConfig} from '../tools';
import {SavedData} from '../../src/types-internal/block-data';
/**
* @interface BlockAPI Describes Block API methods and properties
*/
export interface BlockAPI {
/**
* Tool name
*/
readonly name: string;
/**
* Tool config passed on Editor's initialization
*/
readonly config: ToolConfig;
/**
* Wrapper of Tool's HTML element
*/
readonly holder: HTMLElement;
/**
* True if Block content is empty
*/
readonly isEmpty: boolean;
/**
* True if Block is selected with Cross-Block selection
*/
readonly selected: boolean;
/**
* Setter sets Block's stretch state
*
* Getter returns true if Block is stretched
*/
stretched: boolean;
/**
* Call Tool method with errors handler under-the-hood
*
* @param {string} methodName - method to call
* @param {object} param - object with parameters
*
* @return {void}
*/
call(methodName: string, param?: object): void;
/**
* Save Block content
*
* @return {Promise<void|SavedData>}
*/
save(): Promise<void|SavedData>;
/**
* Validate Block data
*
* @param {BlockToolData} data
*
* @return {Promise<boolean>}
*/
validate(data: BlockToolData): Promise<boolean>;
}

View file

@ -1,5 +1,6 @@
import {OutputData} from '../data-formats/output-data';
import {BlockToolData, ToolConfig} from "../tools";
import {BlockToolData, ToolConfig} from '../tools';
import {BlockAPI} from './block';
/**
* Describes methods to manipulate with Editor`s blocks
@ -50,7 +51,7 @@ export interface Blocks {
* @param {number} index
* @returns {HTMLElement}
*/
getBlockByIndex(index: number): HTMLElement;
getBlockByIndex(index: number): BlockAPI;
/**
* Returns current Block index
@ -62,6 +63,8 @@ export interface Blocks {
* Mark Block as stretched
* @param {number} index - Block to mark
* @param {boolean} status - stretch status
*
* @deprecated Use BlockAPI interface to stretch Blocks
*/
stretchBlock(index: number, status?: boolean): void;

View file

@ -10,4 +10,5 @@ export * from './toolbar';
export * from './notifier';
export * from './tooltip';
export * from './inline-toolbar';
export * from './block';
export * from './i18n';

View file

@ -1,12 +1,12 @@
import {ToolConstructable, ToolSettings} from '../tools';
import {LogLevels, OutputData, API} from '../index';
import {API, LogLevels, OutputData} from '../index';
import {SanitizerConfig} from './sanitizer-config';
import {I18nConfig} from './i18n-config';
export interface EditorConfig {
/**
* Element where Editor will be append
* @deprecated property will be removed in next major release, use holder instead
* @deprecated property will be removed in the next major release, use holder instead
*/
holderId?: string | HTMLElement;

View file

@ -1,5 +1,3 @@
import {BlockToolData} from '../index';
/**
* Tool onPaste configuration object
*/

View file

@ -1,5 +1,19 @@
import {BlockToolData} from '../tools';
/**
* Output of one Tool
*/
export interface OutputBlockData {
/**
* Too type
*/
type: string;
/**
* Saved Block data
*/
data: BlockToolData;
}
export interface OutputData {
/**
* Editor's version
@ -14,8 +28,5 @@ export interface OutputData {
/**
* Saved Blocks
*/
blocks: Array<{
type: string;
data: BlockToolData
}>;
blocks: OutputBlockData[];
}

5
types/index.d.ts vendored
View file

@ -36,7 +36,9 @@ export {
BaseToolConstructable,
InlineTool,
InlineToolConstructable,
InlineToolConstructorOptions,
BlockToolConstructable,
BlockToolConstructorOptions,
BlockTool,
BlockToolData,
Tool,
@ -65,7 +67,8 @@ export {
DictValue,
I18nConfig,
} from './configs';
export {OutputData} from './data-formats/output-data';
export {OutputData, OutputBlockData} from './data-formats/output-data';
export { BlockAPI } from './api'
/**
* We have a namespace API {@link ./api/index.d.ts} (APIMethods) but we can not use it as interface

View file

@ -1,8 +1,8 @@
import { ConversionConfig, PasteConfig, SanitizerConfig } from '../configs';
import { BlockToolData } from './block-tool-data';
import {BaseTool, BaseToolConstructable, BaseToolConstructorOptions} from './tool';
import {BaseTool, BaseToolConstructable} from './tool';
import { ToolConfig } from './tool-config';
import { API } from '../index';
import {API, BlockAPI} from '../index';
import { PasteEvent } from './paste-events';
import { MoveEvent } from './hook-events';
@ -77,10 +77,11 @@ export interface BlockTool extends BaseTool {
/**
* Describe constructor parameters
*/
export interface BlockToolConstructorOptions extends BaseToolConstructorOptions {
export interface BlockToolConstructorOptions<D extends object = any, C extends object = any> {
api: API;
data: BlockToolData;
config?: ToolConfig;
data: BlockToolData<D>;
config?: ToolConfig<C>;
block?: BlockAPI;
}
export interface BlockToolConstructable extends BaseToolConstructable {

View file

@ -11,5 +11,5 @@ export * from './tool-settings';
export * from './paste-events';
export * from './hook-events';
export type Tool = BaseTool | BlockTool | InlineTool;
export type Tool = BlockTool | InlineTool;
export type ToolConstructable = BlockToolConstructable | InlineToolConstructable;

View file

@ -1,4 +1,4 @@
/**
* Tool configuration object. Specified by Tool developer, so leave it as object
*/
export type ToolConfig = object;
export type ToolConfig<T extends object = any> = T;

View file

@ -1,4 +1,4 @@
import {API, BlockToolData, ToolSettings} from '../index';
import {API} from '../index';
import {ToolConfig} from './tool-config';
import {SanitizerConfig} from '../configs';
@ -30,6 +30,11 @@ export interface BaseToolConstructable {
*/
title?: string;
/**
* Describe constructor parameters
*/
new (config: {api: API, config?: ToolConfig}): BaseTool;
/**
* Tool`s prepare method. Can be async
* @param data