diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts index 40937ee9b..d62226c85 100644 --- a/src/commands/config/config.ts +++ b/src/commands/config/config.ts @@ -1,3 +1,5 @@ +import { CustomCommand } from '../view/CommandAbstract'; + export interface CommandsConfig { /** * Style prefix @@ -6,10 +8,10 @@ export interface CommandsConfig { stylePrefix?: string; /** - * Default array of commands - * @default [] + * Default commands + * @default {} */ - defaults: any[]; + defaults: Record; /** * If true, stateful commands (with `run` and `stop` methods) can't be executed multiple times. @@ -21,7 +23,7 @@ export interface CommandsConfig { const config: CommandsConfig = { stylePrefix: 'com-', - defaults: [], + defaults: {}, strict: true, }; diff --git a/src/commands/index.js b/src/commands/index.js deleted file mode 100644 index 774bd14ac..000000000 --- a/src/commands/index.js +++ /dev/null @@ -1,448 +0,0 @@ -/** - * You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/commands/config/config.js) - * ```js - * const editor = grapesjs.init({ - * commands: { - * // options - * } - * }) - * ``` - * - * Once the editor is instantiated you can use its API and listen to its events. Before using these methods, you should get the module from the instance. - * - * ```js - * // Listen to events - * editor.on('run', () => { ... }); - * - * // Use the API - * const commands = editor.Commands; - * commands.add(...); - * ``` - * - ** ## Available Events - * * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview')) - * * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview')) - * * `run:{commandName}:before` - Triggered before the command is called - * * `stop:{commandName}:before` - Triggered before the command is called to stop - * * `abort:{commandName}` - Triggered when the command execution is aborted (`editor.on(`run:preview:before`, opts => opts.abort = 1);`) - * * `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback - * * `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback - * - * ## Methods - * * [add](#add) - * * [get](#get) - * * [getAll](#getall) - * * [extend](#extend) - * * [has](#has) - * * [run](#run) - * * [stop](#stop) - * * [isActive](#isactive) - * * [getActive](#getactive) - * - * @module Commands - */ - -import { isFunction, includes } from 'underscore'; -import CommandAbstract from './view/CommandAbstract'; -import defaults from './config/config'; -import { eventDrag } from '../dom_components/model/Component'; - -const commandsDef = [ - ['preview', 'Preview', 'preview'], - ['resize', 'Resize', 'resize'], - ['fullscreen', 'Fullscreen', 'fullscreen'], - ['copy', 'CopyComponent'], - ['paste', 'PasteComponent'], - ['canvas-move', 'CanvasMove'], - ['canvas-clear', 'CanvasClear'], - ['open-code', 'ExportTemplate', 'export-template'], - ['open-layers', 'OpenLayers', 'open-layers'], - ['open-styles', 'OpenStyleManager', 'open-sm'], - ['open-traits', 'OpenTraitManager', 'open-tm'], - ['open-blocks', 'OpenBlocks', 'open-blocks'], - ['open-assets', 'OpenAssets', 'open-assets'], - ['component-select', 'SelectComponent', 'select-comp'], - ['component-outline', 'SwitchVisibility', 'sw-visibility'], - ['component-offset', 'ShowOffset', 'show-offset'], - ['component-move', 'MoveComponent', 'move-comp'], - ['component-next', 'ComponentNext'], - ['component-prev', 'ComponentPrev'], - ['component-enter', 'ComponentEnter'], - ['component-exit', 'ComponentExit', 'select-parent'], - ['component-delete', 'ComponentDelete'], - ['component-style-clear', 'ComponentStyleClear'], - ['component-drag', 'ComponentDrag'], -]; - -export default () => { - let em; - let c = {}; - const commands = {}; - const defaultCommands = {}; - const active = {}; - - // Need it here as it would be used below - const add = function (id, obj) { - if (isFunction(obj)) obj = { run: obj }; - if (!obj.stop) obj.noStop = 1; - delete obj.initialize; - obj.id = id; - commands[id] = CommandAbstract.extend(obj); - return this; - }; - - return { - CommandAbstract, - - /** - * Name of the module - * @type {String} - * @private - */ - name: 'Commands', - - /** - * Initialize module. Automatically called with a new instance of the editor - * @param {Object} config Configurations - * @private - */ - init(config = {}) { - c = { - ...defaults, - ...config, - }; - em = c.em; - const ppfx = c.pStylePrefix; - if (ppfx) c.stylePrefix = ppfx + c.stylePrefix; - - // Load commands passed via configuration - Object.keys(c.defaults).forEach(k => { - const obj = c.defaults[k]; - if (obj.id) this.add(obj.id, obj); - }); - - defaultCommands['tlb-delete'] = { - run(ed) { - return ed.runCommand('core:component-delete'); - }, - }; - - defaultCommands['tlb-clone'] = { - run(ed) { - ed.runCommand('core:copy'); - ed.runCommand('core:paste', { action: 'clone-component' }); - }, - }; - - defaultCommands['tlb-move'] = { - run(ed, sender, opts = {}) { - let dragger; - const em = ed.getModel(); - const event = opts && opts.event; - const { target } = opts; - const sel = target || ed.getSelected(); - const selAll = target ? [target] : [...ed.getSelectedAll()]; - const nativeDrag = event && event.type == 'dragstart'; - const defComOptions = { preserveSelected: 1 }; - const modes = ['absolute', 'translate']; - - if (!sel || !sel.get('draggable')) { - return em.logWarning('The element is not draggable'); - } - - const mode = sel.get('dmode') || em.get('dmode'); - const hideTlb = () => em.stopDefault(defComOptions); - const altMode = includes(modes, mode); - selAll.forEach(sel => sel.trigger('disable')); - - // Without setTimeout the ghost image disappears - nativeDrag ? setTimeout(hideTlb, 0) : hideTlb(); - - const onStart = data => { - em.trigger(`${eventDrag}:start`, data); - }; - const onDrag = data => { - em.trigger(eventDrag, data); - }; - const onEnd = (e, opts, data) => { - selAll.forEach(sel => sel.set('status', 'selected')); - ed.select(selAll); - sel.emitUpdate(); - em.trigger(`${eventDrag}:end`, data); - - // Defer selectComponent in order to prevent canvas "freeze" #2692 - setTimeout(() => em.runDefault(defComOptions)); - - // Dirty patch to prevent parent selection on drop - (altMode || data.cancelled) && em.set('_cmpDrag', 1); - }; - - if (altMode) { - // TODO move grabbing func in editor/canvas from the Sorter - dragger = ed.runCommand('core:component-drag', { - guidesInfo: 1, - mode, - target: sel, - onStart, - onDrag, - onEnd, - event, - }); - } else { - if (nativeDrag) { - event.dataTransfer.setDragImage(sel.view.el, 0, 0); - //sel.set('status', 'freezed'); - } - - const cmdMove = ed.Commands.get('move-comp'); - cmdMove.onStart = onStart; - cmdMove.onDrag = onDrag; - cmdMove.onEndMoveFromModel = onEnd; - cmdMove.initSorterFromModels(selAll); - } - - selAll.forEach(sel => sel.set('status', 'freezed-selected')); - }, - }; - - // Core commands - defaultCommands['core:undo'] = e => e.UndoManager.undo(); - defaultCommands['core:redo'] = e => e.UndoManager.redo(); - commandsDef.forEach(item => { - const oldCmd = item[2]; - const cmd = require(`./view/${item[1]}`).default; - const cmdName = `core:${item[0]}`; - defaultCommands[cmdName] = cmd; - if (oldCmd) { - defaultCommands[oldCmd] = cmd; - // Propogate old commands (can be removed once we stop to call old commands) - ['run', 'stop'].forEach(name => { - em.on(`${name}:${oldCmd}`, (...args) => em.trigger(`${name}:${cmdName}`, ...args)); - }); - } - }); - - if (c.em) c.model = c.em.get('Canvas'); - - this.loadDefaultCommands(); - - return this; - }, - - /** - * Add new command to the collection - * @param {string} id Command's ID - * @param {Object|Function} command Object representing your command, - * By passing just a function it's intended as a stateless command - * (just like passing an object with only `run` method). - * @return {this} - * @example - * commands.add('myCommand', { - * run(editor, sender) { - * alert('Hello world!'); - * }, - * stop(editor, sender) { - * }, - * }); - * // As a function - * commands.add('myCommand2', editor => { ... }); - * */ - add, - - /** - * Get command by ID - * @param {string} id Command's ID - * @return {Object} Object representing the command - * @example - * var myCommand = commands.get('myCommand'); - * myCommand.run(); - * */ - get(id) { - let el = commands[id]; - - if (isFunction(el)) { - el = new el(c); - commands[id] = el; - } else if (!el) { - em.logWarning(`'${id}' command not found`); - } - - return el; - }, - - /** - * Extend the command. The command to extend should be defined as an object - * @param {string} id Command's ID - * @param {Object} Object with the new command functions - * @returns {this} - * @example - * commands.extend('old-command', { - * someInnerFunction() { - * // ... - * } - * }); - * */ - extend(id, cmd = {}) { - const command = this.get(id); - if (command) { - const cmdObj = { - ...command.constructor.prototype, - ...cmd, - }; - this.add(id, cmdObj); - // Extend also old name commands if exist - const oldCmd = commandsDef.filter(cmd => `core:${cmd[0]}` === id && cmd[2])[0]; - oldCmd && this.add(oldCmd[2], cmdObj); - } - return this; - }, - - /** - * Check if command exists - * @param {string} id Command's ID - * @return {Boolean} - * */ - has(id) { - return !!commands[id]; - }, - - /** - * Get an object containing all the commands - * @return {Object} - */ - getAll() { - return commands; - }, - - /** - * Execute the command - * @param {String} id Command ID - * @param {Object} [options={}] Options - * @return {*} The return is defined by the command - * @example - * commands.run('myCommand', { someOption: 1 }); - */ - run(id, options = {}) { - return this.runCommand(this.get(id), options); - }, - - /** - * Stop the command - * @param {String} id Command ID - * @param {Object} [options={}] Options - * @return {*} The return is defined by the command - * @example - * commands.stop('myCommand', { someOption: 1 }); - */ - stop(id, options = {}) { - return this.stopCommand(this.get(id), options); - }, - - /** - * Check if the command is active. You activate commands with `run` - * and disable them with `stop`. If the command was created without `stop` - * method it can't be registered as active - * @param {String} id Command id - * @return {Boolean} - * @example - * const cId = 'some-command'; - * commands.run(cId); - * commands.isActive(cId); - * // -> true - * commands.stop(cId); - * commands.isActive(cId); - * // -> false - */ - isActive(id) { - return this.getActive().hasOwnProperty(id); - }, - - /** - * Get all active commands - * @return {Object} - * @example - * console.log(commands.getActive()); - * // -> { someCommand: itsLastReturn, anotherOne: ... }; - */ - getActive() { - return active; - }, - - /** - * Load default commands - * @return {this} - * @private - * */ - loadDefaultCommands() { - for (var id in defaultCommands) { - this.add(id, defaultCommands[id]); - } - - return this; - }, - - /** - * Run command via its object - * @param {Object} command - * @param {Object} options - * @return {*} Result of the command - * @private - */ - runCommand(command, options = {}) { - let result; - - if (command && command.run) { - const id = command.id; - const editor = em.get('Editor'); - - if (!this.isActive(id) || options.force || !c.strict) { - result = editor && command.callRun(editor, options); - if (id && command.stop && !command.noStop && !options.abort) { - active[id] = result; - } - } - } - - return result; - }, - - /** - * Stop the command - * @param {Object} command - * @param {Object} options - * @return {*} Result of the command - * @private - */ - stopCommand(command, options = {}) { - let result; - - if (command && command.run) { - const id = command.id; - const editor = em.get('Editor'); - - if (this.isActive(id) || options.force || !c.strict) { - if (id) delete active[id]; - result = command.callStop(editor, options); - } - } - - return result; - }, - - /** - * Create anonymous Command instance - * @param {Object} command Command object - * @return {Command} - * @private - * */ - create(command) { - if (!command.stop) command.noStop = 1; - const cmd = CommandAbstract.extend(command); - return new cmd(c); - }, - - destroy() { - [em, c, commands, defaultCommands, active].forEach(i => (i = {})); - }, - }; -}; diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 000000000..e21fa0a98 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,439 @@ +/** + * You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/commands/config/config.js) + * ```js + * const editor = grapesjs.init({ + * commands: { + * // options + * } + * }) + * ``` + * + * Once the editor is instantiated you can use its API and listen to its events. Before using these methods, you should get the module from the instance. + * + * ```js + * // Listen to events + * editor.on('run', () => { ... }); + * + * // Use the API + * const commands = editor.Commands; + * commands.add(...); + * ``` + * + ** ## Available Events + * * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview')) + * * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview')) + * * `run:{commandName}:before` - Triggered before the command is called + * * `stop:{commandName}:before` - Triggered before the command is called to stop + * * `abort:{commandName}` - Triggered when the command execution is aborted (`editor.on(`run:preview:before`, opts => opts.abort = 1);`) + * * `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback + * * `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback + * + * ## Methods + * * [add](#add) + * * [get](#get) + * * [getAll](#getall) + * * [extend](#extend) + * * [has](#has) + * * [run](#run) + * * [stop](#stop) + * * [isActive](#isactive) + * * [getActive](#getactive) + * + * @module Commands + */ + +import { isFunction, includes } from 'underscore'; +import CommandAbstract, { Command, CommandOptions, CustomCommand } from './view/CommandAbstract'; +import defaults, { CommandsConfig } from './config/config'; +import { Module } from '../abstract'; +import { eventDrag } from '../dom_components/model/Component'; +import Editor from '../editor/model/Editor'; + +const commandsDef = [ + ['preview', 'Preview', 'preview'], + ['resize', 'Resize', 'resize'], + ['fullscreen', 'Fullscreen', 'fullscreen'], + ['copy', 'CopyComponent'], + ['paste', 'PasteComponent'], + ['canvas-move', 'CanvasMove'], + ['canvas-clear', 'CanvasClear'], + ['open-code', 'ExportTemplate', 'export-template'], + ['open-layers', 'OpenLayers', 'open-layers'], + ['open-styles', 'OpenStyleManager', 'open-sm'], + ['open-traits', 'OpenTraitManager', 'open-tm'], + ['open-blocks', 'OpenBlocks', 'open-blocks'], + ['open-assets', 'OpenAssets', 'open-assets'], + ['component-select', 'SelectComponent', 'select-comp'], + ['component-outline', 'SwitchVisibility', 'sw-visibility'], + ['component-offset', 'ShowOffset', 'show-offset'], + ['component-move', 'MoveComponent', 'move-comp'], + ['component-next', 'ComponentNext'], + ['component-prev', 'ComponentPrev'], + ['component-enter', 'ComponentEnter'], + ['component-exit', 'ComponentExit', 'select-parent'], + ['component-delete', 'ComponentDelete'], + ['component-style-clear', 'ComponentStyleClear'], + ['component-drag', 'ComponentDrag'], +]; + +export default class Commands extends Module { + CommandAbstract = CommandAbstract; + defaultCommands: Record = {}; + commands: Record = {}; + active: Record = {}; + + /** + * @private + */ + constructor(em: Editor) { + super(em, 'Commands', defaults); + const { config } = this; + const ppfx = config.pStylePrefix; + const { defaultCommands } = this; + + if (ppfx) { + config.stylePrefix = ppfx + config.stylePrefix; + } + + // Load commands passed via configuration + Object.keys(config.defaults).forEach(k => { + const obj = config.defaults[k]; + if (obj.id) this.add(obj.id, obj); + }); + + defaultCommands['tlb-delete'] = { + run(ed) { + return ed.runCommand('core:component-delete'); + }, + }; + + defaultCommands['tlb-clone'] = { + run(ed) { + ed.runCommand('core:copy'); + ed.runCommand('core:paste', { action: 'clone-component' }); + }, + }; + + defaultCommands['tlb-move'] = { + run(ed, sender, opts = {}) { + let dragger; + const em = ed.getModel(); + const event = opts && opts.event; + const { target } = opts; + const sel = target || ed.getSelected(); + const selAll = target ? [target] : [...ed.getSelectedAll()]; + const nativeDrag = event && event.type == 'dragstart'; + const defComOptions = { preserveSelected: 1 }; + const modes = ['absolute', 'translate']; + + if (!sel || !sel.get('draggable')) { + return em.logWarning('The element is not draggable'); + } + + const mode = sel.get('dmode') || em.get('dmode'); + const hideTlb = () => em.stopDefault(defComOptions); + const altMode = includes(modes, mode); + selAll.forEach(sel => sel.trigger('disable')); + + // Without setTimeout the ghost image disappears + nativeDrag ? setTimeout(hideTlb, 0) : hideTlb(); + + const onStart = (data: any) => { + em.trigger(`${eventDrag}:start`, data); + }; + const onDrag = (data: any) => { + em.trigger(eventDrag, data); + }; + const onEnd = (e: any, opts: any, data: any) => { + selAll.forEach(sel => sel.set('status', 'selected')); + ed.select(selAll); + sel.emitUpdate(); + em.trigger(`${eventDrag}:end`, data); + + // Defer selectComponent in order to prevent canvas "freeze" #2692 + setTimeout(() => em.runDefault(defComOptions)); + + // Dirty patch to prevent parent selection on drop + (altMode || data.cancelled) && em.set('_cmpDrag', 1); + }; + + if (altMode) { + // TODO move grabbing func in editor/canvas from the Sorter + dragger = ed.runCommand('core:component-drag', { + guidesInfo: 1, + mode, + target: sel, + onStart, + onDrag, + onEnd, + event, + }); + } else { + if (nativeDrag) { + event.dataTransfer.setDragImage(sel.view.el, 0, 0); + //sel.set('status', 'freezed'); + } + + const cmdMove = ed.Commands.get('move-comp'); + cmdMove.onStart = onStart; + cmdMove.onDrag = onDrag; + cmdMove.onEndMoveFromModel = onEnd; + cmdMove.initSorterFromModels(selAll); + } + + selAll.forEach(sel => sel.set('status', 'freezed-selected')); + }, + }; + + // Core commands + defaultCommands['core:undo'] = e => e.UndoManager.undo(); + defaultCommands['core:redo'] = e => e.UndoManager.redo(); + commandsDef.forEach(item => { + const oldCmd = item[2]; + const cmd = require(`./view/${item[1]}`).default; + const cmdName = `core:${item[0]}`; + defaultCommands[cmdName] = cmd; + if (oldCmd) { + defaultCommands[oldCmd] = cmd; + // Propogate old commands (can be removed once we stop to call old commands) + ['run', 'stop'].forEach(name => { + em.on(`${name}:${oldCmd}`, (...args) => em.trigger(`${name}:${cmdName}`, ...args)); + }); + } + }); + + // @ts-ignore TODO check where it's used + config.model = em.Canvas; + + for (const id in defaultCommands) { + this.add(id, defaultCommands[id]); + } + + return this; + } + + /** + * Add new command to the collection + * @param {string} id Command's ID + * @param {Object|Function} command Object representing your command, + * By passing just a function it's intended as a stateless command + * (just like passing an object with only `run` method). + * @return {this} + * @example + * commands.add('myCommand', { + * run(editor, sender) { + * alert('Hello world!'); + * }, + * stop(editor, sender) { + * }, + * }); + * // As a function + * commands.add('myCommand2', editor => { ... }); + * */ + add(id: string, command: Command) { + let result: CustomCommand = isFunction(command) ? { run: command } : command; + + if (!result.stop) { + result.noStop = true; + } + + delete result.initialize; + + result.id = id; + this.commands[id] = CommandAbstract.extend(result); + + return this; + } + + /** + * Get command by ID + * @param {string} id Command's ID + * @return {Object} Object representing the command + * @example + * var myCommand = commands.get('myCommand'); + * myCommand.run(); + * */ + get(id: string): CustomCommand | undefined { + let command: any = this.commands[id]; + + if (isFunction(command)) { + command = new command(this.config); + this.commands[id] = command; + } else if (!command) { + this.em.logWarning(`'${id}' command not found`); + } + + return command; + } + + /** + * Extend the command. The command to extend should be defined as an object + * @param {string} id Command's ID + * @param {Object} Object with the new command functions + * @returns {this} + * @example + * commands.extend('old-command', { + * someInnerFunction() { + * // ... + * } + * }); + * */ + extend(id: string, cmd: CustomCommand = {}) { + const command = this.get(id); + + if (command) { + const cmdObj = { + ...command.constructor.prototype, + ...cmd, + }; + this.add(id, cmdObj); + // Extend also old name commands if exist + const oldCmd = commandsDef.filter(cmd => `core:${cmd[0]}` === id && cmd[2])[0]; + oldCmd && this.add(oldCmd[2], cmdObj); + } + + return this; + } + + /** + * Check if command exists + * @param {string} id Command's ID + * @return {Boolean} + * */ + has(id: string) { + return !!this.commands[id]; + } + + /** + * Get an object containing all the commands + * @return {Object} + */ + getAll() { + return this.commands; + } + + /** + * Execute the command + * @param {String} id Command ID + * @param {Object} [options={}] Options + * @return {*} The return is defined by the command + * @example + * commands.run('myCommand', { someOption: 1 }); + */ + run(id: string, options: CommandOptions = {}) { + return this.runCommand(this.get(id), options); + } + + /** + * Stop the command + * @param {String} id Command ID + * @param {Object} [options={}] Options + * @return {*} The return is defined by the command + * @example + * commands.stop('myCommand', { someOption: 1 }); + */ + stop(id: string, options: CommandOptions = {}) { + return this.stopCommand(this.get(id), options); + } + + /** + * Check if the command is active. You activate commands with `run` + * and disable them with `stop`. If the command was created without `stop` + * method it can't be registered as active + * @param {String} id Command id + * @return {Boolean} + * @example + * const cId = 'some-command'; + * commands.run(cId); + * commands.isActive(cId); + * // -> true + * commands.stop(cId); + * commands.isActive(cId); + * // -> false + */ + isActive(id: string) { + return this.getActive().hasOwnProperty(id); + } + + /** + * Get all active commands + * @return {Object} + * @example + * console.log(commands.getActive()); + * // -> { someCommand: itsLastReturn, anotherOne: ... }; + */ + getActive() { + return this.active; + } + + /** + * Run command via its object + * @param {Object} command + * @param {Object} options + * @return {*} Result of the command + * @private + */ + runCommand(command?: CustomCommand, options: CommandOptions = {}) { + let result; + + if (command && command.run) { + const { em, config } = this; + const id = command.id as string; + const editor = em.Editor; + + if (!this.isActive(id) || options.force || !config.strict) { + // @ts-ignore + result = editor && command.callRun(editor, options); + + if (id && command.stop && !command.noStop && !options.abort) { + this.active[id] = result; + } + } + } + + return result; + } + + /** + * Stop the command + * @param {Object} command + * @param {Object} options + * @return {*} Result of the command + * @private + */ + stopCommand(command?: CustomCommand, options: CommandOptions = {}) { + let result; + + if (command && command.run) { + const { em, config } = this; + const id = command.id as string; + const editor = em.get('Editor'); + + if (this.isActive(id) || options.force || !config.strict) { + if (id) delete this.active[id]; + // @ts-ignore + result = command.callStop(editor, options); + } + } + + return result; + } + + /** + * Create anonymous Command instance + * @param {Object} command Command object + * @return {Command} + * @private + * */ + create(command: CustomCommand) { + if (!command.stop) command.noStop = true; + const cmd = CommandAbstract.extend(command); + return new cmd(this.config); + } + + destroy() { + this.defaultCommands = {}; + this.commands = {}; + this.active = {}; + } +} diff --git a/src/commands/view/CommandAbstract.ts b/src/commands/view/CommandAbstract.ts index b380f0f0e..355efc4ed 100644 --- a/src/commands/view/CommandAbstract.ts +++ b/src/commands/view/CommandAbstract.ts @@ -6,9 +6,16 @@ import EditorModel from '../../editor/model/Editor'; interface ICommand { run?: CommandAbstract['run']; stop?: CommandAbstract['stop']; + id?: string; [key: string]: unknown; } +export type CommandFunction = CommandAbstract['run']; + +export type Command = CustomCommand | CommandFunction; + +export type CommandOptions = Record; + type AnyObject = Record; export type CustomCommand = ICommand &