remove legacy code

This commit is contained in:
Philipp Kühn 2018-11-08 21:43:44 +01:00
parent e8fcc8916a
commit 807c2a3d23
20 changed files with 0 additions and 916 deletions

View File

@ -1,330 +0,0 @@
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({
parent: this,
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()
},
}

View File

@ -1,5 +0,0 @@
export { default as Editor } from './components/editor'
export { default as Extension } from './utils/extension'
export { default as Node } from './utils/node'
export { default as Mark } from './utils/mark'
export { default as Plugin } from './utils/plugin'

View File

@ -1,15 +0,0 @@
import Node from '../utils/node'
export default class DocNode extends Node {
get name() {
return 'doc'
}
get schema() {
return {
content: 'block+',
}
}
}

View File

@ -1,26 +0,0 @@
import { setBlockType } from 'tiptap-commands'
import Node from '../utils/node'
export default class ParagraphNode extends Node {
get name() {
return 'paragraph'
}
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [{
tag: 'p',
}],
toDOM: () => ['p', 0],
}
}
command({ type }) {
return setBlockType(type)
}
}

View File

@ -1,15 +0,0 @@
import Node from '../utils/node'
export default class TextNode extends Node {
get name() {
return 'text'
}
get schema() {
return {
group: 'inline',
}
}
}

View File

@ -1,9 +0,0 @@
import Doc from './Doc'
import Paragraph from './Paragraph'
import Text from './Text'
export default [
new Doc(),
new Text(),
new Paragraph(),
]

View File

@ -1,89 +0,0 @@
import Vue from 'vue'
export default class ComponentView {
constructor(component, {
node,
view,
getPos,
decorations,
editable,
}) {
this.component = component
this.node = node
this.view = view
this.getPos = getPos
this.decorations = decorations
this.editable = editable
this.dom = this.createDOM()
this.contentDOM = this.vm.$refs.content
}
createDOM() {
const Component = Vue.extend(this.component)
this.vm = new Component({
propsData: {
node: this.node,
view: this.view,
getPos: this.getPos,
decorations: this.decorations,
editable: this.editable,
updateAttrs: attrs => this.updateAttrs(attrs),
updateContent: content => this.updateContent(content),
},
}).$mount()
return this.vm.$el
}
updateAttrs(attrs) {
if (!this.editable) {
return
}
const transaction = this.view.state.tr.setNodeMarkup(this.getPos(), null, {
...this.node.attrs,
...attrs,
})
this.view.dispatch(transaction)
}
updateContent(content) {
if (!this.editable) {
return
}
const transaction = this.view.state.tr.setNodeMarkup(this.getPos(), this.node.type, { content })
this.view.dispatch(transaction)
}
ignoreMutation() {
return true
}
stopEvent(event) {
// TODO: find a way to pass full extensions to ComponentView
// so we could check for schema.draggable
// for now we're allowing all drag events for node views
return !/drag/.test(event.type)
}
update(node, decorations) {
if (node.type !== this.node.type) {
return false
}
if (node === this.node && this.decorations === decorations) {
return true
}
this.node = node
this.decorations = decorations
this.vm._props.node = node
this.vm._props.decorations = decorations
return true
}
destroy() {
this.vm.$destroy()
}
}

View File

