tabs to spaces whitespace

This commit is contained in:
Philipp Kühn
2018-11-08 22:03:10 +01:00
parent b8b82220ba
commit f04a6be6c1
114 changed files with 4214 additions and 4214 deletions

View File

@@ -4,4 +4,4 @@ This is the core package of [tiptap](https://www.npmjs.com/package/tiptap).
[![](https://img.shields.io/npm/v/tiptap.svg?label=version)](https://www.npmjs.com/package/tiptap)
[![](https://img.shields.io/npm/dm/tiptap.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
[![](https://img.shields.io/npm/l/tiptap.svg)](https://www.npmjs.com/package/tiptap)
[![](http://img.badgesize.io/https://unpkg.com/tiptap/dist/tiptap.min.js?compression=gzip&label=size&colorB=000000)](https://www.npmjs.com/package/tiptap)
[![](http://img.badgesize.io/https://unpkg.com/tiptap/dist/tiptap.min.js?compression=gzip&label=size&colorB=000000)](https://www.npmjs.com/package/tiptap)

View File

@@ -1,21 +1,21 @@
export default {
props: {
editor: {
default: null,
type: Object,
},
},
watch: {
'editor.element': {
immediate: true,
handler(element) {
if (element) {
this.$nextTick(() => this.$el.append(element.firstChild))
}
},
},
},
render(createElement) {
return createElement('div')
},
props: {
editor: {
default: null,
type: Object,
},
},
watch: {
'editor.element': {
immediate: true,
handler(element) {
if (element) {
this.$nextTick(() => this.$el.append(element.firstChild))
}
},
},
},
render(createElement) {
return createElement('div')
},
}

View File

@@ -1,50 +1,50 @@
import FloatingMenu from '../Utils/FloatingMenu'
export default {
props: {
editor: {
default: null,
type: Object,
},
},
data() {
return {
menu: {
isActive: false,
left: 0,
bottom: 0,
},
}
},
watch: {
editor: {
immediate: true,
handler(editor) {
if (editor) {
this.$nextTick(() => {
editor.registerPlugin(FloatingMenu({
element: this.$el,
onUpdate: menu => {
this.menu = menu
},
}))
})
}
},
},
},
render() {
if (!this.editor) {
return null
}
props: {
editor: {
default: null,
type: Object,
},
},
data() {
return {
menu: {
isActive: false,
left: 0,
bottom: 0,
},
}
},
watch: {
editor: {
immediate: true,
handler(editor) {
if (editor) {
this.$nextTick(() => {
editor.registerPlugin(FloatingMenu({
element: this.$el,
onUpdate: menu => {
this.menu = menu
},
}))
})
}
},
},
},
render() {
if (!this.editor) {
return null
}
return this.$scopedSlots.default({
focused: this.editor.view.focused,
focus: this.editor.focus,
commands: this.editor.commands,
isActive: this.editor.isActive.bind(this.editor),
markAttrs: this.editor.markAttrs.bind(this.editor),
menu: this.menu,
})
},
return this.$scopedSlots.default({
focused: this.editor.view.focused,
focus: this.editor.focus,
commands: this.editor.commands,
isActive: this.editor.isActive.bind(this.editor),
markAttrs: this.editor.markAttrs.bind(this.editor),
menu: this.menu,
})
},
}

View File

@@ -1,21 +1,21 @@
export default {
props: {
editor: {
default: null,
type: Object,
},
},
render() {
if (!this.editor) {
return null
}
props: {
editor: {
default: null,
type: Object,
},
},
render() {
if (!this.editor) {
return null
}
return this.$scopedSlots.default({
focused: this.editor.view.focused,
focus: this.editor.focus,
commands: this.editor.commands,
isActive: this.editor.isActive.bind(this.editor),
markAttrs: this.editor.markAttrs.bind(this.editor),
})
},
return this.$scopedSlots.default({
focused: this.editor.view.focused,
focus: this.editor.focus,
commands: this.editor.commands,
isActive: this.editor.isActive.bind(this.editor),
markAttrs: this.editor.markAttrs.bind(this.editor),
})
},
}

View File

@@ -1,50 +1,50 @@
import MenuBubble from '../Utils/MenuBubble'
export default {
props: {
editor: {
default: null,
type: Object,
},
},
data() {
return {
menu: {
isActive: false,
left: 0,
bottom: 0,
},
}
},
watch: {
editor: {
immediate: true,
handler(editor) {
if (editor) {
this.$nextTick(() => {
editor.registerPlugin(MenuBubble({
element: this.$el,
onUpdate: menu => {
this.menu = menu
},
}))
})
}
},
},
},
render() {
if (!this.editor) {
return null
}
props: {
editor: {
default: null,
type: Object,
},
},
data() {
return {
menu: {
isActive: false,
left: 0,
bottom: 0,
},
}
},
watch: {
editor: {
immediate: true,
handler(editor) {
if (editor) {
this.$nextTick(() => {
editor.registerPlugin(MenuBubble({
element: this.$el,
onUpdate: menu => {
this.menu = menu
},
}))
})
}
},
},
},
render() {
if (!this.editor) {
return null
}
return this.$scopedSlots.default({
focused: this.editor.view.focused,
focus: this.editor.focus,
commands: this.editor.commands,
isActive: this.editor.isActive.bind(this.editor),
markAttrs: this.editor.markAttrs.bind(this.editor),
menu: this.menu,
})
},
return this.$scopedSlots.default({
focused: this.editor.view.focused,
focus: this.editor.focus,
commands: this.editor.commands,
isActive: this.editor.isActive.bind(this.editor),
markAttrs: this.editor.markAttrs.bind(this.editor),
menu: this.menu,
})
},
}

View File

@@ -7,4 +7,4 @@ export { default as FloatingMenu } from './Components/FloatingMenu'
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 Plugin } from './Utils/Plugin'

View File

@@ -2,14 +2,14 @@ import Node from '../Utils/Node'
export default class Doc extends Node {
get name() {
return 'doc'
}
get name() {
return 'doc'
}
get schema() {
return {
content: 'block+',
}
}
get schema() {
return {
content: 'block+',
}
}
}

View File

@@ -3,24 +3,24 @@ import Node from '../Utils/Node'
export default class Paragraph extends Node {
get name() {
return 'paragraph'
}
get name() {
return 'paragraph'
}
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [{
tag: 'p',
}],
toDOM: () => ['p', 0],
}
}
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [{
tag: 'p',
}],
toDOM: () => ['p', 0],
}
}
commands({ type }) {
return () => setBlockType(type)
}
commands({ type }) {
return () => setBlockType(type)
}
}

View File

@@ -2,14 +2,14 @@ import Node from '../Utils/Node'
export default class Text extends Node {
get name() {
return 'text'
}
get name() {
return 'text'
}
get schema() {
return {
group: 'inline',
}
}
get schema() {
return {
group: 'inline',
}
}
}

View File

@@ -3,7 +3,7 @@ import Paragraph from './Paragraph'
import Text from './Text'
export default [
new Doc(),
new Text(),
new Paragraph(),
new Doc(),
new Text(),
new Paragraph(),
]

View File

@@ -1,92 +1,92 @@
import Vue from 'vue'
export default class ComponentView {
constructor(component, {
parent,
node,
view,
getPos,
decorations,
editable,
}) {
this.parent = parent
this.component = component
this.node = node
this.view = view
this.getPos = getPos
this.decorations = decorations
this.editable = editable
constructor(component, {
parent,
node,
view,
getPos,
decorations,
editable,
}) {
this.parent = parent
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
}
this.dom = this.createDOM()
this.contentDOM = this.vm.$refs.content
}
createDOM() {
const Component = Vue.extend(this.component)
this.vm = new Component({
parent: this.parent,
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
}
createDOM() {
const Component = Vue.extend(this.component)
this.vm = new Component({
parent: this.parent,
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
}
updateAttrs(attrs) {
if (!this.editable) {
return
}
const transaction = this.view.state.tr.setNodeMarkup(this.getPos(), null, {
...this.node.attrs,
...attrs,
})
this.view.dispatch(transaction)
}
const transaction = this.view.state.tr.setNodeMarkup(this.getPos(), null, {
...this.node.attrs,
...attrs,
})
this.view.dispatch(transaction)
}
updateContent(content) {
if (!this.editable) {
return
}
updateContent(content) {
if (!this.editable) {
return
}
const transaction = this.view.state.tr.setNodeMarkup(this.getPos(), this.node.type, { content })
this.view.dispatch(transaction)
}
const transaction = this.view.state.tr.setNodeMarkup(this.getPos(), this.node.type, { content })
this.view.dispatch(transaction)
}
ignoreMutation() {
return true
}
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)
}
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
}
update(node, decorations) {
if (node.type !== this.node.type) {
return false
}
if (node === this.node && this.decorations === decorations) {
return true
}
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
}
this.node = node
this.decorations = decorations
this.vm._props.node = node
this.vm._props.decorations = decorations
return true
}
destroy() {
this.vm.$destroy()
}
destroy() {
this.vm.$destroy()
}
}

View File

@@ -9,278 +9,278 @@ import { inputRules } from 'prosemirror-inputrules'
import { markIsActive, nodeIsActive, getMarkAttrs } from 'tiptap-utils'
import {
ExtensionManager,
initNodeViews,
builtInKeymap,
ExtensionManager,
initNodeViews,
builtInKeymap,
} from '.'
import builtInNodes from '../Nodes'
export default class Editor {
constructor(options = {}) {
this.setOptions(options)
this.init()
}
constructor(options = {}) {
this.setOptions(options)
this.init()
}
setOptions(options) {
const defaultOptions = {
editable: true,
content: '',
onUpdate: () => {},
}
setOptions(options) {
const defaultOptions = {
editable: true,
content: '',
onUpdate: () => {},
}
this.options = {
...defaultOptions,
...options,
}
}
this.options = {
...defaultOptions,
...options,
}
}
init() {
this.bus = new Vue()
this.element = document.createElement('div')
this.extensions = this.createExtensions()
this.nodes = this.createNodes()
this.marks = this.createMarks()
this.views = this.createViews()
this.schema = this.createSchema()
this.plugins = this.createPlugins()
this.keymaps = this.createKeymaps()
this.inputRules = this.createInputRules()
this.state = this.createState()
this.view = this.createView()
this.commands = this.createCommands()
this.getActiveNodesAndMarks()
this.emit('init')
}
init() {
this.bus = new Vue()
this.element = document.createElement('div')
this.extensions = this.createExtensions()
this.nodes = this.createNodes()
this.marks = this.createMarks()
this.views = this.createViews()
this.schema = this.createSchema()
this.plugins = this.createPlugins()
this.keymaps = this.createKeymaps()
this.inputRules = this.createInputRules()
this.state = this.createState()
this.view = this.createView()
this.commands = this.createCommands()
this.getActiveNodesAndMarks()
this.emit('init')
}
createExtensions() {
return new ExtensionManager([
...builtInNodes,
...this.options.extensions,
])
}
createExtensions() {
return new ExtensionManager([
...builtInNodes,
...this.options.extensions,
])
}
createPlugins() {
return this.extensions.plugins
}
createPlugins() {
return this.extensions.plugins
}
createKeymaps() {
return this.extensions.keymaps({
schema: this.schema,
})
}
createKeymaps() {
return this.extensions.keymaps({
schema: this.schema,
})
}
createInputRules() {
return this.extensions.inputRules({
schema: this.schema,
})
}
createInputRules() {
return this.extensions.inputRules({
schema: this.schema,
})
}
createCommands() {
return this.extensions.commands({
schema: this.schema,
view: this.view,
editable: this.options.editable,
})
}
createCommands() {
return this.extensions.commands({
schema: this.schema,
view: this.view,
editable: this.options.editable,
})
}
createNodes() {
return this.extensions.nodes
}
createNodes() {
return this.extensions.nodes
}
createMarks() {
return this.extensions.marks
}
createMarks() {
return this.extensions.marks
}
createViews() {
return this.extensions.views
}
createViews() {
return this.extensions.views
}
createSchema() {
return new Schema({
nodes: this.nodes,
marks: this.marks,
})
}
createSchema() {
return new Schema({
nodes: this.nodes,
marks: this.marks,
})
}
createState() {
return EditorState.create({
schema: this.schema,
doc: this.createDocument(this.options.content),
plugins: [
...this.plugins,
inputRules({
rules: this.inputRules,
}),
...this.keymaps,
keymap(builtInKeymap),
keymap(baseKeymap),
gapCursor(),
new Plugin({
props: {
editable: () => this.options.editable,
},
}),
],
})
}
createState() {
return EditorState.create({
schema: this.schema,
doc: this.createDocument(this.options.content),
plugins: [
...this.plugins,
inputRules({
rules: this.inputRules,
}),
...this.keymaps,
keymap(builtInKeymap),
keymap(baseKeymap),
gapCursor(),
new Plugin({
props: {
editable: () => this.options.editable,
},
}),
],
})
}
createDocument(content) {
if (typeof content === 'object') {
return this.schema.nodeFromJSON(content)
}
createDocument(content) {
if (typeof content === 'object') {
return this.schema.nodeFromJSON(content)
}
if (typeof content === 'string') {
const element = document.createElement('div')
element.innerHTML = content.trim()
if (typeof content === 'string') {
const element = document.createElement('div')
element.innerHTML = content.trim()
return DOMParser.fromSchema(this.schema).parse(element)
}
return DOMParser.fromSchema(this.schema).parse(element)
}
return false
}
return false
}
createView() {
const view = new EditorView(this.element, {
state: this.state,
dispatchTransaction: this.dispatchTransaction.bind(this),
nodeViews: initNodeViews({
nodes: this.views,
editable: this.options.editable,
}),
})
createView() {
const view = new EditorView(this.element, {
state: this.state,
dispatchTransaction: this.dispatchTransaction.bind(this),
nodeViews: initNodeViews({
nodes: this.views,
editable: this.options.editable,
}),
})
view.dom.style.whiteSpace = 'pre-wrap'
view.dom.style.whiteSpace = 'pre-wrap'
return view
}
return view
}
dispatchTransaction(transaction) {
this.state = this.state.apply(transaction)
this.view.updateState(this.state)
this.getActiveNodesAndMarks()
dispatchTransaction(transaction) {
this.state = this.state.apply(transaction)
this.view.updateState(this.state)
this.getActiveNodesAndMarks()
if (!transaction.docChanged) {
return
}
if (!transaction.docChanged) {
return
}
this.emitUpdate()
}
this.emitUpdate()
}
emitUpdate() {
this.options.onUpdate({
getHTML: this.getHTML.bind(this),
getJSON: this.getJSON.bind(this),
state: this.state,
})
}
emitUpdate() {
this.options.onUpdate({
getHTML: this.getHTML.bind(this),
getJSON: this.getJSON.bind(this),
state: this.state,
})
}
getHTML() {
const div = document.createElement('div')
const fragment = DOMSerializer
.fromSchema(this.schema)
.serializeFragment(this.state.doc.content)
getHTML() {
const div = document.createElement('div')
const fragment = DOMSerializer
.fromSchema(this.schema)
.serializeFragment(this.state.doc.content)
div.appendChild(fragment)
div.appendChild(fragment)
return div.innerHTML
}
return div.innerHTML
}
getJSON() {
return this.state.doc.toJSON()
}
getJSON() {
return this.state.doc.toJSON()
}
setContent(content = {}, emitUpdate = false) {
this.state = EditorState.create({
schema: this.state.schema,
doc: this.createDocument(content),
plugins: this.state.plugins,
})
setContent(content = {}, emitUpdate = false) {
this.state = EditorState.create({
schema: this.state.schema,
doc: this.createDocument(content),
plugins: this.state.plugins,
})
this.view.updateState(this.state)
this.view.updateState(this.state)
if (emitUpdate) {
this.emitUpdate()
}
}
if (emitUpdate) {
this.emitUpdate()
}
}
clearContent(emitUpdate = false) {
this.setContent({
type: 'doc',
content: [{
type: 'paragraph',
}],
}, emitUpdate)
}
clearContent(emitUpdate = false) {
this.setContent({
type: 'doc',
content: [{
type: 'paragraph',
}],
}, emitUpdate)
}
getActiveNodesAndMarks() {
this.activeMarks = Object
.entries(this.schema.marks)
.reduce((marks, [name, mark]) => ({
...marks,
[name]: (attrs = {}) => markIsActive(this.state, mark, attrs),
}), {})
getActiveNodesAndMarks() {
this.activeMarks = Object
.entries(this.schema.marks)
.reduce((marks, [name, mark]) => ({
...marks,
[name]: (attrs = {}) => markIsActive(this.state, mark, attrs),
}), {})
this.activeMarkAttrs = Object
.entries(this.schema.marks)
.reduce((marks, [name, mark]) => ({
...marks,
[name]: getMarkAttrs(this.state, mark),
}), {})
this.activeMarkAttrs = Object
.entries(this.schema.marks)
.reduce((marks, [name, mark]) => ({
...marks,
[name]: getMarkAttrs(this.state, mark),
}), {})
this.activeNodes = Object
.entries(this.schema.nodes)
.reduce((nodes, [name, node]) => ({
...nodes,
[name]: (attrs = {}) => nodeIsActive(this.state, node, attrs),
}), {})
}
this.activeNodes = Object
.entries(this.schema.nodes)
.reduce((nodes, [name, node]) => ({
...nodes,
[name]: (attrs = {}) => nodeIsActive(this.state, node, attrs),
}), {})
}
focus() {
this.view.focus()
}
focus() {
this.view.focus()
}
emit(event, ...data) {
this.bus.$emit(event, ...data)
}
emit(event, ...data) {
this.bus.$emit(event, ...data)
}
on(event, callback) {
this.bus.$on(event, callback)
}
on(event, callback) {
this.bus.$on(event, callback)
}
registerPlugin(plugin = null) {
if (plugin) {
this.state = this.state.reconfigure({
plugins: this.state.plugins.concat([plugin]),
})
this.view.updateState(this.state)
}
}
registerPlugin(plugin = null) {
if (plugin) {
this.state = this.state.reconfigure({
plugins: this.state.plugins.concat([plugin]),
})
this.view.updateState(this.state)
}
}
markAttrs(type = null) {
return this.activeMarkAttrs[type]
}
markAttrs(type = null) {
return this.activeMarkAttrs[type]
}
isActive(type = null, attrs = {}) {
const types = {
...this.activeMarks,
...this.activeNodes,
}
isActive(type = null, attrs = {}) {
const types = {
...this.activeMarks,
...this.activeNodes,
}
if (!types[type]) {
return false
}
if (!types[type]) {
return false
}
return types[type](attrs)
}
return types[type](attrs)
}
destroy() {
this.emit('destroy')
destroy() {
this.emit('destroy')
if (this.view) {
this.view.destroy()
}
}
if (this.view) {
this.view.destroy()
}
}
}

View File

@@ -2,148 +2,148 @@ import { keymap } from 'prosemirror-keymap'
export default class ExtensionManager {
constructor(extensions = []) {
this.extensions = extensions
}
constructor(extensions = []) {
this.extensions = extensions
}
get nodes() {
return this.extensions
.filter(extension => extension.type === 'node')
.reduce((nodes, { name, schema }) => ({
...nodes,
[name]: schema,
}), {})
}
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 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 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,
}), {})
}
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 }))
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,
}))
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))
}
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 }))
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,
}))
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,
]), [])
}
return [
...extensionInputRules,
...nodeMarkInputRules,
].reduce((allInputRules, inputRules) => ([
...allInputRules,
...inputRules,
]), [])
}
commands({ schema, view, editable }) {
return this.extensions
.filter(extension => extension.commands)
.reduce((allCommands, { name, type, commands: provider }) => {
commands({ schema, view, editable }) {
return this.extensions
.filter(extension => extension.commands)
.reduce((allCommands, { name, type, commands: provider }) => {
const commands = {}
const value = provider({
schema,
...['node', 'mark'].includes(type) ? {
type: schema[`${type}s`][name],
} : {},
})
const commands = {}
const value = provider({
schema,
...['node', 'mark'].includes(type) ? {
type: schema[`${type}s`][name],
} : {},
})
if (Array.isArray(value)) {
commands[name] = attrs => value
.forEach(callback => {
if (!editable) {
return false
}
view.focus()
return callback(attrs)(view.state, view.dispatch, view)
})
} else if (typeof value === 'function') {
commands[name] = attrs => {
if (!editable) {
return false
}
view.focus()
return value(attrs)(view.state, view.dispatch, view)
}
} else if (typeof value === 'object') {
Object.entries(value).forEach(([commandName, commandValue]) => {
if (Array.isArray(commandValue)) {
commands[commandName] = attrs => commandValue
.forEach(callback => {
if (!editable) {
return false
}
view.focus()
return callback(attrs)(view.state, view.dispatch, view)
})
} else {
commands[commandName] = attrs => {
if (!editable) {
return false
}
view.focus()
return commandValue(attrs)(view.state, view.dispatch, view)
}
}
})
}
if (Array.isArray(value)) {
commands[name] = attrs => value
.forEach(callback => {
if (!editable) {
return false
}
view.focus()
return callback(attrs)(view.state, view.dispatch, view)
})
} else if (typeof value === 'function') {
commands[name] = attrs => {
if (!editable) {
return false
}
view.focus()
return value(attrs)(view.state, view.dispatch, view)
}
} else if (typeof value === 'object') {
Object.entries(value).forEach(([commandName, commandValue]) => {
if (Array.isArray(commandValue)) {
commands[commandName] = attrs => commandValue
.forEach(callback => {
if (!editable) {
return false
}
view.focus()
return callback(attrs)(view.state, view.dispatch, view)
})
} else {
commands[commandName] = attrs => {
if (!editable) {
return false
}
view.focus()
return commandValue(attrs)(view.state, view.dispatch, view)
}
}
})
}
return {
...allCommands,
...commands,
}
}, {})
}
return {
...allCommands,
...commands,
}
}, {})
}
}

