From faf1e1d4ab72a0e791144669cdbda91f0140608a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 22 Sep 2020 21:25:32 +0200 Subject: [PATCH] add basic commandmanager --- packages/core/src/CommandManager.ts | 137 ++++++++++++++++++++++++++++ packages/core/src/Editor.ts | 129 +++----------------------- 2 files changed, 152 insertions(+), 114 deletions(-) create mode 100644 packages/core/src/CommandManager.ts diff --git a/packages/core/src/CommandManager.ts b/packages/core/src/CommandManager.ts new file mode 100644 index 00000000..604663ce --- /dev/null +++ b/packages/core/src/CommandManager.ts @@ -0,0 +1,137 @@ +import { EditorState, Transaction } from "prosemirror-state"; +import { ChainedCommands, Editor } from "./Editor"; + +export default class CommandManager { + + editor: Editor + commands: { [key: string]: any } = {} + + constructor(editor: Editor, commands: any) { + this.editor = editor + this.commands = commands + } + + public runSingleCommand(name: string) { + const { commands, editor } = this + const { state, view } = editor + const command = commands[name] + + if (!command) { + // TODO: prevent vue devtools to throw error + // throw new Error(`tiptap: command '${name}' not found.`) + return + } + + return (...args: any) => { + const { tr } = state + + const props = { + editor: this.editor, + state: this.chainableEditorState(tr, state), + view, + dispatch: () => false, + // chain: this.chain.bind(this), + tr, + } + + Object.defineProperty(props, 'commands', { + get: function() { + return Object.fromEntries(Object + .entries(commands) + .map(([name, command]) => { + return [name, (...args: any[]) => command(...args)(props)] + })) + } + }) + + const callback = command(...args)(props) + + view.dispatch(tr) + + return callback + } + } + + public createChain() { + const { commands, editor } = this + const { state, view } = editor + const { tr } = state + const callbacks: boolean[] = [] + + return new Proxy({}, { + get: (target, name: string, proxy) => { + if (name === 'run') { + view.dispatch(tr) + + return () => callbacks.every(callback => callback === true) + } + + const command = commands[name] + + if (!command) { + throw new Error(`tiptap: command '${name}' not found.`) + } + + return (...args: any) => { + const props = { + editor: editor, + state: this.chainableEditorState(tr, state), + view: view, + dispatch: () => false, + // chain: this.chain.bind(this), + tr, + } + + // const self = this.editor + Object.defineProperty(props, 'commands', { + get: function() { + return Object.fromEntries(Object + .entries(commands) + .map(([name, command]) => { + return [name, (...args: any[]) => command(...args)(props)] + })) + } + }); + + const callback = command(...args)(props) + callbacks.push(callback) + + return proxy + } + } + }) as ChainedCommands + } + + public chainableEditorState(tr: Transaction, state: EditorState): EditorState { + let selection = tr.selection + let doc = tr.doc + let storedMarks = tr.storedMarks + + return { + ...state, + schema: state.schema, + plugins: state.plugins, + apply: state.apply.bind(state), + applyTransaction: state.applyTransaction.bind(state), + reconfigure: state.reconfigure.bind(state), + toJSON: state.toJSON.bind(state), + get storedMarks() { + return storedMarks + }, + get selection() { + return selection + }, + get doc() { + return doc + }, + get tr() { + selection = tr.selection + doc = tr.doc + storedMarks = tr.storedMarks + + return tr + }, + }; + } + +} \ No newline at end of file diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts index 3fb95e2d..e2f894fc 100644 --- a/packages/core/src/Editor.ts +++ b/packages/core/src/Editor.ts @@ -11,6 +11,7 @@ import getMarkAttrs from './utils/getMarkAttrs' import removeElement from './utils/removeElement' import getSchemaTypeByName from './utils/getSchemaTypeByName' import getHtmlFromFragment from './utils/getHtmlFromFragment' +import CommandManager from './CommandManager' import ExtensionManager from './ExtensionManager' import EventEmitter from './EventEmitter' import Extension from './Extension' @@ -76,6 +77,7 @@ export class Editor extends EventEmitter { public renderer!: any private proxy!: Editor + private commandManager!: CommandManager private extensionManager!: ExtensionManager private commands: { [key: string]: any } = {} private css!: HTMLStyleElement @@ -107,6 +109,7 @@ export class Editor extends EventEmitter { this.extensionManager.resolveConfigs() this.createView() this.registerCommands(commands) + this.createCommandManager() if (this.options.injectCSS) { require('./style.css') @@ -121,123 +124,14 @@ export class Editor extends EventEmitter { * @param name The name of the command */ private __get(name: string) { - const command = this.commands[name] - - if (!command) { - // TODO: prevent vue devtools to throw error - // throw new Error(`tiptap: command '${name}' not found.`) - return - } - - return (...args: any) => { - const { tr } = this.state - - const props = { - editor: this.proxy, - state: this.chainableEditorState(tr, this.state), - view: this.view, - dispatch: () => false, - // chain: this.chain.bind(this), - tr, - } - - const self = this - Object.defineProperty(props, 'commands', { - get: function() { - return Object.fromEntries(Object - .entries(self.commands) - .map(([name, command]) => { - return [name, (...args: any[]) => command(...args)(props)] - })) - }.bind(this) - }) - - const callback = command(...args)(props) - - this.view.dispatch(tr) - - return callback - } + return this.commandManager.runSingleCommand(name) } + /** + * Create a command chain to call multiple commands at once. + */ public chain() { - const { tr } = this.state - const callbacks: boolean[] = [] - - return new Proxy({}, { - get: (target, name: string, proxy) => { - if (name === 'run') { - this.view.dispatch(tr) - - return () => callbacks.every(callback => callback === true) - } - - const command = this.commands[name] - - if (!command) { - throw new Error(`tiptap: command '${name}' not found.`) - } - - return (...args: any) => { - const props = { - editor: this.proxy, - state: this.chainableEditorState(tr, this.state), - view: this.view, - dispatch: () => false, - // chain: this.chain.bind(this), - tr, - } - - const self = this - Object.defineProperty(props, 'commands', { - get: function() { - return Object.fromEntries(Object - .entries(self.commands) - .map(([name, command]) => { - return [name, (...args: any[]) => command(...args)(props)] - })) - }.bind(this) - }); - - const callback = command(...args)(props) - callbacks.push(callback) - - return proxy - } - } - }) as ChainedCommands - } - - protected chainableEditorState(tr: Transaction, state: EditorState): EditorState { - let selection = tr.selection - let doc = tr.doc - let storedMarks = tr.storedMarks - - return { - ...state, - schema: state.schema, - plugins: state.plugins, - apply: state.apply.bind(state), - applyTransaction: state.applyTransaction.bind(state), - reconfigure: state.reconfigure.bind(state), - toJSON: state.toJSON.bind(state), - get storedMarks() { - return storedMarks - }, - get selection() { - return selection - }, - get doc() { - return doc - }, - get tr() { - selection = tr.selection - doc = tr.doc - storedMarks = tr.storedMarks - - return tr - }, - }; + return this.commandManager.createChain() } /** @@ -345,6 +239,13 @@ export class Editor extends EventEmitter { this.extensionManager = new ExtensionManager(this.options.extensions, this.proxy) } + /** + * Creates an command manager. + */ + private createCommandManager() { + this.commandManager = new CommandManager(this.proxy, this.commands) + } + /** * Creates a ProseMirror schema. */