UNPKG

substance

Version:

Substance is a JavaScript library for web-based content editing. It provides building blocks for realizing custom text editors and web-based publishing systems.

155 lines (136 loc) 4.6 kB
import { forEach, Registry, without } from '../util' /* Listens to changes on the document and selection and updates the commandStates accordingly. The contract is that the CommandManager maintains a state for each command contributing to the global application state. */ export default class CommandManager { constructor(context, commands) { const editorSession = context.editorSession if (!editorSession) { throw new Error('EditorSession required.') } this.editorSession = context.editorSession // commands by name this.commands = commands // a context which is provided to the commands // for evaluation of state and for execution this.context = Object.assign({}, context, { // for convenienve we provide access to the doc directly doc: this.editorSession.getDocument() }) // some initializations such as setting up a registry this._initialize() // on any update we will recompute this.editorSession.onUpdate(this._onSessionUpdate, this) // compute initial command states and // promote to editorSession this._updateCommandStates(this.editorSession) } dispose() { this.editorSession.off(this) } /* Execute a command, given a context and arguments. Commands are run async if cmd.isAsync() returns true. */ executeCommand(commandName, userParams, cb) { let cmd = this._getCommand(commandName) if (!cmd) { console.warn('command', commandName, 'not registered') return } let commandStates = this.editorSession.getCommandStates() let commandState = commandStates[commandName] let params = Object.assign(this._getCommandParams(), userParams, { commandState: commandState }) if (cmd.isAsync) { // TODO: Request UI lock here this.editorSession.lock() cmd.execute(params, this._getCommandContext(), (err, info) => { if (err) { if (cb) { cb(err) } else { console.error(err) } } else { if (cb) cb(null, info) } this.editorSession.unlock() }) } else { let info = cmd.execute(params, this._getCommandContext()) return info } } _initialize() { this.commandRegistry = new Registry() forEach(this.commands, (command) => { this.commandRegistry.add(command.name, command) }) } _getCommand(commandName) { return this.commandRegistry.get(commandName) } /* Compute new command states object */ _updateCommandStates(editorSession) { const commandContext = this._getCommandContext() const params = this._getCommandParams() const surface = params.surface const commandRegistry = this.commandRegistry // EXPERIMENTAL: // We want to control which commands are available // in each surface // Trying out a white-list and a black list // TODO: discuss, and maybe think about optimizing this // by caching the result... let commandNames = commandRegistry.names.slice() if (surface) { let included = surface.props.commands let excluded = surface.props.excludedCommands if (included) { commandNames = included } else if (excluded) { commandNames = without(commandRegistry.names, ...excluded) } } const commands = commandNames.map(name => commandRegistry.get(name)) let commandStates = {} commands.forEach((cmd) => { if (cmd) { commandStates[cmd.getName()] = cmd.getCommandState(params, commandContext) } }) // NOTE: We previously did a check if commandStates were actually changed // before updating them. However, we currently have complex objects // in the command state (e.g. EditInlineNodeCommand) so we had to remove it. // See Issue #1004 this.commandStates = commandStates editorSession.setCommandStates(commandStates) } _onSessionUpdate(editorSession) { if (editorSession.hasChanged('change') || editorSession.hasChanged('selection') || editorSession.hasChanged('commandStates')) { this._updateCommandStates(editorSession) } } _getCommandContext() { return this.context } _getCommandParams() { let editorSession = this.context.editorSession let selectionState = editorSession.getSelectionState() let sel = selectionState.getSelection() let surface = this.context.surfaceManager.getFocusedSurface() return { editorSession: editorSession, selectionState: selectionState, surface: surface, selection: sel, } } }