View File

@@ -2,81 +2,81 @@ import { Plugin } from 'prosemirror-state'
class Menu {
constructor({ options, editorView }) {
this.options = {
...{
element: null,
onUpdate: () => false,
},
...options,
}
this.editorView = editorView
this.isActive = false
this.top = 0
constructor({ options, editorView }) {
this.options = {
...{
element: null,
onUpdate: () => false,
},
...options,
}
this.editorView = editorView
this.isActive = false
this.top = 0
this.editorView.dom.addEventListener('blur', this.hide.bind(this))
}
this.editorView.dom.addEventListener('blur', this.hide.bind(this))
}
update(view, lastState) {
const { state } = view
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
}
// 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
}
if (!state.selection.empty) {
this.hide()
return
}
const currentDom = view.domAtPos(state.selection.$anchor.pos)
const currentDom = view.domAtPos(state.selection.$anchor.pos)
const isActive = currentDom.node.innerHTML === '<br>'
&& currentDom.node.tagName === 'P'
&& currentDom.node.parentNode === view.dom
const isActive = currentDom.node.innerHTML === '<br>'
&& currentDom.node.tagName === 'P'
&& currentDom.node.parentNode === view.dom
if (!isActive) {
this.hide()
return
}
if (!isActive) {
this.hide()
return
}
const editorBoundings = this.options.element.offsetParent.getBoundingClientRect()
const cursorBoundings = view.coordsAtPos(state.selection.$anchor.pos)
const top = cursorBoundings.top - editorBoundings.top
const editorBoundings = this.options.element.offsetParent.getBoundingClientRect()
const cursorBoundings = view.coordsAtPos(state.selection.$anchor.pos)
const top = cursorBoundings.top - editorBoundings.top
this.isActive = true
this.top = top
this.isActive = true
this.top = top
this.sendUpdate()
}
this.sendUpdate()
}
sendUpdate() {
this.options.onUpdate({
isActive: this.isActive,
top: this.top,
})
}
sendUpdate() {
this.options.onUpdate({
isActive: this.isActive,
top: this.top,
})
}
hide(event) {
if (event && event.relatedTarget) {
return
}
hide(event) {
if (event && event.relatedTarget) {
return
}
this.isActive = false
this.sendUpdate()
}
this.isActive = false
this.sendUpdate()
}
destroy() {
this.editorView.dom.removeEventListener('blur', this.hide)
}
destroy() {
this.editorView.dom.removeEventListener('blur', this.hide)
}
}
export default function (options) {
return new Plugin({
view(editorView) {
return new Menu({ editorView, options })
},
})
return new Plugin({
view(editorView) {
return new Menu({ editorView, options })
},
})
}

