add emitter, move some collab logic to extension

This commit is contained in:
Philipp Kühn
2019-05-04 00:05:39 +02:00
parent 2475bf6123
commit cd46b163d0
11 changed files with 173 additions and 53 deletions

View File

@@ -12,12 +12,16 @@ import { keymap } from 'prosemirror-keymap'
import { baseKeymap } from 'prosemirror-commands'
import { inputRules, undoInputRule } from 'prosemirror-inputrules'
import { markIsActive, nodeIsActive, getMarkAttrs } from 'tiptap-utils'
import { ExtensionManager, ComponentView } from './Utils'
import {
camelCase, Emitter, ExtensionManager, ComponentView,
} from './Utils'
import { Doc, Paragraph, Text } from './Nodes'
export default class Editor {
export default class Editor extends Emitter {
constructor(options = {}) {
super()
this.defaultOptions = {
editorProps: {},
editable: true,
@@ -41,6 +45,15 @@ export default class Editor {
onDrop: () => {},
}
this.events = [
'init',
'update',
'focus',
'blur',
'paste',
'drop',
]
this.init(options)
}
@@ -69,7 +82,11 @@ export default class Editor {
}, 10)
}
this.options.onInit({
this.events.forEach(name => {
this.on(name, this.options[camelCase(`on ${name}`)])
})
this.emit('init', {
view: this.view,
state: this.state,
})
@@ -105,7 +122,7 @@ export default class Editor {
return new ExtensionManager([
...this.builtInExtensions,
...this.options.extensions,
])
], this)
}
createPlugins() {
@@ -217,20 +234,20 @@ export default class Editor {
createView() {
const view = new EditorView(this.element, {
state: this.state,
handlePaste: this.options.onPaste,
handleDrop: this.options.onDrop,
handlePaste: (...args) => { this.emit('paste', ...args) },
handleDrop: (...args) => { this.emit('drop', ...args) },
dispatchTransaction: this.dispatchTransaction.bind(this),
})
view.dom.style.whiteSpace = 'pre-wrap'
view.dom.addEventListener('focus', event => this.options.onFocus({
view.dom.addEventListener('focus', event => this.emit('focus', {
event,
state: this.state,
view: this.view,
}))
view.dom.addEventListener('blur', event => this.options.onBlur({
view.dom.addEventListener('blur', event => this.emit('blur', {
event,
state: this.state,
view: this.view,
@@ -283,8 +300,6 @@ export default class Editor {
}
dispatchTransaction(transaction) {
const oldState = this.state
this.state = this.state.apply(transaction)
this.view.updateState(this.state)
this.setActiveNodesAndMarks()
@@ -293,15 +308,14 @@ export default class Editor {
return
}
this.emitUpdate(transaction, oldState)
this.emitUpdate(transaction)
}
emitUpdate(transaction, oldState) {
this.options.onUpdate({
emitUpdate(transaction) {
this.emit('update', {
getHTML: this.getHTML.bind(this),
getJSON: this.getJSON.bind(this),
state: this.state,
oldState,
transaction,
})
}

View File

@@ -0,0 +1,59 @@
export default class Emitter {
// Add an event listener for given event
on(event, fn) {
this._callbacks = this._callbacks || {}
// Create namespace for this event
if (!this._callbacks[event]) {
this._callbacks[event] = []
}
this._callbacks[event].push(fn)
return this
}
emit(event, ...args) {
this._callbacks = this._callbacks || {}
const callbacks = this._callbacks[event]
if (callbacks) {
for (const callback of callbacks) {
callback.apply(this, args)
}
}
return this
}
// Remove event listener for given event. If fn is not provided, all event
// listeners for that event will be removed. If neither is provided, all
// event listeners will be removed.
off(event, fn) {
if (!this._callbacks || (arguments.length === 0)) {
this._callbacks = {}
return this
}
// specific event
const callbacks = this._callbacks[event]
if (!callbacks) {
return this
}
// remove all handlers
if (arguments.length === 1) {
delete this._callbacks[event]
return this
}
// remove specific handler
for (let i = 0; i < callbacks.length; i++) {
const callback = callbacks[i]
if (callback === fn) {
callbacks.splice(i, 1)
break
}
}
return this
}
}

View File

@@ -7,6 +7,14 @@ export default class Extension {
}
}
init() {
return null
}
bindEditor(editor = null) {
this.editor = editor
}
get name() {
return null
}

View File

@@ -2,7 +2,11 @@ import { keymap } from 'prosemirror-keymap'
export default class ExtensionManager {
constructor(extensions = []) {
constructor(extensions = [], editor) {
extensions.forEach(extension => {
extension.bindEditor(editor)
extension.init()
})
this.extensions = extensions
}

View File

@@ -0,0 +1,3 @@
export default function (str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => (index == 0 ? word.toLowerCase() : word.toUpperCase())).replace(/\s+/g, '')
}

View File

@@ -1,4 +1,6 @@
export { default as camelCase } from './camelCase'
export { default as ComponentView } from './ComponentView'
export { default as Emitter } from './Emitter'
export { default as Extension } from './Extension'
export { default as ExtensionManager } from './ExtensionManager'
export { default as Mark } from './Mark'