2020-09-24 06:29:05 +08:00
|
|
|
import { EditorState, Transaction } from 'prosemirror-state'
|
2020-11-17 04:42:35 +08:00
|
|
|
import { Editor } from './Editor'
|
2020-11-03 00:18:12 +08:00
|
|
|
import {
|
2020-11-16 22:56:44 +08:00
|
|
|
SingleCommands,
|
|
|
|
ChainedCommands,
|
2020-11-17 00:25:55 +08:00
|
|
|
CanCommands,
|
2020-11-16 23:58:30 +08:00
|
|
|
CommandSpec,
|
2020-11-17 04:42:35 +08:00
|
|
|
} from './types'
|
2020-11-30 16:42:53 +08:00
|
|
|
import getAllMethodNames from './utilities/getAllMethodNames'
|
2020-09-23 03:25:32 +08:00
|
|
|
|
|
|
|
export default class CommandManager {
|
|
|
|
|
|
|
|
editor: Editor
|
2020-09-24 06:29:05 +08:00
|
|
|
|
2020-11-13 18:42:04 +08:00
|
|
|
commands: { [key: string]: any } = {}
|
2020-09-23 03:25:32 +08:00
|
|
|
|
2020-09-24 15:49:46 +08:00
|
|
|
methodNames: string[] = []
|
|
|
|
|
2020-09-23 14:59:21 +08:00
|
|
|
constructor(editor: Editor) {
|
2020-09-23 03:25:32 +08:00
|
|
|
this.editor = editor
|
2020-09-24 15:49:46 +08:00
|
|
|
this.methodNames = getAllMethodNames(this.editor)
|
2020-09-23 14:59:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a command.
|
|
|
|
*
|
|
|
|
* @param name The name of your command
|
|
|
|
* @param callback The method of your command
|
|
|
|
*/
|
|
|
|
public registerCommand(name: string, callback: CommandSpec): Editor {
|
2020-11-13 18:42:04 +08:00
|
|
|
if (this.commands[name]) {
|
2020-09-23 14:59:21 +08:00
|
|
|
throw new Error(`tiptap: command '${name}' is already defined.`)
|
|
|
|
}
|
|
|
|
|
2020-09-24 15:49:46 +08:00
|
|
|
if (this.methodNames.includes(name)) {
|
2020-09-23 14:59:21 +08:00
|
|
|
throw new Error(`tiptap: '${name}' is a protected name.`)
|
|
|
|
}
|
|
|
|
|
2020-11-13 18:42:04 +08:00
|
|
|
this.commands[name] = callback
|
2020-09-23 14:59:21 +08:00
|
|
|
|
|
|
|
return this.editor
|
2020-09-23 03:25:32 +08:00
|
|
|
}
|
|
|
|
|
2020-11-13 18:42:04 +08:00
|
|
|
public createCommands() {
|
|
|
|
const { commands, editor } = this
|
2020-11-13 16:58:30 +08:00
|
|
|
const { state, view } = editor
|
2020-12-01 19:45:30 +08:00
|
|
|
const { tr } = state
|
|
|
|
const props = this.buildProps(tr)
|
2020-11-13 16:58:30 +08:00
|
|
|
|
|
|
|
return Object.fromEntries(Object
|
2020-11-13 18:42:04 +08:00
|
|
|
.entries(commands)
|
2020-11-13 16:58:30 +08:00
|
|
|
.map(([name, command]) => {
|
|
|
|
const method = (...args: any) => {
|
|
|
|
const callback = command(...args)(props)
|
|
|
|
|
2020-12-02 16:28:55 +08:00
|
|
|
if (tr.steps.length) {
|
|
|
|
view.dispatch(tr)
|
|
|
|
}
|
2020-11-13 16:58:30 +08:00
|
|
|
|
|
|
|
return callback
|
|
|
|
}
|
|
|
|
|
|
|
|
return [name, method]
|
2020-11-16 22:56:44 +08:00
|
|
|
})) as SingleCommands
|
2020-11-13 16:58:30 +08:00
|
|
|
}
|
|
|
|
|
2020-11-02 21:29:58 +08:00
|
|
|
public createChain(startTr?: Transaction, shouldDispatch = true) {
|
2020-11-13 18:42:04 +08:00
|
|
|
const { commands, editor } = this
|
2020-09-23 03:25:32 +08:00
|
|
|
const { state, view } = editor
|
|
|
|
const callbacks: boolean[] = []
|
2020-09-23 05:06:37 +08:00
|
|
|
const hasStartTransaction = !!startTr
|
2020-11-13 16:58:30 +08:00
|
|
|
const tr = startTr || state.tr
|
2020-09-23 03:25:32 +08:00
|
|
|
|
|
|
|
return new Proxy({}, {
|
2020-09-23 03:35:02 +08:00
|
|
|
get: (_, name: string, proxy) => {
|
2020-09-23 03:25:32 +08:00
|
|
|
if (name === 'run') {
|
2020-12-02 16:28:55 +08:00
|
|
|
if (!hasStartTransaction && shouldDispatch && tr.steps.length) {
|
2020-09-23 05:06:37 +08:00
|
|
|
view.dispatch(tr)
|
|
|
|
}
|
2020-09-23 03:25:32 +08:00
|
|
|
|
|
|
|
return () => callbacks.every(callback => callback === true)
|
|
|
|
}
|
|
|
|
|
2020-11-13 18:42:04 +08:00
|
|
|
const command = commands[name]
|
2020-09-23 03:25:32 +08:00
|
|
|
|
|
|
|
if (!command) {
|
|
|
|
throw new Error(`tiptap: command '${name}' not found.`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return (...args: any) => {
|
2020-12-02 06:32:39 +08:00
|
|
|
const props = this.buildProps(tr, shouldDispatch)
|
2020-09-23 03:25:32 +08:00
|
|
|
const callback = command(...args)(props)
|
2020-12-02 06:32:39 +08:00
|
|
|
|
2020-09-23 03:25:32 +08:00
|
|
|
callbacks.push(callback)
|
|
|
|
|
|
|
|
return proxy
|
|
|
|
}
|
2020-09-24 06:29:05 +08:00
|
|
|
},
|
2020-11-16 22:56:44 +08:00
|
|
|
}) as ChainedCommands
|
2020-09-23 03:25:32 +08:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:18:12 +08:00
|
|
|
public createCan(startTr?: Transaction) {
|
2020-11-13 18:42:04 +08:00
|
|
|
const { commands, editor } = this
|
2020-11-03 00:18:12 +08:00
|
|
|
const { state } = editor
|
|
|
|
const dispatch = false
|
2020-11-13 16:58:30 +08:00
|
|
|
const tr = startTr || state.tr
|
2020-11-03 00:18:12 +08:00
|
|
|
const props = this.buildProps(tr, dispatch)
|
|
|
|
const formattedCommands = Object.fromEntries(Object
|
2020-11-13 18:42:04 +08:00
|
|
|
.entries(commands)
|
2020-11-03 00:18:12 +08:00
|
|
|
.map(([name, command]) => {
|
|
|
|
return [name, (...args: any[]) => command(...args)({ ...props, dispatch })]
|
2020-11-16 22:56:44 +08:00
|
|
|
})) as SingleCommands
|
2020-11-03 00:18:12 +08:00
|
|
|
|
|
|
|
return {
|
|
|
|
...formattedCommands,
|
|
|
|
chain: () => this.createChain(tr, dispatch),
|
2020-11-17 00:25:55 +08:00
|
|
|
} as CanCommands
|
2020-11-03 00:18:12 +08:00
|
|
|
}
|
|
|
|
|
2020-11-02 21:29:58 +08:00
|
|
|
public buildProps(tr: Transaction, shouldDispatch = true) {
|
2020-11-13 18:42:04 +08:00
|
|
|
const { editor, commands } = this
|
2020-09-23 03:35:02 +08:00
|
|
|
const { state, view } = editor
|
|
|
|
|
2020-12-02 06:32:39 +08:00
|
|
|
if (state.storedMarks) {
|
|
|
|
tr.setStoredMarks(state.storedMarks)
|
|
|
|
}
|
|
|
|
|
2020-09-23 03:35:02 +08:00
|
|
|
const props = {
|
2020-09-23 05:06:37 +08:00
|
|
|
tr,
|
2020-09-23 03:35:02 +08:00
|
|
|
editor,
|
|
|
|
view,
|
2020-09-23 05:06:37 +08:00
|
|
|
state: this.chainableState(tr, state),
|
2020-11-02 21:29:58 +08:00
|
|
|
dispatch: shouldDispatch
|
2020-11-05 00:01:51 +08:00
|
|
|
? () => undefined
|
2020-11-02 21:29:58 +08:00
|
|
|
: undefined,
|
2020-09-23 05:06:37 +08:00
|
|
|
chain: () => this.createChain(tr),
|
2020-11-03 00:18:12 +08:00
|
|
|
can: () => this.createCan(tr),
|
2020-09-23 03:35:02 +08:00
|
|
|
get commands() {
|
|
|
|
return Object.fromEntries(Object
|
2020-11-13 18:42:04 +08:00
|
|
|
.entries(commands)
|
2020-09-23 03:35:02 +08:00
|
|
|
.map(([name, command]) => {
|
|
|
|
return [name, (...args: any[]) => command(...args)(props)]
|
|
|
|
}))
|
2020-09-24 06:29:05 +08:00
|
|
|
},
|
2020-09-23 03:35:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return props
|
|
|
|
}
|
|
|
|
|
|
|
|
public chainableState(tr: Transaction, state: EditorState): EditorState {
|
2020-12-02 06:32:39 +08:00
|
|
|
let { selection } = tr
|
|
|
|
let { doc } = tr
|
|
|
|
let { storedMarks } = tr
|
|
|
|
|
2020-09-23 03:25:32 +08:00
|
|
|
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() {
|
2020-12-02 06:32:39 +08:00
|
|
|
return storedMarks
|
2020-09-23 03:25:32 +08:00
|
|
|
},
|
|
|
|
get selection() {
|
2020-12-02 06:32:39 +08:00
|
|
|
return selection
|
2020-09-23 03:25:32 +08:00
|
|
|
},
|
|
|
|
get doc() {
|
2020-12-02 06:32:39 +08:00
|
|
|
return doc
|
2020-09-23 03:25:32 +08:00
|
|
|
},
|
|
|
|
get tr() {
|
2020-12-02 06:32:39 +08:00
|
|
|
selection = tr.selection
|
|
|
|
doc = tr.doc
|
|
|
|
storedMarks = tr.storedMarks
|
|
|
|
|
2020-09-23 03:25:32 +08:00
|
|
|
return tr
|
|
|
|
},
|
2020-09-24 06:29:05 +08:00
|
|
|
}
|
2020-09-23 03:25:32 +08:00
|
|
|
}
|
|
|
|
|
2020-09-24 06:29:05 +08:00
|
|
|
}
|