View File

@@ -2,84 +2,84 @@ import { Plugin } from 'prosemirror-state'
class Menu {
constructor({ options, editorView }) {
this.options = {
...{
element: null,
onUpdate: () => false,
},
...options,
}
this.editorView = editorView
this.isActive = false
this.left = 0
this.bottom = 0
constructor({ options, editorView }) {
this.options = {
...{
element: null,
onUpdate: () => false,
},
...options,
}
this.editorView = editorView
this.isActive = false
this.left = 0
this.bottom = 0
this.editorView.dom.addEventListener('blur', this.hide.bind(this))
}
this.editorView.dom.addEventListener('blur', this.hide.bind(this))
}
update(view, lastState) {
const { state } = view
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
}
// 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
}
// Hide the tooltip if the selection is empty
if (state.selection.empty) {
this.hide()
return
}
// Otherwise, reposition it and update its content
const { from, to } = state.selection
// Otherwise, reposition it and update its content
const { from, to } = state.selection
// These are in screen coordinates
const start = view.coordsAtPos(from)
const end = view.coordsAtPos(to)
// 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.options.element.offsetParent.getBoundingClientRect()
// The box in which the tooltip is positioned, to use as base
const box = this.options.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)
// 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.isActive = true
this.left = parseInt(left - box.left, 10)
this.bottom = parseInt(box.bottom - start.top, 10)
this.isActive = true
this.left = parseInt(left - box.left, 10)
this.bottom = parseInt(box.bottom - start.top, 10)
this.sendUpdate()
}
this.sendUpdate()
}
sendUpdate() {
this.options.onUpdate({
isActive: this.isActive,
left: this.left,
bottom: this.bottom,
})
}
sendUpdate() {
this.options.onUpdate({
isActive: this.isActive,
left: this.left,
bottom: this.bottom,
})
}
hide(event) {
if (event && event.relatedTarget) {
return
}
hide(event) {
if (event && event.relatedTarget) {
return
}
this.isActive = false
this.sendUpdate()
}
this.isActive = false
this.sendUpdate()
}
destroy() {
this.editorView.dom.removeEventListener('blur', this.hide)
}
destroy() {
this.editorView.dom.removeEventListener('blur', this.hide)
}
}
export default function (options) {
return new Plugin({
view(editorView) {
return new Menu({ editorView, options })
},
})
return new Plugin({
view(editorView) {
return new Menu({ editorView, options })
},
})
}

View File

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

View File

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

View File

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

View File

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