add basic commandmanager

This commit is contained in:
Philipp Kühn
2020-09-22 21:25:32 +02:00
parent 9229de8881
commit faf1e1d4ab
2 changed files with 152 additions and 114 deletions

View File

@@ -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
},
};
}
}

View File

@@ -11,6 +11,7 @@ import getMarkAttrs from './utils/getMarkAttrs'
import removeElement from './utils/removeElement' import removeElement from './utils/removeElement'
import getSchemaTypeByName from './utils/getSchemaTypeByName' import getSchemaTypeByName from './utils/getSchemaTypeByName'
import getHtmlFromFragment from './utils/getHtmlFromFragment' import getHtmlFromFragment from './utils/getHtmlFromFragment'
import CommandManager from './CommandManager'
import ExtensionManager from './ExtensionManager' import ExtensionManager from './ExtensionManager'
import EventEmitter from './EventEmitter' import EventEmitter from './EventEmitter'
import Extension from './Extension' import Extension from './Extension'
@@ -76,6 +77,7 @@ export class Editor extends EventEmitter {
public renderer!: any public renderer!: any
private proxy!: Editor private proxy!: Editor
private commandManager!: CommandManager
private extensionManager!: ExtensionManager private extensionManager!: ExtensionManager
private commands: { [key: string]: any } = {} private commands: { [key: string]: any } = {}
private css!: HTMLStyleElement private css!: HTMLStyleElement
@@ -107,6 +109,7 @@ export class Editor extends EventEmitter {
this.extensionManager.resolveConfigs() this.extensionManager.resolveConfigs()
this.createView() this.createView()
this.registerCommands(commands) this.registerCommands(commands)
this.createCommandManager()
if (this.options.injectCSS) { if (this.options.injectCSS) {
require('./style.css') require('./style.css')
@@ -121,123 +124,14 @@ export class Editor extends EventEmitter {
* @param name The name of the command * @param name The name of the command
*/ */
private __get(name: string) { private __get(name: string) {
const command = this.commands[name] return this.commandManager.runSingleCommand(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
}
} }
/**
* Create a command chain to call multiple commands at once.
*/
public chain() { public chain() {
const { tr } = this.state return this.commandManager.createChain()
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
},
};
} }
/** /**
@@ -345,6 +239,13 @@ export class Editor extends EventEmitter {
this.extensionManager = new ExtensionManager(this.options.extensions, this.proxy) 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. * Creates a ProseMirror schema.
*/ */