@ -1,110 +0,0 @@
import { keymap } from 'prosemirror-keymap'
export default class ExtensionManager {
constructor(extensions = []) {
this.extensions = extensions
}
get nodes() {
return this.extensions
.filter(extension => extension.type === 'node')
.reduce((nodes, { name, schema }) => ({
...nodes,
[name]: schema,
}), {})
}
get marks() {
return this.extensions
.filter(extension => extension.type === 'mark')
.reduce((marks, { name, schema }) => ({
...marks,
[name]: schema,
}), {})
}
get plugins() {
return this.extensions
.filter(extension => extension.plugins)
.reduce((allPlugins, { plugins }) => ([
...allPlugins,
...plugins,
]), [])
}
get views() {
return this.extensions
.filter(extension => ['node', 'mark'].includes(extension.type))
.filter(extension => extension.view)
.reduce((views, { name, view }) => ({
...views,
[name]: view,
}), {})
}
keymaps({ schema }) {
const extensionKeymaps = this.extensions
.filter(extension => ['extension'].includes(extension.type))
.filter(extension => extension.keys)
.map(extension => extension.keys({ schema }))
const nodeMarkKeymaps = this.extensions
.filter(extension => ['node', 'mark'].includes(extension.type))
.filter(extension => extension.keys)
.map(extension => extension.keys({
type: schema[`${extension.type}s`][extension.name],
schema,
}))
return [
...extensionKeymaps,
...nodeMarkKeymaps,
].map(keys => keymap(keys))
}
inputRules({ schema }) {
const extensionInputRules = this.extensions
.filter(extension => ['extension'].includes(extension.type))
.filter(extension => extension.inputRules)
.map(extension => extension.inputRules({ schema }))
const nodeMarkInputRules = this.extensions
.filter(extension => ['node', 'mark'].includes(extension.type))
.filter(extension => extension.inputRules)
.map(extension => extension.inputRules({
type: schema[`${extension.type}s`][extension.name],
schema,
}))
return [
...extensionInputRules,
...nodeMarkInputRules,
].reduce((allInputRules, inputRules) => ([
...allInputRules,
...inputRules,
]), [])
}
commands({ schema, view }) {
return this.extensions
.filter(extension => ['node', 'mark'].includes(extension.type))
.filter(extension => extension.command)
.reduce((commands, { name, type, command }) => ({
...commands,
[name]: attrs => {
view.focus()
const provider = command({
type: schema[`${type}s`][name],
attrs,
schema,
})
const callbacks = Array.isArray(provider) ? provider : [provider]
callbacks.forEach(callback => callback(view.state, view.dispatch, view))
},
}), {})
}
}

View File

@ -1,47 +0,0 @@
import { markIsActive, nodeIsActive, getMarkAttrs } from 'tiptap-utils'
export default function ({ schema, state, commands }) {
const nodes = Object.entries(schema.nodes)
.map(([name]) => {
const active = (attrs = {}) => nodeIsActive(state, schema.nodes[name], attrs)
const command = commands[name] ? commands[name] : () => {}
return { name, active, command }
})
.reduce((actions, { name, active, command }) => ({
...actions,
[name]: {
active,
command,
},
}), {})
const marks = Object.entries(schema.marks)
.map(([name]) => {
const active = () => markIsActive(state, schema.marks[name])
const attrs = getMarkAttrs(state, schema.marks[name])
const command = commands[name] ? commands[name] : () => {}
return {
name,
active,
attrs,
command,
}
})
.reduce((actions, {
name, active, attrs, command,
}) => ({
...actions,
[name]: {
active,
attrs,
command,
},
}), {})
return {
nodes,
marks,
}
}

View File

@ -1,10 +0,0 @@
import { lift, selectParentNode } from 'prosemirror-commands'
import { undoInputRule } from 'prosemirror-inputrules'
const keymap = {
'Mod-BracketLeft': lift,
Backspace: undoInputRule,
Escape: selectParentNode,
}
export default keymap

View File

@ -1,34 +0,0 @@
export default class Extension {
constructor(options = {}) {
this.options = {
...this.defaultOptions,
...options,
}
}
get name() {
return null
}
get type() {
return 'extension'
}
get defaultOptions() {
return {}
}
get plugins() {
return []
}
inputRules() {
return []
}
keys() {
return {}
}
}

View File

