mirror of
https://github.com/codex-team/editor.js
synced 2024-05-28 11:33:04 +02:00
[Refactoring] Tools (#1595)
* 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 * Review changes * Fix * Fixes after review * Readonly fix
This commit is contained in:
parent
51a1b48abb
commit
4cfcb656a8
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -16,8 +16,8 @@
|
||||||
[submodule "example/tools/simple-image"]
|
[submodule "example/tools/simple-image"]
|
||||||
path = example/tools/simple-image
|
path = example/tools/simple-image
|
||||||
url = https://github.com/editor-js/simple-image
|
url = https://github.com/editor-js/simple-image
|
||||||
[submodule "src/components/tools/paragraph"]
|
[submodule "src/tools/paragraph"]
|
||||||
path = src/components/tools/paragraph
|
path = src/tools/paragraph
|
||||||
url = https://github.com/editor-js/paragraph
|
url = https://github.com/editor-js/paragraph
|
||||||
[submodule "example/tools/marker"]
|
[submodule "example/tools/marker"]
|
||||||
path = example/tools/marker
|
path = example/tools/marker
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
BlockAPI as BlockAPIInterface,
|
BlockAPI as BlockAPIInterface,
|
||||||
BlockTool,
|
BlockTool as IBlockTool,
|
||||||
BlockToolConstructable,
|
BlockToolConstructable,
|
||||||
BlockToolData,
|
BlockToolData,
|
||||||
BlockTune,
|
BlockTune,
|
||||||
|
@ -16,12 +16,13 @@ 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 { ToolType } from '../modules/tools';
|
||||||
|
import SelectionUtils from '../selection';
|
||||||
|
import BlockTool from '../tools/block';
|
||||||
|
|
||||||
/** Import default tunes */
|
/** Import default tunes */
|
||||||
import MoveUpTune from '../block-tunes/block-tune-move-up';
|
import MoveUpTune from '../block-tunes/block-tune-move-up';
|
||||||
import DeleteTune from '../block-tunes/block-tune-delete';
|
import DeleteTune from '../block-tunes/block-tune-delete';
|
||||||
import MoveDownTune from '../block-tunes/block-tune-move-down';
|
import MoveDownTune from '../block-tunes/block-tune-move-down';
|
||||||
import SelectionUtils from '../selection';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface describes Block class constructor argument
|
* Interface describes Block class constructor argument
|
||||||
|
@ -38,14 +39,9 @@ interface BlockConstructorOptions {
|
||||||
data: BlockToolData;
|
data: BlockToolData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool's class or constructor function
|
* Tool object
|
||||||
*/
|
*/
|
||||||
Tool: BlockToolConstructable;
|
tool: BlockTool;
|
||||||
|
|
||||||
/**
|
|
||||||
* Tool settings from initial config
|
|
||||||
*/
|
|
||||||
settings: ToolSettings;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor's API methods
|
* Editor's API methods
|
||||||
|
@ -110,32 +106,27 @@ export default class Block {
|
||||||
/**
|
/**
|
||||||
* Block Tool`s name
|
* Block Tool`s name
|
||||||
*/
|
*/
|
||||||
public name: string;
|
public readonly name: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instance of the Tool Block represents
|
* Instance of the Tool Block represents
|
||||||
*/
|
*/
|
||||||
public tool: BlockTool;
|
public readonly tool: BlockTool;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class blueprint of the ool Block represents
|
|
||||||
*/
|
|
||||||
public class: BlockToolConstructable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Tool configuration
|
* User Tool configuration
|
||||||
*/
|
*/
|
||||||
public settings: ToolConfig;
|
public readonly settings: ToolConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for Block`s content
|
* Wrapper for Block`s content
|
||||||
*/
|
*/
|
||||||
public holder: HTMLDivElement;
|
public readonly holder: HTMLDivElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tunes used by Tool
|
* Tunes used by Tool
|
||||||
*/
|
*/
|
||||||
public tunes: BlockTune[];
|
public readonly tunes: BlockTune[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool's user configuration
|
* Tool's user configuration
|
||||||
|
@ -149,6 +140,11 @@ export default class Block {
|
||||||
*/
|
*/
|
||||||
private cachedInputs: HTMLElement[] = [];
|
private cachedInputs: HTMLElement[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool class instance
|
||||||
|
*/
|
||||||
|
private readonly toolInstance: IBlockTool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor`s API module
|
* Editor`s API module
|
||||||
*/
|
*/
|
||||||
|
@ -209,27 +205,20 @@ export default class Block {
|
||||||
constructor({
|
constructor({
|
||||||
name,
|
name,
|
||||||
data,
|
data,
|
||||||
Tool,
|
tool,
|
||||||
settings,
|
|
||||||
api,
|
api,
|
||||||
readOnly,
|
readOnly,
|
||||||
}: BlockConstructorOptions) {
|
}: BlockConstructorOptions) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.class = Tool;
|
this.settings = tool.settings;
|
||||||
this.settings = settings;
|
this.config = tool.settings.config || {};
|
||||||
this.config = settings.config || {};
|
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.blockAPI = new BlockAPI(this);
|
this.blockAPI = new BlockAPI(this);
|
||||||
|
|
||||||
this.mutationObserver = new MutationObserver(this.didMutated);
|
this.mutationObserver = new MutationObserver(this.didMutated);
|
||||||
|
|
||||||
this.tool = new Tool({
|
this.tool = tool;
|
||||||
data,
|
this.toolInstance = tool.instance(data, this.blockAPI, readOnly);
|
||||||
config: this.config,
|
|
||||||
api: this.api.getMethodsForTool(name, ToolType.Block),
|
|
||||||
block: this.blockAPI,
|
|
||||||
readOnly,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.holder = this.compose();
|
this.holder = this.compose();
|
||||||
/**
|
/**
|
||||||
|
@ -349,7 +338,7 @@ export default class Block {
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
public get sanitize(): SanitizerConfig {
|
public get sanitize(): SanitizerConfig {
|
||||||
return this.tool.sanitize;
|
return this.tool.sanitizeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -359,7 +348,7 @@ export default class Block {
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
public get mergeable(): boolean {
|
public get mergeable(): boolean {
|
||||||
return _.isFunction(this.tool.merge);
|
return _.isFunction(this.toolInstance.merge);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -502,7 +491,7 @@ export default class Block {
|
||||||
/**
|
/**
|
||||||
* call Tool's method with the instance context
|
* call Tool's method with the instance context
|
||||||
*/
|
*/
|
||||||
if (this.tool[methodName] && this.tool[methodName] instanceof Function) {
|
if (this.toolInstance[methodName] && this.toolInstance[methodName] instanceof Function) {
|
||||||
if (methodName === BlockToolAPI.APPEND_CALLBACK) {
|
if (methodName === BlockToolAPI.APPEND_CALLBACK) {
|
||||||
_.log(
|
_.log(
|
||||||
'`appendCallback` hook is deprecated and will be removed in the next major release. ' +
|
'`appendCallback` hook is deprecated and will be removed in the next major release. ' +
|
||||||
|
@ -513,7 +502,7 @@ export default class Block {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-useless-call
|
// eslint-disable-next-line no-useless-call
|
||||||
this.tool[methodName].call(this.tool, params);
|
this.toolInstance[methodName].call(this.toolInstance, params);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_.log(`Error during '${methodName}' call: ${e.message}`, 'error');
|
_.log(`Error during '${methodName}' call: ${e.message}`, 'error');
|
||||||
}
|
}
|
||||||
|
@ -526,7 +515,7 @@ export default class Block {
|
||||||
* @param {BlockToolData} data - data to merge
|
* @param {BlockToolData} data - data to merge
|
||||||
*/
|
*/
|
||||||
public async mergeWith(data: BlockToolData): Promise<void> {
|
public async mergeWith(data: BlockToolData): Promise<void> {
|
||||||
await this.tool.merge(data);
|
await this.toolInstance.merge(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -536,7 +525,7 @@ export default class Block {
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
public async save(): Promise<void|SavedData> {
|
public async save(): Promise<void|SavedData> {
|
||||||
const extractedBlock = await this.tool.save(this.pluginsContent as HTMLElement);
|
const extractedBlock = await this.toolInstance.save(this.pluginsContent as HTMLElement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Measuring execution time
|
* Measuring execution time
|
||||||
|
@ -572,8 +561,8 @@ export default class Block {
|
||||||
public async validate(data: BlockToolData): Promise<boolean> {
|
public async validate(data: BlockToolData): Promise<boolean> {
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
|
|
||||||
if (this.tool.validate instanceof Function) {
|
if (this.toolInstance.validate instanceof Function) {
|
||||||
isValid = await this.tool.validate(data);
|
isValid = await this.toolInstance.validate(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isValid;
|
return isValid;
|
||||||
|
@ -672,6 +661,24 @@ export default class Block {
|
||||||
this.removeInputEvents();
|
this.removeInputEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call Tool instance destroy method
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
if (_.isFunction(this.toolInstance.destroy)) {
|
||||||
|
this.toolInstance.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call Tool instance renderSettings method
|
||||||
|
*/
|
||||||
|
public renderSettings(): HTMLElement | undefined {
|
||||||
|
if (_.isFunction(this.toolInstance.renderSettings)) {
|
||||||
|
return this.toolInstance.renderSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make default Block wrappers and put Tool`s content there
|
* Make default Block wrappers and put Tool`s content there
|
||||||
*
|
*
|
||||||
|
@ -680,7 +687,7 @@ export default class Block {
|
||||||
private compose(): HTMLDivElement {
|
private compose(): HTMLDivElement {
|
||||||
const wrapper = $.make('div', Block.CSS.wrapper) as HTMLDivElement,
|
const wrapper = $.make('div', Block.CSS.wrapper) as HTMLDivElement,
|
||||||
contentNode = $.make('div', Block.CSS.content),
|
contentNode = $.make('div', Block.CSS.content),
|
||||||
pluginsContent = this.tool.render();
|
pluginsContent = this.toolInstance.render();
|
||||||
|
|
||||||
contentNode.appendChild(pluginsContent);
|
contentNode.appendChild(pluginsContent);
|
||||||
wrapper.appendChild(contentNode);
|
wrapper.appendChild(contentNode);
|
||||||
|
|
|
@ -125,7 +125,7 @@ export default class BlockEvents extends Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canOpenToolbox = Tools.isDefault(currentBlock.tool) && currentBlock.isEmpty;
|
const canOpenToolbox = currentBlock.tool.isDefault && currentBlock.isEmpty;
|
||||||
const conversionToolbarOpened = !currentBlock.isEmpty && ConversionToolbar.opened;
|
const conversionToolbarOpened = !currentBlock.isEmpty && ConversionToolbar.opened;
|
||||||
const inlineToolbarOpened = !currentBlock.isEmpty && !SelectionUtils.isCollapsed && InlineToolbar.opened;
|
const inlineToolbarOpened = !currentBlock.isEmpty && !SelectionUtils.isCollapsed && InlineToolbar.opened;
|
||||||
|
|
||||||
|
@ -206,15 +206,14 @@ export default class BlockEvents extends Module {
|
||||||
* @param {KeyboardEvent} event - keydown
|
* @param {KeyboardEvent} event - keydown
|
||||||
*/
|
*/
|
||||||
private enter(event: KeyboardEvent): void {
|
private enter(event: KeyboardEvent): void {
|
||||||
const { BlockManager, Tools, UI } = this.Editor;
|
const { BlockManager, UI } = this.Editor;
|
||||||
const currentBlock = BlockManager.currentBlock;
|
const currentBlock = BlockManager.currentBlock;
|
||||||
const tool = Tools.available[currentBlock.name];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't handle Enter keydowns when Tool sets enableLineBreaks to true.
|
* Don't handle Enter keydowns when Tool sets enableLineBreaks to true.
|
||||||
* Uses for Tools like <code> where line breaks should be handled by default behaviour.
|
* Uses for Tools like <code> where line breaks should be handled by default behaviour.
|
||||||
*/
|
*/
|
||||||
if (tool && tool[Tools.INTERNAL_SETTINGS.IS_ENABLED_LINE_BREAKS]) {
|
if (currentBlock.tool.isLineBreaksEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +252,7 @@ export default class BlockEvents extends Module {
|
||||||
/**
|
/**
|
||||||
* If new Block is empty
|
* If new Block is empty
|
||||||
*/
|
*/
|
||||||
if (this.Editor.Tools.isDefault(newCurrent.tool) && newCurrent.isEmpty) {
|
if (newCurrent.tool.isDefault && newCurrent.isEmpty) {
|
||||||
/**
|
/**
|
||||||
* Show Toolbar
|
* Show Toolbar
|
||||||
*/
|
*/
|
||||||
|
@ -276,7 +275,7 @@ export default class BlockEvents extends Module {
|
||||||
private backspace(event: KeyboardEvent): void {
|
private backspace(event: KeyboardEvent): void {
|
||||||
const { BlockManager, BlockSelection, Caret } = this.Editor;
|
const { BlockManager, BlockSelection, Caret } = this.Editor;
|
||||||
const currentBlock = BlockManager.currentBlock;
|
const currentBlock = BlockManager.currentBlock;
|
||||||
const tool = this.Editor.Tools.available[currentBlock.name];
|
const tool = currentBlock.tool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if Block should be removed by current Backspace keydown
|
* Check if Block should be removed by current Backspace keydown
|
||||||
|
@ -314,7 +313,7 @@ export default class BlockEvents extends Module {
|
||||||
*
|
*
|
||||||
* But if caret is at start of the block, we allow to remove it by backspaces
|
* But if caret is at start of the block, we allow to remove it by backspaces
|
||||||
*/
|
*/
|
||||||
if (tool && tool[this.Editor.Tools.INTERNAL_SETTINGS.IS_ENABLED_LINE_BREAKS] && !Caret.isAtStart) {
|
if (tool.isLineBreaksEnabled && !Caret.isAtStart) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ 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 { BlockToolConstructable, BlockToolData, PasteEvent } from '../../../types';
|
||||||
|
import BlockTool from '../tools/block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {BlockManager} BlockManager
|
* @typedef {BlockManager} BlockManager
|
||||||
|
@ -219,15 +220,13 @@ export default class BlockManager extends Module {
|
||||||
*
|
*
|
||||||
* @returns {Block}
|
* @returns {Block}
|
||||||
*/
|
*/
|
||||||
public composeBlock({ tool, data = {} }: {tool: string; data?: BlockToolData}): Block {
|
public composeBlock({ tool: name, data = {} }: {tool: string; data?: BlockToolData}): Block {
|
||||||
const readOnly = this.Editor.ReadOnly.isEnabled;
|
const readOnly = this.Editor.ReadOnly.isEnabled;
|
||||||
const settings = this.Editor.Tools.getToolSettings(tool);
|
const tool = this.Editor.Tools.blockTools.get(name);
|
||||||
const Tool = this.Editor.Tools.available[tool] as BlockToolConstructable;
|
|
||||||
const block = new Block({
|
const block = new Block({
|
||||||
name: tool,
|
name,
|
||||||
data,
|
data,
|
||||||
Tool,
|
tool,
|
||||||
settings,
|
|
||||||
api: this.Editor.API,
|
api: this.Editor.API,
|
||||||
readOnly,
|
readOnly,
|
||||||
});
|
});
|
||||||
|
@ -703,9 +702,7 @@ export default class BlockManager extends Module {
|
||||||
*/
|
*/
|
||||||
public async destroy(): Promise<void> {
|
public async destroy(): Promise<void> {
|
||||||
await Promise.all(this.blocks.map((block) => {
|
await Promise.all(this.blocks.map((block) => {
|
||||||
if (_.isFunction(block.tool.destroy)) {
|
return block.destroy();
|
||||||
return block.tool.destroy();
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -333,7 +333,7 @@ export default class Caret extends Module {
|
||||||
* If last block is empty and it is an defaultBlock, set to that.
|
* If last block is empty and it is an defaultBlock, set to that.
|
||||||
* Otherwise, append new empty block and set to that
|
* Otherwise, append new empty block and set to that
|
||||||
*/
|
*/
|
||||||
if (this.Editor.Tools.isDefault(lastBlock.tool) && lastBlock.isEmpty) {
|
if (lastBlock.tool.isDefault && lastBlock.isEmpty) {
|
||||||
this.setToBlock(lastBlock);
|
this.setToBlock(lastBlock);
|
||||||
} else {
|
} else {
|
||||||
const newBlock = this.Editor.BlockManager.insertAtEnd();
|
const newBlock = this.Editor.BlockManager.insertAtEnd();
|
||||||
|
@ -409,7 +409,7 @@ export default class Caret extends Module {
|
||||||
* 2. If there is a last block and it is non-default --> and caret not at the end <--, do nothing
|
* 2. If there is a last block and it is non-default --> and caret not at the end <--, do nothing
|
||||||
* (https://github.com/codex-team/editor.js/issues/1414)
|
* (https://github.com/codex-team/editor.js/issues/1414)
|
||||||
*/
|
*/
|
||||||
if (Tools.isDefault(currentBlock.tool) || !isAtEnd) {
|
if (currentBlock.tool.isDefault || !isAtEnd) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,13 @@ import Module from '../__module';
|
||||||
import $ from '../dom';
|
import $ from '../dom';
|
||||||
import * as _ from '../utils';
|
import * as _ from '../utils';
|
||||||
import {
|
import {
|
||||||
BlockTool,
|
BlockAPI,
|
||||||
BlockToolConstructable,
|
|
||||||
PasteConfig,
|
|
||||||
PasteEvent,
|
PasteEvent,
|
||||||
PasteEventDetail
|
PasteEventDetail
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
import Block from '../block';
|
import Block from '../block';
|
||||||
import { SavedData } from '../../../types/data-formats';
|
import { SavedData } from '../../../types/data-formats';
|
||||||
|
import BlockTool from '../tools/block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag substitute object.
|
* Tag substitute object.
|
||||||
|
@ -18,9 +17,8 @@ interface TagSubstitute {
|
||||||
/**
|
/**
|
||||||
* Name of related Tool
|
* Name of related Tool
|
||||||
*
|
*
|
||||||
* @type {string}
|
|
||||||
*/
|
*/
|
||||||
tool: string;
|
tool: BlockTool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,24 +27,18 @@ interface TagSubstitute {
|
||||||
interface PatternSubstitute {
|
interface PatternSubstitute {
|
||||||
/**
|
/**
|
||||||
* Pattern`s key
|
* Pattern`s key
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
*/
|
*/
|
||||||
key: string;
|
key: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pattern regexp
|
* Pattern regexp
|
||||||
*
|
|
||||||
* @type {RegExp}
|
|
||||||
*/
|
*/
|
||||||
pattern: RegExp;
|
pattern: RegExp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of related Tool
|
* Name of related Tool
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
*/
|
*/
|
||||||
tool: string;
|
tool: BlockTool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -247,7 +239,7 @@ export default class Paste extends Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCurrentBlockDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool);
|
const isCurrentBlockDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault;
|
||||||
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
|
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
|
||||||
|
|
||||||
dataToInsert.map(
|
dataToInsert.map(
|
||||||
|
@ -279,23 +271,22 @@ export default class Paste extends Module {
|
||||||
private processTools(): void {
|
private processTools(): void {
|
||||||
const tools = this.Editor.Tools.blockTools;
|
const tools = this.Editor.Tools.blockTools;
|
||||||
|
|
||||||
Object.entries(tools).forEach(this.processTool);
|
Array
|
||||||
|
.from(tools.values())
|
||||||
|
.forEach(this.processTool);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process paste config for each tool
|
* Process paste config for each tool
|
||||||
|
*
|
||||||
|
* @param tool - BlockTool object
|
||||||
*/
|
*/
|
||||||
private processTool = ([name, tool]: [string, BlockToolConstructable]): void => {
|
private processTool = (tool: BlockTool): void => {
|
||||||
try {
|
try {
|
||||||
const toolInstance = new this.Editor.Tools.blockTools[name]({
|
const toolInstance = tool.instance({}, {} as BlockAPI, false);
|
||||||
api: this.Editor.API.getMethodsForTool(name),
|
|
||||||
config: {},
|
|
||||||
data: {},
|
|
||||||
readOnly: false,
|
|
||||||
}) as BlockTool;
|
|
||||||
|
|
||||||
if (tool.pasteConfig === false) {
|
if (tool.pasteConfig === false) {
|
||||||
this.exceptionList.push(name);
|
this.exceptionList.push(tool.name);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -304,11 +295,9 @@ export default class Paste extends Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolPasteConfig = tool.pasteConfig || {};
|
this.getTagsConfig(tool);
|
||||||
|
this.getFilesConfig(tool);
|
||||||
this.getTagsConfig(name, toolPasteConfig);
|
this.getPatternsConfig(tool);
|
||||||
this.getFilesConfig(name, toolPasteConfig);
|
|
||||||
this.getPatternsConfig(name, toolPasteConfig);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_.log(
|
_.log(
|
||||||
`Paste handling for «${name}» Tool hasn't been set up because of the error`,
|
`Paste handling for «${name}» Tool hasn't been set up because of the error`,
|
||||||
|
@ -321,17 +310,16 @@ export default class Paste extends Module {
|
||||||
/**
|
/**
|
||||||
* Get tags to substitute by Tool
|
* Get tags to substitute by Tool
|
||||||
*
|
*
|
||||||
* @param {string} name - Tool name
|
* @param tool - BlockTool object
|
||||||
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
|
||||||
*/
|
*/
|
||||||
private getTagsConfig(name: string, toolPasteConfig: PasteConfig): void {
|
private getTagsConfig(tool: BlockTool): void {
|
||||||
const tags = toolPasteConfig.tags || [];
|
const tags = tool.pasteConfig.tags || [];
|
||||||
|
|
||||||
tags.forEach((tag) => {
|
tags.forEach((tag) => {
|
||||||
if (Object.prototype.hasOwnProperty.call(this.toolsTags, tag)) {
|
if (Object.prototype.hasOwnProperty.call(this.toolsTags, tag)) {
|
||||||
_.log(
|
_.log(
|
||||||
`Paste handler for «${name}» Tool on «${tag}» tag is skipped ` +
|
`Paste handler for «${tool.name}» Tool on «${tag}» tag is skipped ` +
|
||||||
`because it is already used by «${this.toolsTags[tag].tool}» Tool.`,
|
`because it is already used by «${this.toolsTags[tag].tool.name}» Tool.`,
|
||||||
'warn'
|
'warn'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -339,21 +327,20 @@ export default class Paste extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toolsTags[tag.toUpperCase()] = {
|
this.toolsTags[tag.toUpperCase()] = {
|
||||||
tool: name,
|
tool,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tagsByTool[name] = tags.map((t) => t.toUpperCase());
|
this.tagsByTool[tool.name] = tags.map((t) => t.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get files` types and extensions to substitute by Tool
|
* Get files` types and extensions to substitute by Tool
|
||||||
*
|
*
|
||||||
* @param {string} name - Tool name
|
* @param tool - BlockTool object
|
||||||
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
|
||||||
*/
|
*/
|
||||||
private getFilesConfig(name: string, toolPasteConfig: PasteConfig): void {
|
private getFilesConfig(tool: BlockTool): void {
|
||||||
const { files = {} } = toolPasteConfig;
|
const { files = {} } = tool.pasteConfig;
|
||||||
let { extensions, mimeTypes } = files;
|
let { extensions, mimeTypes } = files;
|
||||||
|
|
||||||
if (!extensions && !mimeTypes) {
|
if (!extensions && !mimeTypes) {
|
||||||
|
@ -361,19 +348,19 @@ export default class Paste extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extensions && !Array.isArray(extensions)) {
|
if (extensions && !Array.isArray(extensions)) {
|
||||||
_.log(`«extensions» property of the onDrop config for «${name}» Tool should be an array`);
|
_.log(`«extensions» property of the onDrop config for «${tool.name}» Tool should be an array`);
|
||||||
extensions = [];
|
extensions = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mimeTypes && !Array.isArray(mimeTypes)) {
|
if (mimeTypes && !Array.isArray(mimeTypes)) {
|
||||||
_.log(`«mimeTypes» property of the onDrop config for «${name}» Tool should be an array`);
|
_.log(`«mimeTypes» property of the onDrop config for «${tool.name}» Tool should be an array`);
|
||||||
mimeTypes = [];
|
mimeTypes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mimeTypes) {
|
if (mimeTypes) {
|
||||||
mimeTypes = mimeTypes.filter((type) => {
|
mimeTypes = mimeTypes.filter((type) => {
|
||||||
if (!_.isValidMimeType(type)) {
|
if (!_.isValidMimeType(type)) {
|
||||||
_.log(`MIME type value «${type}» for the «${name}» Tool is not a valid MIME type`, 'warn');
|
_.log(`MIME type value «${type}» for the «${tool.name}» Tool is not a valid MIME type`, 'warn');
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -382,7 +369,7 @@ export default class Paste extends Module {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toolsFiles[name] = {
|
this.toolsFiles[tool.name] = {
|
||||||
extensions: extensions || [],
|
extensions: extensions || [],
|
||||||
mimeTypes: mimeTypes || [],
|
mimeTypes: mimeTypes || [],
|
||||||
};
|
};
|
||||||
|
@ -391,15 +378,14 @@ export default class Paste extends Module {
|
||||||
/**
|
/**
|
||||||
* Get RegExp patterns to substitute by Tool
|
* Get RegExp patterns to substitute by Tool
|
||||||
*
|
*
|
||||||
* @param {string} name - Tool name
|
* @param tool - BlockTool object
|
||||||
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
|
||||||
*/
|
*/
|
||||||
private getPatternsConfig(name: string, toolPasteConfig: PasteConfig): void {
|
private getPatternsConfig(tool: BlockTool): void {
|
||||||
if (!toolPasteConfig.patterns || _.isEmpty(toolPasteConfig.patterns)) {
|
if (!tool.pasteConfig.patterns || _.isEmpty(tool.pasteConfig.patterns)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.entries(toolPasteConfig.patterns).forEach(([key, pattern]: [string, RegExp]) => {
|
Object.entries(tool.pasteConfig.patterns).forEach(([key, pattern]: [string, RegExp]) => {
|
||||||
/** 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(
|
||||||
|
@ -411,7 +397,7 @@ export default class Paste extends Module {
|
||||||
this.toolsPatterns.push({
|
this.toolsPatterns.push({
|
||||||
key,
|
key,
|
||||||
pattern,
|
pattern,
|
||||||
tool: name,
|
tool,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -462,9 +448,9 @@ export default class Paste extends Module {
|
||||||
* @param {FileList} items - pasted or dropped items
|
* @param {FileList} items - pasted or dropped items
|
||||||
*/
|
*/
|
||||||
private async processFiles(items: FileList): Promise<void> {
|
private async processFiles(items: FileList): Promise<void> {
|
||||||
const { BlockManager, Tools } = this.Editor;
|
const { BlockManager } = this.Editor;
|
||||||
|
|
||||||
let dataToInsert: Array<{type: string; event: PasteEvent}>;
|
let dataToInsert: {type: string; event: PasteEvent}[];
|
||||||
|
|
||||||
dataToInsert = await Promise.all(
|
dataToInsert = await Promise.all(
|
||||||
Array
|
Array
|
||||||
|
@ -473,7 +459,7 @@ export default class Paste extends Module {
|
||||||
);
|
);
|
||||||
dataToInsert = dataToInsert.filter((data) => !!data);
|
dataToInsert = dataToInsert.filter((data) => !!data);
|
||||||
|
|
||||||
const isCurrentBlockDefault = Tools.isDefault(BlockManager.currentBlock.tool);
|
const isCurrentBlockDefault = BlockManager.currentBlock.tool.isDefault;
|
||||||
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
|
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
|
||||||
|
|
||||||
dataToInsert.forEach(
|
dataToInsert.forEach(
|
||||||
|
@ -530,7 +516,6 @@ export default class Paste extends Module {
|
||||||
*/
|
*/
|
||||||
private processHTML(innerHTML: string): PasteData[] {
|
private processHTML(innerHTML: string): PasteData[] {
|
||||||
const { Tools, Sanitizer } = this.Editor;
|
const { Tools, Sanitizer } = this.Editor;
|
||||||
const initialTool = this.config.defaultBlock;
|
|
||||||
const wrapper = $.make('DIV');
|
const wrapper = $.make('DIV');
|
||||||
|
|
||||||
wrapper.innerHTML = innerHTML;
|
wrapper.innerHTML = innerHTML;
|
||||||
|
@ -539,7 +524,7 @@ export default class Paste extends Module {
|
||||||
|
|
||||||
return nodes
|
return nodes
|
||||||
.map((node) => {
|
.map((node) => {
|
||||||
let content, tool = initialTool, isBlock = false;
|
let content, tool = Tools.defaultTool, isBlock = false;
|
||||||
|
|
||||||
switch (node.nodeType) {
|
switch (node.nodeType) {
|
||||||
/** If node is a document fragment, use temp wrapper to get innerHTML */
|
/** If node is a document fragment, use temp wrapper to get innerHTML */
|
||||||
|
@ -559,7 +544,7 @@ export default class Paste extends Module {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tags } = Tools.blockTools[tool].pasteConfig as PasteConfig;
|
const { tags } = tool.pasteConfig;
|
||||||
|
|
||||||
const toolTags = tags.reduce((result, tag) => {
|
const toolTags = tags.reduce((result, tag) => {
|
||||||
result[tag.toLowerCase()] = {};
|
result[tag.toLowerCase()] = {};
|
||||||
|
@ -577,7 +562,7 @@ export default class Paste extends Module {
|
||||||
return {
|
return {
|
||||||
content,
|
content,
|
||||||
isBlock,
|
isBlock,
|
||||||
tool,
|
tool: tool.name,
|
||||||
event,
|
event,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -627,7 +612,7 @@ export default class Paste extends Module {
|
||||||
* @param {PasteData} dataToInsert - data of Block to inseret
|
* @param {PasteData} dataToInsert - data of Block to inseret
|
||||||
*/
|
*/
|
||||||
private async processSingleBlock(dataToInsert: PasteData): Promise<void> {
|
private async processSingleBlock(dataToInsert: PasteData): Promise<void> {
|
||||||
const { Caret, BlockManager, Tools } = this.Editor;
|
const { Caret, BlockManager } = this.Editor;
|
||||||
const { currentBlock } = BlockManager;
|
const { currentBlock } = BlockManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -638,7 +623,7 @@ export default class Paste extends Module {
|
||||||
dataToInsert.tool !== currentBlock.name ||
|
dataToInsert.tool !== currentBlock.name ||
|
||||||
!$.containsOnlyInlineElements(dataToInsert.content.innerHTML)
|
!$.containsOnlyInlineElements(dataToInsert.content.innerHTML)
|
||||||
) {
|
) {
|
||||||
this.insertBlock(dataToInsert, currentBlock && Tools.isDefault(currentBlock.tool) && currentBlock.isEmpty);
|
this.insertBlock(dataToInsert, currentBlock?.tool.isDefault && currentBlock.isEmpty);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -655,17 +640,17 @@ export default class Paste extends Module {
|
||||||
* @param {PasteData} dataToInsert - data of Block to insert
|
* @param {PasteData} dataToInsert - data of Block to insert
|
||||||
*/
|
*/
|
||||||
private async processInlinePaste(dataToInsert: PasteData): Promise<void> {
|
private async processInlinePaste(dataToInsert: PasteData): Promise<void> {
|
||||||
const { BlockManager, Caret, Sanitizer, Tools } = this.Editor;
|
const { BlockManager, Caret, Sanitizer } = this.Editor;
|
||||||
const { content } = dataToInsert;
|
const { content } = dataToInsert;
|
||||||
|
|
||||||
const currentBlockIsDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool);
|
const currentBlockIsDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault;
|
||||||
|
|
||||||
if (currentBlockIsDefault && content.textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) {
|
if (currentBlockIsDefault && content.textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) {
|
||||||
const blockData = await this.processPattern(content.textContent);
|
const blockData = await this.processPattern(content.textContent);
|
||||||
|
|
||||||
if (blockData) {
|
if (blockData) {
|
||||||
const needToReplaceCurrentBlock = BlockManager.currentBlock &&
|
const needToReplaceCurrentBlock = BlockManager.currentBlock &&
|
||||||
Tools.isDefault(BlockManager.currentBlock.tool) &&
|
BlockManager.currentBlock.tool.isDefault &&
|
||||||
BlockManager.currentBlock.isEmpty;
|
BlockManager.currentBlock.isEmpty;
|
||||||
|
|
||||||
const insertedBlock = BlockManager.paste(blockData.tool, blockData.event, needToReplaceCurrentBlock);
|
const insertedBlock = BlockManager.paste(blockData.tool, blockData.event, needToReplaceCurrentBlock);
|
||||||
|
@ -678,7 +663,7 @@ export default class Paste extends Module {
|
||||||
|
|
||||||
/** If there is no pattern substitute - insert string as it is */
|
/** If there is no pattern substitute - insert string as it is */
|
||||||
if (BlockManager.currentBlock && BlockManager.currentBlock.currentInput) {
|
if (BlockManager.currentBlock && BlockManager.currentBlock.currentInput) {
|
||||||
const currentToolSanitizeConfig = Sanitizer.getInlineToolsConfig(BlockManager.currentBlock.name);
|
const currentToolSanitizeConfig = Sanitizer.getInlineToolsConfig(BlockManager.currentBlock.tool);
|
||||||
|
|
||||||
document.execCommand(
|
document.execCommand(
|
||||||
'insertHTML',
|
'insertHTML',
|
||||||
|
@ -719,7 +704,7 @@ export default class Paste extends Module {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
event,
|
event,
|
||||||
tool: pattern.tool,
|
tool: pattern.tool.name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,15 +740,15 @@ export default class Paste extends Module {
|
||||||
*
|
*
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
private insertEditorJSData(blocks: Array<Pick<SavedData, 'data' | 'tool'>>): void {
|
private insertEditorJSData(blocks: Pick<SavedData, 'data' | 'tool'>[]): void {
|
||||||
const { BlockManager, Caret, Sanitizer, Tools } = this.Editor;
|
const { BlockManager, Caret, Sanitizer } = this.Editor;
|
||||||
const sanitizedBlocks = Sanitizer.sanitizeBlocks(blocks);
|
const sanitizedBlocks = Sanitizer.sanitizeBlocks(blocks);
|
||||||
|
|
||||||
sanitizedBlocks.forEach(({ tool, data }, i) => {
|
sanitizedBlocks.forEach(({ tool, data }, i) => {
|
||||||
let needToReplaceCurrentBlock = false;
|
let needToReplaceCurrentBlock = false;
|
||||||
|
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
const isCurrentBlockDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool);
|
const isCurrentBlockDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault;
|
||||||
|
|
||||||
needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
|
needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
|
||||||
}
|
}
|
||||||
|
@ -792,8 +777,8 @@ export default class Paste extends Module {
|
||||||
|
|
||||||
const element = node as HTMLElement;
|
const element = node as HTMLElement;
|
||||||
|
|
||||||
const { tool = '' } = this.toolsTags[element.tagName] || {};
|
const { tool } = this.toolsTags[element.tagName] || {};
|
||||||
const toolTags = this.tagsByTool[tool] || [];
|
const toolTags = this.tagsByTool[tool?.name] || [];
|
||||||
|
|
||||||
const isSubstitutable = tags.includes(element.tagName);
|
const isSubstitutable = tags.includes(element.tagName);
|
||||||
const isBlockElement = $.blockElements.includes(element.tagName.toLowerCase());
|
const isBlockElement = $.blockElements.includes(element.tagName.toLowerCase());
|
||||||
|
|
|
@ -40,11 +40,13 @@ export default class ReadOnly extends Module {
|
||||||
const { blockTools } = Tools;
|
const { blockTools } = Tools;
|
||||||
const toolsDontSupportReadOnly: string[] = [];
|
const toolsDontSupportReadOnly: string[] = [];
|
||||||
|
|
||||||
Object.entries(blockTools).forEach(([name, tool]) => {
|
Array
|
||||||
if (!Tools.isReadOnlySupported(tool)) {
|
.from(blockTools.entries())
|
||||||
toolsDontSupportReadOnly.push(name);
|
.forEach(([name, tool]) => {
|
||||||
}
|
if (!tool.isReadOnlySupported) {
|
||||||
});
|
toolsDontSupportReadOnly.push(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.toolsDontSupportReadOnly = toolsDontSupportReadOnly;
|
this.toolsDontSupportReadOnly = toolsDontSupportReadOnly;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Module from '../__module';
|
import Module from '../__module';
|
||||||
import * as _ from '../utils';
|
import * as _ from '../utils';
|
||||||
import { BlockToolConstructable, OutputBlockData } from '../../../types';
|
import { OutputBlockData } from '../../../types';
|
||||||
|
import BlockTool from '../tools/block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor.js Renderer Module
|
* Editor.js Renderer Module
|
||||||
|
@ -66,7 +67,7 @@ export default class Renderer extends Module {
|
||||||
const tool = item.type;
|
const tool = item.type;
|
||||||
const data = item.data;
|
const data = item.data;
|
||||||
|
|
||||||
if (tool in Tools.available) {
|
if (Tools.available.has(tool)) {
|
||||||
try {
|
try {
|
||||||
BlockManager.insert({
|
BlockManager.insert({
|
||||||
tool,
|
tool,
|
||||||
|
@ -86,11 +87,10 @@ export default class Renderer extends Module {
|
||||||
title: tool,
|
title: tool,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tool in Tools.unavailable) {
|
if (Tools.unavailable.has(tool)) {
|
||||||
const toolToolboxSettings = (Tools.unavailable[tool] as BlockToolConstructable).toolbox;
|
const toolboxSettings = (Tools.unavailable.get(tool) as BlockTool).toolbox;
|
||||||
const userToolboxSettings = Tools.getToolSettings(tool).toolbox;
|
|
||||||
|
|
||||||
stubData.title = toolToolboxSettings.title || (userToolboxSettings && userToolboxSettings.title) || stubData.title;
|
stubData.title = toolboxSettings?.title || stubData.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stub = BlockManager.insert({
|
const stub = BlockManager.insert({
|
||||||
|
|
|
@ -36,8 +36,10 @@ import * as _ from '../utils';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import HTMLJanitor from 'html-janitor';
|
import HTMLJanitor from 'html-janitor';
|
||||||
import { BlockToolData, InlineToolConstructable, SanitizerConfig } from '../../../types';
|
import { BlockToolData, SanitizerConfig } from '../../../types';
|
||||||
import { SavedData } from '../../../types/data-formats';
|
import { SavedData } from '../../../types/data-formats';
|
||||||
|
import InlineTool from '../tools/inline';
|
||||||
|
import BlockTool from '../tools/block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -61,8 +63,8 @@ export default class Sanitizer extends Module {
|
||||||
* @param {Array<{tool, data: BlockToolData}>} blocksData - blocks' data to sanitize
|
* @param {Array<{tool, data: BlockToolData}>} blocksData - blocks' data to sanitize
|
||||||
*/
|
*/
|
||||||
public sanitizeBlocks(
|
public sanitizeBlocks(
|
||||||
blocksData: Array<Pick<SavedData, 'data' | 'tool'>>
|
blocksData: Pick<SavedData, 'data' | 'tool'>[]
|
||||||
): Array<Pick<SavedData, 'data' | 'tool'>> {
|
): Pick<SavedData, 'data' | 'tool'>[] {
|
||||||
return blocksData.map((block) => {
|
return blocksData.map((block) => {
|
||||||
const toolConfig = this.composeToolConfig(block.tool);
|
const toolConfig = this.composeToolConfig(block.tool);
|
||||||
|
|
||||||
|
@ -150,18 +152,17 @@ export default class Sanitizer extends Module {
|
||||||
return this.configCache[toolName];
|
return this.configCache[toolName];
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizeGetter = this.Editor.Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG;
|
const tool = this.Editor.Tools.available.get(toolName);
|
||||||
const toolClass = this.Editor.Tools.available[toolName];
|
const baseConfig = this.getInlineToolsConfig(tool as BlockTool);
|
||||||
const baseConfig = this.getInlineToolsConfig(toolName);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If Tools doesn't provide sanitizer config or it is empty
|
* If Tools doesn't provide sanitizer config or it is empty
|
||||||
*/
|
*/
|
||||||
if (!toolClass.sanitize || (toolClass[sanitizeGetter] && _.isEmpty(toolClass[sanitizeGetter]))) {
|
if (!tool.sanitizeConfig || (tool.sanitizeConfig && _.isEmpty(tool.sanitizeConfig))) {
|
||||||
return baseConfig;
|
return baseConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolRules = toolClass.sanitize;
|
const toolRules = tool.sanitizeConfig;
|
||||||
|
|
||||||
const toolConfig = {} as SanitizerConfig;
|
const toolConfig = {} as SanitizerConfig;
|
||||||
|
|
||||||
|
@ -186,12 +187,11 @@ export default class Sanitizer extends Module {
|
||||||
* When Tool's "inlineToolbar" value is True, get all sanitizer rules from all tools,
|
* When Tool's "inlineToolbar" value is True, get all sanitizer rules from all tools,
|
||||||
* otherwise get only enabled
|
* otherwise get only enabled
|
||||||
*
|
*
|
||||||
* @param {string} name - Inline Tool name
|
* @param tool - BlockTool object
|
||||||
*/
|
*/
|
||||||
public getInlineToolsConfig(name: string): SanitizerConfig {
|
public getInlineToolsConfig(tool: BlockTool): SanitizerConfig {
|
||||||
const { Tools } = this.Editor;
|
const { Tools } = this.Editor;
|
||||||
const toolsConfig = Tools.getToolSettings(name);
|
const enableInlineTools = tool.enabledInlineTools || [];
|
||||||
const enableInlineTools = toolsConfig.inlineToolbar || [];
|
|
||||||
|
|
||||||
let config = {} as SanitizerConfig;
|
let config = {} as SanitizerConfig;
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ export default class Sanitizer extends Module {
|
||||||
(enableInlineTools as string[]).map((inlineToolName) => {
|
(enableInlineTools as string[]).map((inlineToolName) => {
|
||||||
config = Object.assign(
|
config = Object.assign(
|
||||||
config,
|
config,
|
||||||
Tools.inline[inlineToolName][Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG]
|
Tools.inlineTools.get(inlineToolName).sanitizeConfig
|
||||||
) as SanitizerConfig;
|
) as SanitizerConfig;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -233,9 +233,9 @@ export default class Sanitizer extends Module {
|
||||||
|
|
||||||
const config: SanitizerConfig = {} as SanitizerConfig;
|
const config: SanitizerConfig = {} as SanitizerConfig;
|
||||||
|
|
||||||
Object.entries(Tools.inline)
|
Object.entries(Tools.inlineTools)
|
||||||
.forEach(([, inlineTool]: [string, InlineToolConstructable]) => {
|
.forEach(([, inlineTool]: [string, InlineTool]) => {
|
||||||
Object.assign(config, inlineTool[Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG]);
|
Object.assign(config, inlineTool.sanitizeConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.inlineToolsConfigCache = config;
|
this.inlineToolsConfigCache = config;
|
||||||
|
@ -249,7 +249,7 @@ export default class Sanitizer extends Module {
|
||||||
* @param {Array} array - [1, 2, {}, []]
|
* @param {Array} array - [1, 2, {}, []]
|
||||||
* @param {SanitizerConfig} ruleForItem - sanitizer config for array
|
* @param {SanitizerConfig} ruleForItem - sanitizer config for array
|
||||||
*/
|
*/
|
||||||
private cleanArray(array: Array<object | string>, ruleForItem: SanitizerConfig): Array<object | string> {
|
private cleanArray(array: (object | string)[], ruleForItem: SanitizerConfig): (object | string)[] {
|
||||||
return array.map((arrayItem) => this.deepSanitize(arrayItem, ruleForItem));
|
return array.map((arrayItem) => this.deepSanitize(arrayItem, ruleForItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,8 +229,10 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||||
* Add Tool's settings
|
* Add Tool's settings
|
||||||
*/
|
*/
|
||||||
private addToolSettings(): void {
|
private addToolSettings(): void {
|
||||||
if (_.isFunction(this.Editor.BlockManager.currentBlock.tool.renderSettings)) {
|
const settingsElement = this.Editor.BlockManager.currentBlock.renderSettings();
|
||||||
$.append(this.nodes.toolSettings, this.Editor.BlockManager.currentBlock.tool.renderSettings());
|
|
||||||
|
if (settingsElement) {
|
||||||
|
$.append(this.nodes.toolSettings, settingsElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,10 +182,9 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||||
*
|
*
|
||||||
* @type {BlockToolConstructable}
|
* @type {BlockToolConstructable}
|
||||||
*/
|
*/
|
||||||
const currentBlockClass = this.Editor.BlockManager.currentBlock.class;
|
const currentBlockTool = this.Editor.BlockManager.currentBlock.tool;
|
||||||
const currentBlockName = this.Editor.BlockManager.currentBlock.name;
|
const currentBlockName = this.Editor.BlockManager.currentBlock.name;
|
||||||
const savedBlock = await this.Editor.BlockManager.currentBlock.save() as SavedData;
|
const savedBlock = await this.Editor.BlockManager.currentBlock.save() as SavedData;
|
||||||
const { INTERNAL_SETTINGS } = this.Editor.Tools;
|
|
||||||
const blockData = savedBlock.data;
|
const blockData = savedBlock.data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -201,7 +200,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||||
*
|
*
|
||||||
* @type {BlockToolConstructable}
|
* @type {BlockToolConstructable}
|
||||||
*/
|
*/
|
||||||
const replacingTool = this.Editor.Tools.toolsClasses[replacingToolName] as BlockToolConstructable;
|
const replacingTool = this.Editor.Tools.blockTools.get(replacingToolName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export property can be:
|
* Export property can be:
|
||||||
|
@ -211,7 +210,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||||
* In both cases returning value must be a string
|
* In both cases returning value must be a string
|
||||||
*/
|
*/
|
||||||
let exportData = '';
|
let exportData = '';
|
||||||
const exportProp = currentBlockClass[INTERNAL_SETTINGS.CONVERSION_CONFIG].export;
|
const exportProp = currentBlockTool.conversionConfig.export;
|
||||||
|
|
||||||
if (_.isFunction(exportProp)) {
|
if (_.isFunction(exportProp)) {
|
||||||
exportData = exportProp(blockData);
|
exportData = exportProp(blockData);
|
||||||
|
@ -229,7 +228,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||||
*/
|
*/
|
||||||
const cleaned: string = this.Editor.Sanitizer.clean(
|
const cleaned: string = this.Editor.Sanitizer.clean(
|
||||||
exportData,
|
exportData,
|
||||||
replacingTool.sanitize
|
replacingTool.sanitizeConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -238,7 +237,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||||
* string — the name of data field to import
|
* string — the name of data field to import
|
||||||
*/
|
*/
|
||||||
let newBlockData = {};
|
let newBlockData = {};
|
||||||
const importProp = replacingTool[INTERNAL_SETTINGS.CONVERSION_CONFIG].import;
|
const importProp = replacingTool.conversionConfig.import;
|
||||||
|
|
||||||
if (_.isFunction(importProp)) {
|
if (_.isFunction(importProp)) {
|
||||||
newBlockData = importProp(cleaned);
|
newBlockData = importProp(cleaned);
|
||||||
|
@ -272,37 +271,28 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||||
private addTools(): void {
|
private addTools(): void {
|
||||||
const tools = this.Editor.Tools.blockTools;
|
const tools = this.Editor.Tools.blockTools;
|
||||||
|
|
||||||
for (const toolName in tools) {
|
Array
|
||||||
if (!Object.prototype.hasOwnProperty.call(tools, toolName)) {
|
.from(tools.entries())
|
||||||
continue;
|
.forEach(([name, tool]) => {
|
||||||
}
|
const toolboxSettings = tool.toolbox;
|
||||||
|
const conversionConfig = tool.conversionConfig;
|
||||||
|
|
||||||
const internalSettings = this.Editor.Tools.INTERNAL_SETTINGS;
|
/**
|
||||||
const toolClass = tools[toolName] as BlockToolConstructable;
|
* Skip tools that don't pass 'toolbox' property
|
||||||
const toolToolboxSettings = toolClass[internalSettings.TOOLBOX];
|
*/
|
||||||
const conversionConfig = toolClass[internalSettings.CONVERSION_CONFIG];
|
if (_.isEmpty(toolboxSettings) || !toolboxSettings.icon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userSettings = this.Editor.Tools.USER_SETTINGS;
|
/**
|
||||||
const userToolboxSettings = this.Editor.Tools.getToolSettings(toolName)[userSettings.TOOLBOX];
|
* Skip tools without «import» rule specified
|
||||||
|
*/
|
||||||
|
if (!conversionConfig || !conversionConfig.import) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const toolboxSettings = userToolboxSettings ?? toolToolboxSettings;
|
this.addTool(name, toolboxSettings.icon, toolboxSettings.title);
|
||||||
|
});
|
||||||
/**
|
|
||||||
* Skip tools that don't pass 'toolbox' property
|
|
||||||
*/
|
|
||||||
if (_.isEmpty(toolboxSettings) || !toolboxSettings.icon) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip tools without «import» rule specified
|
|
||||||
*/
|
|
||||||
if (!conversionConfig || !conversionConfig.import) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addTool(toolName, toolboxSettings.icon, toolboxSettings.title);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,12 +2,15 @@ import Module from '../../__module';
|
||||||
import $ from '../../dom';
|
import $ from '../../dom';
|
||||||
import SelectionUtils from '../../selection';
|
import SelectionUtils from '../../selection';
|
||||||
import * as _ from '../../utils';
|
import * as _ from '../../utils';
|
||||||
import { InlineTool, InlineToolConstructable, ToolConstructable, ToolSettings } from '../../../../types';
|
import { InlineTool as IInlineTool } from '../../../../types';
|
||||||
import Flipper from '../../flipper';
|
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 { EditorModules } from '../../../types-internal/editor-modules';
|
import { ToolType } from '../tools';
|
||||||
|
import InlineTool from '../../tools/inline';
|
||||||
|
import { CommonInternalSettings } from '../../tools/base';
|
||||||
|
import BlockTool from '../../tools/block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline Toolbar elements
|
* Inline Toolbar elements
|
||||||
|
@ -66,9 +69,11 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
private readonly toolbarVerticalMargin: number = 5;
|
private readonly toolbarVerticalMargin: number = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO: Get rid of this
|
||||||
|
*
|
||||||
* Currently visible tools instances
|
* Currently visible tools instances
|
||||||
*/
|
*/
|
||||||
private toolsInstances: Map<string, InlineTool>;
|
private toolsInstances: Map<string, IInlineTool>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Buttons List
|
* Buttons List
|
||||||
|
@ -89,38 +94,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
*/
|
*/
|
||||||
private flipper: Flipper = null;
|
private flipper: Flipper = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal inline tools: Link, Bold, Italic
|
|
||||||
*/
|
|
||||||
private internalTools: {[name: string]: InlineToolConstructable} = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Editor modules setter
|
|
||||||
*
|
|
||||||
* @param {EditorModules} Editor - Editor's Modules
|
|
||||||
*/
|
|
||||||
public set state(Editor: EditorModules) {
|
|
||||||
this.Editor = Editor;
|
|
||||||
|
|
||||||
const { Tools } = Editor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set internal inline tools
|
|
||||||
*/
|
|
||||||
Object
|
|
||||||
.entries(Tools.internalTools)
|
|
||||||
.filter(([, toolClass]: [string, ToolConstructable | ToolSettings]) => {
|
|
||||||
if (_.isFunction(toolClass)) {
|
|
||||||
return toolClass[Tools.INTERNAL_SETTINGS.IS_INLINE];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (toolClass as ToolSettings).class[Tools.INTERNAL_SETTINGS.IS_INLINE];
|
|
||||||
})
|
|
||||||
.map(([name, toolClass]: [string, InlineToolConstructable | ToolSettings]) => {
|
|
||||||
this.internalTools[name] = _.isFunction(toolClass) ? toolClass : (toolClass as ToolSettings).class;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles read-only mode
|
* Toggles read-only mode
|
||||||
*
|
*
|
||||||
|
@ -310,16 +283,14 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
/**
|
/**
|
||||||
* Returns inline toolbar settings for a particular tool
|
* Returns inline toolbar settings for a particular tool
|
||||||
*
|
*
|
||||||
* @param {string} toolName - user specified name of tool
|
* @param tool - BlockTool object
|
||||||
* @returns {string[] | boolean} array of ordered tool names or false
|
* @returns {string[] | boolean} array of ordered tool names or false
|
||||||
*/
|
*/
|
||||||
private getInlineToolbarSettings(toolName): string[] | boolean {
|
private getInlineToolbarSettings(tool: BlockTool): string[] | boolean {
|
||||||
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InlineToolbar property of a particular tool
|
* InlineToolbar property of a particular tool
|
||||||
*/
|
*/
|
||||||
const settingsForTool = toolSettings[this.Editor.Tools.USER_SETTINGS.ENABLED_INLINE_TOOLS];
|
const settingsForTool = tool.enabledInlineTools;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to enable IT for a particular tool is the decision of the editor user.
|
* Whether to enable IT for a particular tool is the decision of the editor user.
|
||||||
|
@ -367,15 +338,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
* If common settings is 'true' or not specified (will be set as true at core.ts), get the default order
|
* If common settings is 'true' or not specified (will be set as true at core.ts), get the default order
|
||||||
*/
|
*/
|
||||||
if (commonInlineToolbarSettings === true) {
|
if (commonInlineToolbarSettings === true) {
|
||||||
const defaultToolsOrder: string[] = Object.entries(this.Editor.Tools.available)
|
return Array.from(this.Editor.Tools.inlineTools.keys());
|
||||||
.filter(([name, tool]) => {
|
|
||||||
return tool[this.Editor.Tools.INTERNAL_SETTINGS.IS_INLINE];
|
|
||||||
})
|
|
||||||
.map(([name, tool]) => {
|
|
||||||
return name;
|
|
||||||
});
|
|
||||||
|
|
||||||
return defaultToolsOrder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -492,7 +455,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
/**
|
/**
|
||||||
* getInlineToolbarSettings could return an string[] (order of tools) or false (Inline Toolbar disabled).
|
* getInlineToolbarSettings could return an string[] (order of tools) or false (Inline Toolbar disabled).
|
||||||
*/
|
*/
|
||||||
const inlineToolbarSettings = this.getInlineToolbarSettings(currentBlock.name);
|
const inlineToolbarSettings = this.getInlineToolbarSettings(currentBlock.tool);
|
||||||
|
|
||||||
return inlineToolbarSettings !== false;
|
return inlineToolbarSettings !== false;
|
||||||
}
|
}
|
||||||
|
@ -548,13 +511,14 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
* Changes Conversion Dropdown content for current block's Tool
|
* Changes Conversion Dropdown content for current block's Tool
|
||||||
*/
|
*/
|
||||||
private setConversionTogglerContent(): void {
|
private setConversionTogglerContent(): void {
|
||||||
const { BlockManager, Tools } = this.Editor;
|
const { BlockManager } = this.Editor;
|
||||||
const toolName = BlockManager.currentBlock.name;
|
const { currentBlock } = BlockManager;
|
||||||
|
const toolName = currentBlock.name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If tool does not provide 'export' rule, hide conversion dropdown
|
* If tool does not provide 'export' rule, hide conversion dropdown
|
||||||
*/
|
*/
|
||||||
const conversionConfig = Tools.available[toolName][Tools.INTERNAL_SETTINGS.CONVERSION_CONFIG] || {};
|
const conversionConfig = currentBlock.tool.conversionConfig;
|
||||||
const exportRuleDefined = conversionConfig && conversionConfig.export;
|
const exportRuleDefined = conversionConfig && conversionConfig.export;
|
||||||
|
|
||||||
this.nodes.conversionToggler.hidden = !exportRuleDefined;
|
this.nodes.conversionToggler.hidden = !exportRuleDefined;
|
||||||
|
@ -563,14 +527,10 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
/**
|
/**
|
||||||
* Get icon or title for dropdown
|
* Get icon or title for dropdown
|
||||||
*/
|
*/
|
||||||
const toolSettings = Tools.getToolSettings(toolName);
|
const toolboxSettings = currentBlock.tool.toolbox || {};
|
||||||
const toolboxSettings = Tools.available[toolName][Tools.INTERNAL_SETTINGS.TOOLBOX] || {};
|
|
||||||
const userToolboxSettings = toolSettings.toolbox || {};
|
|
||||||
|
|
||||||
this.nodes.conversionTogglerContent.innerHTML =
|
this.nodes.conversionTogglerContent.innerHTML =
|
||||||
userToolboxSettings.icon ||
|
|
||||||
toolboxSettings.icon ||
|
toolboxSettings.icon ||
|
||||||
userToolboxSettings.title ||
|
|
||||||
toolboxSettings.title ||
|
toolboxSettings.title ||
|
||||||
_.capitalize(toolName);
|
_.capitalize(toolName);
|
||||||
}
|
}
|
||||||
|
@ -610,14 +570,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
* For this moment, inlineToolbarOrder could not be 'false'
|
* For this moment, inlineToolbarOrder could not be 'false'
|
||||||
* because this method will be called only if the Inline Toolbar is enabled
|
* because this method will be called only if the Inline Toolbar is enabled
|
||||||
*/
|
*/
|
||||||
const inlineToolbarOrder = this.getInlineToolbarSettings(currentBlock.name) as string[];
|
const inlineToolbarOrder = this.getInlineToolbarSettings(currentBlock.tool) as string[];
|
||||||
|
|
||||||
inlineToolbarOrder.forEach((toolName) => {
|
inlineToolbarOrder.forEach((toolName) => {
|
||||||
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
|
const tool = this.Editor.Tools.inlineTools.get(toolName);
|
||||||
const tool = this.Editor.Tools.constructInline(this.Editor.Tools.inline[toolName], toolName, toolSettings);
|
|
||||||
|
|
||||||
this.addTool(toolName, tool);
|
this.addTool(tool);
|
||||||
tool.checkState(SelectionUtils.get());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -629,43 +587,42 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
/**
|
/**
|
||||||
* Add tool button and activate clicks
|
* Add tool button and activate clicks
|
||||||
*
|
*
|
||||||
* @param {string} toolName - name of Tool to add
|
* @param {InlineTool} tool - InlineTool object
|
||||||
* @param {InlineTool} tool - Tool class instance
|
|
||||||
*/
|
*/
|
||||||
private addTool(toolName: string, tool: InlineTool): void {
|
private addTool(tool: InlineTool): void {
|
||||||
const {
|
const {
|
||||||
Tools,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} = this.Editor;
|
} = this.Editor;
|
||||||
|
|
||||||
const button = tool.render();
|
const instance = tool.instance();
|
||||||
|
const button = instance.render();
|
||||||
|
|
||||||
if (!button) {
|
if (!button) {
|
||||||
_.log('Render method must return an instance of Node', 'warn', toolName);
|
_.log('Render method must return an instance of Node', 'warn', tool.name);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.dataset.tool = toolName;
|
button.dataset.tool = tool.name;
|
||||||
this.nodes.buttons.appendChild(button);
|
this.nodes.buttons.appendChild(button);
|
||||||
this.toolsInstances.set(toolName, tool);
|
this.toolsInstances.set(tool.name, instance);
|
||||||
|
|
||||||
if (_.isFunction(tool.renderActions)) {
|
if (_.isFunction(instance.renderActions)) {
|
||||||
const actions = tool.renderActions();
|
const actions = instance.renderActions();
|
||||||
|
|
||||||
this.nodes.actions.appendChild(actions);
|
this.nodes.actions.appendChild(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listeners.on(button, 'click', (event) => {
|
this.listeners.on(button, 'click', (event) => {
|
||||||
this.toolClicked(tool);
|
this.toolClicked(instance);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
const shortcut = this.getToolShortcut(toolName);
|
const shortcut = this.getToolShortcut(tool.name);
|
||||||
|
|
||||||
if (shortcut) {
|
if (shortcut) {
|
||||||
try {
|
try {
|
||||||
this.enableShortcuts(tool, shortcut);
|
this.enableShortcuts(instance, shortcut);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,7 +632,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
const tooltipContent = $.make('div');
|
const tooltipContent = $.make('div');
|
||||||
const toolTitle = I18n.t(
|
const toolTitle = I18n.t(
|
||||||
I18nInternalNS.toolNames,
|
I18nInternalNS.toolNames,
|
||||||
Tools.toolsClasses[toolName][Tools.INTERNAL_SETTINGS.TITLE] || _.capitalize(toolName)
|
tool.title || _.capitalize(tool.name)
|
||||||
);
|
);
|
||||||
|
|
||||||
tooltipContent.appendChild($.text(toolTitle));
|
tooltipContent.appendChild($.text(toolTitle));
|
||||||
|
@ -690,6 +647,8 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
hidingDelay: 100,
|
hidingDelay: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
instance.checkState(SelectionUtils.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -704,21 +663,20 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
* Enable shortcuts
|
* Enable shortcuts
|
||||||
* Ignore tool that doesn't have shortcut or empty string
|
* Ignore tool that doesn't have shortcut or empty string
|
||||||
*/
|
*/
|
||||||
const toolSettings = Tools.getToolSettings(toolName);
|
const tool = Tools.inlineTools.get(toolName);
|
||||||
const tool = this.toolsInstances.get(toolName);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1) For internal tools, check public getter 'shortcut'
|
* 1) For internal tools, check public getter 'shortcut'
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
if (Object.keys(this.internalTools).includes(toolName)) {
|
const internalTools = Tools.getInternal(ToolType.Inline);
|
||||||
return this.inlineTools[toolName][Tools.INTERNAL_SETTINGS.SHORTCUT];
|
|
||||||
} else if (toolSettings && toolSettings[Tools.USER_SETTINGS.SHORTCUT]) {
|
if (Array.from(internalTools.keys()).includes(toolName)) {
|
||||||
return toolSettings[Tools.USER_SETTINGS.SHORTCUT];
|
return this.inlineTools[toolName][CommonInternalSettings.Shortcut];
|
||||||
} else if (tool.shortcut) {
|
|
||||||
return tool.shortcut;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return tool.shortcut;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -727,7 +685,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
* @param {InlineTool} tool - Tool instance
|
* @param {InlineTool} tool - Tool instance
|
||||||
* @param {string} shortcut - shortcut according to the ShortcutData Module format
|
* @param {string} shortcut - shortcut according to the ShortcutData Module format
|
||||||
*/
|
*/
|
||||||
private enableShortcuts(tool: InlineTool, shortcut: string): void {
|
private enableShortcuts(tool: IInlineTool, shortcut: string): void {
|
||||||
Shortcuts.add({
|
Shortcuts.add({
|
||||||
name: shortcut,
|
name: shortcut,
|
||||||
handler: (event) => {
|
handler: (event) => {
|
||||||
|
@ -747,9 +705,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
*/
|
*/
|
||||||
// if (SelectionUtils.isCollapsed) return;
|
// if (SelectionUtils.isCollapsed) return;
|
||||||
|
|
||||||
const toolSettings = this.Editor.Tools.getToolSettings(currentBlock.name);
|
if (!currentBlock.tool.enabledInlineTools) {
|
||||||
|
|
||||||
if (!toolSettings || !toolSettings[this.Editor.Tools.USER_SETTINGS.ENABLED_INLINE_TOOLS]) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -765,7 +721,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
*
|
*
|
||||||
* @param {InlineTool} tool - Tool's instance
|
* @param {InlineTool} tool - Tool's instance
|
||||||
*/
|
*/
|
||||||
private toolClicked(tool: InlineTool): void {
|
private toolClicked(tool: IInlineTool): void {
|
||||||
const range = SelectionUtils.range;
|
const range = SelectionUtils.range;
|
||||||
|
|
||||||
tool.surround(range);
|
tool.surround(range);
|
||||||
|
@ -785,16 +741,14 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
* Get inline tools tools
|
* Get inline tools tools
|
||||||
* Tools that has isInline is true
|
* Tools that has isInline is true
|
||||||
*/
|
*/
|
||||||
private get inlineTools(): { [name: string]: InlineTool } {
|
private get inlineTools(): { [name: string]: IInlineTool } {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
for (const tool in this.Editor.Tools.inline) {
|
Array
|
||||||
if (Object.prototype.hasOwnProperty.call(this.Editor.Tools.inline, tool)) {
|
.from(this.Editor.Tools.inlineTools.entries())
|
||||||
const toolSettings = this.Editor.Tools.getToolSettings(tool);
|
.forEach(([name, tool]) => {
|
||||||
|
result[name] = tool.instance();
|
||||||
result[tool] = this.Editor.Tools.constructInline(this.Editor.Tools.inline[tool], tool, toolSettings);
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import Module from '../../__module';
|
import Module from '../../__module';
|
||||||
import $ from '../../dom';
|
import $ from '../../dom';
|
||||||
import * as _ from '../../utils';
|
import * as _ from '../../utils';
|
||||||
import { BlockToolConstructable, ToolConstructable } from '../../../../types';
|
import { BlockToolConstructable } from '../../../../types';
|
||||||
import Flipper from '../../flipper';
|
import Flipper from '../../flipper';
|
||||||
import { BlockToolAPI } from '../../block';
|
import { BlockToolAPI } from '../../block';
|
||||||
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 BlockTool from '../../tools/block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTMLElements used for Toolbox UI
|
* HTMLElements used for Toolbox UI
|
||||||
|
@ -116,9 +117,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
||||||
* @param {string} toolName - button to activate
|
* @param {string} toolName - button to activate
|
||||||
*/
|
*/
|
||||||
public toolButtonActivate(event: MouseEvent|KeyboardEvent, toolName: string): void {
|
public toolButtonActivate(event: MouseEvent|KeyboardEvent, toolName: string): void {
|
||||||
const tool = this.Editor.Tools.toolsClasses[toolName] as BlockToolConstructable;
|
this.insertNewBlock(toolName);
|
||||||
|
|
||||||
this.insertNewBlock(tool, toolName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,36 +161,30 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
||||||
* Iterates available tools and appends them to the Toolbox
|
* Iterates available tools and appends them to the Toolbox
|
||||||
*/
|
*/
|
||||||
private addTools(): void {
|
private addTools(): void {
|
||||||
const tools = this.Editor.Tools.available;
|
const tools = this.Editor.Tools.blockTools;
|
||||||
|
|
||||||
for (const toolName in tools) {
|
Array
|
||||||
if (Object.prototype.hasOwnProperty.call(tools, toolName)) {
|
.from(tools.values())
|
||||||
this.addTool(toolName, tools[toolName] as BlockToolConstructable);
|
.forEach((tool) => this.addTool(tool));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append Tool to the Toolbox
|
* Append Tool to the Toolbox
|
||||||
*
|
*
|
||||||
* @param {string} toolName - tool name
|
* @param {BlockToolConstructable} tool - BlockTool object
|
||||||
* @param {BlockToolConstructable} tool - tool class
|
|
||||||
*/
|
*/
|
||||||
private addTool(toolName: string, tool: BlockToolConstructable): void {
|
private addTool(tool: BlockTool): void {
|
||||||
const internalSettings = this.Editor.Tools.INTERNAL_SETTINGS;
|
const toolToolboxSettings = tool.toolbox;
|
||||||
const userSettings = this.Editor.Tools.USER_SETTINGS;
|
|
||||||
|
|
||||||
const toolToolboxSettings = tool[internalSettings.TOOLBOX];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skip tools that don't pass 'toolbox' property
|
* Skip tools that don't pass 'toolbox' property
|
||||||
*/
|
*/
|
||||||
if (_.isEmpty(toolToolboxSettings)) {
|
if (!toolToolboxSettings) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolToolboxSettings && !toolToolboxSettings.icon) {
|
if (toolToolboxSettings && !toolToolboxSettings.icon) {
|
||||||
_.log('Toolbar icon is missed. Tool %o skipped', 'warn', toolName);
|
_.log('Toolbar icon is missed. Tool %o skipped', 'warn', tool.name);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -204,19 +197,10 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const userToolboxSettings = this.Editor.Tools.getToolSettings(toolName)[userSettings.TOOLBOX];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide Toolbox button if Toolbox settings is false
|
|
||||||
*/
|
|
||||||
if ((userToolboxSettings ?? toolToolboxSettings) === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const button = $.make('li', [ this.CSS.toolboxButton ]);
|
const button = $.make('li', [ this.CSS.toolboxButton ]);
|
||||||
|
|
||||||
button.dataset.tool = toolName;
|
button.dataset.tool = tool.name;
|
||||||
button.innerHTML = (userToolboxSettings && userToolboxSettings.icon) || toolToolboxSettings.icon;
|
button.innerHTML = toolToolboxSettings.icon;
|
||||||
|
|
||||||
$.append(this.nodes.toolbox, button);
|
$.append(this.nodes.toolbox, button);
|
||||||
|
|
||||||
|
@ -227,61 +211,40 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
||||||
* Add click listener
|
* Add click listener
|
||||||
*/
|
*/
|
||||||
this.listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => {
|
this.listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => {
|
||||||
this.toolButtonActivate(event, toolName);
|
this.toolButtonActivate(event, tool.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add listeners to show/hide toolbox tooltip
|
* Add listeners to show/hide toolbox tooltip
|
||||||
*/
|
*/
|
||||||
const tooltipContent = this.drawTooltip(toolName);
|
const tooltipContent = this.drawTooltip(tool);
|
||||||
|
|
||||||
this.Editor.Tooltip.onHover(button, tooltipContent, {
|
this.Editor.Tooltip.onHover(button, tooltipContent, {
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
hidingDelay: 200,
|
hidingDelay: 200,
|
||||||
});
|
});
|
||||||
|
|
||||||
const shortcut = this.getToolShortcut(toolName, tool);
|
const shortcut = tool.shortcut;
|
||||||
|
|
||||||
if (shortcut) {
|
if (shortcut) {
|
||||||
this.enableShortcut(tool, toolName, shortcut);
|
this.enableShortcut(tool.name, shortcut);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Increment Tools count */
|
/** Increment Tools count */
|
||||||
this.displayedToolsCount++;
|
this.displayedToolsCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns tool's shortcut
|
|
||||||
* It can be specified via internal 'shortcut' static getter or by user settings for tool
|
|
||||||
*
|
|
||||||
* @param {string} toolName - tool's name
|
|
||||||
* @param {ToolConstructable} tool - tool's class (not instance)
|
|
||||||
*/
|
|
||||||
private getToolShortcut(toolName: string, tool: ToolConstructable): string|null {
|
|
||||||
/**
|
|
||||||
* Enable shortcut
|
|
||||||
*/
|
|
||||||
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
|
|
||||||
const internalToolShortcut = tool[this.Editor.Tools.INTERNAL_SETTINGS.SHORTCUT];
|
|
||||||
const userSpecifiedShortcut = toolSettings ? toolSettings[this.Editor.Tools.USER_SETTINGS.SHORTCUT] : null;
|
|
||||||
|
|
||||||
return userSpecifiedShortcut || internalToolShortcut;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw tooltip for toolbox tools
|
* Draw tooltip for toolbox tools
|
||||||
*
|
*
|
||||||
* @param {string} toolName - toolbox tool name
|
* @param tool - BlockTool object
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
private drawTooltip(toolName: string): HTMLElement {
|
private drawTooltip(tool: BlockTool): HTMLElement {
|
||||||
const tool = this.Editor.Tools.available[toolName];
|
const toolboxSettings = tool.toolbox || {};
|
||||||
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
|
const name = I18n.t(I18nInternalNS.toolNames, toolboxSettings.title || tool.name);
|
||||||
const toolboxSettings = this.Editor.Tools.available[toolName][this.Editor.Tools.INTERNAL_SETTINGS.TOOLBOX] || {};
|
|
||||||
const userToolboxSettings = toolSettings.toolbox || {};
|
|
||||||
const name = I18n.t(I18nInternalNS.toolNames, userToolboxSettings.title || toolboxSettings.title || toolName);
|
|
||||||
|
|
||||||
let shortcut = this.getToolShortcut(toolName, tool);
|
let shortcut = tool.shortcut;
|
||||||
|
|
||||||
const tooltip = $.make('div', this.CSS.buttonTooltip);
|
const tooltip = $.make('div', this.CSS.buttonTooltip);
|
||||||
const hint = document.createTextNode(_.capitalize(name));
|
const hint = document.createTextNode(_.capitalize(name));
|
||||||
|
@ -302,16 +265,15 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
||||||
/**
|
/**
|
||||||
* Enable shortcut Block Tool implemented shortcut
|
* Enable shortcut Block Tool implemented shortcut
|
||||||
*
|
*
|
||||||
* @param {BlockToolConstructable} tool - Tool class
|
|
||||||
* @param {string} toolName - Tool name
|
* @param {string} toolName - Tool name
|
||||||
* @param {string} shortcut - shortcut according to the ShortcutData Module format
|
* @param {string} shortcut - shortcut according to the ShortcutData Module format
|
||||||
*/
|
*/
|
||||||
private enableShortcut(tool: BlockToolConstructable, toolName: string, shortcut: string): void {
|
private enableShortcut(toolName: string, shortcut: string): void {
|
||||||
Shortcuts.add({
|
Shortcuts.add({
|
||||||
name: shortcut,
|
name: shortcut,
|
||||||
handler: (event: KeyboardEvent) => {
|
handler: (event: KeyboardEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.insertNewBlock(tool, toolName);
|
this.insertNewBlock(toolName);
|
||||||
},
|
},
|
||||||
on: this.Editor.UI.nodes.redactor,
|
on: this.Editor.UI.nodes.redactor,
|
||||||
});
|
});
|
||||||
|
@ -322,17 +284,17 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
||||||
* Fired when the Read-Only mode is activated
|
* Fired when the Read-Only mode is activated
|
||||||
*/
|
*/
|
||||||
private removeAllShortcuts(): void {
|
private removeAllShortcuts(): void {
|
||||||
const tools = this.Editor.Tools.available;
|
const tools = this.Editor.Tools.blockTools;
|
||||||
|
|
||||||
for (const toolName in tools) {
|
Array
|
||||||
if (Object.prototype.hasOwnProperty.call(tools, toolName)) {
|
.from(tools.values())
|
||||||
const shortcut = this.getToolShortcut(toolName, tools[toolName]);
|
.forEach((tool) => {
|
||||||
|
const shortcut = tool.shortcut;
|
||||||
|
|
||||||
if (shortcut) {
|
if (shortcut) {
|
||||||
Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut);
|
Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -351,10 +313,9 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
||||||
* Inserts new block
|
* Inserts new block
|
||||||
* Can be called when button clicked on Toolbox or by ShortcutData
|
* Can be called when button clicked on Toolbox or by ShortcutData
|
||||||
*
|
*
|
||||||
* @param {BlockToolConstructable} tool - Tool Class
|
|
||||||
* @param {string} toolName - Tool name
|
* @param {string} toolName - Tool name
|
||||||
*/
|
*/
|
||||||
private insertNewBlock(tool: BlockToolConstructable, toolName: string): void {
|
private insertNewBlock(toolName: string): void {
|
||||||
const { BlockManager, Caret } = this.Editor;
|
const { BlockManager, Caret } = this.Editor;
|
||||||
const { currentBlock } = BlockManager;
|
const { currentBlock } = BlockManager;
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import Paragraph from '../tools/paragraph/dist/bundle';
|
import Paragraph from '../../tools/paragraph/dist/bundle';
|
||||||
import Module from '../__module';
|
import Module from '../__module';
|
||||||
import * as _ from '../utils';
|
import * as _ from '../utils';
|
||||||
import {
|
import {
|
||||||
BlockToolConstructable,
|
|
||||||
EditorConfig,
|
EditorConfig,
|
||||||
InlineTool,
|
Tool,
|
||||||
InlineToolConstructable, Tool,
|
|
||||||
ToolConfig,
|
|
||||||
ToolConstructable,
|
ToolConstructable,
|
||||||
ToolSettings
|
ToolSettings
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
import BoldInlineTool from '../inline-tools/inline-tool-bold';
|
import BoldInlineTool from '../inline-tools/inline-tool-bold';
|
||||||
import ItalicInlineTool from '../inline-tools/inline-tool-italic';
|
import ItalicInlineTool from '../inline-tools/inline-tool-italic';
|
||||||
import LinkInlineTool from '../inline-tools/inline-tool-link';
|
import LinkInlineTool from '../inline-tools/inline-tool-link';
|
||||||
import Stub from '../tools/stub';
|
import Stub from '../../tools/stub';
|
||||||
import { ModuleConfig } from '../../types-internal/module-config';
|
import ToolsFactory from '../tools/factory';
|
||||||
import EventsDispatcher from '../utils/events';
|
import InlineTool from '../tools/inline';
|
||||||
|
import BlockTool from '../tools/block';
|
||||||
|
import BlockTune from '../tools/tune';
|
||||||
|
import BaseTool from '../tools/base';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module Editor.js Tools Submodule
|
* @module Editor.js Tools Submodule
|
||||||
|
@ -23,6 +23,8 @@ import EventsDispatcher from '../utils/events';
|
||||||
* Creates Instances from Plugins and binds external config to the instances
|
* Creates Instances from Plugins and binds external config to the instances
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type ToolClass = BlockTool | InlineTool | BlockTune;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class properties:
|
* Class properties:
|
||||||
*
|
*
|
||||||
|
@ -47,7 +49,7 @@ export default class Tools extends Module {
|
||||||
*
|
*
|
||||||
* @returns {object<Tool>}
|
* @returns {object<Tool>}
|
||||||
*/
|
*/
|
||||||
public get available(): { [name: string]: ToolConstructable } {
|
public get available(): Map<string, ToolClass> {
|
||||||
return this.toolsAvailable;
|
return this.toolsAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +58,7 @@ export default class Tools extends Module {
|
||||||
*
|
*
|
||||||
* @returns {Tool[]}
|
* @returns {Tool[]}
|
||||||
*/
|
*/
|
||||||
public get unavailable(): { [name: string]: ToolConstructable } {
|
public get unavailable(): Map<string, ToolClass> {
|
||||||
return this.toolsUnavailable;
|
return this.toolsUnavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,48 +67,40 @@ export default class Tools extends Module {
|
||||||
*
|
*
|
||||||
* @returns {object} - object of Inline Tool's classes
|
* @returns {object} - object of Inline Tool's classes
|
||||||
*/
|
*/
|
||||||
public get inline(): { [name: string]: InlineToolConstructable } {
|
public get inlineTools(): Map<string, InlineTool> {
|
||||||
if (this._inlineTools) {
|
if (this._inlineTools) {
|
||||||
return this._inlineTools;
|
return this._inlineTools;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tools = Object.entries(this.available).filter(([name, tool]) => {
|
const tools = Array
|
||||||
if (!tool[this.INTERNAL_SETTINGS.IS_INLINE]) {
|
.from(this.available.entries())
|
||||||
return false;
|
.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) {
|
||||||
* Some Tools validation
|
_.log(
|
||||||
*/
|
`Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`,
|
||||||
const inlineToolRequiredMethods = ['render', 'surround', 'checkState'];
|
'warn',
|
||||||
const notImplementedMethods = inlineToolRequiredMethods.filter((method) => !this.constructInline(tool, name)[method]);
|
notImplementedMethods
|
||||||
|
);
|
||||||
|
|
||||||
if (notImplementedMethods.length) {
|
return false;
|
||||||
_.log(
|
}
|
||||||
`Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`,
|
|
||||||
'warn',
|
|
||||||
notImplementedMethods
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return true;
|
||||||
}
|
});
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* collected inline tools with key of tool name
|
|
||||||
*/
|
|
||||||
const result = {};
|
|
||||||
|
|
||||||
tools.forEach(([name, tool]) => {
|
|
||||||
result[name] = tool;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache prepared Tools
|
* Cache prepared Tools
|
||||||
*/
|
*/
|
||||||
this._inlineTools = result;
|
this._inlineTools = new Map(tools) as Map<string, InlineTool>;
|
||||||
|
|
||||||
return this._inlineTools;
|
return this._inlineTools;
|
||||||
}
|
}
|
||||||
|
@ -114,79 +108,43 @@ export default class Tools extends Module {
|
||||||
/**
|
/**
|
||||||
* Return editor block tools
|
* Return editor block tools
|
||||||
*/
|
*/
|
||||||
public get blockTools(): { [name: string]: BlockToolConstructable } {
|
public get blockTools(): Map<string, BlockTool> {
|
||||||
const tools = Object.entries(this.available).filter(([, tool]) => {
|
if (this._blockTools) {
|
||||||
return !tool[this.INTERNAL_SETTINGS.IS_INLINE];
|
return this._blockTools;
|
||||||
});
|
}
|
||||||
|
|
||||||
/**
|
const tools = Array
|
||||||
* collected block tools with key of tool name
|
.from(this.available.entries())
|
||||||
*/
|
.filter(([, tool]) => {
|
||||||
const result = {};
|
return tool.type === ToolType.Block;
|
||||||
|
});
|
||||||
|
|
||||||
tools.forEach(([name, tool]) => {
|
this._blockTools = new Map(tools) as Map<string, BlockTool>;
|
||||||
result[name] = tool;
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
return this._blockTools;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant for available Tools internal settings provided by Tool developer
|
* Returns default Tool object
|
||||||
*
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
*/
|
||||||
public get INTERNAL_SETTINGS(): { [name: string]: string } {
|
public get defaultTool(): BlockTool {
|
||||||
return {
|
return this.blockTools.get(this.config.defaultBlock);
|
||||||
IS_ENABLED_LINE_BREAKS: 'enableLineBreaks',
|
|
||||||
IS_INLINE: 'isInline',
|
|
||||||
TITLE: 'title', // for Inline Tools. Block Tools can pass title along with icon through the 'toolbox' static prop.
|
|
||||||
SHORTCUT: 'shortcut',
|
|
||||||
TOOLBOX: 'toolbox',
|
|
||||||
SANITIZE_CONFIG: 'sanitize',
|
|
||||||
CONVERSION_CONFIG: 'conversionConfig',
|
|
||||||
IS_READ_ONLY_SUPPORTED: 'isReadOnlySupported',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant for available Tools settings provided by user
|
* Tools objects factory
|
||||||
*
|
|
||||||
* return {object}
|
|
||||||
*/
|
*/
|
||||||
public get USER_SETTINGS(): { [name: string]: string } {
|
private factory: ToolsFactory;
|
||||||
return {
|
|
||||||
SHORTCUT: 'shortcut',
|
|
||||||
TOOLBOX: 'toolbox',
|
|
||||||
ENABLED_INLINE_TOOLS: 'inlineToolbar',
|
|
||||||
CONFIG: 'config',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map {name: Class, ...} where:
|
|
||||||
* name — block type name in JSON. Got from EditorConfig.tools keys
|
|
||||||
*
|
|
||||||
* @type {object}
|
|
||||||
*/
|
|
||||||
public readonly toolsClasses: { [name: string]: ToolConstructable } = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tools` classes available to use
|
* Tools` classes available to use
|
||||||
*/
|
*/
|
||||||
private readonly toolsAvailable: { [name: string]: ToolConstructable } = {};
|
private readonly toolsAvailable: Map<string, ToolClass> = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tools` classes not available to use because of preparation failure
|
* Tools` classes not available to use because of preparation failure
|
||||||
*/
|
*/
|
||||||
private readonly toolsUnavailable: { [name: string]: ToolConstructable } = {};
|
private readonly toolsUnavailable: Map<string, ToolClass> = new Map();
|
||||||
|
|
||||||
/**
|
|
||||||
* Tools settings in a map {name: settings, ...}
|
|
||||||
*
|
|
||||||
* @type {object}
|
|
||||||
*/
|
|
||||||
private readonly toolsSettings: { [name: string]: ToolSettings } = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache for the prepared inline tools
|
* Cache for the prepared inline tools
|
||||||
|
@ -194,41 +152,30 @@ export default class Tools extends Module {
|
||||||
* @type {null|object}
|
* @type {null|object}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private _inlineTools: { [name: string]: ToolConstructable } = {};
|
private _inlineTools: Map<string, InlineTool> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* Cache for the prepared block tools
|
||||||
*
|
|
||||||
* @param {EditorConfig} config - Editor's configuration
|
|
||||||
* @param {EventsDispatcher} eventsDispatcher - Editor's event dispatcher
|
|
||||||
*/
|
*/
|
||||||
constructor({ config, eventsDispatcher }: ModuleConfig) {
|
private _blockTools: Map<string, BlockTool> = null;
|
||||||
super({
|
|
||||||
config,
|
|
||||||
eventsDispatcher,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.toolsClasses = {};
|
/**
|
||||||
|
* Returns internal tools
|
||||||
|
*
|
||||||
|
* @param type - if passed, Tools will be filtered by type
|
||||||
|
*/
|
||||||
|
public getInternal(type?: ToolType): Map<string, ToolClass> {
|
||||||
|
let tools = Array
|
||||||
|
.from(this.available.entries())
|
||||||
|
.filter(([, tool]) => {
|
||||||
|
return tool.isInternal;
|
||||||
|
});
|
||||||
|
|
||||||
this.toolsSettings = {};
|
if (type) {
|
||||||
|
tools = tools.filter(([, tool]) => tool.type === type);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return new Map(tools);
|
||||||
* Available tools list
|
|
||||||
* {name: Class, ...}
|
|
||||||
*
|
|
||||||
* @type {object}
|
|
||||||
*/
|
|
||||||
this.toolsAvailable = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tools that rejected a prepare method
|
|
||||||
* {name: Class, ... }
|
|
||||||
*
|
|
||||||
* @type {object}
|
|
||||||
*/
|
|
||||||
this.toolsUnavailable = {};
|
|
||||||
|
|
||||||
this._inlineTools = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -248,54 +195,14 @@ export default class Tools extends Module {
|
||||||
throw Error('Can\'t start without tools');
|
throw Error('Can\'t start without tools');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const config = this.prepareConfig();
|
||||||
* Save Tools settings to a map
|
|
||||||
*/
|
|
||||||
for (const toolName in this.config.tools) {
|
|
||||||
/**
|
|
||||||
* If Tool is an object not a Tool's class then
|
|
||||||
* save class and settings separately
|
|
||||||
*/
|
|
||||||
if (_.isObject(this.config.tools[toolName])) {
|
|
||||||
/**
|
|
||||||
* Save Tool's class from 'class' field
|
|
||||||
*
|
|
||||||
* @type {Tool}
|
|
||||||
*/
|
|
||||||
this.toolsClasses[toolName] = (this.config.tools[toolName] as ToolSettings).class;
|
|
||||||
|
|
||||||
/**
|
this.factory = new ToolsFactory(config, this.config, this.Editor.API);
|
||||||
* Save Tool's settings
|
|
||||||
*
|
|
||||||
* @type {ToolSettings}
|
|
||||||
*/
|
|
||||||
this.toolsSettings[toolName] = this.config.tools[toolName] as ToolSettings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove Tool's class from settings
|
|
||||||
*/
|
|
||||||
delete this.toolsSettings[toolName].class;
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* Save Tool's class
|
|
||||||
*
|
|
||||||
* @type {Tool}
|
|
||||||
*/
|
|
||||||
this.toolsClasses[toolName] = this.config.tools[toolName] as ToolConstructable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set empty settings for Block by default
|
|
||||||
*
|
|
||||||
* @type {{}}
|
|
||||||
*/
|
|
||||||
this.toolsSettings[toolName] = { class: this.config.tools[toolName] as ToolConstructable };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getting classes that has prepare method
|
* getting classes that has prepare method
|
||||||
*/
|
*/
|
||||||
const sequenceData = this.getListOfPrepareFunctions();
|
const sequenceData = this.getListOfPrepareFunctions(config);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if sequence data contains nothing then resolve current chain and run other module prepare
|
* if sequence data contains nothing then resolve current chain and run other module prepare
|
||||||
|
@ -308,110 +215,42 @@ export default class Tools extends Module {
|
||||||
* to see how it works {@link '../utils.ts#sequence'}
|
* to see how it works {@link '../utils.ts#sequence'}
|
||||||
*/
|
*/
|
||||||
return _.sequence(sequenceData, (data: { toolName: string }) => {
|
return _.sequence(sequenceData, (data: { toolName: string }) => {
|
||||||
this.success(data);
|
this.toolPrepareMethodSuccess(data);
|
||||||
}, (data: { toolName: string }) => {
|
}, (data: { toolName: string }) => {
|
||||||
this.fallback(data);
|
this.toolPrepareMethodFallback(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Success callback
|
|
||||||
*
|
|
||||||
* @param {object} data - append tool to available list
|
|
||||||
*/
|
|
||||||
public success(data: { toolName: string }): void {
|
|
||||||
this.toolsAvailable[data.toolName] = this.toolsClasses[data.toolName];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fail callback
|
|
||||||
*
|
|
||||||
* @param {object} data - append tool to unavailable list
|
|
||||||
*/
|
|
||||||
public fallback(data: { toolName: string }): void {
|
|
||||||
this.toolsUnavailable[data.toolName] = this.toolsClasses[data.toolName];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return Inline Tool's instance
|
|
||||||
*
|
|
||||||
* @param {InlineTool} tool - Inline Tool instance
|
|
||||||
* @param {string} name - tool name
|
|
||||||
* @param {ToolSettings} toolSettings - tool settings
|
|
||||||
*
|
|
||||||
* @returns {InlineTool} — instance
|
|
||||||
*/
|
|
||||||
public constructInline(
|
|
||||||
tool: InlineToolConstructable,
|
|
||||||
name: string,
|
|
||||||
toolSettings: ToolSettings = {} as ToolSettings
|
|
||||||
): InlineTool {
|
|
||||||
const constructorOptions = {
|
|
||||||
api: this.Editor.API.getMethodsForTool(name),
|
|
||||||
config: (toolSettings[this.USER_SETTINGS.CONFIG] || {}) as ToolSettings,
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line new-cap
|
|
||||||
return new tool(constructorOptions) as InlineTool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if passed Tool is an instance of Default Block Tool
|
|
||||||
*
|
|
||||||
* @param {Tool} tool - Tool to check
|
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
public isDefault(tool): boolean {
|
|
||||||
return tool instanceof this.available[this.config.defaultBlock];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return Tool's config by name
|
|
||||||
*
|
|
||||||
* @param {string} toolName - name of tool
|
|
||||||
*
|
|
||||||
* @returns {ToolSettings}
|
|
||||||
*/
|
|
||||||
public getToolSettings(toolName): ToolSettings {
|
|
||||||
const settings = this.toolsSettings[toolName];
|
|
||||||
const config = settings[this.USER_SETTINGS.CONFIG] || {};
|
|
||||||
|
|
||||||
// Pass placeholder to default Block config
|
|
||||||
if (toolName === this.config.defaultBlock && !config.placeholder) {
|
|
||||||
config.placeholder = this.config.placeholder;
|
|
||||||
settings[this.USER_SETTINGS.CONFIG] = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 } {
|
public get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings & { isInternal?: boolean } } {
|
||||||
return {
|
return {
|
||||||
bold: { class: BoldInlineTool },
|
bold: {
|
||||||
italic: { class: ItalicInlineTool },
|
class: BoldInlineTool,
|
||||||
link: { class: LinkInlineTool },
|
isInternal: true,
|
||||||
|
},
|
||||||
|
italic: {
|
||||||
|
class: ItalicInlineTool,
|
||||||
|
isInternal: true,
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
class: LinkInlineTool,
|
||||||
|
isInternal: true,
|
||||||
|
},
|
||||||
paragraph: {
|
paragraph: {
|
||||||
class: Paragraph,
|
class: Paragraph,
|
||||||
inlineToolbar: true,
|
inlineToolbar: true,
|
||||||
|
isInternal: true,
|
||||||
|
},
|
||||||
|
stub: {
|
||||||
|
class: Stub,
|
||||||
|
isInternal: true,
|
||||||
},
|
},
|
||||||
stub: { class: Stub },
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if tool supports read-only mode
|
|
||||||
*
|
|
||||||
* @param tool - tool to check
|
|
||||||
*/
|
|
||||||
public isReadOnlySupported(tool: BlockToolConstructable): boolean {
|
|
||||||
return tool[this.INTERNAL_SETTINGS.IS_READ_ONLY_SUPPORTED] === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls each Tool reset method to clean up anything set by Tool
|
* Calls each Tool reset method to clean up anything set by Tool
|
||||||
*/
|
*/
|
||||||
|
@ -423,41 +262,50 @@ export default class Tools extends Module {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool prepare method success callback
|
||||||
|
*
|
||||||
|
* @param {object} data - append tool to available list
|
||||||
|
*/
|
||||||
|
private toolPrepareMethodSuccess(data: { toolName: string }): void {
|
||||||
|
this.toolsAvailable.set(data.toolName, this.factory.get(data.toolName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool prepare method fail callback
|
||||||
|
*
|
||||||
|
* @param {object} data - append tool to unavailable list
|
||||||
|
*/
|
||||||
|
private toolPrepareMethodFallback(data: { toolName: string }): void {
|
||||||
|
this.toolsUnavailable.set(data.toolName, this.factory.get(data.toolName));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds prepare function of plugins with user or default config
|
* Binds prepare function of plugins with user or default config
|
||||||
*
|
*
|
||||||
* @returns {Array} list of functions that needs to be fired sequentially
|
* @returns {Array} list of functions that needs to be fired sequentially
|
||||||
|
* @param config - tools config
|
||||||
*/
|
*/
|
||||||
private getListOfPrepareFunctions(): Array<{
|
private getListOfPrepareFunctions(config: {[name: string]: ToolSettings}): {
|
||||||
function: (data: { toolName: string; config: ToolConfig }) => void;
|
function: (data: { toolName: string }) => void | Promise<void>;
|
||||||
data: { toolName: string; config: ToolConfig };
|
data: { toolName: string };
|
||||||
}> {
|
}[] {
|
||||||
const toolPreparationList: Array<{
|
const toolPreparationList: {
|
||||||
function: (data: { toolName: string; config: ToolConfig }) => void;
|
function: (data: { toolName: string }) => void | Promise<void>;
|
||||||
data: { toolName: string; config: ToolConfig };
|
data: { toolName: string };
|
||||||
}
|
}[] = [];
|
||||||
> = [];
|
|
||||||
|
|
||||||
for (const toolName in this.toolsClasses) {
|
Object
|
||||||
if (Object.prototype.hasOwnProperty.call(this.toolsClasses, toolName)) {
|
.entries(config)
|
||||||
const toolClass = this.toolsClasses[toolName];
|
.forEach(([toolName, settings]) => {
|
||||||
const toolConfig = this.toolsSettings[toolName][this.USER_SETTINGS.CONFIG];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If Tool hasn't a prepare method,
|
|
||||||
* still push it to tool preparation list to save tools order in Toolbox.
|
|
||||||
* As Tool's prepare method might be async, _.sequence util helps to save the order.
|
|
||||||
*/
|
|
||||||
toolPreparationList.push({
|
toolPreparationList.push({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
function: _.isFunction(toolClass.prepare) ? toolClass.prepare : (): void => { },
|
function: _.isFunction(settings.class.prepare) ? settings.class.prepare : (): void => {},
|
||||||
data: {
|
data: {
|
||||||
toolName,
|
toolName,
|
||||||
config: toolConfig,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return toolPreparationList;
|
return toolPreparationList;
|
||||||
}
|
}
|
||||||
|
@ -485,6 +333,30 @@ export default class Tools extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unify tools config
|
||||||
|
*/
|
||||||
|
private prepareConfig(): {[name: string]: ToolSettings} {
|
||||||
|
const config: {[name: string]: ToolSettings} = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save Tools settings to a map
|
||||||
|
*/
|
||||||
|
for (const toolName in this.config.tools) {
|
||||||
|
/**
|
||||||
|
* If Tool is an object not a Tool's class then
|
||||||
|
* save class and settings separately
|
||||||
|
*/
|
||||||
|
if (_.isObject(this.config.tools[toolName])) {
|
||||||
|
config[toolName] = this.config.tools[toolName] as ToolSettings;
|
||||||
|
} else {
|
||||||
|
config[toolName] = { class: this.config.tools[toolName] as ToolConstructable };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -702,7 +702,7 @@ export default class UI extends Module<UINodes> {
|
||||||
* - Block is an default-block (Text)
|
* - Block is an default-block (Text)
|
||||||
* - Block is empty
|
* - Block is empty
|
||||||
*/
|
*/
|
||||||
const isDefaultBlock = this.Editor.Tools.isDefault(this.Editor.BlockManager.currentBlock.tool);
|
const isDefaultBlock = this.Editor.BlockManager.currentBlock.tool.isDefault;
|
||||||
|
|
||||||
if (isDefaultBlock) {
|
if (isDefaultBlock) {
|
||||||
stopPropagation();
|
stopPropagation();
|
||||||
|
|
235
src/components/tools/base.ts
Normal file
235
src/components/tools/base.ts
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
import { ToolType } from '../modules/tools';
|
||||||
|
import { Tool, ToolConstructable, ToolSettings } from '../../../types/tools';
|
||||||
|
import { API, SanitizerConfig } from '../../../types';
|
||||||
|
import * as _ from '../utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum of Tool options provided by user
|
||||||
|
*/
|
||||||
|
export enum UserSettings {
|
||||||
|
/**
|
||||||
|
* Shortcut for Tool
|
||||||
|
*/
|
||||||
|
Shortcut = 'shortcut',
|
||||||
|
/**
|
||||||
|
* Toolbox config for Tool
|
||||||
|
*/
|
||||||
|
Toolbox = 'toolbox',
|
||||||
|
/**
|
||||||
|
* Enabled Inline Tools for Block Tool
|
||||||
|
*/
|
||||||
|
EnabledInlineTools = 'inlineToolbar',
|
||||||
|
/**
|
||||||
|
* Tool configuration
|
||||||
|
*/
|
||||||
|
Config = 'config',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum of Tool options provided by Tool
|
||||||
|
*/
|
||||||
|
export enum CommonInternalSettings {
|
||||||
|
/**
|
||||||
|
* Shortcut for Tool
|
||||||
|
*/
|
||||||
|
Shortcut = 'shortcut',
|
||||||
|
/**
|
||||||
|
* Sanitize configuration for Tool
|
||||||
|
*/
|
||||||
|
SanitizeConfig = 'sanitize',
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum of Tool optoins provided by Block Tool
|
||||||
|
*/
|
||||||
|
export enum InternalBlockToolSettings {
|
||||||
|
/**
|
||||||
|
* Is linebreaks enabled for Tool
|
||||||
|
*/
|
||||||
|
IsEnabledLineBreaks = 'enableLineBreaks',
|
||||||
|
/**
|
||||||
|
* Tool Toolbox config
|
||||||
|
*/
|
||||||
|
Toolbox = 'toolbox',
|
||||||
|
/**
|
||||||
|
* Tool conversion config
|
||||||
|
*/
|
||||||
|
ConversionConfig = 'conversionConfig',
|
||||||
|
/**
|
||||||
|
* Is readonly mode supported for Tool
|
||||||
|
*/
|
||||||
|
IsReadOnlySupported = 'isReadOnlySupported',
|
||||||
|
/**
|
||||||
|
* Tool paste config
|
||||||
|
*/
|
||||||
|
PasteConfig = 'pasteConfig'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum of Tool options provided by Inline Tool
|
||||||
|
*/
|
||||||
|
export enum InternalInlineToolSettings {
|
||||||
|
/**
|
||||||
|
* Flag specifies Tool is inline
|
||||||
|
*/
|
||||||
|
IsInline = 'isInline',
|
||||||
|
/**
|
||||||
|
* Inline Tool title for toolbar
|
||||||
|
*/
|
||||||
|
Title = 'title', // for Inline Tools. Block Tools can pass title along with icon through the 'toolbox' static prop.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum of Tool options provided by Block Tune
|
||||||
|
*/
|
||||||
|
export enum InternalTuneSettings {
|
||||||
|
/**
|
||||||
|
* Flag specifies Tool is Block Tune
|
||||||
|
*/
|
||||||
|
IsTune = 'isTune',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToolOptions = Omit<ToolSettings, 'class'>
|
||||||
|
|
||||||
|
interface ConstructorOptions {
|
||||||
|
name: string;
|
||||||
|
constructable: ToolConstructable;
|
||||||
|
config: ToolOptions;
|
||||||
|
api: API;
|
||||||
|
isDefault: boolean;
|
||||||
|
isInternal: boolean;
|
||||||
|
defaultPlaceholder?: string | false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base abstract class for Tools
|
||||||
|
*/
|
||||||
|
export default abstract class BaseTool<Type extends Tool> {
|
||||||
|
/**
|
||||||
|
* Tool type: Block, Inline or Tune
|
||||||
|
*/
|
||||||
|
public type: ToolType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool name specified in EditorJS config
|
||||||
|
*/
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag show is current Tool internal (bundled with EditorJS core) or not
|
||||||
|
*/
|
||||||
|
public readonly isInternal: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag show is current Tool default or not
|
||||||
|
*/
|
||||||
|
public readonly isDefault: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EditorJS API for current Tool
|
||||||
|
*/
|
||||||
|
protected api: API;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current tool user configuration
|
||||||
|
*/
|
||||||
|
protected config: ToolOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool's constructable blueprint
|
||||||
|
*/
|
||||||
|
protected constructable: ToolConstructable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default placeholder specified in EditorJS user configuration
|
||||||
|
*/
|
||||||
|
protected defaultPlaceholder?: string | false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
*
|
||||||
|
* @param name - Tool name
|
||||||
|
* @param constructable - Tool constructable blueprint
|
||||||
|
* @param config - user specified Tool config
|
||||||
|
* @param api - EditorJS API module
|
||||||
|
* @param defaultTool - default Tool name
|
||||||
|
* @param isInternal - is current Tool internal
|
||||||
|
* @param defaultPlaceholder - default user specified placeholder
|
||||||
|
*/
|
||||||
|
constructor({
|
||||||
|
name,
|
||||||
|
constructable,
|
||||||
|
config,
|
||||||
|
api,
|
||||||
|
isDefault,
|
||||||
|
isInternal = false,
|
||||||
|
defaultPlaceholder,
|
||||||
|
}: ConstructorOptions) {
|
||||||
|
this.api = api;
|
||||||
|
this.name = name;
|
||||||
|
this.constructable = constructable;
|
||||||
|
this.config = config;
|
||||||
|
this.isDefault = isDefault;
|
||||||
|
this.isInternal = isInternal;
|
||||||
|
this.defaultPlaceholder = defaultPlaceholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Tool user configuration
|
||||||
|
*/
|
||||||
|
public get settings(): ToolOptions {
|
||||||
|
const config = this.config[UserSettings.Config] || {};
|
||||||
|
|
||||||
|
if (this.isDefault && !('placeholder' in config) && this.defaultPlaceholder) {
|
||||||
|
config.placeholder = this.defaultPlaceholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls Tool's reset method
|
||||||
|
*/
|
||||||
|
public reset(): void | Promise<void> {
|
||||||
|
if (_.isFunction(this.constructable.reset)) {
|
||||||
|
return this.constructable.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls Tool's prepare method
|
||||||
|
*/
|
||||||
|
public prepare(): void | Promise<void> {
|
||||||
|
if (_.isFunction(this.constructable.prepare)) {
|
||||||
|
return this.constructable.prepare({
|
||||||
|
toolName: this.name,
|
||||||
|
config: this.settings,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns shortcut for Tool (internal or specified by user)
|
||||||
|
*/
|
||||||
|
public get shortcut(): string | undefined {
|
||||||
|
const toolShortcut = this.constructable[CommonInternalSettings.Shortcut];
|
||||||
|
const userShortcut = this.settings[UserSettings.Shortcut];
|
||||||
|
|
||||||
|
return userShortcut || toolShortcut;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Tool's sanitizer configuration
|
||||||
|
*/
|
||||||
|
public get sanitizeConfig(): SanitizerConfig {
|
||||||
|
return this.constructable[CommonInternalSettings.SanitizeConfig];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs new Tool instance from constructable blueprint
|
||||||
|
*
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public abstract instance(...args: any[]): Type;
|
||||||
|
}
|
92
src/components/tools/block.ts
Normal file
92
src/components/tools/block.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import BaseTool, { InternalBlockToolSettings, UserSettings } from './base';
|
||||||
|
import { ToolType } from '../modules/tools';
|
||||||
|
import {
|
||||||
|
BlockAPI,
|
||||||
|
BlockTool as IBlockTool,
|
||||||
|
BlockToolData,
|
||||||
|
ConversionConfig,
|
||||||
|
PasteConfig,
|
||||||
|
ToolboxConfig
|
||||||
|
} from '../../../types';
|
||||||
|
import * as _ from '../utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to work with Block tools constructables
|
||||||
|
*/
|
||||||
|
export default class BlockTool extends BaseTool<IBlockTool> {
|
||||||
|
/**
|
||||||
|
* Tool type — Block
|
||||||
|
*/
|
||||||
|
public type = ToolType.Block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new Tool instance
|
||||||
|
*
|
||||||
|
* @param data - Tool data
|
||||||
|
* @param block - BlockAPI for current Block
|
||||||
|
* @param readOnly - True if Editor is in read-only mode
|
||||||
|
*/
|
||||||
|
public instance(data: BlockToolData, block: BlockAPI, readOnly: boolean): IBlockTool {
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
return new this.constructable({
|
||||||
|
data,
|
||||||
|
block,
|
||||||
|
readOnly,
|
||||||
|
api: this.api,
|
||||||
|
config: this.settings,
|
||||||
|
}) as IBlockTool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if read-only mode is supported by Tool
|
||||||
|
*/
|
||||||
|
public get isReadOnlySupported(): boolean {
|
||||||
|
return this.constructable[InternalBlockToolSettings.IsReadOnlySupported] === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if Tool supports linebreaks
|
||||||
|
*/
|
||||||
|
public get isLineBreaksEnabled(): boolean {
|
||||||
|
return this.constructable[InternalBlockToolSettings.IsEnabledLineBreaks];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Tool toolbox configuration (internal or user-specified)
|
||||||
|
*/
|
||||||
|
public get toolbox(): ToolboxConfig {
|
||||||
|
const toolToolboxSettings = this.constructable[InternalBlockToolSettings.Toolbox] as ToolboxConfig;
|
||||||
|
const userToolboxSettings = this.settings[UserSettings.Toolbox];
|
||||||
|
|
||||||
|
if (_.isEmpty(toolToolboxSettings)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((userToolboxSettings ?? toolToolboxSettings) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, toolToolboxSettings, userToolboxSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Tool conversion configuration
|
||||||
|
*/
|
||||||
|
public get conversionConfig(): ConversionConfig {
|
||||||
|
return this.constructable[InternalBlockToolSettings.ConversionConfig];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns enabled inline tools for Tool
|
||||||
|
*/
|
||||||
|
public get enabledInlineTools(): boolean | string[] {
|
||||||
|
return this.config[UserSettings.EnabledInlineTools];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Tool paste configuration
|
||||||
|
*/
|
||||||
|
public get pasteConfig(): PasteConfig {
|
||||||
|
return this.constructable[InternalBlockToolSettings.PasteConfig] || {};
|
||||||
|
}
|
||||||
|
}
|
84
src/components/tools/factory.ts
Normal file
84
src/components/tools/factory.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { ToolConstructable, ToolSettings } from '../../../types/tools';
|
||||||
|
import { InternalInlineToolSettings, InternalTuneSettings } from './base';
|
||||||
|
import InlineTool from './inline';
|
||||||
|
import BlockTune from './tune';
|
||||||
|
import BlockTool from './block';
|
||||||
|
import API from '../modules/api';
|
||||||
|
import { ToolType } from '../modules/tools';
|
||||||
|
import { EditorConfig } from '../../../types/configs';
|
||||||
|
|
||||||
|
type ToolConstructor = typeof InlineTool | typeof BlockTool | typeof BlockTune;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to construct classes to work with tools
|
||||||
|
*/
|
||||||
|
export default class ToolsFactory {
|
||||||
|
/**
|
||||||
|
* Tools configuration specified by user
|
||||||
|
*/
|
||||||
|
private config: {[name: string]: ToolSettings & { isInternal?: boolean }};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EditorJS API Module
|
||||||
|
*/
|
||||||
|
private api: API;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EditorJS configuration
|
||||||
|
*/
|
||||||
|
private editorConfig: EditorConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
*
|
||||||
|
* @param config - tools config
|
||||||
|
* @param editorConfig - EditorJS config
|
||||||
|
* @param api - EditorJS API module
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
config: {[name: string]: ToolSettings & { isInternal?: boolean }},
|
||||||
|
editorConfig: EditorConfig,
|
||||||
|
api: API
|
||||||
|
) {
|
||||||
|
this.api = api;
|
||||||
|
this.config = config;
|
||||||
|
this.editorConfig = editorConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Tool object based on it's type
|
||||||
|
*
|
||||||
|
* @param name - tool name
|
||||||
|
*/
|
||||||
|
public get(name: string): InlineTool | BlockTool | BlockTune {
|
||||||
|
const { class: constructable, isInternal = false, ...config } = this.config[name];
|
||||||
|
|
||||||
|
const [Constructor, type] = this.getConstructor(constructable);
|
||||||
|
|
||||||
|
return new Constructor({
|
||||||
|
name,
|
||||||
|
constructable,
|
||||||
|
config,
|
||||||
|
api: this.api.getMethodsForTool(name, type),
|
||||||
|
isDefault: name === this.editorConfig.defaultBlock,
|
||||||
|
defaultPlaceholder: this.editorConfig.placeholder,
|
||||||
|
isInternal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find appropriate Tool object constructor for Tool constructable
|
||||||
|
*
|
||||||
|
* @param constructable - Tools constructable
|
||||||
|
*/
|
||||||
|
private getConstructor(constructable: ToolConstructable): [ToolConstructor, ToolType] {
|
||||||
|
switch (true) {
|
||||||
|
case constructable[InternalInlineToolSettings.IsInline]:
|
||||||
|
return [InlineTool, ToolType.Inline];
|
||||||
|
case constructable[InternalTuneSettings.IsTune]:
|
||||||
|
return [BlockTune, ToolType.Tune];
|
||||||
|
default:
|
||||||
|
return [BlockTool, ToolType.Block];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/components/tools/inline.ts
Normal file
31
src/components/tools/inline.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import BaseTool, { InternalInlineToolSettings } from './base';
|
||||||
|
import { ToolType } from '../modules/tools';
|
||||||
|
import { InlineTool as IInlineTool } from '../../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InlineTool object to work with Inline Tools constructables
|
||||||
|
*/
|
||||||
|
export default class InlineTool extends BaseTool<IInlineTool> {
|
||||||
|
/**
|
||||||
|
* Tool type — Inline
|
||||||
|
*/
|
||||||
|
public type = ToolType.Inline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns title for Inline Tool if specified by user
|
||||||
|
*/
|
||||||
|
public get title(): string {
|
||||||
|
return this.constructable[InternalInlineToolSettings.Title];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs new InlineTool instance from constructable
|
||||||
|
*/
|
||||||
|
public instance(): IInlineTool {
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
return new this.constructable({
|
||||||
|
api: this.api,
|
||||||
|
config: this.settings,
|
||||||
|
}) as IInlineTool;
|
||||||
|
}
|
||||||
|
}
|
21
src/components/tools/tune.ts
Normal file
21
src/components/tools/tune.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import BaseTool from './base';
|
||||||
|
import { ToolType } from '../modules/tools';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub class for BlockTunes
|
||||||
|
*
|
||||||
|
* @todo Implement
|
||||||
|
*/
|
||||||
|
export default class BlockTune extends BaseTool<any> {
|
||||||
|
/**
|
||||||
|
* Tool type — Tune
|
||||||
|
*/
|
||||||
|
public type = ToolType.Tune;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo implement
|
||||||
|
*/
|
||||||
|
public instance(): any {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import $ from '../../dom';
|
import $ from '../../components/dom';
|
||||||
import { API, BlockTool, BlockToolData, BlockToolConstructorOptions } from '../../../../types';
|
import { API, BlockTool, BlockToolConstructorOptions, BlockToolData } from '../../../types';
|
||||||
|
|
||||||
export interface StubData extends BlockToolData {
|
export interface StubData extends BlockToolData {
|
||||||
title: string;
|
title: string;
|
2
types/tools/inline-tool.d.ts
vendored
2
types/tools/inline-tool.d.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
import {BaseTool, BaseToolConstructable} from './tool';
|
import {BaseTool, BaseToolConstructable} from './tool';
|
||||||
import {API, ToolConfig} from "../index";
|
import {API, ToolConfig} from '../index';
|
||||||
/**
|
/**
|
||||||
* Base structure for the Inline Toolbar Tool
|
* Base structure for the Inline Toolbar Tool
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue