start moving everything to components
This commit is contained in:
329
packages/_legacy-tiptap/src/components/editor.js
Normal file
329
packages/_legacy-tiptap/src/components/editor.js
Normal file
@@ -0,0 +1,329 @@
|
||||
import { EditorState, Plugin } from 'prosemirror-state'
|
||||
import { EditorView } from 'prosemirror-view'
|
||||
import { Schema, DOMParser, DOMSerializer } 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,
|
||||
floatingMenu,
|
||||
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,
|
||||
},
|
||||
watchDoc: {
|
||||
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,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
||||
doc: {
|
||||
deep: true,
|
||||
handler() {
|
||||
if (this.watchDoc) {
|
||||
this.setContent(this.doc, true)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
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.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.focus,
|
||||
})
|
||||
slots.push(this.menububbleNode)
|
||||
} else if (name === 'floatingMenu') {
|
||||
this.floatingMenuNode = slot({
|
||||
nodes: this.menuActions ? this.menuActions.nodes : null,
|
||||
marks: this.menuActions ? this.menuActions.marks : null,
|
||||
focused: this.view ? this.view.focused : false,
|
||||
focus: this.focus,
|
||||
})
|
||||
slots.push(this.floatingMenuNode)
|
||||
}
|
||||
})
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
if (this.floatingMenuNode) {
|
||||
plugins.push(floatingMenu(this.floatingMenuNode))
|
||||
}
|
||||
|
||||
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,
|
||||
}),
|
||||
})
|
||||
},
|
||||
|
||||
destroyEditor() {
|
||||
if (this.view) {
|
||||
this.view.destroy()
|
||||
}
|
||||
},
|
||||
|
||||
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.emitUpdate()
|
||||
},
|
||||
|
||||
getHTML() {
|
||||
const div = document.createElement('div')
|
||||
const fragment = DOMSerializer
|
||||
.fromSchema(this.schema)
|
||||
.serializeFragment(this.state.doc.content)
|
||||
|
||||
div.appendChild(fragment)
|
||||
|
||||
return div.innerHTML
|
||||
},
|
||||
|
||||
getJSON() {
|
||||
return this.state.doc.toJSON()
|
||||
},
|
||||
|
||||
emitUpdate() {
|
||||
this.$emit('update', {
|
||||
getHTML: this.getHTML,
|
||||
getJSON: this.getJSON,
|
||||
state: this.state,
|
||||
})
|
||||
},
|
||||
|
||||
getDocFromContent(content) {
|
||||
if (typeof content === 'object') {
|
||||
return this.schema.nodeFromJSON(content)
|
||||
}
|
||||
|
||||
if (typeof content === 'string') {
|
||||
const element = document.createElement('div')
|
||||
element.innerHTML = content.trim()
|
||||
|
||||
return DOMParser.fromSchema(this.schema).parse(element)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
setContent(content = {}, emitUpdate = false) {
|
||||
this.state = EditorState.create({
|
||||
schema: this.state.schema,
|
||||
doc: this.getDocFromContent(content),
|
||||
plugins: this.state.plugins,
|
||||
})
|
||||
|
||||
this.view.updateState(this.state)
|
||||
|
||||
if (emitUpdate) {
|
||||
this.emitUpdate()
|
||||
}
|
||||
},
|
||||
|
||||
clearContent(emitUpdate = false) {
|
||||
this.setContent({
|
||||
type: 'doc',
|
||||
content: [{
|
||||
type: 'paragraph',
|
||||
}],
|
||||
}, emitUpdate)
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.view.focus()
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initEditor()
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.destroyEditor()
|
||||
},
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user