@ -1,72 +0,0 @@
import { Plugin } from 'prosemirror-state'
class Toolbar {
constructor({ node, editorView }) {
this.editorView = editorView
this.node = node
this.element = this.node.elm
this.element.style.visibility = 'hidden'
this.element.style.opacity = 0
this.editorView.dom.addEventListener('blur', this.hide.bind(this))
}
update(view, lastState) {
const { state } = view
// Don't do anything if the document/selection didn't change
if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) {
return
}
if (!state.selection.empty) {
this.hide()
return
}
const currentDom = view.domAtPos(state.selection.$anchor.pos)
const isActive = currentDom.node.innerHTML === '<br>'
&& currentDom.node.tagName === 'P'
&& currentDom.node.parentNode === view.dom
if (!isActive) {
this.hide()
return
}
const editorBoundings = this.element.offsetParent.getBoundingClientRect()
const cursorBoundings = view.coordsAtPos(state.selection.$anchor.pos)
const top = cursorBoundings.top - editorBoundings.top
this.element.style.top = `${top}px`
this.show()
}
show() {
this.element.style.visibility = 'visible'
this.element.style.opacity = 1
}
hide(event) {
if (event && event.relatedTarget) {
return
}
this.element.style.visibility = 'hidden'
this.element.style.opacity = 0
}
destroy() {
this.editorView.dom.removeEventListener('blur', this.hide)
}
}
export default function (node) {
return new Plugin({
view(editorView) {
return new Toolbar({ editorView, node })
},
})
}

View File

@ -1,7 +0,0 @@
export { default as buildMenuActions } from './buildMenuActions'
export { default as builtInKeymap } from './builtInKeymap'
export { default as ComponentView } from './ComponentView'
export { default as initNodeViews } from './initNodeViews'
export { default as floatingMenu } from './floatingMenu'
export { default as menuBubble } from './menuBubble'
export { default as ExtensionManager } from './ExtensionManager'

View File

@ -1,21 +0,0 @@
import ComponentView from './ComponentView'
export default function initNodeViews({ nodes, editable }) {
const nodeViews = {}
Object.keys(nodes).forEach(nodeName => {
nodeViews[nodeName] = (node, view, getPos, decorations) => {
const component = nodes[nodeName]
return new ComponentView(component, {
node,
view,
getPos,
decorations,
editable,
})
}
})
return nodeViews
}

View File

@ -1,25 +0,0 @@
import Extension from './extension'
export default class Mark extends Extension {
constructor(options = {}) {
super(options)
}
get type() {
return 'mark'
}
get view() {
return null
}
get schema() {
return null
}
command() {
return () => {}
}
}

View File

@ -1,73 +0,0 @@
import { Plugin } from 'prosemirror-state'
class Toolbar {
constructor({ node, editorView }) {
this.editorView = editorView
this.node = node
this.element = this.node.elm
this.element.style.visibility = 'hidden'
this.element.style.opacity = 0
this.editorView.dom.addEventListener('blur', this.hide.bind(this))
}
update(view, lastState) {
const { state } = view
// Don't do anything if the document/selection didn't change
if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) {
return
}
// Hide the tooltip if the selection is empty
if (state.selection.empty) {
this.hide()
return
}
// Otherwise, reposition it and update its content
this.show()
const { from, to } = state.selection
// These are in screen coordinates
const start = view.coordsAtPos(from)
const end = view.coordsAtPos(to)
// The box in which the tooltip is positioned, to use as base
const box = this.element.offsetParent.getBoundingClientRect()
// Find a center-ish x position from the selection endpoints (when
// crossing lines, end may be more to the left)
const left = Math.max((start.left + end.left) / 2, start.left + 3)
this.element.style.left = `${left - box.left}px`
this.element.style.bottom = `${box.bottom - start.top}px`
}
show() {
this.element.style.visibility = 'visible'
this.element.style.opacity = 1
}
hide(event) {
if (event && event.relatedTarget) {
return
}
this.element.style.visibility = 'hidden'
this.element.style.opacity = 0
}
destroy() {
this.editorView.dom.removeEventListener('blur', this.hide)
}
}
export default function (node) {
return new Plugin({
view(editorView) {
return new Toolbar({ editorView, node })
},
})
}

View File

@ -1,25 +0,0 @@
import Extension from './extension'
export default class Node extends Extension {
constructor(options = {}) {
super(options)
}
get type() {
return 'node'
}
get view() {
return null
}
get schema() {
return null
}
command() {
return () => {}
}
}

View File

@ -1,3 +0,0 @@
import { Plugin } from 'prosemirror-state'
export default Plugin