start moving everything to components

This commit is contained in:
Philipp Kühn 2018-10-21 15:50:10 +02:00
parent 287a2bc523
commit d5e25de018
30 changed files with 524 additions and 56 deletions

View File

@ -1,6 +1,11 @@
<template>
<div>
<editor class="editor" :extensions="extensions">
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
<!-- <editor class="editor" :extensions="extensions">
<div class="menubar" slot="menubar" slot-scope="{ nodes, marks }">
<div v-if="nodes && marks">
@ -135,57 +140,71 @@
</blockquote>
</div>
</editor>
</editor> -->
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor } from 'tiptap'
import {
BlockquoteNode,
BulletListNode,
CodeBlockNode,
HardBreakNode,
HeadingNode,
ListItemNode,
OrderedListNode,
TodoItemNode,
TodoListNode,
BoldMark,
CodeMark,
ItalicMark,
LinkMark,
StrikeMark,
UnderlineMark,
HistoryExtension,
} from 'tiptap-extensions'
import { Editor, EditorContent } from 'tiptap'
// import {
// BlockquoteNode,
// BulletListNode,
// CodeBlockNode,
// HardBreakNode,
// HeadingNode,
// ListItemNode,
// OrderedListNode,
// TodoItemNode,
// TodoListNode,
// BoldMark,
// CodeMark,
// ItalicMark,
// LinkMark,
// StrikeMark,
// UnderlineMark,
// HistoryExtension,
// } from 'tiptap-extensions'
export default {
components: {
Editor,
EditorContent,
Icon,
},
data() {
return {
extensions: [
new BlockquoteNode(),
new BulletListNode(),
new CodeBlockNode(),
new HardBreakNode(),
new HeadingNode({ maxLevel: 3 }),
new ListItemNode(),
new OrderedListNode(),
new TodoItemNode(),
new TodoListNode(),
new BoldMark(),
new CodeMark(),
new ItalicMark(),
new LinkMark(),
new StrikeMark(),
new UnderlineMark(),
new HistoryExtension(),
],
editor: new Editor({
extensions: [
// new BlockquoteNode(),
// new BulletListNode(),
// new CodeBlockNode(),
// new HardBreakNode(),
// new HeadingNode({ maxLevel: 3 }),
// new ListItemNode(),
// new OrderedListNode(),
// new TodoItemNode(),
// new TodoListNode(),
// new BoldMark(),
// new CodeMark(),
// new ItalicMark(),
// new LinkMark(),
// new StrikeMark(),
// new UnderlineMark(),
// new HistoryExtension(),
],
content: {
type: 'doc',
content: [{
type: 'paragraph',
content: [
{
type: 'text',
text: 'This is some inserted text. 👋',
},
],
}],
},
}),
}
},
}

View File

@ -0,0 +1,5 @@
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

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

View File

@ -0,0 +1,26 @@
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

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

View File

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

View File

@ -0,0 +1,110 @@
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

@ -0,0 +1,34 @@
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

@ -0,0 +1,7 @@
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

@ -0,0 +1,25 @@
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

@ -0,0 +1,25 @@
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

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

View File

@ -0,0 +1,39 @@
export default {
props: {
editor: {
default: null,
type: Object,
},
},
render(createElement) {
// console.log('createElement', this.editor)
// if (this.editor) {
// console.log('create element', this.editor.element)
// return this.editor.element
// // return createElement('div', {}, this.editor.element.innerHTML)
// }
return createElement('div')
},
watch: {
'editor.element': {
immediate: true,
handler(element) {
if (element) {
this.$nextTick(() => {
this.$el.append(element)
})
}
},
}
},
created() {
// console.log('on init')
// this.editor.on('init', () => {
// console.log('iniiiitttt!!!')
// })
// setTimeout(() => {
// // this.$el.innerHTML = this.editor.element.innerHTML
// this.$el.append(this.editor.element)
// }, 1000);
},
}

View File

@ -1,5 +1,7 @@
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'
export { default as Editor } from './Utils/Editor'
export { default as EditorContent } from './Components/EditorContent'
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,4 +1,4 @@
import Node from '../utils/node'
import Node from '../Utils/Node'
export default class DocNode extends Node {

View File

@ -1,5 +1,5 @@
import { setBlockType } from 'tiptap-commands'
import Node from '../utils/node'
import Node from '../Utils/Node'
export default class ParagraphNode extends Node {

View File

@ -1,4 +1,4 @@
import Node from '../utils/node'
import Node from '../Utils/Node'
export default class TextNode extends Node {

View File

@ -0,0 +1,134 @@
import Vue from 'vue'
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 class Editor {
constructor(options = {}) {
console.log('construct editor')
this.element = document.createElement('div')
this.bus = new Vue()
this.options = options
this.extensions = this.createExtensions()
this.nodes = this.createNodes()
this.marks = this.createMarks()
this.schema = this.createSchema()
this.state = this.createState()
this.view = this.createView()
console.log('emit init')
this.emit('init')
console.log(this.view)
}
createExtensions() {
return new ExtensionManager([
...builtInNodes,
...this.options.extensions,
])
}
createNodes() {
const { nodes } = this.extensions
return nodes
}
createMarks() {
return []
}
createSchema() {
return new Schema({
nodes: this.nodes,
marks: this.marks,
})
}
createState() {
return EditorState.create({
schema: this.schema,
doc: this.createDocument(this.options.content),
})
}
createDocument(content) {
if (typeof content === 'object') {
return this.schema.nodeFromJSON(content)
}
// return DOMParser.fromSchema(this.schema).parse(this.contentNode.elm)
}
createView() {
this.element.style.whiteSpace = 'pre-wrap'
return new EditorView(this.element, {
state: this.state,
dispatchTransaction: this.dispatchTransaction.bind(this),
// nodeViews: initNodeViews({
// nodes: this.views,
// editable: this.editable,
// }),
})
}
dispatchTransaction(transaction) {
this.state = this.state.apply(transaction)
this.view.updateState(this.state)
// this.updateMenuActions()
if (!transaction.docChanged) {
return
}
this.emitUpdate()
}
emitUpdate() {
console.log(this.getHTML())
// this.$emit('update', {
// getHTML: this.getHTML,
// getJSON: this.getJSON,
// state: this.state,
// })
}
getHTML() {
const div = document.createElement('div')
const fragment = DOMSerializer
.fromSchema(this.schema)
.serializeFragment(this.state.doc.content)
div.appendChild(fragment)
return div.innerHTML
}
emit(event, ...data) {
this.bus.$emit(event, ...data)
}
on(event, callback) {
this.bus.$on(event, callback)
}
}

View File

@ -1,7 +1,7 @@
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 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,4 +1,4 @@
import Extension from './extension'
import Extension from './Extension'
export default class Mark extends Extension {

View File

@ -1,4 +1,4 @@
import Extension from './extension'
import Extension from './Extension'
export default class Node extends Extension {