mirror of
https://github.com/codex-team/editor.js
synced 2024-06-08 08:52:15 +02:00
[Feature] Block Tunes API (#1596)
* Add internal wrappers for tools classes * FIx lint * Change tools collections to map * Apply some more refactoring * Make tool instance private field * Add some docs * Fix eslint * Basic implementation for Block Tunes * Small fix for demo * Review changes * Fix * Add common tunes and ToolsCollection class * Fixes after review * Rename tools collections * Readonly fix * Some fixes after review * Apply suggestions from code review Co-authored-by: Peter Savchenko <specc.dev@gmail.com> * Fixes after review * Add docs and changelog * Update docs/block-tunes.md Co-authored-by: Peter Savchenko <specc.dev@gmail.com> * Apply suggestions from code review Co-authored-by: Peter Savchenko <specc.dev@gmail.com> * Update src/components/block/index.ts Co-authored-by: Murod Khaydarov <murod.haydarov@gmail.com> * [Dev] Tools utils tests (#1602) * Add tests for tools utils and coverage report * Fix eslint * Adjust test * Add more tests * Update after code review * Fix test & bump version Co-authored-by: Peter Savchenko <specc.dev@gmail.com> Co-authored-by: Murod Khaydarov <murod.haydarov@gmail.com>
This commit is contained in:
parent
4cfcb656a8
commit
2d89105670
7
.babelrc
7
.babelrc
|
@ -10,5 +10,10 @@
|
||||||
"babel-plugin-add-module-exports",
|
"babel-plugin-add-module-exports",
|
||||||
"babel-plugin-class-display-name",
|
"babel-plugin-class-display-name",
|
||||||
"@babel/plugin-transform-runtime"
|
"@babel/plugin-transform-runtime"
|
||||||
]
|
],
|
||||||
|
"env": {
|
||||||
|
"test": {
|
||||||
|
"plugins": [ "istanbul" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -14,3 +14,6 @@ test/cypress/screenshots
|
||||||
test/cypress/videos
|
test/cypress/videos
|
||||||
|
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
|
"NODE_ENV": "test"
|
||||||
},
|
},
|
||||||
"fixturesFolder": "test/cypress/fixtures",
|
"fixturesFolder": "test/cypress/fixtures",
|
||||||
"integrationFolder": "test/cypress/tests",
|
"integrationFolder": "test/cypress/tests",
|
||||||
"screenshotsFolder": "test/cypress/screenshots",
|
"screenshotsFolder": "test/cypress/screenshots",
|
||||||
"videosFolder": "test/cypress/videos",
|
"videosFolder": "test/cypress/videos",
|
||||||
"supportFile": "test/cypress/support/index.ts"
|
"supportFile": "test/cypress/support/index.ts",
|
||||||
|
"pluginsFile": "test/cypress/plugins/index.ts"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### 2.20.0
|
||||||
|
|
||||||
|
- `New` — [Block Tunes API](block-tunes.md) added
|
||||||
|
|
||||||
### 2.19.3
|
### 2.19.3
|
||||||
|
|
||||||
- `Fix` — Ignore error raised by Shortcut module
|
- `Fix` — Ignore error raised by Shortcut module
|
||||||
|
|
168
docs/block-tunes.md
Normal file
168
docs/block-tunes.md
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
# Block Tunes
|
||||||
|
|
||||||
|
Similar with [Tools](tools.md) represented Blocks, you can create Block Tunes and connect it to particular Tool or for all Tools.
|
||||||
|
|
||||||
|
Block Tunes allows you to set any additional options to Blocks. For example, with corresponded Block Tunes you can mark Block as «spoiler», give it an anchor, set a background, and so on.
|
||||||
|
|
||||||
|
## Base structure
|
||||||
|
|
||||||
|
Tune's class should have the `isTune` property (static getter) set to `true`.
|
||||||
|
|
||||||
|
Block Tune must implement the `render()` method which returns an HTML Element that will be appended to the Block Settings panel.
|
||||||
|
|
||||||
|
- `render()` — create a button
|
||||||
|
|
||||||
|
Also, you can provide optional methods
|
||||||
|
|
||||||
|
- `wrap()` — wraps Block content with own HTML elements
|
||||||
|
- `save()` — save Tunes state on Editor's save
|
||||||
|
|
||||||
|
At the constructor of Tune's class exemplar you will receive an object with following parameters:
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
| --------- | ----------- |
|
||||||
|
| api | Editor's [API](api.md) obejct |
|
||||||
|
| settings | Configuration of Block Tool Tune is connected to (might be useful in some cases) |
|
||||||
|
| block | [Block API](api.md#block-api) methods for block Tune is connected to |
|
||||||
|
| data | Saved Tune data |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### render(): HTMLElement
|
||||||
|
|
||||||
|
Method that returns button to append to the block settings area
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
Method does not accept any parameters
|
||||||
|
|
||||||
|
#### Return value
|
||||||
|
|
||||||
|
type | description |
|
||||||
|
-- | -- |
|
||||||
|
`HTMLElement` | element that will be added to the block settings area |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### wrap(blockContent: HTMLElement): HTMLElement
|
||||||
|
|
||||||
|
Method that accepts Block's content and wrap it with your own layout.
|
||||||
|
Might be useful if you want to modify Block appearance.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Tune {
|
||||||
|
wrap(blockContent) {
|
||||||
|
const myWrapper = document.createElement('div');
|
||||||
|
|
||||||
|
myWrapper.append(blockContent);
|
||||||
|
|
||||||
|
return myWrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
name | type | description |
|
||||||
|
-- |-- | -- |
|
||||||
|
blockContent | HTMLElement | Block's content (might be wrapped by other Tunes) |
|
||||||
|
|
||||||
|
#### Return value
|
||||||
|
|
||||||
|
| type | description |
|
||||||
|
| -- | -- |
|
||||||
|
| HTMLElement | Your element that wraps block content |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### save()
|
||||||
|
|
||||||
|
Method should return Tune's state you want to save to Editor's output
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
No parameters
|
||||||
|
|
||||||
|
#### Return value
|
||||||
|
|
||||||
|
type | description |
|
||||||
|
-- | -- |
|
||||||
|
`any` | any data you want to save |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### static prepare()
|
||||||
|
|
||||||
|
If you need to prepare some data for Tune (eg. load external script, create HTML nodes in the document, etc) you can use the static `prepare()` method.
|
||||||
|
|
||||||
|
It accepts tunes config passed on Editor's initialization as an argument:
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Tune {
|
||||||
|
static prepare(config) {
|
||||||
|
loadScript();
|
||||||
|
insertNodes();
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
type | description |
|
||||||
|
-- | -- |
|
||||||
|
`object` | your Tune configuration |
|
||||||
|
|
||||||
|
|
||||||
|
#### Return value
|
||||||
|
|
||||||
|
No return value
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### static reset()
|
||||||
|
|
||||||
|
On Editor destroy you can use an opposite method `reset` to clean up all prepared data:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Tune {
|
||||||
|
static reset() {
|
||||||
|
cleanUpScripts();
|
||||||
|
deleteNodes();
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
No parameters
|
||||||
|
|
||||||
|
#### Return value
|
||||||
|
|
||||||
|
No return value
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Format
|
||||||
|
|
||||||
|
Tunes data is saved to `tunes` property of output object:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'This is paragraph with Tune'
|
||||||
|
},
|
||||||
|
tunes: {
|
||||||
|
'my-tune-name': {},
|
||||||
|
favorite: true,
|
||||||
|
anchor: 'might be string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
|
@ -59,6 +59,7 @@ Options that Tool can specify. All settings should be passed as static propertie
|
||||||
| `toolbox` | _Object_ | `undefined` | Pass here `icon` and `title` to display this `Tool` in the Editor's `Toolbox` <br /> `icon` - HTML string with icon for Toolbox <br /> `title` - optional title to display in Toolbox |
|
| `toolbox` | _Object_ | `undefined` | Pass here `icon` and `title` to display this `Tool` in the Editor's `Toolbox` <br /> `icon` - HTML string with icon for Toolbox <br /> `title` - optional title to display in Toolbox |
|
||||||
| `enableLineBreaks` | _Boolean_ | `false` | With this option, Editor.js won't handle Enter keydowns. Can be helpful for Tools like `<code>` where line breaks should be handled by default behaviour. |
|
| `enableLineBreaks` | _Boolean_ | `false` | With this option, Editor.js won't handle Enter keydowns. Can be helpful for Tools like `<code>` where line breaks should be handled by default behaviour. |
|
||||||
| `isInline` | _Boolean_ | `false` | Describes Tool as a [Tool for the Inline Toolbar](tools-inline.md) |
|
| `isInline` | _Boolean_ | `false` | Describes Tool as a [Tool for the Inline Toolbar](tools-inline.md) |
|
||||||
|
| `isTune` | _Boolean_ | `false` | Describes Tool as a [Block Tune](block-tunes.md) |
|
||||||
| `sanitize` | _Object_ | `undefined` | Config for automatic sanitizing of saved data. See [Sanitize](#sanitize) section. |
|
| `sanitize` | _Object_ | `undefined` | Config for automatic sanitizing of saved data. See [Sanitize](#sanitize) section. |
|
||||||
| `conversionConfig` | _Object_ | `undefined` | Config allows Tool to specify how it can be converted into/from another Tool. See [Conversion config](#conversion-config) section. |
|
| `conversionConfig` | _Object_ | `undefined` | Config allows Tool to specify how it can be converted into/from another Tool. See [Conversion config](#conversion-config) section. |
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,7 @@
|
||||||
* Tools list
|
* Tools list
|
||||||
*/
|
*/
|
||||||
tools: {
|
tools: {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md}
|
* Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md}
|
||||||
*/
|
*/
|
||||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@editorjs/editorjs",
|
"name": "@editorjs/editorjs",
|
||||||
"version": "2.19.3",
|
"version": "2.20.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",
|
||||||
|
@ -37,21 +37,27 @@
|
||||||
"@babel/plugin-transform-runtime": "^7.9.0",
|
"@babel/plugin-transform-runtime": "^7.9.0",
|
||||||
"@babel/polyfill": "^7.8.7",
|
"@babel/polyfill": "^7.8.7",
|
||||||
"@babel/preset-env": "^7.9.5",
|
"@babel/preset-env": "^7.9.5",
|
||||||
|
"@babel/preset-typescript": "^7.13.0",
|
||||||
"@babel/register": "^7.9.0",
|
"@babel/register": "^7.9.0",
|
||||||
"@babel/runtime": "^7.9.2",
|
"@babel/runtime": "^7.9.2",
|
||||||
"@codexteam/shortcuts": "^1.1.1",
|
"@codexteam/shortcuts": "^1.1.1",
|
||||||
|
"@cypress/code-coverage": "^3.9.2",
|
||||||
|
"@cypress/webpack-preprocessor": "^5.6.0",
|
||||||
|
"@types/node": "^14.14.35",
|
||||||
"@types/webpack": "^4.41.12",
|
"@types/webpack": "^4.41.12",
|
||||||
"@types/webpack-env": "^1.15.2",
|
"@types/webpack-env": "^1.15.2",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"babel-plugin-add-module-exports": "^1.0.0",
|
"babel-plugin-add-module-exports": "^1.0.0",
|
||||||
"babel-plugin-class-display-name": "^2.1.0",
|
"babel-plugin-class-display-name": "^2.1.0",
|
||||||
|
"babel-plugin-istanbul": "^6.0.0",
|
||||||
"core-js": "3.6.5",
|
"core-js": "3.6.5",
|
||||||
"css-loader": "^3.5.3",
|
"css-loader": "^3.5.3",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"cypress": "^5.5.0",
|
"cypress": "^6.8.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-codex": "^1.3.3",
|
"eslint-config-codex": "^1.3.3",
|
||||||
"eslint-loader": "^4.0.2",
|
"eslint-loader": "^4.0.2",
|
||||||
|
"eslint-plugin-chai-friendly": "^0.6.0",
|
||||||
"eslint-plugin-cypress": "^2.11.2",
|
"eslint-plugin-cypress": "^2.11.2",
|
||||||
"extract-text-webpack-plugin": "^3.0.2",
|
"extract-text-webpack-plugin": "^3.0.2",
|
||||||
"html-janitor": "^2.0.4",
|
"html-janitor": "^2.0.4",
|
||||||
|
|
|
@ -11,6 +11,11 @@ import $ from '../dom';
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default class DeleteTune implements BlockTune {
|
export default class DeleteTune implements BlockTune {
|
||||||
|
/**
|
||||||
|
* Set Tool is Tune
|
||||||
|
*/
|
||||||
|
public static readonly isTune = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property that contains Editor.js API methods
|
* Property that contains Editor.js API methods
|
||||||
*
|
*
|
||||||
|
|
|
@ -12,6 +12,11 @@ import { API, BlockTune } from '../../../types';
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default class MoveDownTune implements BlockTune {
|
export default class MoveDownTune implements BlockTune {
|
||||||
|
/**
|
||||||
|
* Set Tool is Tune
|
||||||
|
*/
|
||||||
|
public static readonly isTune = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property that contains Editor.js API methods
|
* Property that contains Editor.js API methods
|
||||||
*
|
*
|
||||||
|
|
|
@ -11,6 +11,11 @@ import { API, BlockTune } from '../../../types';
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default class MoveUpTune implements BlockTune {
|
export default class MoveUpTune implements BlockTune {
|
||||||
|
/**
|
||||||
|
* Set Tool is Tune
|
||||||
|
*/
|
||||||
|
public static readonly isTune = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property that contains Editor.js API methods
|
* Property that contains Editor.js API methods
|
||||||
*
|
*
|
||||||
|
|
|
@ -3,8 +3,7 @@ import {
|
||||||
BlockTool as IBlockTool,
|
BlockTool as IBlockTool,
|
||||||
BlockToolConstructable,
|
BlockToolConstructable,
|
||||||
BlockToolData,
|
BlockToolData,
|
||||||
BlockTune,
|
BlockTune as IBlockTune,
|
||||||
BlockTuneConstructable,
|
|
||||||
SanitizerConfig,
|
SanitizerConfig,
|
||||||
ToolConfig,
|
ToolConfig,
|
||||||
ToolSettings
|
ToolSettings
|
||||||
|
@ -15,24 +14,17 @@ import $ from '../dom';
|
||||||
import * as _ from '../utils';
|
import * as _ from '../utils';
|
||||||
import ApiModules from '../modules/api';
|
import ApiModules from '../modules/api';
|
||||||
import BlockAPI from './api';
|
import BlockAPI from './api';
|
||||||
import { ToolType } from '../modules/tools';
|
|
||||||
import SelectionUtils from '../selection';
|
import SelectionUtils from '../selection';
|
||||||
import BlockTool from '../tools/block';
|
import BlockTool from '../tools/block';
|
||||||
|
|
||||||
/** Import default tunes */
|
import BlockTune from '../tools/tune';
|
||||||
import MoveUpTune from '../block-tunes/block-tune-move-up';
|
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
|
||||||
import DeleteTune from '../block-tunes/block-tune-delete';
|
import ToolsCollection from '../tools/collection';
|
||||||
import MoveDownTune from '../block-tunes/block-tune-move-down';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface describes Block class constructor argument
|
* Interface describes Block class constructor argument
|
||||||
*/
|
*/
|
||||||
interface BlockConstructorOptions {
|
interface BlockConstructorOptions {
|
||||||
/**
|
|
||||||
* Tool's name
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initial Block data
|
* Initial Block data
|
||||||
*/
|
*/
|
||||||
|
@ -52,6 +44,16 @@ interface BlockConstructorOptions {
|
||||||
* This flag indicates that the Block should be constructed in the read-only mode.
|
* This flag indicates that the Block should be constructed in the read-only mode.
|
||||||
*/
|
*/
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tunes for current Block
|
||||||
|
*/
|
||||||
|
tunes: ToolsCollection<BlockTune>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tunes data for current Block
|
||||||
|
*/
|
||||||
|
tunesData: {[name: string]: BlockTuneData};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,7 +128,7 @@ export default class Block {
|
||||||
/**
|
/**
|
||||||
* Tunes used by Tool
|
* Tunes used by Tool
|
||||||
*/
|
*/
|
||||||
public readonly tunes: BlockTune[];
|
public readonly tunes: ToolsCollection<BlockTune>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool's user configuration
|
* Tool's user configuration
|
||||||
|
@ -145,6 +147,22 @@ export default class Block {
|
||||||
*/
|
*/
|
||||||
private readonly toolInstance: IBlockTool;
|
private readonly toolInstance: IBlockTool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User provided Block Tunes instances
|
||||||
|
*/
|
||||||
|
private readonly tunesInstances: Map<string, IBlockTune> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor provided Block Tunes instances
|
||||||
|
*/
|
||||||
|
private readonly defaultTunesInstances: Map<string, IBlockTune> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is saved data for Tune which is not available at the moment,
|
||||||
|
* we will store it here and provide back on save so data is not lost
|
||||||
|
*/
|
||||||
|
private unavailableTunesData: {[name: string]: BlockTuneData} = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor`s API module
|
* Editor`s API module
|
||||||
*/
|
*/
|
||||||
|
@ -195,7 +213,6 @@ export default class Block {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} options - block constructor options
|
* @param {object} options - block constructor options
|
||||||
* @param {string} options.name - Tool name that passed on initialization
|
|
||||||
* @param {BlockToolData} options.data - Tool's initial data
|
* @param {BlockToolData} options.data - Tool's initial data
|
||||||
* @param {BlockToolConstructable} options.Tool — Tool's class
|
* @param {BlockToolConstructable} options.Tool — Tool's class
|
||||||
* @param {ToolSettings} options.settings - default tool's config
|
* @param {ToolSettings} options.settings - default tool's config
|
||||||
|
@ -203,13 +220,14 @@ export default class Block {
|
||||||
* @param {boolean} options.readOnly - Read-Only flag
|
* @param {boolean} options.readOnly - Read-Only flag
|
||||||
*/
|
*/
|
||||||
constructor({
|
constructor({
|
||||||
name,
|
|
||||||
data,
|
data,
|
||||||
tool,
|
tool,
|
||||||
api,
|
api,
|
||||||
readOnly,
|
readOnly,
|
||||||
|
tunes,
|
||||||
|
tunesData,
|
||||||
}: BlockConstructorOptions) {
|
}: BlockConstructorOptions) {
|
||||||
this.name = name;
|
this.name = tool.name;
|
||||||
this.settings = tool.settings;
|
this.settings = tool.settings;
|
||||||
this.config = tool.settings.config || {};
|
this.config = tool.settings.config || {};
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
@ -218,13 +236,16 @@ export default class Block {
|
||||||
this.mutationObserver = new MutationObserver(this.didMutated);
|
this.mutationObserver = new MutationObserver(this.didMutated);
|
||||||
|
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
this.toolInstance = tool.instance(data, this.blockAPI, readOnly);
|
this.toolInstance = tool.create(data, this.blockAPI, readOnly);
|
||||||
|
|
||||||
this.holder = this.compose();
|
|
||||||
/**
|
/**
|
||||||
* @type {BlockTune[]}
|
* @type {BlockTune[]}
|
||||||
*/
|
*/
|
||||||
this.tunes = this.makeTunes();
|
this.tunes = tunes;
|
||||||
|
|
||||||
|
this.composeTunes(tunesData);
|
||||||
|
|
||||||
|
this.holder = this.compose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -526,6 +547,21 @@ export default class Block {
|
||||||
*/
|
*/
|
||||||
public async save(): Promise<void|SavedData> {
|
public async save(): Promise<void|SavedData> {
|
||||||
const extractedBlock = await this.toolInstance.save(this.pluginsContent as HTMLElement);
|
const extractedBlock = await this.toolInstance.save(this.pluginsContent as HTMLElement);
|
||||||
|
const tunesData: {[name: string]: BlockTuneData} = this.unavailableTunesData;
|
||||||
|
|
||||||
|
[
|
||||||
|
...this.tunesInstances.entries(),
|
||||||
|
...this.defaultTunesInstances.entries(),
|
||||||
|
]
|
||||||
|
.forEach(([name, tune]) => {
|
||||||
|
if (_.isFunction(tune.save)) {
|
||||||
|
try {
|
||||||
|
tunesData[name] = tune.save();
|
||||||
|
} catch (e) {
|
||||||
|
_.log(`Tune ${tune.constructor.name} save method throws an Error %o`, 'warn', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Measuring execution time
|
* Measuring execution time
|
||||||
|
@ -541,6 +577,7 @@ export default class Block {
|
||||||
return {
|
return {
|
||||||
tool: this.name,
|
tool: this.name,
|
||||||
data: finishedExtraction,
|
data: finishedExtraction,
|
||||||
|
tunes: tunesData,
|
||||||
time: measuringEnd - measuringStart,
|
time: measuringEnd - measuringStart,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -568,50 +605,23 @@ export default class Block {
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Make an array with default settings
|
|
||||||
* Each block has default tune instance that have states
|
|
||||||
*
|
|
||||||
* @returns {BlockTune[]}
|
|
||||||
*/
|
|
||||||
public makeTunes(): BlockTune[] {
|
|
||||||
const tunesList = [
|
|
||||||
{
|
|
||||||
name: 'moveUp',
|
|
||||||
Tune: MoveUpTune,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'delete',
|
|
||||||
Tune: DeleteTune,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'moveDown',
|
|
||||||
Tune: MoveDownTune,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Pluck tunes list and return tune instances with passed Editor API and settings
|
|
||||||
return tunesList.map(({ name, Tune }: {name: string; Tune: BlockTuneConstructable}) => {
|
|
||||||
return new Tune({
|
|
||||||
api: this.api.getMethodsForTool(name, ToolType.Tune),
|
|
||||||
settings: this.config,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumerates initialized tunes and returns fragment that can be appended to the toolbars area
|
* Enumerates initialized tunes and returns fragment that can be appended to the toolbars area
|
||||||
*
|
*
|
||||||
* @returns {DocumentFragment}
|
* @returns {DocumentFragment[]}
|
||||||
*/
|
*/
|
||||||
public renderTunes(): DocumentFragment {
|
public renderTunes(): [DocumentFragment, DocumentFragment] {
|
||||||
const tunesElement = document.createDocumentFragment();
|
const tunesElement = document.createDocumentFragment();
|
||||||
|
const defaultTunesElement = document.createDocumentFragment();
|
||||||
|
|
||||||
this.tunes.forEach((tune) => {
|
this.tunesInstances.forEach((tune) => {
|
||||||
$.append(tunesElement, tune.render());
|
$.append(tunesElement, tune.render());
|
||||||
});
|
});
|
||||||
|
this.defaultTunesInstances.forEach((tune) => {
|
||||||
|
$.append(defaultTunesElement, tune.render());
|
||||||
|
});
|
||||||
|
|
||||||
return tunesElement;
|
return [tunesElement, defaultTunesElement];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -690,11 +700,57 @@ export default class Block {
|
||||||
pluginsContent = this.toolInstance.render();
|
pluginsContent = this.toolInstance.render();
|
||||||
|
|
||||||
contentNode.appendChild(pluginsContent);
|
contentNode.appendChild(pluginsContent);
|
||||||
wrapper.appendChild(contentNode);
|
|
||||||
|
/**
|
||||||
|
* Block Tunes might wrap Block's content node to provide any UI changes
|
||||||
|
*
|
||||||
|
* <tune2wrapper>
|
||||||
|
* <tune1wrapper>
|
||||||
|
* <blockContent />
|
||||||
|
* </tune1wrapper>
|
||||||
|
* </tune2wrapper>
|
||||||
|
*/
|
||||||
|
let wrappedContentNode: HTMLElement = contentNode;
|
||||||
|
|
||||||
|
[...this.tunesInstances.values(), ...this.defaultTunesInstances.values()]
|
||||||
|
.forEach((tune) => {
|
||||||
|
if (_.isFunction(tune.wrap)) {
|
||||||
|
try {
|
||||||
|
wrappedContentNode = tune.wrap(wrappedContentNode);
|
||||||
|
} catch (e) {
|
||||||
|
_.log(`Tune ${tune.constructor.name} wrap method throws an Error %o`, 'warn', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.appendChild(wrappedContentNode);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate Block Tunes
|
||||||
|
*
|
||||||
|
* @param tunesData - current Block tunes data
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private composeTunes(tunesData: {[name: string]: BlockTuneData}): void {
|
||||||
|
Array.from(this.tunes.values()).forEach((tune) => {
|
||||||
|
const collection = tune.isInternal ? this.defaultTunesInstances : this.tunesInstances;
|
||||||
|
|
||||||
|
collection.set(tune.name, tune.create(tunesData[tune.name], this.blockAPI));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is some data for not available tunes
|
||||||
|
*/
|
||||||
|
Object.entries(tunesData).forEach(([name, data]) => {
|
||||||
|
if (!this.tunesInstances.has(name)) {
|
||||||
|
this.unavailableTunesData[name] = data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is fired when text input or contentEditable is focused
|
* Is fired when text input or contentEditable is focused
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { I18n } from '../../../../types/api';
|
import { I18n } from '../../../../types/api';
|
||||||
import I18nInternal from '../../i18n';
|
import I18nInternal from '../../i18n';
|
||||||
import { ToolType } from '../tools';
|
|
||||||
import { logLabeled } from '../../utils';
|
import { logLabeled } from '../../utils';
|
||||||
import Module from '../../__module';
|
import Module from '../../__module';
|
||||||
|
import { ToolClass } from '../../tools/collection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides methods for working with i18n
|
* Provides methods for working with i18n
|
||||||
|
@ -11,17 +11,14 @@ export default class I18nAPI extends Module {
|
||||||
/**
|
/**
|
||||||
* Return namespace section for tool or block tune
|
* Return namespace section for tool or block tune
|
||||||
*
|
*
|
||||||
* @param toolName - name of tool. Used to provide dictionary only for this tool
|
* @param tool - tool object
|
||||||
* @param toolType - 'block' for Block Tool, 'inline' for Inline Tool, 'tune' for Block Tunes
|
|
||||||
*/
|
*/
|
||||||
private static getNamespace(toolName: string, toolType: ToolType): string {
|
private static getNamespace(tool: ToolClass): string {
|
||||||
switch (toolType) {
|
if (tool.isTune) {
|
||||||
case ToolType.Block:
|
return `blockTunes.${tool.name}`;
|
||||||
case ToolType.Inline:
|
|
||||||
return `tools.${toolName}`;
|
|
||||||
case ToolType.Tune:
|
|
||||||
return `blockTunes.${toolName}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return `tools.${tool.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,15 +37,14 @@ export default class I18nAPI extends Module {
|
||||||
/**
|
/**
|
||||||
* Return I18n API methods with tool namespaced dictionary
|
* Return I18n API methods with tool namespaced dictionary
|
||||||
*
|
*
|
||||||
* @param toolName - name of tool. Used to provide dictionary only for this tool
|
* @param tool - Tool object
|
||||||
* @param toolType - 'block' for Block Tool, 'inline' for Inline Tool, 'tune' for Block Tunes
|
|
||||||
*/
|
*/
|
||||||
public getMethodsForTool(toolName: string, toolType: ToolType): I18n {
|
public getMethodsForTool(tool: ToolClass): I18n {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
this.methods,
|
this.methods,
|
||||||
{
|
{
|
||||||
t: (dictKey: string): string => {
|
t: (dictKey: string): string => {
|
||||||
return I18nInternal.t(I18nAPI.getNamespace(toolName, toolType), dictKey);
|
return I18nInternal.t(I18nAPI.getNamespace(tool), dictKey);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import Module from '../../__module';
|
import Module from '../../__module';
|
||||||
import { API as APIInterfaces } from '../../../../types';
|
import { API as APIInterfaces } from '../../../../types';
|
||||||
import { ToolType } from '../tools';
|
import { ToolClass } from '../../tools/collection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class API
|
* @class API
|
||||||
|
@ -38,16 +38,13 @@ export default class API extends Module {
|
||||||
/**
|
/**
|
||||||
* Returns Editor.js Core API methods for passed tool
|
* Returns Editor.js Core API methods for passed tool
|
||||||
*
|
*
|
||||||
* @param toolName - how user name tool. It can be used in some API logic,
|
* @param tool - tool object
|
||||||
* for example in i18n to provide namespaced dictionary
|
|
||||||
*
|
|
||||||
* @param toolType - 'block' for Block Tool, 'inline' for Inline Tool, 'tune' for Block Tunes
|
|
||||||
*/
|
*/
|
||||||
public getMethodsForTool(toolName: string, toolType = ToolType.Block): APIInterfaces {
|
public getMethodsForTool(tool: ToolClass): APIInterfaces {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
this.methods,
|
this.methods,
|
||||||
{
|
{
|
||||||
i18n: this.Editor.I18nAPI.getMethodsForTool(toolName, toolType),
|
i18n: this.Editor.I18nAPI.getMethodsForTool(tool),
|
||||||
}
|
}
|
||||||
) as APIInterfaces;
|
) as APIInterfaces;
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ export default class BlockEvents extends Module {
|
||||||
*/
|
*/
|
||||||
this.Editor.BlockSelection.clearSelection(event);
|
this.Editor.BlockSelection.clearSelection(event);
|
||||||
|
|
||||||
const { BlockManager, Tools, InlineToolbar, ConversionToolbar } = this.Editor;
|
const { BlockManager, InlineToolbar, ConversionToolbar } = this.Editor;
|
||||||
const currentBlock = BlockManager.currentBlock;
|
const currentBlock = BlockManager.currentBlock;
|
||||||
|
|
||||||
if (!currentBlock) {
|
if (!currentBlock) {
|
||||||
|
|
|
@ -11,8 +11,8 @@ import Module from '../__module';
|
||||||
import $ from '../dom';
|
import $ from '../dom';
|
||||||
import * as _ from '../utils';
|
import * as _ from '../utils';
|
||||||
import Blocks from '../blocks';
|
import Blocks from '../blocks';
|
||||||
import { BlockToolConstructable, BlockToolData, PasteEvent } from '../../../types';
|
import { BlockToolData, PasteEvent } from '../../../types';
|
||||||
import BlockTool from '../tools/block';
|
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {BlockManager} BlockManager
|
* @typedef {BlockManager} BlockManager
|
||||||
|
@ -220,15 +220,21 @@ export default class BlockManager extends Module {
|
||||||
*
|
*
|
||||||
* @returns {Block}
|
* @returns {Block}
|
||||||
*/
|
*/
|
||||||
public composeBlock({ tool: name, data = {} }: {tool: string; data?: BlockToolData}): Block {
|
public composeBlock({
|
||||||
|
tool: name,
|
||||||
|
data = {},
|
||||||
|
tunes: tunesData = {},
|
||||||
|
}: {tool: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}}): Block {
|
||||||
const readOnly = this.Editor.ReadOnly.isEnabled;
|
const readOnly = this.Editor.ReadOnly.isEnabled;
|
||||||
const tool = this.Editor.Tools.blockTools.get(name);
|
const tool = this.Editor.Tools.blockTools.get(name);
|
||||||
|
const tunes = this.Editor.Tools.getTunesForTool(tool);
|
||||||
const block = new Block({
|
const block = new Block({
|
||||||
name,
|
|
||||||
data,
|
data,
|
||||||
tool,
|
tool,
|
||||||
api: this.Editor.API,
|
api: this.Editor.API,
|
||||||
readOnly,
|
readOnly,
|
||||||
|
tunes,
|
||||||
|
tunesData,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!readOnly) {
|
if (!readOnly) {
|
||||||
|
@ -256,12 +262,14 @@ export default class BlockManager extends Module {
|
||||||
index,
|
index,
|
||||||
needToFocus = true,
|
needToFocus = true,
|
||||||
replace = false,
|
replace = false,
|
||||||
|
tunes = {},
|
||||||
}: {
|
}: {
|
||||||
tool?: string;
|
tool?: string;
|
||||||
data?: BlockToolData;
|
data?: BlockToolData;
|
||||||
index?: number;
|
index?: number;
|
||||||
needToFocus?: boolean;
|
needToFocus?: boolean;
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
|
tunes?: {[name: string]: BlockTuneData};
|
||||||
} = {}): Block {
|
} = {}): Block {
|
||||||
let newIndex = index;
|
let newIndex = index;
|
||||||
|
|
||||||
|
@ -272,6 +280,7 @@ export default class BlockManager extends Module {
|
||||||
const block = this.composeBlock({
|
const block = this.composeBlock({
|
||||||
tool,
|
tool,
|
||||||
data,
|
data,
|
||||||
|
tunes,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._blocks.insert(newIndex, block, replace);
|
this._blocks.insert(newIndex, block, replace);
|
||||||
|
|
|
@ -222,7 +222,7 @@ export default class Paste extends Module {
|
||||||
* @param {boolean} isHTML - if passed string is HTML, this parameter should be true
|
* @param {boolean} isHTML - if passed string is HTML, this parameter should be true
|
||||||
*/
|
*/
|
||||||
public async processText(data: string, isHTML = false): Promise<void> {
|
public async processText(data: string, isHTML = false): Promise<void> {
|
||||||
const { Caret, BlockManager, Tools } = this.Editor;
|
const { Caret, BlockManager } = this.Editor;
|
||||||
const dataToInsert = isHTML ? this.processHTML(data) : this.processPlain(data);
|
const dataToInsert = isHTML ? this.processHTML(data) : this.processPlain(data);
|
||||||
|
|
||||||
if (!dataToInsert.length) {
|
if (!dataToInsert.length) {
|
||||||
|
@ -283,7 +283,7 @@ export default class Paste extends Module {
|
||||||
*/
|
*/
|
||||||
private processTool = (tool: BlockTool): void => {
|
private processTool = (tool: BlockTool): void => {
|
||||||
try {
|
try {
|
||||||
const toolInstance = tool.instance({}, {} as BlockAPI, false);
|
const toolInstance = tool.create({}, {} as BlockAPI, false);
|
||||||
|
|
||||||
if (tool.pasteConfig === false) {
|
if (tool.pasteConfig === false) {
|
||||||
this.exceptionList.push(tool.name);
|
this.exceptionList.push(tool.name);
|
||||||
|
@ -300,7 +300,7 @@ export default class Paste extends Module {
|
||||||
this.getPatternsConfig(tool);
|
this.getPatternsConfig(tool);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_.log(
|
_.log(
|
||||||
`Paste handling for «${name}» Tool hasn't been set up because of the error`,
|
`Paste handling for «${tool.name}» Tool hasn't been set up because of the error`,
|
||||||
'warn',
|
'warn',
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
|
@ -389,7 +389,7 @@ export default class Paste extends Module {
|
||||||
/** Still need to validate pattern as it provided by user */
|
/** Still need to validate pattern as it provided by user */
|
||||||
if (!(pattern instanceof RegExp)) {
|
if (!(pattern instanceof RegExp)) {
|
||||||
_.log(
|
_.log(
|
||||||
`Pattern ${pattern} for «${name}» Tool is skipped because it should be a Regexp instance.`,
|
`Pattern ${pattern} for «${tool.name}» Tool is skipped because it should be a Regexp instance.`,
|
||||||
'warn'
|
'warn'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,14 +64,14 @@ export default class Renderer extends Module {
|
||||||
*/
|
*/
|
||||||
public async insertBlock(item: OutputBlockData): Promise<void> {
|
public async insertBlock(item: OutputBlockData): Promise<void> {
|
||||||
const { Tools, BlockManager } = this.Editor;
|
const { Tools, BlockManager } = this.Editor;
|
||||||
const tool = item.type;
|
const { type: tool, data, tunes } = item;
|
||||||
const data = item.data;
|
|
||||||
|
|
||||||
if (Tools.available.has(tool)) {
|
if (Tools.available.has(tool)) {
|
||||||
try {
|
try {
|
||||||
BlockManager.insert({
|
BlockManager.insert({
|
||||||
tool,
|
tool,
|
||||||
data,
|
data,
|
||||||
|
tunes,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
_.log(`Block «${tool}» skipped because of plugins error`, 'warn', data);
|
_.log(`Block «${tool}» skipped because of plugins error`, 'warn', data);
|
||||||
|
|
|
@ -78,7 +78,7 @@ export default class Saver extends Module {
|
||||||
|
|
||||||
_.log('[Editor.js saving]:', 'groupCollapsed');
|
_.log('[Editor.js saving]:', 'groupCollapsed');
|
||||||
|
|
||||||
allExtractedData.forEach(({ tool, data, time, isValid }) => {
|
allExtractedData.forEach(({ tool, data, tunes, time, isValid }) => {
|
||||||
totalTime += time;
|
totalTime += time;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,10 +104,16 @@ export default class Saver extends Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks.push({
|
const output: any = {
|
||||||
type: tool,
|
type: tool,
|
||||||
data,
|
data,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (!_.isEmpty(tunes)) {
|
||||||
|
output.tunes = tunes;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks.push(output);
|
||||||
});
|
});
|
||||||
|
|
||||||
_.log('Total', 'log', totalTime);
|
_.log('Total', 'log', totalTime);
|
||||||
|
|
|
@ -144,7 +144,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
/**
|
/**
|
||||||
* Add default settings that presents for all Blocks
|
* Add default settings that presents for all Blocks
|
||||||
*/
|
*/
|
||||||
this.addDefaultSettings();
|
this.addTunes();
|
||||||
|
|
||||||
/** Tell to subscribers that block settings is opened */
|
/** Tell to subscribers that block settings is opened */
|
||||||
this.eventsDispatcher.emit(this.events.opened);
|
this.eventsDispatcher.emit(this.events.opened);
|
||||||
|
@ -237,10 +237,13 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add default settings
|
* Add tunes: provided by user and default ones
|
||||||
*/
|
*/
|
||||||
private addDefaultSettings(): void {
|
private addTunes(): void {
|
||||||
$.append(this.nodes.defaultSettings, this.Editor.BlockManager.currentBlock.renderTunes());
|
const [toolTunes, defaultTunes] = this.Editor.BlockManager.currentBlock.renderTunes();
|
||||||
|
|
||||||
|
$.append(this.nodes.toolSettings, toolTunes);
|
||||||
|
$.append(this.nodes.defaultSettings, defaultTunes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,7 +7,6 @@ import Flipper from '../../flipper';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||||
import Shortcuts from '../../utils/shortcuts';
|
import Shortcuts from '../../utils/shortcuts';
|
||||||
import { ToolType } from '../tools';
|
|
||||||
import InlineTool from '../../tools/inline';
|
import InlineTool from '../../tools/inline';
|
||||||
import { CommonInternalSettings } from '../../tools/base';
|
import { CommonInternalSettings } from '../../tools/base';
|
||||||
import BlockTool from '../../tools/block';
|
import BlockTool from '../../tools/block';
|
||||||
|
@ -594,7 +593,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} = this.Editor;
|
} = this.Editor;
|
||||||
|
|
||||||
const instance = tool.instance();
|
const instance = tool.create();
|
||||||
const button = instance.render();
|
const button = instance.render();
|
||||||
|
|
||||||
if (!button) {
|
if (!button) {
|
||||||
|
@ -670,7 +669,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
* 2) For external tools, check tool's settings
|
* 2) For external tools, check tool's settings
|
||||||
* 3) If shortcut is not set in settings, check Tool's public property
|
* 3) If shortcut is not set in settings, check Tool's public property
|
||||||
*/
|
*/
|
||||||
const internalTools = Tools.getInternal(ToolType.Inline);
|
const internalTools = Tools.internal.inlineTools;
|
||||||
|
|
||||||
if (Array.from(internalTools.keys()).includes(toolName)) {
|
if (Array.from(internalTools.keys()).includes(toolName)) {
|
||||||
return this.inlineTools[toolName][CommonInternalSettings.Shortcut];
|
return this.inlineTools[toolName][CommonInternalSettings.Shortcut];
|
||||||
|
@ -747,7 +746,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
Array
|
Array
|
||||||
.from(this.Editor.Tools.inlineTools.entries())
|
.from(this.Editor.Tools.inlineTools.entries())
|
||||||
.forEach(([name, tool]) => {
|
.forEach(([name, tool]) => {
|
||||||
result[name] = tool.instance();
|
result[name] = tool.create();
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -15,7 +15,10 @@ import ToolsFactory from '../tools/factory';
|
||||||
import InlineTool from '../tools/inline';
|
import InlineTool from '../tools/inline';
|
||||||
import BlockTool from '../tools/block';
|
import BlockTool from '../tools/block';
|
||||||
import BlockTune from '../tools/tune';
|
import BlockTune from '../tools/tune';
|
||||||
import BaseTool from '../tools/base';
|
import MoveDownTune from '../block-tunes/block-tune-move-down';
|
||||||
|
import DeleteTune from '../block-tunes/block-tune-delete';
|
||||||
|
import MoveUpTune from '../block-tunes/block-tune-move-up';
|
||||||
|
import ToolsCollection from '../tools/collection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module Editor.js Tools Submodule
|
* @module Editor.js Tools Submodule
|
||||||
|
@ -49,7 +52,7 @@ export default class Tools extends Module {
|
||||||
*
|
*
|
||||||
* @returns {object<Tool>}
|
* @returns {object<Tool>}
|
||||||
*/
|
*/
|
||||||
public get available(): Map<string, ToolClass> {
|
public get available(): ToolsCollection {
|
||||||
return this.toolsAvailable;
|
return this.toolsAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +61,7 @@ export default class Tools extends Module {
|
||||||
*
|
*
|
||||||
* @returns {Tool[]}
|
* @returns {Tool[]}
|
||||||
*/
|
*/
|
||||||
public get unavailable(): Map<string, ToolClass> {
|
public get unavailable(): ToolsCollection {
|
||||||
return this.toolsUnavailable;
|
return this.toolsUnavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,61 +70,24 @@ export default class Tools extends Module {
|
||||||
*
|
*
|
||||||
* @returns {object} - object of Inline Tool's classes
|
* @returns {object} - object of Inline Tool's classes
|
||||||
*/
|
*/
|
||||||
public get inlineTools(): Map<string, InlineTool> {
|
public get inlineTools(): ToolsCollection<InlineTool> {
|
||||||
if (this._inlineTools) {
|
return this.available.inlineTools;
|
||||||
return this._inlineTools;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tools = Array
|
|
||||||
.from(this.available.entries())
|
|
||||||
.filter(([name, tool]: [string, BaseTool<any>]) => {
|
|
||||||
if (tool.type !== ToolType.Inline) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Some Tools validation
|
|
||||||
*/
|
|
||||||
const inlineToolRequiredMethods = ['render', 'surround', 'checkState'];
|
|
||||||
const notImplementedMethods = inlineToolRequiredMethods.filter((method) => !tool.instance()[method]);
|
|
||||||
|
|
||||||
if (notImplementedMethods.length) {
|
|
||||||
_.log(
|
|
||||||
`Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`,
|
|
||||||
'warn',
|
|
||||||
notImplementedMethods
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache prepared Tools
|
|
||||||
*/
|
|
||||||
this._inlineTools = new Map(tools) as Map<string, InlineTool>;
|
|
||||||
|
|
||||||
return this._inlineTools;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return editor block tools
|
* Return editor block tools
|
||||||
*/
|
*/
|
||||||
public get blockTools(): Map<string, BlockTool> {
|
public get blockTools(): ToolsCollection<BlockTool> {
|
||||||
if (this._blockTools) {
|
return this.available.blockTools;
|
||||||
return this._blockTools;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tools = Array
|
/**
|
||||||
.from(this.available.entries())
|
* Return available Block Tunes
|
||||||
.filter(([, tool]) => {
|
*
|
||||||
return tool.type === ToolType.Block;
|
* @returns {object} - object of Inline Tool's classes
|
||||||
});
|
*/
|
||||||
|
public get blockTunes(): ToolsCollection<BlockTune> {
|
||||||
this._blockTools = new Map(tools) as Map<string, BlockTool>;
|
return this.available.blockTunes;
|
||||||
|
|
||||||
return this._blockTools;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,43 +105,18 @@ export default class Tools extends Module {
|
||||||
/**
|
/**
|
||||||
* Tools` classes available to use
|
* Tools` classes available to use
|
||||||
*/
|
*/
|
||||||
private readonly toolsAvailable: Map<string, ToolClass> = new Map();
|
private readonly toolsAvailable: ToolsCollection = new ToolsCollection();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tools` classes not available to use because of preparation failure
|
* Tools` classes not available to use because of preparation failure
|
||||||
*/
|
*/
|
||||||
private readonly toolsUnavailable: Map<string, ToolClass> = new Map();
|
private readonly toolsUnavailable: ToolsCollection = new ToolsCollection();
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache for the prepared inline tools
|
|
||||||
*
|
|
||||||
* @type {null|object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private _inlineTools: Map<string, InlineTool> = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache for the prepared block tools
|
|
||||||
*/
|
|
||||||
private _blockTools: Map<string, BlockTool> = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns internal tools
|
* Returns internal tools
|
||||||
*
|
|
||||||
* @param type - if passed, Tools will be filtered by type
|
|
||||||
*/
|
*/
|
||||||
public getInternal(type?: ToolType): Map<string, ToolClass> {
|
public get internal(): ToolsCollection {
|
||||||
let tools = Array
|
return this.available.internalTools;
|
||||||
.from(this.available.entries())
|
|
||||||
.filter(([, tool]) => {
|
|
||||||
return tool.isInternal;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (type) {
|
|
||||||
tools = tools.filter(([, tool]) => tool.type === type);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Map(tools);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -221,11 +162,57 @@ export default class Tools extends Module {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Block Tunes for passed Tool
|
||||||
|
*
|
||||||
|
* @param tool - Tool object
|
||||||
|
*/
|
||||||
|
public getTunesForTool(tool: BlockTool): ToolsCollection<BlockTune> {
|
||||||
|
const names = tool.enabledBlockTunes;
|
||||||
|
|
||||||
|
if (names === false) {
|
||||||
|
return new ToolsCollection<BlockTune>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(names)) {
|
||||||
|
return new ToolsCollection<BlockTune>(
|
||||||
|
Array
|
||||||
|
.from(this.blockTunes.entries())
|
||||||
|
.filter(([, tune]) => names.includes(tune.name))
|
||||||
|
.concat([ ...this.blockTunes.internalTools.entries() ])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultTuneNames = this.config.tunes;
|
||||||
|
|
||||||
|
if (Array.isArray(defaultTuneNames)) {
|
||||||
|
return new ToolsCollection<BlockTune>(
|
||||||
|
Array
|
||||||
|
.from(this.blockTunes.entries())
|
||||||
|
.filter(([, tune]) => defaultTuneNames.includes(tune.name))
|
||||||
|
.concat([ ...this.blockTunes.internalTools.entries() ])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.blockTunes.internalTools;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls each Tool reset method to clean up anything set by Tool
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
Object.values(this.available).forEach(async tool => {
|
||||||
|
if (_.isFunction(tool.reset)) {
|
||||||
|
await tool.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns internal tools
|
* Returns internal tools
|
||||||
* Includes Bold, Italic, Link and Paragraph
|
* Includes Bold, Italic, Link and Paragraph
|
||||||
*/
|
*/
|
||||||
public get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings & { isInternal?: boolean } } {
|
private get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings & { isInternal?: boolean } } {
|
||||||
return {
|
return {
|
||||||
bold: {
|
bold: {
|
||||||
class: BoldInlineTool,
|
class: BoldInlineTool,
|
||||||
|
@ -248,27 +235,50 @@ export default class Tools extends Module {
|
||||||
class: Stub,
|
class: Stub,
|
||||||
isInternal: true,
|
isInternal: true,
|
||||||
},
|
},
|
||||||
|
moveUpTune: {
|
||||||
|
class: MoveUpTune,
|
||||||
|
isInternal: true,
|
||||||
|
},
|
||||||
|
deleteTune: {
|
||||||
|
class: DeleteTune,
|
||||||
|
isInternal: true,
|
||||||
|
},
|
||||||
|
moveDownTune: {
|
||||||
|
class: MoveDownTune,
|
||||||
|
isInternal: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls each Tool reset method to clean up anything set by Tool
|
|
||||||
*/
|
|
||||||
public destroy(): void {
|
|
||||||
Object.values(this.available).forEach(async tool => {
|
|
||||||
if (_.isFunction(tool.reset)) {
|
|
||||||
await tool.reset();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool prepare method success callback
|
* Tool prepare method success callback
|
||||||
*
|
*
|
||||||
* @param {object} data - append tool to available list
|
* @param {object} data - append tool to available list
|
||||||
*/
|
*/
|
||||||
private toolPrepareMethodSuccess(data: { toolName: string }): void {
|
private toolPrepareMethodSuccess(data: { toolName: string }): void {
|
||||||
this.toolsAvailable.set(data.toolName, this.factory.get(data.toolName));
|
const tool = this.factory.get(data.toolName);
|
||||||
|
|
||||||
|
if (tool.isInline()) {
|
||||||
|
/**
|
||||||
|
* Some Tools validation
|
||||||
|
*/
|
||||||
|
const inlineToolRequiredMethods = ['render', 'surround', 'checkState'];
|
||||||
|
const notImplementedMethods = inlineToolRequiredMethods.filter((method) => !tool.create()[method]);
|
||||||
|
|
||||||
|
if (notImplementedMethods.length) {
|
||||||
|
_.log(
|
||||||
|
`Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`,
|
||||||
|
'warn',
|
||||||
|
notImplementedMethods
|
||||||
|
);
|
||||||
|
|
||||||
|
this.toolsUnavailable.set(tool.name, tool);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toolsAvailable.set(tool.name, tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -358,22 +368,3 @@ export default class Tools extends Module {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* What kind of plugins developers can create
|
|
||||||
*/
|
|
||||||
export enum ToolType {
|
|
||||||
/**
|
|
||||||
* Block tool
|
|
||||||
*/
|
|
||||||
Block,
|
|
||||||
/**
|
|
||||||
* Inline tool
|
|
||||||
*/
|
|
||||||
Inline,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Block tune
|
|
||||||
*/
|
|
||||||
Tune,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,29 @@
|
||||||
import { ToolType } from '../modules/tools';
|
|
||||||
import { Tool, ToolConstructable, ToolSettings } from '../../../types/tools';
|
import { Tool, ToolConstructable, ToolSettings } from '../../../types/tools';
|
||||||
import { API, SanitizerConfig } from '../../../types';
|
import { SanitizerConfig } from '../../../types';
|
||||||
import * as _ from '../utils';
|
import * as _ from '../utils';
|
||||||
|
import type InlineTool from './inline';
|
||||||
|
import type BlockTool from './block';
|
||||||
|
import type BlockTune from './tune';
|
||||||
|
import API from '../modules/api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What kind of plugins developers can create
|
||||||
|
*/
|
||||||
|
export enum ToolType {
|
||||||
|
/**
|
||||||
|
* Block tool
|
||||||
|
*/
|
||||||
|
Block,
|
||||||
|
/**
|
||||||
|
* Inline tool
|
||||||
|
*/
|
||||||
|
Inline,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block tune
|
||||||
|
*/
|
||||||
|
Tune,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum of Tool options provided by user
|
* Enum of Tool options provided by user
|
||||||
|
@ -19,6 +41,10 @@ export enum UserSettings {
|
||||||
* Enabled Inline Tools for Block Tool
|
* Enabled Inline Tools for Block Tool
|
||||||
*/
|
*/
|
||||||
EnabledInlineTools = 'inlineToolbar',
|
EnabledInlineTools = 'inlineToolbar',
|
||||||
|
/**
|
||||||
|
* Enabled Block Tunes for Block Tool
|
||||||
|
*/
|
||||||
|
EnabledBlockTunes = 'tunes',
|
||||||
/**
|
/**
|
||||||
* Tool configuration
|
* Tool configuration
|
||||||
*/
|
*/
|
||||||
|
@ -105,7 +131,7 @@ interface ConstructorOptions {
|
||||||
/**
|
/**
|
||||||
* Base abstract class for Tools
|
* Base abstract class for Tools
|
||||||
*/
|
*/
|
||||||
export default abstract class BaseTool<Type extends Tool> {
|
export default abstract class BaseTool<Type extends Tool = Tool> {
|
||||||
/**
|
/**
|
||||||
* Tool type: Block, Inline or Tune
|
* Tool type: Block, Inline or Tune
|
||||||
*/
|
*/
|
||||||
|
@ -214,7 +240,7 @@ export default abstract class BaseTool<Type extends Tool> {
|
||||||
*/
|
*/
|
||||||
public get shortcut(): string | undefined {
|
public get shortcut(): string | undefined {
|
||||||
const toolShortcut = this.constructable[CommonInternalSettings.Shortcut];
|
const toolShortcut = this.constructable[CommonInternalSettings.Shortcut];
|
||||||
const userShortcut = this.settings[UserSettings.Shortcut];
|
const userShortcut = this.config[UserSettings.Shortcut];
|
||||||
|
|
||||||
return userShortcut || toolShortcut;
|
return userShortcut || toolShortcut;
|
||||||
}
|
}
|
||||||
|
@ -226,10 +252,31 @@ export default abstract class BaseTool<Type extends Tool> {
|
||||||
return this.constructable[CommonInternalSettings.SanitizeConfig];
|
return this.constructable[CommonInternalSettings.SanitizeConfig];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if Tools is inline
|
||||||
|
*/
|
||||||
|
public isInline(): this is InlineTool {
|
||||||
|
return this.type === ToolType.Inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if Tools is block
|
||||||
|
*/
|
||||||
|
public isBlock(): this is BlockTool {
|
||||||
|
return this.type === ToolType.Block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if Tools is tune
|
||||||
|
*/
|
||||||
|
public isTune(): this is BlockTune {
|
||||||
|
return this.type === ToolType.Tune;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs new Tool instance from constructable blueprint
|
* Constructs new Tool instance from constructable blueprint
|
||||||
*
|
*
|
||||||
* @param args
|
* @param args
|
||||||
*/
|
*/
|
||||||
public abstract instance(...args: any[]): Type;
|
public abstract create(...args: any[]): Type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import BaseTool, { InternalBlockToolSettings, UserSettings } from './base';
|
import BaseTool, { InternalBlockToolSettings, ToolType, UserSettings } from './base';
|
||||||
import { ToolType } from '../modules/tools';
|
|
||||||
import {
|
import {
|
||||||
BlockAPI,
|
BlockAPI,
|
||||||
BlockTool as IBlockTool,
|
BlockTool as IBlockTool,
|
||||||
|
BlockToolConstructable,
|
||||||
BlockToolData,
|
BlockToolData,
|
||||||
ConversionConfig,
|
ConversionConfig,
|
||||||
PasteConfig,
|
PasteConfig,
|
||||||
|
@ -19,6 +19,11 @@ export default class BlockTool extends BaseTool<IBlockTool> {
|
||||||
*/
|
*/
|
||||||
public type = ToolType.Block;
|
public type = ToolType.Block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool's constructable blueprint
|
||||||
|
*/
|
||||||
|
protected constructable: BlockToolConstructable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new Tool instance
|
* Creates new Tool instance
|
||||||
*
|
*
|
||||||
|
@ -26,13 +31,13 @@ export default class BlockTool extends BaseTool<IBlockTool> {
|
||||||
* @param block - BlockAPI for current Block
|
* @param block - BlockAPI for current Block
|
||||||
* @param readOnly - True if Editor is in read-only mode
|
* @param readOnly - True if Editor is in read-only mode
|
||||||
*/
|
*/
|
||||||
public instance(data: BlockToolData, block: BlockAPI, readOnly: boolean): IBlockTool {
|
public create(data: BlockToolData, block: BlockAPI, readOnly: boolean): IBlockTool {
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
return new this.constructable({
|
return new this.constructable({
|
||||||
data,
|
data,
|
||||||
block,
|
block,
|
||||||
readOnly,
|
readOnly,
|
||||||
api: this.api,
|
api: this.api.getMethodsForTool(this),
|
||||||
config: this.settings,
|
config: this.settings,
|
||||||
}) as IBlockTool;
|
}) as IBlockTool;
|
||||||
}
|
}
|
||||||
|
@ -56,7 +61,7 @@ export default class BlockTool extends BaseTool<IBlockTool> {
|
||||||
*/
|
*/
|
||||||
public get toolbox(): ToolboxConfig {
|
public get toolbox(): ToolboxConfig {
|
||||||
const toolToolboxSettings = this.constructable[InternalBlockToolSettings.Toolbox] as ToolboxConfig;
|
const toolToolboxSettings = this.constructable[InternalBlockToolSettings.Toolbox] as ToolboxConfig;
|
||||||
const userToolboxSettings = this.settings[UserSettings.Toolbox];
|
const userToolboxSettings = this.config[UserSettings.Toolbox];
|
||||||
|
|
||||||
if (_.isEmpty(toolToolboxSettings)) {
|
if (_.isEmpty(toolToolboxSettings)) {
|
||||||
return;
|
return;
|
||||||
|
@ -83,6 +88,13 @@ export default class BlockTool extends BaseTool<IBlockTool> {
|
||||||
return this.config[UserSettings.EnabledInlineTools];
|
return this.config[UserSettings.EnabledInlineTools];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns enabled tunes for Tool
|
||||||
|
*/
|
||||||
|
public get enabledBlockTunes(): boolean | string[] {
|
||||||
|
return this.config[UserSettings.EnabledBlockTunes];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns Tool paste configuration
|
* Returns Tool paste configuration
|
||||||
*/
|
*/
|
||||||
|
|
65
src/components/tools/collection.ts
Normal file
65
src/components/tools/collection.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import BlockTool from './block';
|
||||||
|
import InlineTool from './inline';
|
||||||
|
import BlockTune from './tune';
|
||||||
|
|
||||||
|
export type ToolClass = BlockTool | InlineTool | BlockTune;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to store Editor Tools
|
||||||
|
*/
|
||||||
|
export default class ToolsCollection<V extends ToolClass = ToolClass> extends Map<string, V> {
|
||||||
|
/**
|
||||||
|
* Returns Block Tools collection
|
||||||
|
*/
|
||||||
|
public get blockTools(): ToolsCollection<BlockTool> {
|
||||||
|
const tools = Array
|
||||||
|
.from(this.entries())
|
||||||
|
.filter(([, tool]) => tool.isBlock()) as [string, BlockTool][];
|
||||||
|
|
||||||
|
return new ToolsCollection<BlockTool>(tools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Inline Tools collection
|
||||||
|
*/
|
||||||
|
public get inlineTools(): ToolsCollection<InlineTool> {
|
||||||
|
const tools = Array
|
||||||
|
.from(this.entries())
|
||||||
|
.filter(([, tool]) => tool.isInline()) as [string, InlineTool][];
|
||||||
|
|
||||||
|
return new ToolsCollection<InlineTool>(tools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Block Tunes collection
|
||||||
|
*/
|
||||||
|
public get blockTunes(): ToolsCollection<BlockTune> {
|
||||||
|
const tools = Array
|
||||||
|
.from(this.entries())
|
||||||
|
.filter(([, tool]) => tool.isTune()) as [string, BlockTune][];
|
||||||
|
|
||||||
|
return new ToolsCollection<BlockTune>(tools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns internal Tools collection
|
||||||
|
*/
|
||||||
|
public get internalTools(): ToolsCollection<V> {
|
||||||
|
const tools = Array
|
||||||
|
.from(this.entries())
|
||||||
|
.filter(([, tool]) => tool.isInternal);
|
||||||
|
|
||||||
|
return new ToolsCollection<V>(tools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Tools collection provided by user
|
||||||
|
*/
|
||||||
|
public get externalTools(): ToolsCollection<V> {
|
||||||
|
const tools = Array
|
||||||
|
.from(this.entries())
|
||||||
|
.filter(([, tool]) => !tool.isInternal);
|
||||||
|
|
||||||
|
return new ToolsCollection<V>(tools);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import InlineTool from './inline';
|
||||||
import BlockTune from './tune';
|
import BlockTune from './tune';
|
||||||
import BlockTool from './block';
|
import BlockTool from './block';
|
||||||
import API from '../modules/api';
|
import API from '../modules/api';
|
||||||
import { ToolType } from '../modules/tools';
|
|
||||||
import { EditorConfig } from '../../../types/configs';
|
import { EditorConfig } from '../../../types/configs';
|
||||||
|
|
||||||
type ToolConstructor = typeof InlineTool | typeof BlockTool | typeof BlockTune;
|
type ToolConstructor = typeof InlineTool | typeof BlockTool | typeof BlockTune;
|
||||||
|
@ -53,13 +52,13 @@ export default class ToolsFactory {
|
||||||
public get(name: string): InlineTool | BlockTool | BlockTune {
|
public get(name: string): InlineTool | BlockTool | BlockTune {
|
||||||
const { class: constructable, isInternal = false, ...config } = this.config[name];
|
const { class: constructable, isInternal = false, ...config } = this.config[name];
|
||||||
|
|
||||||
const [Constructor, type] = this.getConstructor(constructable);
|
const Constructor = this.getConstructor(constructable);
|
||||||
|
|
||||||
return new Constructor({
|
return new Constructor({
|
||||||
name,
|
name,
|
||||||
constructable,
|
constructable,
|
||||||
config,
|
config,
|
||||||
api: this.api.getMethodsForTool(name, type),
|
api: this.api,
|
||||||
isDefault: name === this.editorConfig.defaultBlock,
|
isDefault: name === this.editorConfig.defaultBlock,
|
||||||
defaultPlaceholder: this.editorConfig.placeholder,
|
defaultPlaceholder: this.editorConfig.placeholder,
|
||||||
isInternal,
|
isInternal,
|
||||||
|
@ -71,14 +70,14 @@ export default class ToolsFactory {
|
||||||
*
|
*
|
||||||
* @param constructable - Tools constructable
|
* @param constructable - Tools constructable
|
||||||
*/
|
*/
|
||||||
private getConstructor(constructable: ToolConstructable): [ToolConstructor, ToolType] {
|
private getConstructor(constructable: ToolConstructable): ToolConstructor {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case constructable[InternalInlineToolSettings.IsInline]:
|
case constructable[InternalInlineToolSettings.IsInline]:
|
||||||
return [InlineTool, ToolType.Inline];
|
return InlineTool;
|
||||||
case constructable[InternalTuneSettings.IsTune]:
|
case constructable[InternalTuneSettings.IsTune]:
|
||||||
return [BlockTune, ToolType.Tune];
|
return BlockTune;
|
||||||
default:
|
default:
|
||||||
return [BlockTool, ToolType.Block];
|
return BlockTool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import BaseTool, { InternalInlineToolSettings } from './base';
|
import BaseTool, { InternalInlineToolSettings, ToolType } from './base';
|
||||||
import { ToolType } from '../modules/tools';
|
import { InlineTool as IInlineTool, InlineToolConstructable } from '../../../types';
|
||||||
import { InlineTool as IInlineTool } from '../../../types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InlineTool object to work with Inline Tools constructables
|
* InlineTool object to work with Inline Tools constructables
|
||||||
|
@ -11,6 +10,11 @@ export default class InlineTool extends BaseTool<IInlineTool> {
|
||||||
*/
|
*/
|
||||||
public type = ToolType.Inline;
|
public type = ToolType.Inline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool's constructable blueprint
|
||||||
|
*/
|
||||||
|
protected constructable: InlineToolConstructable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns title for Inline Tool if specified by user
|
* Returns title for Inline Tool if specified by user
|
||||||
*/
|
*/
|
||||||
|
@ -21,10 +25,10 @@ export default class InlineTool extends BaseTool<IInlineTool> {
|
||||||
/**
|
/**
|
||||||
* Constructs new InlineTool instance from constructable
|
* Constructs new InlineTool instance from constructable
|
||||||
*/
|
*/
|
||||||
public instance(): IInlineTool {
|
public create(): IInlineTool {
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
return new this.constructable({
|
return new this.constructable({
|
||||||
api: this.api,
|
api: this.api.getMethodsForTool(this),
|
||||||
config: this.settings,
|
config: this.settings,
|
||||||
}) as IInlineTool;
|
}) as IInlineTool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,36 @@
|
||||||
import BaseTool from './base';
|
import BaseTool, { ToolType } from './base';
|
||||||
import { ToolType } from '../modules/tools';
|
import { BlockAPI, BlockTune as IBlockTune, BlockTuneConstructable } from '../../../types';
|
||||||
|
import { BlockTuneData } from '../../../types/block-tunes/block-tune-data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stub class for BlockTunes
|
* Stub class for BlockTunes
|
||||||
*
|
*
|
||||||
* @todo Implement
|
* @todo Implement
|
||||||
*/
|
*/
|
||||||
export default class BlockTune extends BaseTool<any> {
|
export default class BlockTune extends BaseTool<IBlockTune> {
|
||||||
/**
|
/**
|
||||||
* Tool type — Tune
|
* Tool type — Tune
|
||||||
*/
|
*/
|
||||||
public type = ToolType.Tune;
|
public type = ToolType.Tune;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo implement
|
* Tool's constructable blueprint
|
||||||
*/
|
*/
|
||||||
public instance(): any {
|
protected readonly constructable: BlockTuneConstructable;
|
||||||
return undefined;
|
|
||||||
|
/**
|
||||||
|
* Constructs new BlockTune instance from constructable
|
||||||
|
*
|
||||||
|
* @param data - Tune data
|
||||||
|
* @param block - Block API object
|
||||||
|
*/
|
||||||
|
public create(data: BlockTuneData, block: BlockAPI): IBlockTune {
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
return new this.constructable({
|
||||||
|
api: this.api.getMethodsForTool(this),
|
||||||
|
settings: this.settings,
|
||||||
|
block,
|
||||||
|
data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
{
|
{
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"cypress"
|
"cypress",
|
||||||
|
"chai-friendly"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"cypress/globals": true
|
"cypress/globals": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:cypress/recommended"
|
"plugin:cypress/recommended",
|
||||||
|
"plugin:chai-friendly/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"cypress/require-data-selectors": 2
|
"cypress/require-data-selectors": 2
|
||||||
|
|
|
@ -1,5 +1,38 @@
|
||||||
|
/* tslint:disable:no-var-requires */
|
||||||
/**
|
/**
|
||||||
* This file contains connection of Cypres plugins
|
* This file contains connection of Cypres plugins
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
const webpackConfig = require('../../../webpack.config.js');
|
||||||
// export default function(on, config): void {}
|
const preprocessor = require('@cypress/webpack-preprocessor');
|
||||||
|
const codeCoverageTask = require('@cypress/code-coverage/task');
|
||||||
|
|
||||||
|
module.exports = (on, config): any => {
|
||||||
|
/**
|
||||||
|
* Add Cypress task to get code coverage
|
||||||
|
*/
|
||||||
|
codeCoverageTask(on, config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare webpack preprocessor options
|
||||||
|
*/
|
||||||
|
const options = preprocessor.defaultOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide path to typescript package
|
||||||
|
*/
|
||||||
|
options.typescript = require.resolve('typescript');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide our webpack config
|
||||||
|
*/
|
||||||
|
options.webpackOptions = webpackConfig({}, { mode: 'test' });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register webpack preprocessor
|
||||||
|
*/
|
||||||
|
on('file:preprocessor', preprocessor(options));
|
||||||
|
|
||||||
|
// It's IMPORTANT to return the config object
|
||||||
|
// with any changed environment variables
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
* behavior that modifies Cypress.
|
* behavior that modifies Cypress.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import '@cypress/code-coverage/support';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File with the helpful commands
|
* File with the helpful commands
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe('Editor basic initialization', () => {
|
||||||
const editorConfig = {};
|
const editorConfig = {};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
if (this.editorInstance) {
|
if (this && this.editorInstance) {
|
||||||
this.editorInstance.destroy();
|
this.editorInstance.destroy();
|
||||||
} else {
|
} else {
|
||||||
cy.createEditor(editorConfig).as('editorInstance');
|
cy.createEditor(editorConfig).as('editorInstance');
|
||||||
|
|
215
test/cypress/tests/modules/Tools.spec.ts
Normal file
215
test/cypress/tests/modules/Tools.spec.ts
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
/* eslint-disable @typescript-eslint/ban-ts-ignore */
|
||||||
|
import Tools from '../../../../src/components/modules/tools';
|
||||||
|
import { EditorConfig } from '../../../../types';
|
||||||
|
import BlockTool from '../../../../src/components/tools/block';
|
||||||
|
|
||||||
|
describe('Tools module', () => {
|
||||||
|
const defaultConfig = {
|
||||||
|
tools: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct Tools module for testing purposes
|
||||||
|
*
|
||||||
|
* @param config - Editor config
|
||||||
|
*/
|
||||||
|
function constructModule(config: EditorConfig = defaultConfig): Tools {
|
||||||
|
const module = new Tools({
|
||||||
|
config,
|
||||||
|
eventsDispatcher: {},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
const APIMethods = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
method(): void {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module state should be Editor modules, so we mock required ones only
|
||||||
|
*/
|
||||||
|
module.state = {
|
||||||
|
API: {
|
||||||
|
getMethodsForTool(): typeof APIMethods {
|
||||||
|
return APIMethods;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
context('.prepare()', () => {
|
||||||
|
it('should return Promise resolved to void', async () => {
|
||||||
|
const module = constructModule();
|
||||||
|
|
||||||
|
let err;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await module.prepare();
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(err).to.be.undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if tools config is corrupted', async () => {
|
||||||
|
const module = constructModule({
|
||||||
|
tools: {
|
||||||
|
// @ts-ignore
|
||||||
|
corruptedTool: 'value',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let err;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await module.prepare();
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(err).to.be.instanceOf(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('collection accessors', () => {
|
||||||
|
let module: Tools;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
module = constructModule({
|
||||||
|
defaultBlock: 'withoutPrepare',
|
||||||
|
tools: {
|
||||||
|
withSuccessfulPrepare: class {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
public static prepare(): void {}
|
||||||
|
} as any,
|
||||||
|
withFailedPrepare: class {
|
||||||
|
public static prepare(): void {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
} as any,
|
||||||
|
withoutPrepare: class {
|
||||||
|
} as any,
|
||||||
|
inlineTool: class {
|
||||||
|
public static isInline = true
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
public render(): void {}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
public surround(): void {}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
public checkState(): void {}
|
||||||
|
} as any,
|
||||||
|
/**
|
||||||
|
* This tool will be unavailable as it doesn't have required methods
|
||||||
|
*/
|
||||||
|
unavailableInlineTool: class {
|
||||||
|
public static isInline = true;
|
||||||
|
} as any,
|
||||||
|
blockTune: class {
|
||||||
|
public static isTune = true;
|
||||||
|
} as any,
|
||||||
|
unavailableBlockTune: class {
|
||||||
|
public static isTune = true;
|
||||||
|
|
||||||
|
public static prepare(): void {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
} as any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await module.prepare();
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.available', () => {
|
||||||
|
it('should return Map instance', () => {
|
||||||
|
expect(module.available).to.be.instanceOf(Map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain only ready to use Tools', () => {
|
||||||
|
expect(module.available.has('withSuccessfulPrepare')).to.be.true;
|
||||||
|
expect(module.available.has('withoutPrepare')).to.be.true;
|
||||||
|
expect(module.available.has('withFailedPrepare')).to.be.false;
|
||||||
|
expect(module.available.has('unavailableInlineTool')).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.unavailable', () => {
|
||||||
|
it('should return Map instance', () => {
|
||||||
|
expect(module.unavailable).to.be.instanceOf(Map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain only ready to use Tools', () => {
|
||||||
|
expect(module.unavailable.has('withSuccessfulPrepare')).to.be.false;
|
||||||
|
expect(module.unavailable.has('withoutPrepare')).to.be.false;
|
||||||
|
expect(module.unavailable.has('withFailedPrepare')).to.be.true;
|
||||||
|
expect(module.unavailable.has('unavailableInlineTool')).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.inlineTools', () => {
|
||||||
|
it('should return Map instance', () => {
|
||||||
|
expect(module.inlineTools).to.be.instanceOf(Map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain only available Inline Tools', () => {
|
||||||
|
expect(module.inlineTools.has('inlineTool')).to.be.true;
|
||||||
|
expect(module.inlineTools.has('unavailableInlineTool')).to.be.false;
|
||||||
|
expect(Array.from(module.inlineTools.values()).every(tool => tool.isInline())).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.blockTools', () => {
|
||||||
|
it('should return Map instance', () => {
|
||||||
|
expect(module.blockTools).to.be.instanceOf(Map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain only available Block Tools', () => {
|
||||||
|
expect(module.blockTools.has('withSuccessfulPrepare')).to.be.true;
|
||||||
|
expect(module.blockTools.has('withoutPrepare')).to.be.true;
|
||||||
|
expect(module.blockTools.has('withFailedPrepare')).to.be.false;
|
||||||
|
expect(Array.from(module.blockTools.values()).every(tool => tool.isBlock())).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.blockTunes', () => {
|
||||||
|
it('should return Map instance', () => {
|
||||||
|
expect(module.blockTunes).to.be.instanceOf(Map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain only available Block Tunes', () => {
|
||||||
|
expect(module.blockTunes.has('blockTune')).to.be.true;
|
||||||
|
expect(module.blockTunes.has('unavailableBlockTune')).to.be.false;
|
||||||
|
expect(Array.from(module.blockTunes.values()).every(tool => tool.isTune())).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.internal', () => {
|
||||||
|
it('should return Map instance', () => {
|
||||||
|
expect(module.internal).to.be.instanceOf(Map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain only internal tunes', () => {
|
||||||
|
expect(Array.from(module.internal.values()).every(tool => tool.isInternal)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.defaultTools', () => {
|
||||||
|
/**
|
||||||
|
* @todo add check if user provided default tool is not Block Tool
|
||||||
|
*/
|
||||||
|
it('should return BlockTool instance', () => {
|
||||||
|
expect(module.defaultTool).to.be.instanceOf(BlockTool);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return default Tool', () => {
|
||||||
|
expect(module.defaultTool.isDefault).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
379
test/cypress/tests/tools/BlockTool.spec.ts
Normal file
379
test/cypress/tests/tools/BlockTool.spec.ts
Normal file
|
@ -0,0 +1,379 @@
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
import { BlockToolData, ToolSettings } from '../../../../types';
|
||||||
|
import { ToolType } from '../../../../src/components/tools/base';
|
||||||
|
import BlockTool from '../../../../src/components/tools/block';
|
||||||
|
|
||||||
|
describe('BlockTool', () => {
|
||||||
|
/**
|
||||||
|
* Mock for BlockTool constructor options
|
||||||
|
*/
|
||||||
|
const options = {
|
||||||
|
name: 'blockTool',
|
||||||
|
constructable: class {
|
||||||
|
public static sanitize = {
|
||||||
|
rule1: 'rule1',
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toolbox = {
|
||||||
|
icon: 'Tool icon',
|
||||||
|
title: 'Tool title',
|
||||||
|
};
|
||||||
|
|
||||||
|
public static enableLineBreaks = true;
|
||||||
|
|
||||||
|
public static pasteConfig = {
|
||||||
|
tags: [ 'div' ],
|
||||||
|
};
|
||||||
|
|
||||||
|
public static conversionConfig = {
|
||||||
|
import: 'import',
|
||||||
|
export: 'export',
|
||||||
|
};
|
||||||
|
|
||||||
|
public static isReadOnlySupported = true;
|
||||||
|
|
||||||
|
public static reset;
|
||||||
|
public static prepare;
|
||||||
|
|
||||||
|
public static shortcut = 'CTRL+N';
|
||||||
|
|
||||||
|
public data: BlockToolData;
|
||||||
|
public block: object;
|
||||||
|
public readonly: boolean;
|
||||||
|
public api: object;
|
||||||
|
public config: ToolSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor({ data, block, readOnly, api, config }) {
|
||||||
|
this.data = data;
|
||||||
|
this.block = block;
|
||||||
|
this.readonly = readOnly;
|
||||||
|
this.api = api;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
config: {
|
||||||
|
option1: 'option1',
|
||||||
|
option2: 'option2',
|
||||||
|
},
|
||||||
|
inlineToolbar: ['link', 'bold'],
|
||||||
|
tunes: ['anchor', 'favorites'],
|
||||||
|
shortcut: 'CMD+SHIFT+B',
|
||||||
|
toolbox: {
|
||||||
|
title: 'User Block Tool',
|
||||||
|
icon: 'User icon',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
getMethodsForTool(): object {
|
||||||
|
return {
|
||||||
|
prop1: 'prop1',
|
||||||
|
prop2: 'prop2',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isDefault: false,
|
||||||
|
isInternal: false,
|
||||||
|
defaultPlaceholder: 'Default placeholder',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('.type should return ToolType.Block', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.type).to.be.eq(ToolType.Block);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.name should return correct value', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.name).to.be.eq(options.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isDefault should return correct value', () => {
|
||||||
|
const tool1 = new BlockTool(options as any);
|
||||||
|
const tool2 = new BlockTool({
|
||||||
|
...options,
|
||||||
|
isDefault: true,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool1.isDefault).to.be.false;
|
||||||
|
expect(tool2.isDefault).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isInternal should return correct value', () => {
|
||||||
|
const tool1 = new BlockTool(options as any);
|
||||||
|
const tool2 = new BlockTool({
|
||||||
|
...options,
|
||||||
|
isInternal: true,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool1.isInternal).to.be.false;
|
||||||
|
expect(tool2.isInternal).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.settings', () => {
|
||||||
|
it('should return correct value', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.settings).to.be.deep.eq(options.config.config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add default placeholder if Tool is default', () => {
|
||||||
|
const tool = new BlockTool({
|
||||||
|
...options,
|
||||||
|
isDefault: true,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.settings).to.have.property('placeholder').that.eq(options.defaultPlaceholder);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.sanitizeConfig should return correct value', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.sanitizeConfig).to.be.deep.eq(options.constructable.sanitize);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isBlock() should return true', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.isBlock()).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isInline() should return false', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.isInline()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isTune() should return false', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.isTune()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isReadOnlySupported should return correct value', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.isReadOnlySupported).to.be.eq(options.constructable.isReadOnlySupported);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isLineBreaksEnabled should return correct value', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.isLineBreaksEnabled).to.be.eq(options.constructable.enableLineBreaks);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.conversionConfig should return correct value', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.conversionConfig).to.be.deep.eq(options.constructable.conversionConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.pasteConfig should return correct value', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.pasteConfig).to.be.deep.eq(options.constructable.pasteConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.enabledInlineTools should return correct value', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.enabledInlineTools).to.be.deep.eq(options.config.inlineToolbar);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.enabledBlockTunes should return correct value', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.enabledBlockTunes).to.be.deep.eq(options.config.tunes);
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.prepare()', () => {
|
||||||
|
it('should call Tool prepare method', () => {
|
||||||
|
options.constructable.prepare = cy.stub();
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
tool.prepare();
|
||||||
|
|
||||||
|
expect(options.constructable.prepare).to.have.been.calledWithMatch({
|
||||||
|
toolName: tool.name,
|
||||||
|
config: tool.settings,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fail if Tool prepare method is not exist', () => {
|
||||||
|
const tool = new BlockTool({
|
||||||
|
...options,
|
||||||
|
constructable: {},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.prepare).to.not.throw;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.reset()', () => {
|
||||||
|
it('should call Tool reset method', () => {
|
||||||
|
options.constructable.reset = cy.stub();
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
tool.reset();
|
||||||
|
|
||||||
|
expect(options.constructable.reset).to.be.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fail if Tool reset method is not exist', () => {
|
||||||
|
const tool = new BlockTool({
|
||||||
|
...options,
|
||||||
|
constructable: {},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.reset).to.not.throw;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.shortcut', () => {
|
||||||
|
it('should return user provided shortcut', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.shortcut).to.be.eq(options.config.shortcut);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool provided shortcut if user one is not specified', () => {
|
||||||
|
const tool = new BlockTool({
|
||||||
|
...options,
|
||||||
|
config: {
|
||||||
|
...options.config,
|
||||||
|
shortcut: undefined,
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.shortcut).to.be.eq(options.constructable.shortcut);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.toolbox', () => {
|
||||||
|
it('should return user provided toolbox config', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.toolbox).to.be.deep.eq(options.config.toolbox);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool provided toolbox config if user one is not specified', () => {
|
||||||
|
const tool = new BlockTool({
|
||||||
|
...options,
|
||||||
|
config: {
|
||||||
|
...options.config,
|
||||||
|
toolbox: undefined,
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.toolbox).to.be.deep.eq(options.constructable.toolbox);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge Tool provided toolbox config and user one', () => {
|
||||||
|
const tool1 = new BlockTool({
|
||||||
|
...options,
|
||||||
|
config: {
|
||||||
|
...options.config,
|
||||||
|
toolbox: {
|
||||||
|
title: options.config.toolbox.title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
const tool2 = new BlockTool({
|
||||||
|
...options,
|
||||||
|
config: {
|
||||||
|
...options.config,
|
||||||
|
toolbox: {
|
||||||
|
icon: options.config.toolbox.icon,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool1.toolbox).to.be.deep.eq(Object.assign({}, options.constructable.toolbox, { title: options.config.toolbox.title }));
|
||||||
|
expect(tool2.toolbox).to.be.deep.eq(Object.assign({}, options.constructable.toolbox, { icon: options.config.toolbox.icon }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if user specifies false as a value', () => {
|
||||||
|
const tool = new BlockTool({
|
||||||
|
...options,
|
||||||
|
config: {
|
||||||
|
...options.config,
|
||||||
|
toolbox: false,
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.toolbox).to.be.undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if Tool specifies false as a value', () => {
|
||||||
|
const tool = new BlockTool({
|
||||||
|
...options,
|
||||||
|
constructable: class {
|
||||||
|
public static toolbox = false
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.toolbox).to.be.undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if Tool provides empty config', () => {
|
||||||
|
const tool = new BlockTool({
|
||||||
|
...options,
|
||||||
|
constructable: class {
|
||||||
|
public static toolbox = {}
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.toolbox).to.be.undefined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.create()', () => {
|
||||||
|
const tool = new BlockTool(options as any);
|
||||||
|
const data = { text: 'text' };
|
||||||
|
const blockAPI = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
method(): void {},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should return Tool instance', () => {
|
||||||
|
expect(tool.create(data, blockAPI as any, false)).to.be.instanceOf(options.constructable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed data', () => {
|
||||||
|
const instance = tool.create(data, blockAPI as any, false) as any;
|
||||||
|
|
||||||
|
expect(instance.data).to.be.deep.eq(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed BlockAPI object', () => {
|
||||||
|
const instance = tool.create(data, blockAPI as any, false) as any;
|
||||||
|
|
||||||
|
expect(instance.block).to.be.deep.eq(blockAPI);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed readOnly flag', () => {
|
||||||
|
const instance1 = tool.create(data, blockAPI as any, false) as any;
|
||||||
|
const instance2 = tool.create(data, blockAPI as any, true) as any;
|
||||||
|
|
||||||
|
expect(instance1.readonly).to.be.eq(false);
|
||||||
|
expect(instance2.readonly).to.be.eq(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed API object', () => {
|
||||||
|
const instance = tool.create(data, blockAPI as any, false) as any;
|
||||||
|
|
||||||
|
expect(instance.api).to.be.deep.eq(options.api.getMethodsForTool());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed config', () => {
|
||||||
|
const instance = tool.create(data, blockAPI as any, false) as any;
|
||||||
|
|
||||||
|
expect(instance.config).to.be.deep.eq(options.config.config);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
178
test/cypress/tests/tools/BlockTune.spec.ts
Normal file
178
test/cypress/tests/tools/BlockTune.spec.ts
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
import { ToolSettings } from '../../../../types';
|
||||||
|
import { ToolType } from '../../../../src/components/tools/base';
|
||||||
|
import BlockTune from '../../../../src/components/tools/tune';
|
||||||
|
import { BlockTuneData } from '../../../../types/block-tunes/block-tune-data';
|
||||||
|
|
||||||
|
describe('BlockTune', () => {
|
||||||
|
/**
|
||||||
|
* Mock for BlockTune constructor options
|
||||||
|
*/
|
||||||
|
const options = {
|
||||||
|
name: 'blockTune',
|
||||||
|
constructable: class {
|
||||||
|
public static reset;
|
||||||
|
public static prepare;
|
||||||
|
|
||||||
|
public api: object;
|
||||||
|
public settings: ToolSettings;
|
||||||
|
public data: BlockTuneData;
|
||||||
|
public block: object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor({ api, settings, block, data }) {
|
||||||
|
this.api = api;
|
||||||
|
this.settings = settings;
|
||||||
|
this.block = block;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
config: {
|
||||||
|
option1: 'option1',
|
||||||
|
option2: 'option2',
|
||||||
|
},
|
||||||
|
shortcut: 'CMD+SHIFT+B',
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
getMethodsForTool(): object {
|
||||||
|
return {
|
||||||
|
prop1: 'prop1',
|
||||||
|
prop2: 'prop2',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isDefault: false,
|
||||||
|
isInternal: false,
|
||||||
|
defaultPlaceholder: 'Default placeholder',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('.type should return ToolType.Tune', () => {
|
||||||
|
const tool = new BlockTune(options as any);
|
||||||
|
|
||||||
|
expect(tool.type).to.be.eq(ToolType.Tune);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.name should return correct value', () => {
|
||||||
|
const tool = new BlockTune(options as any);
|
||||||
|
|
||||||
|
expect(tool.name).to.be.eq(options.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isInternal should return correct value', () => {
|
||||||
|
const tool1 = new BlockTune(options as any);
|
||||||
|
const tool2 = new BlockTune({
|
||||||
|
...options,
|
||||||
|
isInternal: true,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool1.isInternal).to.be.false;
|
||||||
|
expect(tool2.isInternal).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.settings should return correct value', () => {
|
||||||
|
const tool = new BlockTune(options as any);
|
||||||
|
|
||||||
|
expect(tool.settings).to.be.deep.eq(options.config.config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isBlock() should return false', () => {
|
||||||
|
const tool = new BlockTune(options as any);
|
||||||
|
|
||||||
|
expect(tool.isBlock()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isInline() should return false', () => {
|
||||||
|
const tool = new BlockTune(options as any);
|
||||||
|
|
||||||
|
expect(tool.isInline()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isTune() should return true', () => {
|
||||||
|
const tool = new BlockTune(options as any);
|
||||||
|
|
||||||
|
expect(tool.isTune()).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.prepare()', () => {
|
||||||
|
it('should call Tool prepare method', () => {
|
||||||
|
options.constructable.prepare = cy.stub();
|
||||||
|
const tool = new BlockTune(options as any);
|
||||||
|
|
||||||
|
tool.prepare();
|
||||||
|
|
||||||
|
expect(options.constructable.prepare).to.have.been.calledWithMatch({
|
||||||
|
toolName: tool.name,
|
||||||
|
config: tool.settings,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fail if Tool prepare method is not exist', () => {
|
||||||
|
const tool = new BlockTune({
|
||||||
|
...options,
|
||||||
|
constructable: {},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.prepare).to.not.throw;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.reset()', () => {
|
||||||
|
it('should call Tool reset method', () => {
|
||||||
|
options.constructable.reset = cy.stub();
|
||||||
|
const tool = new BlockTune(options as any);
|
||||||
|
|
||||||
|
tool.reset();
|
||||||
|
|
||||||
|
expect(options.constructable.reset).to.be.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fail if Tool reset method is not exist', () => {
|
||||||
|
const tool = new BlockTune({
|
||||||
|
...options,
|
||||||
|
constructable: {},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.reset).to.not.throw;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.create()', () => {
|
||||||
|
const tool = new BlockTune(options as any);
|
||||||
|
const data = { text: 'text' };
|
||||||
|
const blockAPI = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
method(): void {},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should return Tool instance', () => {
|
||||||
|
expect(tool.create(data, blockAPI as any)).to.be.instanceOf(options.constructable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed data', () => {
|
||||||
|
const instance = tool.create(data, blockAPI as any) as any;
|
||||||
|
|
||||||
|
expect(instance.data).to.be.deep.eq(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed BlockAPI object', () => {
|
||||||
|
const instance = tool.create(data, blockAPI as any) as any;
|
||||||
|
|
||||||
|
expect(instance.block).to.be.deep.eq(blockAPI);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed API object', () => {
|
||||||
|
const instance = tool.create(data, blockAPI as any) as any;
|
||||||
|
|
||||||
|
expect(instance.api).to.be.deep.eq(options.api.getMethodsForTool());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed settings', () => {
|
||||||
|
const instance = tool.create(data, blockAPI as any) as any;
|
||||||
|
|
||||||
|
expect(instance.settings).to.be.deep.eq(options.config.config);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
196
test/cypress/tests/tools/InlineTool.spec.ts
Normal file
196
test/cypress/tests/tools/InlineTool.spec.ts
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
import { ToolSettings } from '../../../../types';
|
||||||
|
import { ToolType } from '../../../../src/components/tools/base';
|
||||||
|
import InlineTool from '../../../../src/components/tools/inline';
|
||||||
|
|
||||||
|
describe('InlineTool', () => {
|
||||||
|
/**
|
||||||
|
* Mock for InlineTool constructor options
|
||||||
|
*/
|
||||||
|
const options = {
|
||||||
|
name: 'inlineTool',
|
||||||
|
constructable: class {
|
||||||
|
public static sanitize = {
|
||||||
|
rule1: 'rule1',
|
||||||
|
}
|
||||||
|
|
||||||
|
public static title = 'Title'
|
||||||
|
|
||||||
|
public static reset;
|
||||||
|
public static prepare;
|
||||||
|
|
||||||
|
public static shortcut = 'CTRL+N';
|
||||||
|
|
||||||
|
public api: object;
|
||||||
|
public config: ToolSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor({ api, config }) {
|
||||||
|
this.api = api;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
config: {
|
||||||
|
option1: 'option1',
|
||||||
|
option2: 'option2',
|
||||||
|
},
|
||||||
|
shortcut: 'CMD+SHIFT+B',
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
getMethodsForTool(): object {
|
||||||
|
return {
|
||||||
|
prop1: 'prop1',
|
||||||
|
prop2: 'prop2',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isDefault: false,
|
||||||
|
isInternal: false,
|
||||||
|
defaultPlaceholder: 'Default placeholder',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('.type should return ToolType.Inline', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.type).to.be.eq(ToolType.Inline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.name should return correct value', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.name).to.be.eq(options.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.title should return correct title', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.title).to.be.eq(options.constructable.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isInternal should return correct value', () => {
|
||||||
|
const tool1 = new InlineTool(options as any);
|
||||||
|
const tool2 = new InlineTool({
|
||||||
|
...options,
|
||||||
|
isInternal: true,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool1.isInternal).to.be.false;
|
||||||
|
expect(tool2.isInternal).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.settings should return correct value', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.settings).to.be.deep.eq(options.config.config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.sanitizeConfig should return correct value', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.sanitizeConfig).to.be.deep.eq(options.constructable.sanitize);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isBlock() should return false', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.isBlock()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isInline() should return true', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.isInline()).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('.isTune() should return false', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.isTune()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.prepare()', () => {
|
||||||
|
it('should call Tool prepare method', () => {
|
||||||
|
options.constructable.prepare = cy.stub();
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
tool.prepare();
|
||||||
|
|
||||||
|
expect(options.constructable.prepare).to.have.been.calledWithMatch({
|
||||||
|
toolName: tool.name,
|
||||||
|
config: tool.settings,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fail if Tool prepare method is not exist', () => {
|
||||||
|
const tool = new InlineTool({
|
||||||
|
...options,
|
||||||
|
constructable: {},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.prepare).to.not.throw;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.reset()', () => {
|
||||||
|
it('should call Tool reset method', () => {
|
||||||
|
options.constructable.reset = cy.stub();
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
tool.reset();
|
||||||
|
|
||||||
|
expect(options.constructable.reset).to.be.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fail if Tool reset method is not exist', () => {
|
||||||
|
const tool = new InlineTool({
|
||||||
|
...options,
|
||||||
|
constructable: {},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.reset).to.not.throw;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.shortcut', () => {
|
||||||
|
it('should return user provided shortcut', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
expect(tool.shortcut).to.be.eq(options.config.shortcut);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool provided shortcut if user one is not specified', () => {
|
||||||
|
const tool = new InlineTool({
|
||||||
|
...options,
|
||||||
|
config: {
|
||||||
|
...options.config,
|
||||||
|
shortcut: undefined,
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
expect(tool.shortcut).to.be.eq(options.constructable.shortcut);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.create()', () => {
|
||||||
|
const tool = new InlineTool(options as any);
|
||||||
|
|
||||||
|
it('should return Tool instance', () => {
|
||||||
|
expect(tool.create()).to.be.instanceOf(options.constructable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed API object', () => {
|
||||||
|
const instance = tool.create() as any;
|
||||||
|
|
||||||
|
expect(instance.api).to.be.deep.eq(options.api.getMethodsForTool());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return Tool instance with passed config', () => {
|
||||||
|
const instance = tool.create() as any;
|
||||||
|
|
||||||
|
expect(instance.config).to.be.deep.eq(options.config.config);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
185
test/cypress/tests/tools/ToolsCollection.spec.ts
Normal file
185
test/cypress/tests/tools/ToolsCollection.spec.ts
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
import ToolsCollection from '../../../../src/components/tools/collection';
|
||||||
|
import BlockTool from '../../../../src/components/tools/block';
|
||||||
|
import InlineTool from '../../../../src/components/tools/inline';
|
||||||
|
import BlockTune from '../../../../src/components/tools/tune';
|
||||||
|
import BaseTool from '../../../../src/components/tools/base';
|
||||||
|
|
||||||
|
const FakeTool = {
|
||||||
|
isBlock(): boolean {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isInline(): boolean {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isTune(): boolean {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isInternal: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FakeBlockTool = {
|
||||||
|
...FakeTool,
|
||||||
|
isBlock(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FakeInlineTool = {
|
||||||
|
...FakeTool,
|
||||||
|
isInline(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FakeBlockTune = {
|
||||||
|
...FakeTool,
|
||||||
|
isTune(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for ToolsCollection class
|
||||||
|
*/
|
||||||
|
describe('ToolsCollection', (): void => {
|
||||||
|
let collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock for Tools in collection
|
||||||
|
*/
|
||||||
|
const fakeTools = [
|
||||||
|
['block1', FakeBlockTool],
|
||||||
|
['inline1', FakeInlineTool],
|
||||||
|
['block2', {
|
||||||
|
...FakeBlockTool,
|
||||||
|
isInternal: true,
|
||||||
|
} ],
|
||||||
|
['tune1', FakeBlockTune],
|
||||||
|
['block3', FakeBlockTool],
|
||||||
|
['inline2', {
|
||||||
|
...FakeInlineTool,
|
||||||
|
isInternal: true,
|
||||||
|
} ],
|
||||||
|
['tune2', FakeBlockTune],
|
||||||
|
['tune3', {
|
||||||
|
...FakeBlockTune,
|
||||||
|
isInternal: true,
|
||||||
|
} ],
|
||||||
|
['block3', FakeInlineTool],
|
||||||
|
['block4', FakeBlockTool],
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
collection = new ToolsCollection(fakeTools as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be instance of Map', (): void => {
|
||||||
|
expect(collection instanceof Map).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.blockTools', (): void => {
|
||||||
|
it('should return new instance of ToolsCollection', (): void => {
|
||||||
|
expect(collection.blockTools instanceof ToolsCollection).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('result should contain only block tools', (): void => {
|
||||||
|
expect(
|
||||||
|
Array
|
||||||
|
.from(
|
||||||
|
collection.blockTools.values()
|
||||||
|
)
|
||||||
|
.every((tool: BlockTool) => tool.isBlock())
|
||||||
|
).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.inlineTools', (): void => {
|
||||||
|
it('should return new instance of ToolsCollection', (): void => {
|
||||||
|
expect(collection.inlineTools instanceof ToolsCollection).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('result should contain only inline tools', (): void => {
|
||||||
|
expect(
|
||||||
|
Array
|
||||||
|
.from(
|
||||||
|
collection.inlineTools.values()
|
||||||
|
)
|
||||||
|
.every((tool: InlineTool) => tool.isInline())
|
||||||
|
).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.blockTunes', (): void => {
|
||||||
|
it('should return new instance of ToolsCollection', (): void => {
|
||||||
|
expect(collection.blockTunes instanceof ToolsCollection).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('result should contain only block tools', (): void => {
|
||||||
|
expect(
|
||||||
|
Array
|
||||||
|
.from(
|
||||||
|
collection.blockTunes.values()
|
||||||
|
)
|
||||||
|
.every((tool: BlockTune) => tool.isTune())
|
||||||
|
).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.internalTools', (): void => {
|
||||||
|
it('should return new instance of ToolsCollection', (): void => {
|
||||||
|
expect(collection.internalTools instanceof ToolsCollection).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('result should contain only internal tools', (): void => {
|
||||||
|
expect(
|
||||||
|
Array
|
||||||
|
.from(
|
||||||
|
collection.internalTools.values()
|
||||||
|
)
|
||||||
|
.every((tool: BaseTool) => tool.isInternal)
|
||||||
|
).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.externalTools', (): void => {
|
||||||
|
it('should return new instance of ToolsCollection', (): void => {
|
||||||
|
expect(collection.externalTools instanceof ToolsCollection).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('result should contain only external tools', (): void => {
|
||||||
|
expect(
|
||||||
|
Array
|
||||||
|
.from(
|
||||||
|
collection.externalTools.values()
|
||||||
|
)
|
||||||
|
.every((tool: BaseTool) => !tool.isInternal)
|
||||||
|
).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('mixed access', (): void => {
|
||||||
|
context('.blockTunes.internalTools', (): void => {
|
||||||
|
it('should return only internal tunes', (): void => {
|
||||||
|
expect(
|
||||||
|
Array
|
||||||
|
.from(
|
||||||
|
collection.blockTunes.internalTools.values()
|
||||||
|
)
|
||||||
|
.every((tool: BlockTune) => tool.isTune() && tool.isInternal)
|
||||||
|
).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.externalTools.blockTools', (): void => {
|
||||||
|
it('should return only external block tools', (): void => {
|
||||||
|
expect(
|
||||||
|
Array
|
||||||
|
.from(
|
||||||
|
collection.externalTools.blockTools.values()
|
||||||
|
)
|
||||||
|
.every((tool: BlockTool) => tool.isBlock() && !tool.isInternal)
|
||||||
|
).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
59
test/cypress/tests/tools/ToolsFactory.spec.ts
Normal file
59
test/cypress/tests/tools/ToolsFactory.spec.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
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';
|
||||||
|
import InlineTool from '../../../../src/components/tools/inline';
|
||||||
|
import BlockTool from '../../../../src/components/tools/block';
|
||||||
|
import BlockTune from '../../../../src/components/tools/tune';
|
||||||
|
import Paragraph from '../../../../src/tools/paragraph/dist/bundle';
|
||||||
|
|
||||||
|
describe('ToolsFactory', (): void => {
|
||||||
|
let factory;
|
||||||
|
const config = {
|
||||||
|
paragraph: {
|
||||||
|
class: Paragraph,
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
class: LinkInlineTool,
|
||||||
|
},
|
||||||
|
moveUp: {
|
||||||
|
class: MoveUpTune,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
factory = new ToolsFactory(
|
||||||
|
config,
|
||||||
|
{
|
||||||
|
placeholder: 'Placeholder',
|
||||||
|
defaultBlock: 'paragraph',
|
||||||
|
} as any,
|
||||||
|
{} as any
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
context('.get', (): void => {
|
||||||
|
it('should return appropriate tool object', (): void => {
|
||||||
|
const tool = factory.get('link');
|
||||||
|
|
||||||
|
expect(tool.name).to.be.eq('link');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return InlineTool object for inline tool', (): void => {
|
||||||
|
const tool = factory.get('link');
|
||||||
|
|
||||||
|
expect(tool instanceof InlineTool).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return BlockTool object for block tool', (): void => {
|
||||||
|
const tool = factory.get('paragraph');
|
||||||
|
|
||||||
|
expect(tool instanceof BlockTool).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return BlockTune object for tune', (): void => {
|
||||||
|
const tool = factory.get('moveUp');
|
||||||
|
|
||||||
|
expect(tool instanceof BlockTune).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,10 +1,12 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"lib": ["es2017", "dom"],
|
"lib": ["dom", "es2017", "es2018"],
|
||||||
"types": ["cypress"]
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts"
|
"../../**/*.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
1
types/block-tunes/block-tune-data.d.ts
vendored
Normal file
1
types/block-tunes/block-tune-data.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type BlockTuneData = any;
|
50
types/block-tunes/block-tune.d.ts
vendored
50
types/block-tunes/block-tune.d.ts
vendored
|
@ -1,4 +1,5 @@
|
||||||
import {API, ToolConfig} from '../index';
|
import {API, BlockAPI, ToolConfig} from '../index';
|
||||||
|
import { BlockTuneData } from './block-tune-data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes BLockTune blueprint
|
* Describes BLockTune blueprint
|
||||||
|
@ -10,11 +11,56 @@ export interface BlockTune {
|
||||||
* @return {HTMLElement}
|
* @return {HTMLElement}
|
||||||
*/
|
*/
|
||||||
render(): HTMLElement;
|
render(): HTMLElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on Tool render. Pass Tool content as an argument.
|
||||||
|
*
|
||||||
|
* You can wrap Tool's content with any wrapper you want to provide Tune's UI
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} pluginsContent — Tool's content wrapper
|
||||||
|
*
|
||||||
|
* @return {HTMLElement}
|
||||||
|
*/
|
||||||
|
wrap?(pluginsContent: HTMLElement): HTMLElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on Tool's saving. Should return any data Tune needs to save
|
||||||
|
*
|
||||||
|
* @return {BlockTuneData}
|
||||||
|
*/
|
||||||
|
save?(): BlockTuneData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes BlockTune class constructor function
|
* Describes BlockTune class constructor function
|
||||||
*/
|
*/
|
||||||
export interface BlockTuneConstructable {
|
export interface BlockTuneConstructable {
|
||||||
new (config: {api: API, settings?: ToolConfig}): BlockTune;
|
|
||||||
|
/**
|
||||||
|
* Flag show Tool is Block Tune
|
||||||
|
*/
|
||||||
|
isTune: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*
|
||||||
|
* @param config - Block Tune config
|
||||||
|
*/
|
||||||
|
new(config: {
|
||||||
|
api: API,
|
||||||
|
settings?: ToolConfig,
|
||||||
|
block: BlockAPI,
|
||||||
|
data: BlockTuneData,
|
||||||
|
}): BlockTune;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tune`s prepare method. Can be async
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
prepare?(): Promise<void> | void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tune`s reset method to clean up anything set by prepare. Can be async
|
||||||
|
*/
|
||||||
|
reset?(): void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
5
types/configs/editor-config.d.ts
vendored
5
types/configs/editor-config.d.ts
vendored
|
@ -95,4 +95,9 @@ export interface EditorConfig {
|
||||||
* Defines default toolbar for all tools.
|
* Defines default toolbar for all tools.
|
||||||
*/
|
*/
|
||||||
inlineToolbar?: string[]|boolean;
|
inlineToolbar?: string[]|boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common Block Tunes list. Will be added to all the blocks which do not specify their own 'tunes' set
|
||||||
|
*/
|
||||||
|
tunes?: string[];
|
||||||
}
|
}
|
||||||
|
|
6
types/data-formats/output-data.d.ts
vendored
6
types/data-formats/output-data.d.ts
vendored
|
@ -1,4 +1,5 @@
|
||||||
import {BlockToolData} from '../tools';
|
import {BlockToolData} from '../tools';
|
||||||
|
import {BlockTuneData} from "../block-tunes/block-tune-data";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output of one Tool
|
* Output of one Tool
|
||||||
|
@ -15,6 +16,11 @@ export interface OutputBlockData<Type extends string = string, Data extends obje
|
||||||
* Saved Block data
|
* Saved Block data
|
||||||
*/
|
*/
|
||||||
data: BlockToolData<Data>;
|
data: BlockToolData<Data>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block Tunes data
|
||||||
|
*/
|
||||||
|
tunes?: {[name: string]: BlockTuneData};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OutputData {
|
export interface OutputData {
|
||||||
|
|
10
types/tools/index.d.ts
vendored
10
types/tools/index.d.ts
vendored
|
@ -1,6 +1,6 @@
|
||||||
import {BlockTool, BlockToolConstructable} from './block-tool';
|
import { BlockTool, BlockToolConstructable } from './block-tool';
|
||||||
import {InlineTool, InlineToolConstructable} from './inline-tool';
|
import { InlineTool, InlineToolConstructable } from './inline-tool';
|
||||||
import {BaseTool, BaseToolConstructable} from './tool';
|
import { BlockTune, BlockTuneConstructable } from '../block-tunes';
|
||||||
|
|
||||||
export * from './block-tool';
|
export * from './block-tool';
|
||||||
export * from './block-tool-data';
|
export * from './block-tool-data';
|
||||||
|
@ -11,5 +11,5 @@ export * from './tool-settings';
|
||||||
export * from './paste-events';
|
export * from './paste-events';
|
||||||
export * from './hook-events';
|
export * from './hook-events';
|
||||||
|
|
||||||
export type Tool = BlockTool | InlineTool;
|
export type Tool = BlockTool | InlineTool | BlockTune;
|
||||||
export type ToolConstructable = BlockToolConstructable | InlineToolConstructable;
|
export type ToolConstructable = BlockToolConstructable | InlineToolConstructable | BlockTuneConstructable;
|
||||||
|
|
6
types/tools/tool-settings.d.ts
vendored
6
types/tools/tool-settings.d.ts
vendored
|
@ -39,6 +39,12 @@ export interface ToolSettings <Config extends object = any> {
|
||||||
*/
|
*/
|
||||||
inlineToolbar?: boolean | string[];
|
inlineToolbar?: boolean | string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BlockTunes for Tool
|
||||||
|
* Can accept array of tune names or boolean.
|
||||||
|
*/
|
||||||
|
tunes?: boolean | string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define shortcut that will render Tool
|
* Define shortcut that will render Tool
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue