mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-12-15 02:59:01 +08:00
add emitter, move some collab logic to extension
This commit is contained in:
parent
2475bf6123
commit
cd46b163d0
@ -4,5 +4,6 @@ module.exports = {
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
],
|
||||
}
|
||||
|
@ -1,15 +1,25 @@
|
||||
import { Extension } from 'tiptap'
|
||||
import { collab } from 'prosemirror-collab'
|
||||
import { collab, sendableSteps } from 'prosemirror-collab'
|
||||
import { debounce } from 'lodash-es'
|
||||
|
||||
export default class CollabExtension extends Extension {
|
||||
|
||||
get name() {
|
||||
return 'collab'
|
||||
}
|
||||
|
||||
init() {
|
||||
this.editor.on('update', ({ state }) => {
|
||||
this.getSendableSteps(state)
|
||||
})
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return {
|
||||
version: 0,
|
||||
clientID: Math.floor(Math.random() * 0xFFFFFFFF),
|
||||
debounce: 250,
|
||||
onSend: () => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,4 +31,13 @@ export default class CollabExtension extends Extension {
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
getSendableSteps = debounce(state => {
|
||||
const sendable = sendableSteps(state)
|
||||
|
||||
if (sendable) {
|
||||
this.options.onSend(sendable)
|
||||
}
|
||||
}, this.options.debounce)
|
||||
|
||||
}
|
||||
|
@ -9,10 +9,9 @@
|
||||
|
||||
<script>
|
||||
import io from 'socket.io-client'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { Editor, EditorContent } from 'tiptap'
|
||||
import { Step } from 'prosemirror-transform'
|
||||
import { receiveTransaction, sendableSteps, getVersion } from 'prosemirror-collab'
|
||||
import { receiveTransaction, getVersion } from 'prosemirror-collab'
|
||||
import Collab from './Collab'
|
||||
|
||||
export default {
|
||||
@ -29,7 +28,7 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
initEditor({ doc, version }) {
|
||||
onInit({ doc, version }) {
|
||||
this.loading = false
|
||||
|
||||
if (this.editor) {
|
||||
@ -39,23 +38,20 @@ export default {
|
||||
this.editor = new Editor({
|
||||
content: doc,
|
||||
extensions: [
|
||||
new Collab({ version }),
|
||||
],
|
||||
onUpdate: ({ state }) => {
|
||||
this.getSendableSteps(state)
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
getSendableSteps: debounce(function (state) {
|
||||
const sendable = sendableSteps(state)
|
||||
|
||||
if (sendable) {
|
||||
new Collab({
|
||||
version,
|
||||
debounce: 250,
|
||||
onSend: sendable => {
|
||||
this.socket.emit('update', sendable)
|
||||
}
|
||||
}, 250),
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
receiveData({ steps, version }) {
|
||||
// console.log(this.editor.extensions.options.collab.version)
|
||||
},
|
||||
|
||||
onUpdate({ steps, version }) {
|
||||
const { state, view, schema } = this.editor
|
||||
|
||||
if (getVersion(state) > version) {
|
||||
@ -72,8 +68,8 @@ export default {
|
||||
|
||||
mounted() {
|
||||
this.socket = io('wss://tiptap-sockets.glitch.me')
|
||||
.on('init', data => this.initEditor(data))
|
||||
.on('update', data => this.receiveData(data))
|
||||
.on('init', data => this.onInit(data))
|
||||
.on('update', data => this.onUpdate(data))
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
|
@ -26,6 +26,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.4",
|
||||
"@babel/node": "^7.2.2",
|
||||
"@babel/plugin-proposal-class-properties": "^7.4.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-runtime": "^7.4.4",
|
||||
"@babel/polyfill": "^7.4.4",
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
59
packages/tiptap/src/Utils/Emitter.js
Normal file
59
packages/tiptap/src/Utils/Emitter.js
Normal 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
|
||||
}
|
||||
}
|
@ -7,6 +7,14 @@ export default class Extension {
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
return null
|
||||
}
|
||||
|
||||
bindEditor(editor = null) {
|
||||
this.editor = editor
|
||||
}
|
||||
|
||||
get name() {
|
||||
return null
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
3
packages/tiptap/src/Utils/camelCase.js
Normal file
3
packages/tiptap/src/Utils/camelCase.js
Normal 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, '')
|
||||
}
|
@ -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'
|
||||
|
51
yarn.lock
51
yarn.lock
@ -64,6 +64,18 @@
|
||||
"@babel/traverse" "^7.4.4"
|
||||
"@babel/types" "^7.4.4"
|
||||
|
||||
"@babel/helper-create-class-features-plugin@^7.4.4":
|
||||
version "7.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz#fc3d690af6554cc9efc607364a82d48f58736dba"
|
||||
integrity sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==
|
||||
dependencies:
|
||||
"@babel/helper-function-name" "^7.1.0"
|
||||
"@babel/helper-member-expression-to-functions" "^7.0.0"
|
||||
"@babel/helper-optimise-call-expression" "^7.0.0"
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
"@babel/helper-replace-supers" "^7.4.4"
|
||||
"@babel/helper-split-export-declaration" "^7.4.4"
|
||||
|
||||
"@babel/helper-define-map@^7.4.4":
|
||||
version "7.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz#6969d1f570b46bdc900d1eba8e5d59c48ba2c12a"
|
||||
@ -238,6 +250,14 @@
|
||||
"@babel/helper-remap-async-to-generator" "^7.1.0"
|
||||
"@babel/plugin-syntax-async-generators" "^7.2.0"
|
||||
|
||||
"@babel/plugin-proposal-class-properties@^7.4.4":
|
||||
version "7.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz#93a6486eed86d53452ab9bab35e368e9461198ce"
|
||||
integrity sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==
|
||||
dependencies:
|
||||
"@babel/helper-create-class-features-plugin" "^7.4.4"
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
|
||||
"@babel/plugin-proposal-json-strings@^7.2.0":
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317"
|
||||
@ -3032,11 +3052,6 @@ chardet@^0.7.0:
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
charset@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/charset/-/charset-1.0.1.tgz#8d59546c355be61049a8fa9164747793319852bd"
|
||||
integrity sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==
|
||||
|
||||
chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.4:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d"
|
||||
@ -4399,16 +4414,14 @@ ecc-jsbn@~0.1.1:
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
ecstatic@^3.0.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-4.1.2.tgz#3afbe29849b32bc2a1f8a90f67e01dc048c7ad40"
|
||||
integrity sha512-lnrAOpU2f7Ra8dm1pW0D1ucyUxQIEk8RjFrvROg1YqCV0ueVu9hzgiSEbSyROqXDDiHREdqC4w3AwOTb23P4UQ==
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-3.3.2.tgz#6d1dd49814d00594682c652adb66076a69d46c48"
|
||||
integrity sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==
|
||||
dependencies:
|
||||
charset "^1.0.1"
|
||||
he "^1.1.1"
|
||||
mime "^2.4.1"
|
||||
mime "^1.6.0"
|
||||
minimist "^1.1.0"
|
||||
on-finished "^2.3.0"
|
||||
url-join "^4.0.0"
|
||||
url-join "^2.0.5"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
@ -7893,12 +7906,12 @@ mime@1.4.1:
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
|
||||
integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
|
||||
|
||||
mime@^1.4.1:
|
||||
mime@^1.4.1, mime@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mime@^2.3.1, mime@^2.4.1:
|
||||
mime@^2.3.1:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.2.tgz#ce5229a5e99ffc313abac806b482c10e7ba6ac78"
|
||||
integrity sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==
|
||||
@ -8489,7 +8502,7 @@ octokit-pagination-methods@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
|
||||
integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
|
||||
|
||||
on-finished@^2.3.0, on-finished@~2.3.0:
|
||||
on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||
@ -12008,10 +12021,10 @@ urix@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
||||
|
||||
url-join@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
|
||||
integrity sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=
|
||||
url-join@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728"
|
||||
integrity sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=
|
||||
|
||||
url-parse-lax@^1.0.0:
|
||||
version "1.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user