mirror of
https://github.com/codex-team/editor.js
synced 2024-05-19 14:56:46 +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"]
|
||||
path = example/tools/simple-image
|
||||
url = https://github.com/editor-js/simple-image
|
||||
[submodule "src/components/tools/paragraph"]
|
||||
path = src/components/tools/paragraph
|
||||
[submodule "src/tools/paragraph"]
|
||||
path = src/tools/paragraph
|
||||
url = https://github.com/editor-js/paragraph
|
||||
[submodule "example/tools/marker"]
|
||||
path = example/tools/marker
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
BlockAPI as BlockAPIInterface,
|
||||
BlockTool,
|
||||
BlockTool as IBlockTool,
|
||||
BlockToolConstructable,
|
||||
BlockToolData,
|
||||
BlockTune,
|
||||
|
@ -16,12 +16,13 @@ import * as _ from '../utils';
|
|||
import ApiModules from '../modules/api';
|
||||
import BlockAPI from './api';
|
||||
import { ToolType } from '../modules/tools';
|
||||
import SelectionUtils from '../selection';
|
||||
import BlockTool from '../tools/block';
|
||||
|
||||
/** Import default tunes */
|
||||
import MoveUpTune from '../block-tunes/block-tune-move-up';
|
||||
import DeleteTune from '../block-tunes/block-tune-delete';
|
||||
import MoveDownTune from '../block-tunes/block-tune-move-down';
|
||||
import SelectionUtils from '../selection';
|
||||
|
||||
/**
|
||||
* Interface describes Block class constructor argument
|
||||
|
@ -38,14 +39,9 @@ interface BlockConstructorOptions {
|
|||
data: BlockToolData;
|
||||
|
||||
/**
|
||||
* Tool's class or constructor function
|
||||
* Tool object
|
||||
*/
|
||||
Tool: BlockToolConstructable;
|
||||
|
||||
/**
|
||||
* Tool settings from initial config
|
||||
*/
|
||||
settings: ToolSettings;
|
||||
tool: BlockTool;
|
||||
|
||||
/**
|
||||
* Editor's API methods
|
||||
|
@ -110,32 +106,27 @@ export default class Block {
|
|||
/**
|
||||
* Block Tool`s name
|
||||
*/
|
||||
public name: string;
|
||||
public readonly name: string;
|
||||
|
||||
/**
|
||||
* Instance of the Tool Block represents
|
||||
*/
|
||||
public tool: BlockTool;
|
||||
|
||||
/**
|
||||
* Class blueprint of the ool Block represents
|
||||
*/
|
||||
public class: BlockToolConstructable;
|
||||
public readonly tool: BlockTool;
|
||||
|
||||
/**
|
||||
* User Tool configuration
|
||||
*/
|
||||
public settings: ToolConfig;
|
||||
public readonly settings: ToolConfig;
|
||||
|
||||
/**
|
||||
* Wrapper for Block`s content
|
||||
*/
|
||||
public holder: HTMLDivElement;
|
||||
public readonly holder: HTMLDivElement;
|
||||
|
||||
/**
|
||||
* Tunes used by Tool
|
||||
*/
|
||||
public tunes: BlockTune[];
|
||||
public readonly tunes: BlockTune[];
|
||||
|
||||
/**
|
||||
* Tool's user configuration
|
||||
|
@ -149,6 +140,11 @@ export default class Block {
|
|||
*/
|
||||
private cachedInputs: HTMLElement[] = [];
|
||||
|
||||
/**
|
||||
* Tool class instance
|
||||
*/
|
||||
private readonly toolInstance: IBlockTool;
|
||||
|
||||
/**
|
||||
* Editor`s API module
|
||||
*/
|
||||
|
@ -209,27 +205,20 @@ export default class Block {
|
|||
constructor({
|
||||
name,
|
||||
data,
|
||||
Tool,
|
||||
settings,
|
||||
tool,
|
||||
api,
|
||||
readOnly,
|
||||
}: BlockConstructorOptions) {
|
||||
this.name = name;
|
||||
this.class = Tool;
|
||||
this.settings = settings;
|
||||
this.config = settings.config || {};
|
||||
this.settings = tool.settings;
|
||||
this.config = tool.settings.config || {};
|
||||
this.api = api;
|
||||
this.blockAPI = new BlockAPI(this);
|
||||
|
||||
this.mutationObserver = new MutationObserver(this.didMutated);
|
||||
|
||||
this.tool = new Tool({
|
||||
data,
|
||||
config: this.config,
|
||||
api: this.api.getMethodsForTool(name, ToolType.Block),
|
||||
block: this.blockAPI,
|
||||
readOnly,
|
||||
});
|
||||
this.tool = tool;
|
||||
this.toolInstance = tool.instance(data, this.blockAPI, readOnly);
|
||||
|
||||
this.holder = this.compose();
|
||||
/**
|
||||
|
@ -349,7 +338,7 @@ export default class Block {
|
|||
* @returns {object}
|
||||
*/
|
||||
public get sanitize(): SanitizerConfig {
|
||||
return this.tool.sanitize;
|
||||
return this.tool.sanitizeConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -359,7 +348,7 @@ export default class Block {
|
|||
* @returns {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
|
||||
*/
|
||||
if (this.tool[methodName] && this.tool[methodName] instanceof Function) {
|
||||
if (this.toolInstance[methodName] && this.toolInstance[methodName] instanceof Function) {
|
||||
if (methodName === BlockToolAPI.APPEND_CALLBACK) {
|
||||
_.log(
|
||||
'`appendCallback` hook is deprecated and will be removed in the next major release. ' +
|
||||
|
@ -513,7 +502,7 @@ export default class Block {
|
|||
|
||||
try {
|
||||
// eslint-disable-next-line no-useless-call
|
||||
this.tool[methodName].call(this.tool, params);
|
||||
this.toolInstance[methodName].call(this.toolInstance, params);
|
||||
} catch (e) {
|
||||
_.log(`Error during '${methodName}' call: ${e.message}`, 'error');
|
||||
}
|
||||
|
@ -526,7 +515,7 @@ export default class Block {
|
|||
* @param {BlockToolData} data - data to merge
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
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
|
||||
|
@ -572,8 +561,8 @@ export default class Block {
|
|||
public async validate(data: BlockToolData): Promise<boolean> {
|
||||
let isValid = true;
|
||||
|
||||
if (this.tool.validate instanceof Function) {
|
||||
isValid = await this.tool.validate(data);
|
||||
if (this.toolInstance.validate instanceof Function) {
|
||||
isValid = await this.toolInstance.validate(data);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
|
@ -672,6 +661,24 @@ export default class Block {
|
|||
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
|
||||
*
|
||||
|
@ -680,7 +687,7 @@ export default class Block {
|
|||
private compose(): HTMLDivElement {
|
||||
const wrapper = $.make('div', Block.CSS.wrapper) as HTMLDivElement,
|
||||
contentNode = $.make('div', Block.CSS.content),
|
||||
pluginsContent = this.tool.render();
|
||||
pluginsContent = this.toolInstance.render();
|
||||
|
||||
contentNode.appendChild(pluginsContent);
|
||||
wrapper.appendChild(contentNode);
|
||||
|
|
|
@ -125,7 +125,7 @@ export default class BlockEvents extends Module {
|
|||
return;
|
||||
}
|
||||
|
||||
const canOpenToolbox = Tools.isDefault(currentBlock.tool) && currentBlock.isEmpty;
|
||||
const canOpenToolbox = currentBlock.tool.isDefault && currentBlock.isEmpty;
|
||||
const conversionToolbarOpened = !currentBlock.isEmpty && ConversionToolbar.opened;
|
||||
const inlineToolbarOpened = !currentBlock.isEmpty && !SelectionUtils.isCollapsed && InlineToolbar.opened;
|
||||
|
||||
|
@ -206,15 +206,14 @@ export default class BlockEvents extends Module {
|
|||
* @param {KeyboardEvent} event - keydown
|
||||
*/
|
||||
private enter(event: KeyboardEvent): void {
|
||||
const { BlockManager, Tools, UI } = this.Editor;
|
||||
const { BlockManager, UI } = this.Editor;
|
||||
const currentBlock = BlockManager.currentBlock;
|
||||
const tool = Tools.available[currentBlock.name];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
if (tool && tool[Tools.INTERNAL_SETTINGS.IS_ENABLED_LINE_BREAKS]) {
|
||||
if (currentBlock.tool.isLineBreaksEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -253,7 +252,7 @@ export default class BlockEvents extends Module {
|
|||
/**
|
||||
* If new Block is empty
|
||||
*/
|
||||
if (this.Editor.Tools.isDefault(newCurrent.tool) && newCurrent.isEmpty) {
|
||||
if (newCurrent.tool.isDefault && newCurrent.isEmpty) {
|
||||
/**
|
||||
* Show Toolbar
|
||||
*/
|
||||
|
@ -276,7 +275,7 @@ export default class BlockEvents extends Module {
|
|||
private backspace(event: KeyboardEvent): void {
|
||||
const { BlockManager, BlockSelection, Caret } = this.Editor;
|
||||
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
|
||||
|
@ -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
|
||||
*/
|
||||
if (tool && tool[this.Editor.Tools.INTERNAL_SETTINGS.IS_ENABLED_LINE_BREAKS] && !Caret.isAtStart) {
|
||||
if (tool.isLineBreaksEnabled && !Caret.isAtStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import $ from '../dom';
|
|||
import * as _ from '../utils';
|
||||
import Blocks from '../blocks';
|
||||
import { BlockToolConstructable, BlockToolData, PasteEvent } from '../../../types';
|
||||
import BlockTool from '../tools/block';
|
||||
|
||||
/**
|
||||
* @typedef {BlockManager} BlockManager
|
||||
|
@ -219,15 +220,13 @@ export default class BlockManager extends Module {
|
|||
*
|
||||
* @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 settings = this.Editor.Tools.getToolSettings(tool);
|
||||
const Tool = this.Editor.Tools.available[tool] as BlockToolConstructable;
|
||||
const tool = this.Editor.Tools.blockTools.get(name);
|
||||
const block = new Block({
|
||||
name: tool,
|
||||
name,
|
||||
data,
|
||||
Tool,
|
||||
settings,
|
||||
tool,
|
||||
api: this.Editor.API,
|
||||
readOnly,
|
||||
});
|
||||
|
@ -703,9 +702,7 @@ export default class BlockManager extends Module {
|
|||
*/
|
||||
public async destroy(): Promise<void> {
|
||||
await Promise.all(this.blocks.map((block) => {
|
||||
if (_.isFunction(block.tool.destroy)) {
|
||||
return block.tool.destroy();
|
||||
}
|
||||
return block.destroy();
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -333,7 +333,7 @@ export default class Caret extends Module {
|
|||
* If last block is empty and it is an defaultBlock, 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);
|
||||
} else {
|
||||
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
|
||||
* (https://github.com/codex-team/editor.js/issues/1414)
|
||||
*/
|
||||
if (Tools.isDefault(currentBlock.tool) || !isAtEnd) {
|
||||
if (currentBlock.tool.isDefault || !isAtEnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,14 +2,13 @@ import Module from '../__module';
|
|||
import $ from '../dom';
|
||||
import * as _ from '../utils';
|
||||
import {
|
||||
BlockTool,
|
||||
BlockToolConstructable,
|
||||
PasteConfig,
|
||||
BlockAPI,
|
||||
PasteEvent,
|
||||
PasteEventDetail
|
||||
} from '../../../types';
|
||||
import Block from '../block';
|
||||
import { SavedData } from '../../../types/data-formats';
|
||||
import BlockTool from '../tools/block';
|
||||
|
||||
/**
|
||||
* Tag substitute object.
|
||||
|
@ -18,9 +17,8 @@ interface TagSubstitute {
|
|||
/**
|
||||
* Name of related Tool
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
tool: string;
|
||||
tool: BlockTool;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,24 +27,18 @@ interface TagSubstitute {
|
|||
interface PatternSubstitute {
|
||||
/**
|
||||
* Pattern`s key
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
key: string;
|
||||
|
||||
/**
|
||||
* Pattern regexp
|
||||
*
|
||||
* @type {RegExp}
|
||||
*/
|
||||
pattern: RegExp;
|
||||
|
||||
/**
|
||||
* Name of related Tool
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
tool: string;
|
||||
tool: BlockTool;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -247,7 +239,7 @@ export default class Paste extends Module {
|
|||
return;
|
||||
}
|
||||
|
||||
const isCurrentBlockDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool);
|
||||
const isCurrentBlockDefault = BlockManager.currentBlock && BlockManager.currentBlock.tool.isDefault;
|
||||
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
|
||||
|
||||
dataToInsert.map(
|
||||
|
@ -279,23 +271,22 @@ export default class Paste extends Module {
|
|||
private processTools(): void {
|
||||
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
|
||||
*
|
||||
* @param tool - BlockTool object
|
||||
*/
|
||||
private processTool = ([name, tool]: [string, BlockToolConstructable]): void => {
|
||||
private processTool = (tool: BlockTool): void => {
|
||||
try {
|
||||
const toolInstance = new this.Editor.Tools.blockTools[name]({
|
||||
api: this.Editor.API.getMethodsForTool(name),
|
||||
config: {},
|
||||
data: {},
|
||||
readOnly: false,
|
||||
}) as BlockTool;
|
||||
const toolInstance = tool.instance({}, {} as BlockAPI, false);
|
||||
|
||||
if (tool.pasteConfig === false) {
|
||||
this.exceptionList.push(name);
|
||||
this.exceptionList.push(tool.name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -304,11 +295,9 @@ export default class Paste extends Module {
|
|||
return;
|
||||
}
|
||||
|
||||
const toolPasteConfig = tool.pasteConfig || {};
|
||||
|
||||
this.getTagsConfig(name, toolPasteConfig);
|
||||
this.getFilesConfig(name, toolPasteConfig);
|
||||
this.getPatternsConfig(name, toolPasteConfig);
|
||||
this.getTagsConfig(tool);
|
||||
this.getFilesConfig(tool);
|
||||
this.getPatternsConfig(tool);
|
||||
} catch (e) {
|
||||
_.log(
|
||||
`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
|
||||
*
|
||||
* @param {string} name - Tool name
|
||||
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
||||
* @param tool - BlockTool object
|
||||
*/
|
||||
private getTagsConfig(name: string, toolPasteConfig: PasteConfig): void {
|
||||
const tags = toolPasteConfig.tags || [];
|
||||
private getTagsConfig(tool: BlockTool): void {
|
||||
const tags = tool.pasteConfig.tags || [];
|
||||
|
||||
tags.forEach((tag) => {
|
||||
if (Object.prototype.hasOwnProperty.call(this.toolsTags, tag)) {
|
||||
_.log(
|
||||
`Paste handler for «${name}» Tool on «${tag}» tag is skipped ` +
|
||||
`because it is already used by «${this.toolsTags[tag].tool}» Tool.`,
|
||||
`Paste handler for «${tool.name}» Tool on «${tag}» tag is skipped ` +
|
||||
`because it is already used by «${this.toolsTags[tag].tool.name}» Tool.`,
|
||||
'warn'
|
||||
);
|
||||
|
||||
|
@ -339,21 +327,20 @@ export default class Paste extends Module {
|
|||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @param {string} name - Tool name
|
||||
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
||||
* @param tool - BlockTool object
|
||||
*/
|
||||
private getFilesConfig(name: string, toolPasteConfig: PasteConfig): void {
|
||||
const { files = {} } = toolPasteConfig;
|
||||
private getFilesConfig(tool: BlockTool): void {
|
||||
const { files = {} } = tool.pasteConfig;
|
||||
let { extensions, mimeTypes } = files;
|
||||
|
||||
if (!extensions && !mimeTypes) {
|
||||
|
@ -361,19 +348,19 @@ export default class Paste extends Module {
|
|||
}
|
||||
|
||||
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 = [];
|
||||
}
|
||||
|
||||
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 = [];
|
||||
}
|
||||
|
||||
if (mimeTypes) {
|
||||
mimeTypes = mimeTypes.filter((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;
|
||||
}
|
||||
|
@ -382,7 +369,7 @@ export default class Paste extends Module {
|
|||
});
|
||||
}
|
||||
|
||||
this.toolsFiles[name] = {
|
||||
this.toolsFiles[tool.name] = {
|
||||
extensions: extensions || [],
|
||||
mimeTypes: mimeTypes || [],
|
||||
};
|
||||
|
@ -391,15 +378,14 @@ export default class Paste extends Module {
|
|||
/**
|
||||
* Get RegExp patterns to substitute by Tool
|
||||
*
|
||||
* @param {string} name - Tool name
|
||||
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
||||
* @param tool - BlockTool object
|
||||
*/
|
||||
private getPatternsConfig(name: string, toolPasteConfig: PasteConfig): void {
|
||||
if (!toolPasteConfig.patterns || _.isEmpty(toolPasteConfig.patterns)) {
|
||||
private getPatternsConfig(tool: BlockTool): void {
|
||||
if (!tool.pasteConfig.patterns || _.isEmpty(tool.pasteConfig.patterns)) {
|
||||
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 */
|
||||
if (!(pattern instanceof RegExp)) {
|
||||
_.log(
|
||||
|
@ -411,7 +397,7 @@ export default class Paste extends Module {
|
|||
this.toolsPatterns.push({
|
||||
key,
|
||||
pattern,
|
||||
tool: name,
|
||||
tool,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -462,9 +448,9 @@ export default class Paste extends Module {
|
|||
* @param {FileList} items - pasted or dropped items
|
||||
*/
|
||||
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(
|
||||
Array
|
||||
|
@ -473,7 +459,7 @@ export default class Paste extends Module {
|
|||
);
|
||||
dataToInsert = dataToInsert.filter((data) => !!data);
|
||||
|
||||
const isCurrentBlockDefault = Tools.isDefault(BlockManager.currentBlock.tool);
|
||||
const isCurrentBlockDefault = BlockManager.currentBlock.tool.isDefault;
|
||||
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
|
||||
|
||||
dataToInsert.forEach(
|
||||
|
@ -530,7 +516,6 @@ export default class Paste extends Module {
|
|||
*/
|
||||
private processHTML(innerHTML: string): PasteData[] {
|
||||
const { Tools, Sanitizer } = this.Editor;
|
||||
const initialTool = this.config.defaultBlock;
|
||||
const wrapper = $.make('DIV');
|
||||
|
||||
wrapper.innerHTML = innerHTML;
|
||||
|
@ -539,7 +524,7 @@ export default class Paste extends Module {
|
|||
|
||||
return nodes
|
||||
.map((node) => {
|
||||
let content, tool = initialTool, isBlock = false;
|
||||
let content, tool = Tools.defaultTool, isBlock = false;
|
||||
|
||||
switch (node.nodeType) {
|
||||
/** If node is a document fragment, use temp wrapper to get innerHTML */
|
||||
|
@ -559,7 +544,7 @@ export default class Paste extends Module {
|
|||
break;
|
||||
}
|
||||
|
||||
const { tags } = Tools.blockTools[tool].pasteConfig as PasteConfig;
|
||||
const { tags } = tool.pasteConfig;
|
||||
|
||||
const toolTags = tags.reduce((result, tag) => {
|
||||
result[tag.toLowerCase()] = {};
|
||||
|
@ -577,7 +562,7 @@ export default class Paste extends Module {
|
|||
return {
|
||||
content,
|
||||
isBlock,
|
||||
tool,
|
||||
tool: tool.name,
|
||||
event,
|
||||
};
|
||||
})
|
||||
|
@ -627,7 +612,7 @@ export default class Paste extends Module {
|
|||
* @param {PasteData} dataToInsert - data of Block to inseret
|
||||
*/
|
||||
private async processSingleBlock(dataToInsert: PasteData): Promise<void> {
|
||||
const { Caret, BlockManager, Tools } = this.Editor;
|
||||
const { Caret, BlockManager } = this.Editor;
|
||||
const { currentBlock } = BlockManager;
|
||||
|
||||
/**
|
||||
|
@ -638,7 +623,7 @@ export default class Paste extends Module {
|
|||
dataToInsert.tool !== currentBlock.name ||
|
||||
!$.containsOnlyInlineElements(dataToInsert.content.innerHTML)
|
||||
) {
|
||||
this.insertBlock(dataToInsert, currentBlock && Tools.isDefault(currentBlock.tool) && currentBlock.isEmpty);
|
||||
this.insertBlock(dataToInsert, currentBlock?.tool.isDefault && currentBlock.isEmpty);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -655,17 +640,17 @@ export default class Paste extends Module {
|
|||
* @param {PasteData} dataToInsert - data of Block to insert
|
||||
*/
|
||||
private async processInlinePaste(dataToInsert: PasteData): Promise<void> {
|
||||
const { BlockManager, Caret, Sanitizer, Tools } = this.Editor;
|
||||
const { BlockManager, Caret, Sanitizer } = this.Editor;
|
||||
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) {
|
||||
const blockData = await this.processPattern(content.textContent);
|
||||
|
||||
if (blockData) {
|
||||
const needToReplaceCurrentBlock = BlockManager.currentBlock &&
|
||||
Tools.isDefault(BlockManager.currentBlock.tool) &&
|
||||
BlockManager.currentBlock.tool.isDefault &&
|
||||
BlockManager.currentBlock.isEmpty;
|
||||
|
||||
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 (BlockManager.currentBlock && BlockManager.currentBlock.currentInput) {
|
||||
const currentToolSanitizeConfig = Sanitizer.getInlineToolsConfig(BlockManager.currentBlock.name);
|
||||
const currentToolSanitizeConfig = Sanitizer.getInlineToolsConfig(BlockManager.currentBlock.tool);
|
||||
|
||||
document.execCommand(
|
||||
'insertHTML',
|
||||
|
@ -719,7 +704,7 @@ export default class Paste extends Module {
|
|||
|
||||
return {
|
||||
event,
|
||||
tool: pattern.tool,
|
||||
tool: pattern.tool.name,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -755,15 +740,15 @@ export default class Paste extends Module {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
private insertEditorJSData(blocks: Array<Pick<SavedData, 'data' | 'tool'>>): void {
|
||||
const { BlockManager, Caret, Sanitizer, Tools } = this.Editor;
|
||||
private insertEditorJSData(blocks: Pick<SavedData, 'data' | 'tool'>[]): void {
|
||||
const { BlockManager, Caret, Sanitizer } = this.Editor;
|
||||
const sanitizedBlocks = Sanitizer.sanitizeBlocks(blocks);
|
||||
|
||||
sanitizedBlocks.forEach(({ tool, data }, i) => {
|
||||
let needToReplaceCurrentBlock = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -792,8 +777,8 @@ export default class Paste extends Module {
|
|||
|
||||
const element = node as HTMLElement;
|
||||
|
||||
const { tool = '' } = this.toolsTags[element.tagName] || {};
|
||||
const toolTags = this.tagsByTool[tool] || [];
|
||||
const { tool } = this.toolsTags[element.tagName] || {};
|
||||
const toolTags = this.tagsByTool[tool?.name] || [];
|
||||
|
||||
const isSubstitutable = tags.includes(element.tagName);
|
||||
const isBlockElement = $.blockElements.includes(element.tagName.toLowerCase());
|
||||
|
|
|
@ -40,11 +40,13 @@ export default class ReadOnly extends Module {
|
|||
const { blockTools } = Tools;
|
||||
const toolsDontSupportReadOnly: string[] = [];
|
||||
|
||||
Object.entries(blockTools).forEach(([name, tool]) => {
|
||||
if (!Tools.isReadOnlySupported(tool)) {
|
||||
toolsDontSupportReadOnly.push(name);
|
||||
}
|
||||
});
|
||||
Array
|
||||
.from(blockTools.entries())
|
||||
.forEach(([name, tool]) => {
|
||||
if (!tool.isReadOnlySupported) {
|
||||
toolsDontSupportReadOnly.push(name);
|
||||
}
|
||||
});
|
||||
|
||||
this.toolsDontSupportReadOnly = toolsDontSupportReadOnly;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Module from '../__module';
|
||||
import * as _ from '../utils';
|
||||
import { BlockToolConstructable, OutputBlockData } from '../../../types';
|
||||
import { OutputBlockData } from '../../../types';
|
||||
import BlockTool from '../tools/block';
|
||||
|
||||
/**
|
||||
* Editor.js Renderer Module
|
||||
|
@ -66,7 +67,7 @@ export default class Renderer extends Module {
|
|||
const tool = item.type;
|
||||
const data = item.data;
|
||||
|
||||
if (tool in Tools.available) {
|
||||
if (Tools.available.has(tool)) {
|
||||
try {
|
||||
BlockManager.insert({
|
||||
tool,
|
||||
|
@ -86,11 +87,10 @@ export default class Renderer extends Module {
|
|||
title: tool,
|
||||
};
|
||||
|
||||
if (tool in Tools.unavailable) {
|
||||
const toolToolboxSettings = (Tools.unavailable[tool] as BlockToolConstructable).toolbox;
|
||||
const userToolboxSettings = Tools.getToolSettings(tool).toolbox;
|
||||
if (Tools.unavailable.has(tool)) {
|
||||
const toolboxSettings = (Tools.unavailable.get(tool) as BlockTool).toolbox;
|
||||
|
||||
stubData.title = toolToolboxSettings.title || (userToolboxSettings && userToolboxSettings.title) || stubData.title;
|
||||
stubData.title = toolboxSettings?.title || stubData.title;
|
||||
}
|
||||
|
||||
const stub = BlockManager.insert({
|
||||
|
|
|
@ -36,8 +36,10 @@ import * as _ from '../utils';
|
|||
*/
|
||||
|
||||
import HTMLJanitor from 'html-janitor';
|
||||
import { BlockToolData, InlineToolConstructable, SanitizerConfig } from '../../../types';
|
||||
import { BlockToolData, SanitizerConfig } from '../../../types';
|
||||
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
|
||||
*/
|
||||
public sanitizeBlocks(
|
||||
blocksData: Array<Pick<SavedData, 'data' | 'tool'>>
|
||||
): Array<Pick<SavedData, 'data' | 'tool'>> {
|
||||
blocksData: Pick<SavedData, 'data' | 'tool'>[]
|
||||
): Pick<SavedData, 'data' | 'tool'>[] {
|
||||
return blocksData.map((block) => {
|
||||
const toolConfig = this.composeToolConfig(block.tool);
|
||||
|
||||
|
@ -150,18 +152,17 @@ export default class Sanitizer extends Module {
|
|||
return this.configCache[toolName];
|
||||
}
|
||||
|
||||
const sanitizeGetter = this.Editor.Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG;
|
||||
const toolClass = this.Editor.Tools.available[toolName];
|
||||
const baseConfig = this.getInlineToolsConfig(toolName);
|
||||
const tool = this.Editor.Tools.available.get(toolName);
|
||||
const baseConfig = this.getInlineToolsConfig(tool as BlockTool);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
const toolRules = toolClass.sanitize;
|
||||
const toolRules = tool.sanitizeConfig;
|
||||
|
||||
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,
|
||||
* 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 toolsConfig = Tools.getToolSettings(name);
|
||||
const enableInlineTools = toolsConfig.inlineToolbar || [];
|
||||
const enableInlineTools = tool.enabledInlineTools || [];
|
||||
|
||||
let config = {} as SanitizerConfig;
|
||||
|
||||
|
@ -207,7 +207,7 @@ export default class Sanitizer extends Module {
|
|||
(enableInlineTools as string[]).map((inlineToolName) => {
|
||||
config = Object.assign(
|
||||
config,
|
||||
Tools.inline[inlineToolName][Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG]
|
||||
Tools.inlineTools.get(inlineToolName).sanitizeConfig
|
||||
) as SanitizerConfig;
|
||||
});
|
||||
}
|
||||
|
@ -233,9 +233,9 @@ export default class Sanitizer extends Module {
|
|||
|
||||
const config: SanitizerConfig = {} as SanitizerConfig;
|
||||
|
||||
Object.entries(Tools.inline)
|
||||
.forEach(([, inlineTool]: [string, InlineToolConstructable]) => {
|
||||
Object.assign(config, inlineTool[Tools.INTERNAL_SETTINGS.SANITIZE_CONFIG]);
|
||||
Object.entries(Tools.inlineTools)
|
||||
.forEach(([, inlineTool]: [string, InlineTool]) => {
|
||||
Object.assign(config, inlineTool.sanitizeConfig);
|
||||
});
|
||||
|
||||
this.inlineToolsConfigCache = config;
|
||||
|
@ -249,7 +249,7 @@ export default class Sanitizer extends Module {
|
|||
* @param {Array} array - [1, 2, {}, []]
|
||||
* @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));
|
||||
}
|
||||
|
||||
|
|
|
@ -229,8 +229,10 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
* Add Tool's settings
|
||||
*/
|
||||
private addToolSettings(): void {
|
||||
if (_.isFunction(this.Editor.BlockManager.currentBlock.tool.renderSettings)) {
|
||||
$.append(this.nodes.toolSettings, this.Editor.BlockManager.currentBlock.tool.renderSettings());
|
||||
const settingsElement = this.Editor.BlockManager.currentBlock.renderSettings();
|
||||
|
||||
if (settingsElement) {
|
||||
$.append(this.nodes.toolSettings, settingsElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -182,10 +182,9 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
|||
*
|
||||
* @type {BlockToolConstructable}
|
||||
*/
|
||||
const currentBlockClass = this.Editor.BlockManager.currentBlock.class;
|
||||
const currentBlockTool = this.Editor.BlockManager.currentBlock.tool;
|
||||
const currentBlockName = this.Editor.BlockManager.currentBlock.name;
|
||||
const savedBlock = await this.Editor.BlockManager.currentBlock.save() as SavedData;
|
||||
const { INTERNAL_SETTINGS } = this.Editor.Tools;
|
||||
const blockData = savedBlock.data;
|
||||
|
||||
/**
|
||||
|
@ -201,7 +200,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
|||
*
|
||||
* @type {BlockToolConstructable}
|
||||
*/
|
||||
const replacingTool = this.Editor.Tools.toolsClasses[replacingToolName] as BlockToolConstructable;
|
||||
const replacingTool = this.Editor.Tools.blockTools.get(replacingToolName);
|
||||
|
||||
/**
|
||||
* Export property can be:
|
||||
|
@ -211,7 +210,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
|||
* In both cases returning value must be a string
|
||||
*/
|
||||
let exportData = '';
|
||||
const exportProp = currentBlockClass[INTERNAL_SETTINGS.CONVERSION_CONFIG].export;
|
||||
const exportProp = currentBlockTool.conversionConfig.export;
|
||||
|
||||
if (_.isFunction(exportProp)) {
|
||||
exportData = exportProp(blockData);
|
||||
|
@ -229,7 +228,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
|||
*/
|
||||
const cleaned: string = this.Editor.Sanitizer.clean(
|
||||
exportData,
|
||||
replacingTool.sanitize
|
||||
replacingTool.sanitizeConfig
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -238,7 +237,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
|||
* string — the name of data field to import
|
||||
*/
|
||||
let newBlockData = {};
|
||||
const importProp = replacingTool[INTERNAL_SETTINGS.CONVERSION_CONFIG].import;
|
||||
const importProp = replacingTool.conversionConfig.import;
|
||||
|
||||
if (_.isFunction(importProp)) {
|
||||
newBlockData = importProp(cleaned);
|
||||
|
@ -272,37 +271,28 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
|||
private addTools(): void {
|
||||
const tools = this.Editor.Tools.blockTools;
|
||||
|
||||
for (const toolName in tools) {
|
||||
if (!Object.prototype.hasOwnProperty.call(tools, toolName)) {
|
||||
continue;
|
||||
}
|
||||
Array
|
||||
.from(tools.entries())
|
||||
.forEach(([name, tool]) => {
|
||||
const toolboxSettings = tool.toolbox;
|
||||
const conversionConfig = tool.conversionConfig;
|
||||
|
||||
const internalSettings = this.Editor.Tools.INTERNAL_SETTINGS;
|
||||
const toolClass = tools[toolName] as BlockToolConstructable;
|
||||
const toolToolboxSettings = toolClass[internalSettings.TOOLBOX];
|
||||
const conversionConfig = toolClass[internalSettings.CONVERSION_CONFIG];
|
||||
/**
|
||||
* Skip tools that don't pass 'toolbox' property
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
this.addTool(name, toolboxSettings.icon, toolboxSettings.title);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,12 +2,15 @@ import Module from '../../__module';
|
|||
import $ from '../../dom';
|
||||
import SelectionUtils from '../../selection';
|
||||
import * as _ from '../../utils';
|
||||
import { InlineTool, InlineToolConstructable, ToolConstructable, ToolSettings } from '../../../../types';
|
||||
import { InlineTool as IInlineTool } from '../../../../types';
|
||||
import Flipper from '../../flipper';
|
||||
import I18n from '../../i18n';
|
||||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||
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
|
||||
|
@ -66,9 +69,11 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
private readonly toolbarVerticalMargin: number = 5;
|
||||
|
||||
/**
|
||||
* TODO: Get rid of this
|
||||
*
|
||||
* Currently visible tools instances
|
||||
*/
|
||||
private toolsInstances: Map<string, InlineTool>;
|
||||
private toolsInstances: Map<string, IInlineTool>;
|
||||
|
||||
/**
|
||||
* Buttons List
|
||||
|
@ -89,38 +94,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
@ -310,16 +283,14 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
private getInlineToolbarSettings(toolName): string[] | boolean {
|
||||
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
|
||||
|
||||
private getInlineToolbarSettings(tool: BlockTool): string[] | boolean {
|
||||
/**
|
||||
* 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.
|
||||
|
@ -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 (commonInlineToolbarSettings === true) {
|
||||
const defaultToolsOrder: string[] = Object.entries(this.Editor.Tools.available)
|
||||
.filter(([name, tool]) => {
|
||||
return tool[this.Editor.Tools.INTERNAL_SETTINGS.IS_INLINE];
|
||||
})
|
||||
.map(([name, tool]) => {
|
||||
return name;
|
||||
});
|
||||
|
||||
return defaultToolsOrder;
|
||||
return Array.from(this.Editor.Tools.inlineTools.keys());
|
||||
}
|
||||
|
||||
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).
|
||||
*/
|
||||
const inlineToolbarSettings = this.getInlineToolbarSettings(currentBlock.name);
|
||||
const inlineToolbarSettings = this.getInlineToolbarSettings(currentBlock.tool);
|
||||
|
||||
return inlineToolbarSettings !== false;
|
||||
}
|
||||
|
@ -548,13 +511,14 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
* Changes Conversion Dropdown content for current block's Tool
|
||||
*/
|
||||
private setConversionTogglerContent(): void {
|
||||
const { BlockManager, Tools } = this.Editor;
|
||||
const toolName = BlockManager.currentBlock.name;
|
||||
const { BlockManager } = this.Editor;
|
||||
const { currentBlock } = BlockManager;
|
||||
const toolName = currentBlock.name;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
this.nodes.conversionToggler.hidden = !exportRuleDefined;
|
||||
|
@ -563,14 +527,10 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
/**
|
||||
* Get icon or title for dropdown
|
||||
*/
|
||||
const toolSettings = Tools.getToolSettings(toolName);
|
||||
const toolboxSettings = Tools.available[toolName][Tools.INTERNAL_SETTINGS.TOOLBOX] || {};
|
||||
const userToolboxSettings = toolSettings.toolbox || {};
|
||||
const toolboxSettings = currentBlock.tool.toolbox || {};
|
||||
|
||||
this.nodes.conversionTogglerContent.innerHTML =
|
||||
userToolboxSettings.icon ||
|
||||
toolboxSettings.icon ||
|
||||
userToolboxSettings.title ||
|
||||
toolboxSettings.title ||
|
||||
_.capitalize(toolName);
|
||||
}
|
||||
|
@ -610,14 +570,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
* For this moment, inlineToolbarOrder could not be 'false'
|
||||
* 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) => {
|
||||
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
|
||||
const tool = this.Editor.Tools.constructInline(this.Editor.Tools.inline[toolName], toolName, toolSettings);
|
||||
const tool = this.Editor.Tools.inlineTools.get(toolName);
|
||||
|
||||
this.addTool(toolName, tool);
|
||||
tool.checkState(SelectionUtils.get());
|
||||
this.addTool(tool);
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -629,43 +587,42 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
/**
|
||||
* Add tool button and activate clicks
|
||||
*
|
||||
* @param {string} toolName - name of Tool to add
|
||||
* @param {InlineTool} tool - Tool class instance
|
||||
* @param {InlineTool} tool - InlineTool object
|
||||
*/
|
||||
private addTool(toolName: string, tool: InlineTool): void {
|
||||
private addTool(tool: InlineTool): void {
|
||||
const {
|
||||
Tools,
|
||||
Tooltip,
|
||||
} = this.Editor;
|
||||
|
||||
const button = tool.render();
|
||||
const instance = tool.instance();
|
||||
const button = instance.render();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
button.dataset.tool = toolName;
|
||||
button.dataset.tool = tool.name;
|
||||
this.nodes.buttons.appendChild(button);
|
||||
this.toolsInstances.set(toolName, tool);
|
||||
this.toolsInstances.set(tool.name, instance);
|
||||
|
||||
if (_.isFunction(tool.renderActions)) {
|
||||
const actions = tool.renderActions();
|
||||
if (_.isFunction(instance.renderActions)) {
|
||||
const actions = instance.renderActions();
|
||||
|
||||
this.nodes.actions.appendChild(actions);
|
||||
}
|
||||
|
||||
this.listeners.on(button, 'click', (event) => {
|
||||
this.toolClicked(tool);
|
||||
this.toolClicked(instance);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
const shortcut = this.getToolShortcut(toolName);
|
||||
const shortcut = this.getToolShortcut(tool.name);
|
||||
|
||||
if (shortcut) {
|
||||
try {
|
||||
this.enableShortcuts(tool, shortcut);
|
||||
this.enableShortcuts(instance, shortcut);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
|
@ -675,7 +632,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
const tooltipContent = $.make('div');
|
||||
const toolTitle = I18n.t(
|
||||
I18nInternalNS.toolNames,
|
||||
Tools.toolsClasses[toolName][Tools.INTERNAL_SETTINGS.TITLE] || _.capitalize(toolName)
|
||||
tool.title || _.capitalize(tool.name)
|
||||
);
|
||||
|
||||
tooltipContent.appendChild($.text(toolTitle));
|
||||
|
@ -690,6 +647,8 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
placement: 'top',
|
||||
hidingDelay: 100,
|
||||
});
|
||||
|
||||
instance.checkState(SelectionUtils.get());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -704,21 +663,20 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
* Enable shortcuts
|
||||
* Ignore tool that doesn't have shortcut or empty string
|
||||
*/
|
||||
const toolSettings = Tools.getToolSettings(toolName);
|
||||
const tool = this.toolsInstances.get(toolName);
|
||||
const tool = Tools.inlineTools.get(toolName);
|
||||
|
||||
/**
|
||||
* 1) For internal tools, check public getter 'shortcut'
|
||||
* 2) For external tools, check tool's settings
|
||||
* 3) If shortcut is not set in settings, check Tool's public property
|
||||
*/
|
||||
if (Object.keys(this.internalTools).includes(toolName)) {
|
||||
return this.inlineTools[toolName][Tools.INTERNAL_SETTINGS.SHORTCUT];
|
||||
} else if (toolSettings && toolSettings[Tools.USER_SETTINGS.SHORTCUT]) {
|
||||
return toolSettings[Tools.USER_SETTINGS.SHORTCUT];
|
||||
} else if (tool.shortcut) {
|
||||
return tool.shortcut;
|
||||
const internalTools = Tools.getInternal(ToolType.Inline);
|
||||
|
||||
if (Array.from(internalTools.keys()).includes(toolName)) {
|
||||
return this.inlineTools[toolName][CommonInternalSettings.Shortcut];
|
||||
}
|
||||
|
||||
return tool.shortcut;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -727,7 +685,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
* @param {InlineTool} tool - Tool instance
|
||||
* @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({
|
||||
name: shortcut,
|
||||
handler: (event) => {
|
||||
|
@ -747,9 +705,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
*/
|
||||
// if (SelectionUtils.isCollapsed) return;
|
||||
|
||||
const toolSettings = this.Editor.Tools.getToolSettings(currentBlock.name);
|
||||
|
||||
if (!toolSettings || !toolSettings[this.Editor.Tools.USER_SETTINGS.ENABLED_INLINE_TOOLS]) {
|
||||
if (!currentBlock.tool.enabledInlineTools) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -765,7 +721,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
*
|
||||
* @param {InlineTool} tool - Tool's instance
|
||||
*/
|
||||
private toolClicked(tool: InlineTool): void {
|
||||
private toolClicked(tool: IInlineTool): void {
|
||||
const range = SelectionUtils.range;
|
||||
|
||||
tool.surround(range);
|
||||
|
@ -785,16 +741,14 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
* Get inline tools tools
|
||||
* Tools that has isInline is true
|
||||
*/
|
||||
private get inlineTools(): { [name: string]: InlineTool } {
|
||||
private get inlineTools(): { [name: string]: IInlineTool } {
|
||||
const result = {};
|
||||
|
||||
for (const tool in this.Editor.Tools.inline) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.Editor.Tools.inline, tool)) {
|
||||
const toolSettings = this.Editor.Tools.getToolSettings(tool);
|
||||
|
||||
result[tool] = this.Editor.Tools.constructInline(this.Editor.Tools.inline[tool], tool, toolSettings);
|
||||
}
|
||||
}
|
||||
Array
|
||||
.from(this.Editor.Tools.inlineTools.entries())
|
||||
.forEach(([name, tool]) => {
|
||||
result[name] = tool.instance();
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import Module from '../../__module';
|
||||
import $ from '../../dom';
|
||||
import * as _ from '../../utils';
|
||||
import { BlockToolConstructable, ToolConstructable } from '../../../../types';
|
||||
import { BlockToolConstructable } from '../../../../types';
|
||||
import Flipper from '../../flipper';
|
||||
import { BlockToolAPI } from '../../block';
|
||||
import I18n from '../../i18n';
|
||||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||
import Shortcuts from '../../utils/shortcuts';
|
||||
import BlockTool from '../../tools/block';
|
||||
|
||||
/**
|
||||
* HTMLElements used for Toolbox UI
|
||||
|
@ -116,9 +117,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
* @param {string} toolName - button to activate
|
||||
*/
|
||||
public toolButtonActivate(event: MouseEvent|KeyboardEvent, toolName: string): void {
|
||||
const tool = this.Editor.Tools.toolsClasses[toolName] as BlockToolConstructable;
|
||||
|
||||
this.insertNewBlock(tool, toolName);
|
||||
this.insertNewBlock(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,36 +161,30 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
* Iterates available tools and appends them to the Toolbox
|
||||
*/
|
||||
private addTools(): void {
|
||||
const tools = this.Editor.Tools.available;
|
||||
const tools = this.Editor.Tools.blockTools;
|
||||
|
||||
for (const toolName in tools) {
|
||||
if (Object.prototype.hasOwnProperty.call(tools, toolName)) {
|
||||
this.addTool(toolName, tools[toolName] as BlockToolConstructable);
|
||||
}
|
||||
}
|
||||
Array
|
||||
.from(tools.values())
|
||||
.forEach((tool) => this.addTool(tool));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append Tool to the Toolbox
|
||||
*
|
||||
* @param {string} toolName - tool name
|
||||
* @param {BlockToolConstructable} tool - tool class
|
||||
* @param {BlockToolConstructable} tool - BlockTool object
|
||||
*/
|
||||
private addTool(toolName: string, tool: BlockToolConstructable): void {
|
||||
const internalSettings = this.Editor.Tools.INTERNAL_SETTINGS;
|
||||
const userSettings = this.Editor.Tools.USER_SETTINGS;
|
||||
|
||||
const toolToolboxSettings = tool[internalSettings.TOOLBOX];
|
||||
private addTool(tool: BlockTool): void {
|
||||
const toolToolboxSettings = tool.toolbox;
|
||||
|
||||
/**
|
||||
* Skip tools that don't pass 'toolbox' property
|
||||
*/
|
||||
if (_.isEmpty(toolToolboxSettings)) {
|
||||
if (!toolToolboxSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -204,19 +197,10 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
// 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 ]);
|
||||
|
||||
button.dataset.tool = toolName;
|
||||
button.innerHTML = (userToolboxSettings && userToolboxSettings.icon) || toolToolboxSettings.icon;
|
||||
button.dataset.tool = tool.name;
|
||||
button.innerHTML = toolToolboxSettings.icon;
|
||||
|
||||
$.append(this.nodes.toolbox, button);
|
||||
|
||||
|
@ -227,61 +211,40 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
* Add click listener
|
||||
*/
|
||||
this.listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => {
|
||||
this.toolButtonActivate(event, toolName);
|
||||
this.toolButtonActivate(event, tool.name);
|
||||
});
|
||||
|
||||
/**
|
||||
* Add listeners to show/hide toolbox tooltip
|
||||
*/
|
||||
const tooltipContent = this.drawTooltip(toolName);
|
||||
const tooltipContent = this.drawTooltip(tool);
|
||||
|
||||
this.Editor.Tooltip.onHover(button, tooltipContent, {
|
||||
placement: 'bottom',
|
||||
hidingDelay: 200,
|
||||
});
|
||||
|
||||
const shortcut = this.getToolShortcut(toolName, tool);
|
||||
const shortcut = tool.shortcut;
|
||||
|
||||
if (shortcut) {
|
||||
this.enableShortcut(tool, toolName, shortcut);
|
||||
this.enableShortcut(tool.name, shortcut);
|
||||
}
|
||||
|
||||
/** Increment Tools count */
|
||||
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
|
||||
*
|
||||
* @param {string} toolName - toolbox tool name
|
||||
* @param tool - BlockTool object
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
private drawTooltip(toolName: string): HTMLElement {
|
||||
const tool = this.Editor.Tools.available[toolName];
|
||||
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
|
||||
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);
|
||||
private drawTooltip(tool: BlockTool): HTMLElement {
|
||||
const toolboxSettings = tool.toolbox || {};
|
||||
const name = I18n.t(I18nInternalNS.toolNames, toolboxSettings.title || tool.name);
|
||||
|
||||
let shortcut = this.getToolShortcut(toolName, tool);
|
||||
let shortcut = tool.shortcut;
|
||||
|
||||
const tooltip = $.make('div', this.CSS.buttonTooltip);
|
||||
const hint = document.createTextNode(_.capitalize(name));
|
||||
|
@ -302,16 +265,15 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
/**
|
||||
* Enable shortcut Block Tool implemented shortcut
|
||||
*
|
||||
* @param {BlockToolConstructable} tool - Tool class
|
||||
* @param {string} toolName - Tool name
|
||||
* @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({
|
||||
name: shortcut,
|
||||
handler: (event: KeyboardEvent) => {
|
||||
event.preventDefault();
|
||||
this.insertNewBlock(tool, toolName);
|
||||
this.insertNewBlock(toolName);
|
||||
},
|
||||
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
|
||||
*/
|
||||
private removeAllShortcuts(): void {
|
||||
const tools = this.Editor.Tools.available;
|
||||
const tools = this.Editor.Tools.blockTools;
|
||||
|
||||
for (const toolName in tools) {
|
||||
if (Object.prototype.hasOwnProperty.call(tools, toolName)) {
|
||||
const shortcut = this.getToolShortcut(toolName, tools[toolName]);
|
||||
Array
|
||||
.from(tools.values())
|
||||
.forEach((tool) => {
|
||||
const shortcut = tool.shortcut;
|
||||
|
||||
if (shortcut) {
|
||||
Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -351,10 +313,9 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
* Inserts new block
|
||||
* Can be called when button clicked on Toolbox or by ShortcutData
|
||||
*
|
||||
* @param {BlockToolConstructable} tool - Tool Class
|
||||
* @param {string} toolName - Tool name
|
||||
*/
|
||||
private insertNewBlock(tool: BlockToolConstructable, toolName: string): void {
|
||||
private insertNewBlock(toolName: string): void {
|
||||
const { BlockManager, Caret } = this.Editor;
|
||||
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 * as _ from '../utils';
|
||||
import {
|
||||
BlockToolConstructable,
|
||||
EditorConfig,
|
||||
InlineTool,
|
||||
InlineToolConstructable, Tool,
|
||||
ToolConfig,
|
||||
Tool,
|
||||
ToolConstructable,
|
||||
ToolSettings
|
||||
} from '../../../types';
|
||||
import BoldInlineTool from '../inline-tools/inline-tool-bold';
|
||||
import ItalicInlineTool from '../inline-tools/inline-tool-italic';
|
||||
import LinkInlineTool from '../inline-tools/inline-tool-link';
|
||||
import Stub from '../tools/stub';
|
||||
import { ModuleConfig } from '../../types-internal/module-config';
|
||||
import EventsDispatcher from '../utils/events';
|
||||
import Stub from '../../tools/stub';
|
||||
import ToolsFactory from '../tools/factory';
|
||||
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
|
||||
|
@ -23,6 +23,8 @@ import EventsDispatcher from '../utils/events';
|
|||
* Creates Instances from Plugins and binds external config to the instances
|
||||
*/
|
||||
|
||||
type ToolClass = BlockTool | InlineTool | BlockTune;
|
||||
|
||||
/**
|
||||
* Class properties:
|
||||
*
|
||||
|
@ -47,7 +49,7 @@ export default class Tools extends Module {
|
|||
*
|
||||
* @returns {object<Tool>}
|
||||
*/
|
||||
public get available(): { [name: string]: ToolConstructable } {
|
||||
public get available(): Map<string, ToolClass> {
|
||||
return this.toolsAvailable;
|
||||
}
|
||||
|
||||
|
@ -56,7 +58,7 @@ export default class Tools extends Module {
|
|||
*
|
||||
* @returns {Tool[]}
|
||||
*/
|
||||
public get unavailable(): { [name: string]: ToolConstructable } {
|
||||
public get unavailable(): Map<string, ToolClass> {
|
||||
return this.toolsUnavailable;
|
||||
}
|
||||
|
||||
|
@ -65,48 +67,40 @@ export default class Tools extends Module {
|
|||
*
|
||||
* @returns {object} - object of Inline Tool's classes
|
||||
*/
|
||||
public get inline(): { [name: string]: InlineToolConstructable } {
|
||||
public get inlineTools(): Map<string, InlineTool> {
|
||||
if (this._inlineTools) {
|
||||
return this._inlineTools;
|
||||
}
|
||||
|
||||
const tools = Object.entries(this.available).filter(([name, tool]) => {
|
||||
if (!tool[this.INTERNAL_SETTINGS.IS_INLINE]) {
|
||||
return false;
|
||||
}
|
||||
const tools = Array
|
||||
.from(this.available.entries())
|
||||
.filter(([name, tool]: [string, BaseTool<any>]) => {
|
||||
if (tool.type !== ToolType.Inline) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Some Tools validation
|
||||
*/
|
||||
const inlineToolRequiredMethods = ['render', 'surround', 'checkState'];
|
||||
const notImplementedMethods = inlineToolRequiredMethods.filter((method) => !tool.instance()[method]);
|
||||
|
||||
/**
|
||||
* Some Tools validation
|
||||
*/
|
||||
const inlineToolRequiredMethods = ['render', 'surround', 'checkState'];
|
||||
const notImplementedMethods = inlineToolRequiredMethods.filter((method) => !this.constructInline(tool, name)[method]);
|
||||
if (notImplementedMethods.length) {
|
||||
_.log(
|
||||
`Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`,
|
||||
'warn',
|
||||
notImplementedMethods
|
||||
);
|
||||
|
||||
if (notImplementedMethods.length) {
|
||||
_.log(
|
||||
`Incorrect Inline Tool: ${tool.name}. Some of required methods is not implemented %o`,
|
||||
'warn',
|
||||
notImplementedMethods
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
/**
|
||||
* collected inline tools with key of tool name
|
||||
*/
|
||||
const result = {};
|
||||
|
||||
tools.forEach(([name, tool]) => {
|
||||
result[name] = tool;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache prepared Tools
|
||||
*/
|
||||
this._inlineTools = result;
|
||||
this._inlineTools = new Map(tools) as Map<string, InlineTool>;
|
||||
|
||||
return this._inlineTools;
|
||||
}
|
||||
|
@ -114,79 +108,43 @@ export default class Tools extends Module {
|
|||
/**
|
||||
* Return editor block tools
|
||||
*/
|
||||
public get blockTools(): { [name: string]: BlockToolConstructable } {
|
||||
const tools = Object.entries(this.available).filter(([, tool]) => {
|
||||
return !tool[this.INTERNAL_SETTINGS.IS_INLINE];
|
||||
});
|
||||
public get blockTools(): Map<string, BlockTool> {
|
||||
if (this._blockTools) {
|
||||
return this._blockTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* collected block tools with key of tool name
|
||||
*/
|
||||
const result = {};
|
||||
const tools = Array
|
||||
.from(this.available.entries())
|
||||
.filter(([, tool]) => {
|
||||
return tool.type === ToolType.Block;
|
||||
});
|
||||
|
||||
tools.forEach(([name, tool]) => {
|
||||
result[name] = tool;
|
||||
});
|
||||
this._blockTools = new Map(tools) as Map<string, BlockTool>;
|
||||
|
||||
return result;
|
||||
return this._blockTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant for available Tools internal settings provided by Tool developer
|
||||
*
|
||||
* @returns {object}
|
||||
* Returns default Tool object
|
||||
*/
|
||||
public get INTERNAL_SETTINGS(): { [name: string]: string } {
|
||||
return {
|
||||
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',
|
||||
};
|
||||
public get defaultTool(): BlockTool {
|
||||
return this.blockTools.get(this.config.defaultBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant for available Tools settings provided by user
|
||||
*
|
||||
* return {object}
|
||||
* Tools objects factory
|
||||
*/
|
||||
public get USER_SETTINGS(): { [name: string]: string } {
|
||||
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 } = {};
|
||||
private factory: ToolsFactory;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private readonly toolsUnavailable: { [name: string]: ToolConstructable } = {};
|
||||
|
||||
/**
|
||||
* Tools settings in a map {name: settings, ...}
|
||||
*
|
||||
* @type {object}
|
||||
*/
|
||||
private readonly toolsSettings: { [name: string]: ToolSettings } = {};
|
||||
private readonly toolsUnavailable: Map<string, ToolClass> = new Map();
|
||||
|
||||
/**
|
||||
* Cache for the prepared inline tools
|
||||
|
@ -194,41 +152,30 @@ export default class Tools extends Module {
|
|||
* @type {null|object}
|
||||
* @private
|
||||
*/
|
||||
private _inlineTools: { [name: string]: ToolConstructable } = {};
|
||||
private _inlineTools: Map<string, InlineTool> = null;
|
||||
|
||||
/**
|
||||
* @class
|
||||
*
|
||||
* @param {EditorConfig} config - Editor's configuration
|
||||
* @param {EventsDispatcher} eventsDispatcher - Editor's event dispatcher
|
||||
* Cache for the prepared block tools
|
||||
*/
|
||||
constructor({ config, eventsDispatcher }: ModuleConfig) {
|
||||
super({
|
||||
config,
|
||||
eventsDispatcher,
|
||||
});
|
||||
private _blockTools: Map<string, BlockTool> = null;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Available tools list
|
||||
* {name: Class, ...}
|
||||
*
|
||||
* @type {object}
|
||||
*/
|
||||
this.toolsAvailable = {};
|
||||
|
||||
/**
|
||||
* Tools that rejected a prepare method
|
||||
* {name: Class, ... }
|
||||
*
|
||||
* @type {object}
|
||||
*/
|
||||
this.toolsUnavailable = {};
|
||||
|
||||
this._inlineTools = null;
|
||||
return new Map(tools);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -248,54 +195,14 @@ export default class Tools extends Module {
|
|||
throw Error('Can\'t start without tools');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
const config = this.prepareConfig();
|
||||
|
||||
/**
|
||||
* 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 };
|
||||
}
|
||||
}
|
||||
this.factory = new ToolsFactory(config, this.config, this.Editor.API);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -308,110 +215,42 @@ export default class Tools extends Module {
|
|||
* to see how it works {@link '../utils.ts#sequence'}
|
||||
*/
|
||||
return _.sequence(sequenceData, (data: { toolName: string }) => {
|
||||
this.success(data);
|
||||
this.toolPrepareMethodSuccess(data);
|
||||
}, (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
|
||||
* Includes Bold, Italic, Link and Paragraph
|
||||
*/
|
||||
public get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings } {
|
||||
public get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings & { isInternal?: boolean } } {
|
||||
return {
|
||||
bold: { class: BoldInlineTool },
|
||||
italic: { class: ItalicInlineTool },
|
||||
link: { class: LinkInlineTool },
|
||||
bold: {
|
||||
class: BoldInlineTool,
|
||||
isInternal: true,
|
||||
},
|
||||
italic: {
|
||||
class: ItalicInlineTool,
|
||||
isInternal: true,
|
||||
},
|
||||
link: {
|
||||
class: LinkInlineTool,
|
||||
isInternal: true,
|
||||
},
|
||||
paragraph: {
|
||||
class: Paragraph,
|
||||
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
|
||||
*/
|
||||
|
@ -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
|
||||
*
|
||||
* @returns {Array} list of functions that needs to be fired sequentially
|
||||
* @param config - tools config
|
||||
*/
|
||||
private getListOfPrepareFunctions(): Array<{
|
||||
function: (data: { toolName: string; config: ToolConfig }) => void;
|
||||
data: { toolName: string; config: ToolConfig };
|
||||
}> {
|
||||
const toolPreparationList: Array<{
|
||||
function: (data: { toolName: string; config: ToolConfig }) => void;
|
||||
data: { toolName: string; config: ToolConfig };
|
||||
}
|
||||
> = [];
|
||||
private getListOfPrepareFunctions(config: {[name: string]: ToolSettings}): {
|
||||
function: (data: { toolName: string }) => void | Promise<void>;
|
||||
data: { toolName: string };
|
||||
}[] {
|
||||
const toolPreparationList: {
|
||||
function: (data: { toolName: string }) => void | Promise<void>;
|
||||
data: { toolName: string };
|
||||
}[] = [];
|
||||
|
||||
for (const toolName in this.toolsClasses) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.toolsClasses, toolName)) {
|
||||
const toolClass = this.toolsClasses[toolName];
|
||||
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.
|
||||
*/
|
||||
Object
|
||||
.entries(config)
|
||||
.forEach(([toolName, settings]) => {
|
||||
toolPreparationList.push({
|
||||
// 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: {
|
||||
toolName,
|
||||
config: toolConfig,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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 empty
|
||||
*/
|
||||
const isDefaultBlock = this.Editor.Tools.isDefault(this.Editor.BlockManager.currentBlock.tool);
|
||||
const isDefaultBlock = this.Editor.BlockManager.currentBlock.tool.isDefault;
|
||||
|
||||
if (isDefaultBlock) {
|
||||
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 { API, BlockTool, BlockToolData, BlockToolConstructorOptions } from '../../../../types';
|
||||
import $ from '../../components/dom';
|
||||
import { API, BlockTool, BlockToolConstructorOptions, BlockToolData } from '../../../types';
|
||||
|
||||
export interface StubData extends BlockToolData {
|
||||
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 {API, ToolConfig} from "../index";
|
||||
import {API, ToolConfig} from '../index';
|
||||
/**
|
||||
* Base structure for the Inline Toolbar Tool
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue