Files
tiptap/packages/tiptap/src/components/editor.js
2018-09-03 23:38:22 +02:00

237 lines
4.6 KiB
JavaScript

import { EditorState, Plugin } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { Schema, DOMParser } from 'prosemirror-model'
import { gapCursor } from 'prosemirror-gapcursor'
import { keymap } from 'prosemirror-keymap'
import { baseKeymap } from 'prosemirror-commands'
import { inputRules } from 'prosemirror-inputrules'
import {
buildMenuActions,
ExtensionManager,
initNodeViews,
menuBubble,
builtInKeymap,
} from '../utils'
import builtInNodes from '../nodes'
export default {
props: {
doc: {
type: Object,
required: false,
default: null,
},
extensions: {
type: Array,
required: false,
default: () => [],
},
editable: {
type: Boolean,
default: true,
},
},
data() {
const allExtensions = new ExtensionManager([
...builtInNodes,
...this.extensions,
])
const { nodes, marks, views } = allExtensions
return {
state: null,
view: null,
plugins: [],
allExtensions,
schema: null,
nodes,
marks,
views,
keymaps: [],
commands: {},
menuActions: null,
}
},
render(createElement) {
const slots = []
Object
.entries(this.$scopedSlots)
.forEach(([name, slot]) => {
if (name === 'content') {
this.contentNode = slot({})
slots.push(this.contentNode)
} else if (name === 'menubar') {
this.menubarNode = slot({
nodes: this.menuActions ? this.menuActions.nodes : null,
marks: this.menuActions ? this.menuActions.marks : null,
focused: this.view ? this.view.focused : false,
focus: () => this.view.focus(),
})
slots.push(this.menubarNode)
} else if (name === 'menububble') {
this.menububbleNode = slot({
nodes: this.menuActions ? this.menuActions.nodes : null,
marks: this.menuActions ? this.menuActions.marks : null,
focused: this.view ? this.view.focused : false,
focus: () => this.view.focus(),
})
slots.push(this.menububbleNode)
}
})
return createElement('div', {
class: 'vue-editor',
}, slots)
},
methods: {
initEditor() {
this.schema = this.createSchema()
this.plugins = this.createPlugins()
this.keymaps = this.createKeymaps()
this.inputRules = this.createInputRules()
this.state = this.createState()
this.clearSlot()
this.view = this.createView()
this.commands = this.createCommands()
this.updateMenuActions()
this.$emit('init', {
view: this.view,
state: this.state,
})
},
createSchema() {
return new Schema({
nodes: this.nodes,
marks: this.marks,
})
},
createPlugins() {
return this.allExtensions.plugins
},
createKeymaps() {
return this.allExtensions.keymaps({
schema: this.schema,
})
},
createInputRules() {
return this.allExtensions.inputRules({
schema: this.schema,
})
},
createCommands() {
return this.allExtensions.commands({
schema: this.schema,
view: this.view,
})
},
createState() {
return EditorState.create({
schema: this.schema,
doc: this.getDocument(),
plugins: [
...this.plugins,
...this.getPlugins(),
],
})
},
getDocument() {
if (this.doc) {
return this.schema.nodeFromJSON(this.doc)
}
return DOMParser.fromSchema(this.schema).parse(this.contentNode.elm)
},
clearSlot() {
this.contentNode.elm.innerHTML = ''
},
getPlugins() {
const plugins = [
inputRules({
rules: this.inputRules,
}),
...this.keymaps,
keymap(builtInKeymap),
keymap(baseKeymap),
gapCursor(),
new Plugin({
props: {
editable: () => this.editable,
},
}),
]
if (this.menububbleNode) {
plugins.push(menuBubble(this.menububbleNode))
}
return plugins
},
createView() {
this.contentNode.elm.style.whiteSpace = 'pre-wrap'
return new EditorView(this.contentNode.elm, {
state: this.state,
dispatchTransaction: this.dispatchTransaction,
nodeViews: initNodeViews({
nodes: this.views,
editable: this.editable,
}),
})
},
updateMenuActions() {
this.menuActions = buildMenuActions({
schema: this.schema,
state: this.view.state,
commands: this.commands,
})
},
dispatchTransaction(transaction) {
this.state = this.state.apply(transaction)
this.view.updateState(this.state)
this.updateMenuActions()
if (!transaction.docChanged) {
return
}
this.$emit('update', {
getHTML: this.getHTML,
getJSON: this.getJSON,
state: this.state,
})
},
getHTML() {
return this.view.dom.innerHTML
},
getJSON() {
return this.state.doc.toJSON()
},
},
mounted() {
this.initEditor()
},
}