Merge branch 'master' into issue-232
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tiptap-commands",
|
||||
"version": "1.7.1",
|
||||
"version": "1.9.1",
|
||||
"description": "Commands for tiptap",
|
||||
"homepage": "https://tiptap.scrumpy.io",
|
||||
"license": "MIT",
|
||||
@@ -22,9 +22,9 @@
|
||||
"dependencies": {
|
||||
"prosemirror-commands": "^1.0.7",
|
||||
"prosemirror-inputrules": "^1.0.1",
|
||||
"prosemirror-schema-list": "^1.0.2",
|
||||
"prosemirror-schema-list": "^1.0.3",
|
||||
"prosemirror-state": "^1.2.2",
|
||||
"prosemirror-model": "^1.7.0",
|
||||
"tiptap-utils": "^1.3.0"
|
||||
"tiptap-utils": "^1.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ index = $pos.index(d)
|
||||
// this is a copy of splitListItem
|
||||
// see https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.js
|
||||
|
||||
export default function splitListItem(itemType) {
|
||||
return function _splitListItem(state, dispatch) {
|
||||
export default function splitToDefaultListItem(itemType) {
|
||||
return function (state, dispatch) {
|
||||
const { $from, $to, node } = state.selection
|
||||
if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to)) return false
|
||||
const grandParent = $from.node(-1)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tiptap-extensions",
|
||||
"version": "1.16.2",
|
||||
"version": "1.18.1",
|
||||
"description": "Extensions for tiptap",
|
||||
"homepage": "https://tiptap.scrumpy.io",
|
||||
"license": "MIT",
|
||||
@@ -22,13 +22,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"lowlight": "^1.11.0",
|
||||
"prosemirror-collab": "^1.1.1",
|
||||
"prosemirror-history": "^1.0.4",
|
||||
"prosemirror-model": "^1.7.0",
|
||||
"prosemirror-state": "^1.2.2",
|
||||
"prosemirror-tables": "^0.7.10",
|
||||
"prosemirror-tables": "^0.7.11",
|
||||
"prosemirror-transform": "^1.1.3",
|
||||
"prosemirror-utils": "^0.7.6",
|
||||
"prosemirror-view": "^1.8.9",
|
||||
"tiptap": "^1.16.2",
|
||||
"tiptap-commands": "^1.7.1"
|
||||
"prosemirror-view": "^1.9.1",
|
||||
"tiptap": "^1.18.1",
|
||||
"tiptap-commands": "^1.9.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^2.5.17",
|
||||
|
||||
78
packages/tiptap-extensions/src/extensions/Collaboration.js
Normal file
78
packages/tiptap-extensions/src/extensions/Collaboration.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Extension } from 'tiptap'
|
||||
import { Step } from 'prosemirror-transform'
|
||||
import {
|
||||
collab,
|
||||
sendableSteps,
|
||||
getVersion,
|
||||
receiveTransaction,
|
||||
} from 'prosemirror-collab'
|
||||
|
||||
export default class Collaboration extends Extension {
|
||||
|
||||
get name() {
|
||||
return 'collaboration'
|
||||
}
|
||||
|
||||
init() {
|
||||
this.getSendableSteps = this.debounce(state => {
|
||||
const sendable = sendableSteps(state)
|
||||
|
||||
if (sendable) {
|
||||
this.options.onSendable({
|
||||
version: sendable.version,
|
||||
steps: sendable.steps.map(step => step.toJSON()),
|
||||
clientID: sendable.clientID,
|
||||
})
|
||||
}
|
||||
}, this.options.debounce)
|
||||
|
||||
this.editor.on('update', ({ state }) => {
|
||||
this.getSendableSteps(state)
|
||||
})
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return {
|
||||
version: 0,
|
||||
clientID: Math.floor(Math.random() * 0xFFFFFFFF),
|
||||
debounce: 250,
|
||||
onSendable: () => {},
|
||||
update: ({ steps, version }) => {
|
||||
const { state, view, schema } = this.editor
|
||||
|
||||
if (getVersion(state) > version) {
|
||||
return
|
||||
}
|
||||
|
||||
view.dispatch(receiveTransaction(
|
||||
state,
|
||||
steps.map(item => Step.fromJSON(schema, item.step)),
|
||||
steps.map(item => item.clientID),
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return [
|
||||
collab({
|
||||
version: this.options.version,
|
||||
clientID: this.options.clientID,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
debounce(fn, delay) {
|
||||
let timeout
|
||||
return function (...args) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
timeout = setTimeout(() => {
|
||||
fn(...args)
|
||||
timeout = null
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export { default as Link } from './marks/Link'
|
||||
export { default as Strike } from './marks/Strike'
|
||||
export { default as Underline } from './marks/Underline'
|
||||
|
||||
export { default as Collaboration } from './extensions/Collaboration'
|
||||
export { default as History } from './extensions/History'
|
||||
export { default as Placeholder } from './extensions/Placeholder'
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export default class Link extends Mark {
|
||||
pasteRules({ type }) {
|
||||
return [
|
||||
pasteRule(
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/g,
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-zA-Z]{2,}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/g,
|
||||
type,
|
||||
url => ({ href: url }),
|
||||
),
|
||||
@@ -59,7 +59,7 @@ export default class Link extends Mark {
|
||||
const { schema } = view.state
|
||||
const attrs = getMarkAttrs(view.state, schema.marks.link)
|
||||
|
||||
if (attrs.href) {
|
||||
if (attrs.href && event.target instanceof HTMLAnchorElement) {
|
||||
event.stopPropagation()
|
||||
window.open(attrs.href)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tiptap-utils",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.1",
|
||||
"description": "Utility functions for tiptap",
|
||||
"homepage": "https://tiptap.scrumpy.io",
|
||||
"license": "MIT",
|
||||
@@ -22,7 +22,7 @@
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.7.0",
|
||||
"prosemirror-state": "^1.2.2",
|
||||
"prosemirror-tables": "^0.7.9",
|
||||
"prosemirror-tables": "^0.7.11",
|
||||
"prosemirror-utils": "^0.7.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,18 @@ export default function ($pos = null, type = null) {
|
||||
|
||||
let startIndex = $pos.index()
|
||||
let startPos = $pos.start() + start.offset
|
||||
let endIndex = startIndex + 1
|
||||
let endPos = startPos + start.node.nodeSize
|
||||
|
||||
while (startIndex > 0 && link.isInSet($pos.parent.child(startIndex - 1).marks)) {
|
||||
startIndex -= 1
|
||||
startPos -= $pos.parent.child(startIndex).nodeSize
|
||||
}
|
||||
|
||||
// const endIndex = $pos.indexAfter()
|
||||
const endPos = startPos + start.node.nodeSize
|
||||
|
||||
// disable for now. see #156
|
||||
// while (endIndex < $pos.parent.childCount && link.isInSet($pos.parent.child(endIndex).marks)) {
|
||||
// endPos += $pos.parent.child(endIndex).nodeSize
|
||||
// endIndex += 1
|
||||
// }
|
||||
while (endIndex < $pos.parent.childCount && link.isInSet($pos.parent.child(endIndex).marks)) {
|
||||
endPos += $pos.parent.child(endIndex).nodeSize
|
||||
endIndex += 1
|
||||
}
|
||||
|
||||
return { from: startPos, to: endPos }
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { findParentNode } from 'prosemirror-utils'
|
||||
import {
|
||||
findParentNode,
|
||||
findSelectedNodeOfType,
|
||||
} from 'prosemirror-utils'
|
||||
|
||||
export default function (state, type, attrs = {}) {
|
||||
const predicate = node => node.type === type
|
||||
const parent = findParentNode(predicate)(state.selection)
|
||||
const node = findSelectedNodeOfType(type)(state.selection)
|
||||
|| findParentNode(predicate)(state.selection)
|
||||
|
||||
if (!Object.keys(attrs).length || !parent) {
|
||||
return !!parent
|
||||
if (!Object.keys(attrs).length || !node) {
|
||||
return !!node
|
||||
}
|
||||
|
||||
return parent.node.hasMarkup(type, attrs)
|
||||
return node.node.hasMarkup(type, attrs)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tiptap",
|
||||
"version": "1.16.2",
|
||||
"version": "1.18.1",
|
||||
"description": "A rich-text editor for Vue.js",
|
||||
"homepage": "https://tiptap.scrumpy.io",
|
||||
"license": "MIT",
|
||||
@@ -26,10 +26,10 @@
|
||||
"prosemirror-inputrules": "^1.0.1",
|
||||
"prosemirror-keymap": "^1.0.1",
|
||||
"prosemirror-model": "^1.7.0",
|
||||
"prosemirror-state": "^1.2.1",
|
||||
"prosemirror-view": "^1.8.9",
|
||||
"tiptap-commands": "^1.7.1",
|
||||
"tiptap-utils": "^1.3.0"
|
||||
"prosemirror-state": "^1.2.2",
|
||||
"prosemirror-view": "^1.9.1",
|
||||
"tiptap-commands": "^1.9.1",
|
||||
"tiptap-utils": "^1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^2.5.17",
|
||||
|
||||
@@ -7,6 +7,10 @@ export default {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
keepInBounds: {
|
||||
default: true,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
@@ -27,6 +31,7 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
editor.registerPlugin(MenuBubble({
|
||||
element: this.$el,
|
||||
keepInBounds: this.keepInBounds,
|
||||
onUpdate: menu => {
|
||||
// the second check ensures event is fired only once
|
||||
if (menu.isActive && this.menu.isActive === false) {
|
||||
|
||||
@@ -12,12 +12,16 @@ import { keymap } from 'prosemirror-keymap'
|
||||
import { baseKeymap } from 'prosemirror-commands'
|
||||
import { inputRules, undoInputRule } from 'prosemirror-inputrules'
|
||||
import { markIsActive, nodeIsActive, getMarkAttrs } from 'tiptap-utils'
|
||||
import { ExtensionManager, ComponentView } from './Utils'
|
||||
import {
|
||||
camelCase, Emitter, ExtensionManager, ComponentView,
|
||||
} from './Utils'
|
||||
import { Doc, Paragraph, Text } from './Nodes'
|
||||
|
||||
export default class Editor {
|
||||
export default class Editor extends Emitter {
|
||||
|
||||
constructor(options = {}) {
|
||||
super()
|
||||
|
||||
this.defaultOptions = {
|
||||
editorProps: {},
|
||||
editable: true,
|
||||
@@ -41,6 +45,15 @@ export default class Editor {
|
||||
onDrop: () => {},
|
||||
}
|
||||
|
||||
this.events = [
|
||||
'init',
|
||||
'update',
|
||||
'focus',
|
||||
'blur',
|
||||
'paste',
|
||||
'drop',
|
||||
]
|
||||
|
||||
this.init(options)
|
||||
}
|
||||
|
||||
@@ -58,7 +71,6 @@ export default class Editor {
|
||||
this.keymaps = this.createKeymaps()
|
||||
this.inputRules = this.createInputRules()
|
||||
this.pasteRules = this.createPasteRules()
|
||||
this.state = this.createState()
|
||||
this.view = this.createView()
|
||||
this.commands = this.createCommands()
|
||||
this.setActiveNodesAndMarks()
|
||||
@@ -69,7 +81,11 @@ export default class Editor {
|
||||
}, 10)
|
||||
}
|
||||
|
||||
this.options.onInit({
|
||||
this.events.forEach(name => {
|
||||
this.on(name, this.options[camelCase(`on ${name}`)])
|
||||
})
|
||||
|
||||
this.emit('init', {
|
||||
view: this.view,
|
||||
state: this.state,
|
||||
})
|
||||
@@ -101,11 +117,15 @@ export default class Editor {
|
||||
]
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.view ? this.view.state : null
|
||||
}
|
||||
|
||||
createExtensions() {
|
||||
return new ExtensionManager([
|
||||
...this.builtInExtensions,
|
||||
...this.options.extensions,
|
||||
])
|
||||
], this)
|
||||
}
|
||||
|
||||
createPlugins() {
|
||||
@@ -216,21 +236,21 @@ export default class Editor {
|
||||
|
||||
createView() {
|
||||
const view = new EditorView(this.element, {
|
||||
state: this.state,
|
||||
handlePaste: this.options.onPaste,
|
||||
handleDrop: this.options.onDrop,
|
||||
state: this.createState(),
|
||||
handlePaste: (...args) => { this.emit('paste', ...args) },
|
||||
handleDrop: (...args) => { this.emit('drop', ...args) },
|
||||
dispatchTransaction: this.dispatchTransaction.bind(this),
|
||||
})
|
||||
|
||||
view.dom.style.whiteSpace = 'pre-wrap'
|
||||
|
||||
view.dom.addEventListener('focus', event => this.options.onFocus({
|
||||
view.dom.addEventListener('focus', event => this.emit('focus', {
|
||||
event,
|
||||
state: this.state,
|
||||
view: this.view,
|
||||
}))
|
||||
|
||||
view.dom.addEventListener('blur', event => this.options.onBlur({
|
||||
view.dom.addEventListener('blur', event => this.emit('blur', {
|
||||
event,
|
||||
state: this.state,
|
||||
view: this.view,
|
||||
@@ -283,8 +303,8 @@ export default class Editor {
|
||||
}
|
||||
|
||||
dispatchTransaction(transaction) {
|
||||
this.state = this.state.apply(transaction)
|
||||
this.view.updateState(this.state)
|
||||
const newState = this.state.apply(transaction)
|
||||
this.view.updateState(newState)
|
||||
this.setActiveNodesAndMarks()
|
||||
|
||||
if (!transaction.docChanged) {
|
||||
@@ -295,7 +315,7 @@ export default class Editor {
|
||||
}
|
||||
|
||||
emitUpdate(transaction) {
|
||||
this.options.onUpdate({
|
||||
this.emit('update', {
|
||||
getHTML: this.getHTML.bind(this),
|
||||
getJSON: this.getJSON.bind(this),
|
||||
state: this.state,
|
||||
@@ -325,6 +345,13 @@ export default class Editor {
|
||||
this.view.dom.blur()
|
||||
}
|
||||
|
||||
getSchemaJSON() {
|
||||
return JSON.parse(JSON.stringify({
|
||||
nodes: this.extensions.nodes,
|
||||
marks: this.extensions.marks,
|
||||
}))
|
||||
}
|
||||
|
||||
getHTML() {
|
||||
const div = document.createElement('div')
|
||||
const fragment = DOMSerializer
|
||||
@@ -341,13 +368,13 @@ export default class Editor {
|
||||
}
|
||||
|
||||
setContent(content = {}, emitUpdate = false, parseOptions) {
|
||||
this.state = EditorState.create({
|
||||
const newState = EditorState.create({
|
||||
schema: this.state.schema,
|
||||
doc: this.createDocument(content, parseOptions),
|
||||
plugins: this.state.plugins,
|
||||
})
|
||||
|
||||
this.view.updateState(this.state)
|
||||
this.view.updateState(newState)
|
||||
|
||||
if (emitUpdate) {
|
||||
this.emitUpdate()
|
||||
@@ -402,10 +429,11 @@ export default class Editor {
|
||||
return
|
||||
}
|
||||
|
||||
this.state = this.state.reconfigure({
|
||||
const newState = this.state.reconfigure({
|
||||
plugins: this.state.plugins.concat([plugin]),
|
||||
})
|
||||
this.view.updateState(this.state)
|
||||
this.view.updateState(newState)
|
||||
this.view.updatePluginViews()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
||||
@@ -1,11 +1,63 @@
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
|
||||
function textRange(node, from, to) {
|
||||
const range = document.createRange()
|
||||
range.setEnd(node, to == null ? node.nodeValue.length : to)
|
||||
range.setStart(node, from || 0)
|
||||
return range
|
||||
}
|
||||
|
||||
function singleRect(object, bias) {
|
||||
const rects = object.getClientRects()
|
||||
return !rects.length ? object.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1]
|
||||
}
|
||||
|
||||
function coordsAtPos(view, pos, end = false) {
|
||||
const { node, offset } = view.docView.domFromPos(pos)
|
||||
let side
|
||||
let rect
|
||||
if (node.nodeType === 3) {
|
||||
if (end && offset < node.nodeValue.length) {
|
||||
rect = singleRect(textRange(node, offset - 1, offset), -1)
|
||||
side = 'right'
|
||||
} else if (offset < node.nodeValue.length) {
|
||||
rect = singleRect(textRange(node, offset, offset + 1), -1)
|
||||
side = 'left'
|
||||
}
|
||||
} else if (node.firstChild) {
|
||||
if (offset < node.childNodes.length) {
|
||||
const child = node.childNodes[offset]
|
||||
rect = singleRect(child.nodeType === 3 ? textRange(child) : child, -1)
|
||||
side = 'left'
|
||||
}
|
||||
if ((!rect || rect.top === rect.bottom) && offset) {
|
||||
const child = node.childNodes[offset - 1]
|
||||
rect = singleRect(child.nodeType === 3 ? textRange(child) : child, 1)
|
||||
side = 'right'
|
||||
}
|
||||
} else {
|
||||
rect = node.getBoundingClientRect()
|
||||
side = 'left'
|
||||
}
|
||||
|
||||
const x = rect[side]
|
||||
|
||||
return {
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
left: x,
|
||||
right: x,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Menu {
|
||||
|
||||
constructor({ options, editorView }) {
|
||||
this.options = {
|
||||
...{
|
||||
element: null,
|
||||
keepInBounds: true,
|
||||
onUpdate: () => false,
|
||||
},
|
||||
...options,
|
||||
@@ -36,19 +88,24 @@ class Menu {
|
||||
const { from, to } = state.selection
|
||||
|
||||
// These are in screen coordinates
|
||||
const start = view.coordsAtPos(from)
|
||||
const end = view.coordsAtPos(to)
|
||||
// We can't use EditorView.cordsAtPos here because it can't handle linebreaks correctly
|
||||
// See: https://github.com/ProseMirror/prosemirror-view/pull/47
|
||||
const start = coordsAtPos(view, from)
|
||||
const end = coordsAtPos(view, to, true)
|
||||
|
||||
// The box in which the tooltip is positioned, to use as base
|
||||
const box = this.options.element.offsetParent.getBoundingClientRect()
|
||||
const el = this.options.element.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)
|
||||
const left = ((start.left + end.left) / 2) - box.left
|
||||
|
||||
// Keep the menuBubble in the bounding box of the offsetParent i
|
||||
this.left = Math.round(this.options.keepInBounds
|
||||
? Math.min(box.width - (el.width / 2), Math.max(left, el.width / 2)) : left)
|
||||
this.bottom = Math.round(box.bottom - start.top)
|
||||
this.isActive = true
|
||||
this.left = parseInt(left - box.left, 10)
|
||||
this.bottom = parseInt(box.bottom - start.top, 10)
|
||||
|
||||
this.sendUpdate()
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ export default class ComponentView {
|
||||
}
|
||||
|
||||
updateComponentProps(props) {
|
||||
if (!this.vm._props) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update props in component
|
||||
// TODO: Avoid mutating a prop directly.
|
||||
// Maybe there is a better way to do this?
|
||||
|
||||
45
packages/tiptap/src/Utils/Emitter.js
Normal file
45
packages/tiptap/src/Utils/Emitter.js
Normal file
@@ -0,0 +1,45 @@
|
||||
export default class Emitter {
|
||||
// Add an event listener for given event
|
||||
on(event, fn) {
|
||||
this._callbacks = this._callbacks || {}
|
||||
// Create namespace for this event
|
||||
if (!this._callbacks[event]) {
|
||||
this._callbacks[event] = []
|
||||
}
|
||||
this._callbacks[event].push(fn)
|
||||
return this
|
||||
}
|
||||
|
||||
emit(event, ...args) {
|
||||
this._callbacks = this._callbacks || {}
|
||||
const callbacks = this._callbacks[event]
|
||||
|
||||
if (callbacks) {
|
||||
callbacks.forEach(callback => callback.apply(this, args))
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// Remove event listener for given event.
|
||||
// If fn is not provided, all event listeners for that event will be removed.
|
||||
// If neither is provided, all event listeners will be removed.
|
||||
off(event, fn) {
|
||||
|
||||
if (!arguments.length) {
|
||||
this._callbacks = {}
|
||||
} else {
|
||||
// event listeners for the given event
|
||||
const callbacks = this._callbacks ? this._callbacks[event] : null
|
||||
if (callbacks) {
|
||||
if (fn) {
|
||||
this._callbacks[event] = callbacks.filter(cb => cb !== fn) // remove specific handler
|
||||
} else {
|
||||
delete this._callbacks[event] // remove all handlers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,14 @@ export default class Extension {
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
return null
|
||||
}
|
||||
|
||||
bindEditor(editor = null) {
|
||||
this.editor = editor
|
||||
}
|
||||
|
||||
get name() {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ import { keymap } from 'prosemirror-keymap'
|
||||
|
||||
export default class ExtensionManager {
|
||||
|
||||
constructor(extensions = []) {
|
||||
constructor(extensions = [], editor) {
|
||||
extensions.forEach(extension => {
|
||||
extension.bindEditor(editor)
|
||||
extension.init()
|
||||
})
|
||||
this.extensions = extensions
|
||||
}
|
||||
|
||||
@@ -133,44 +137,28 @@ export default class ExtensionManager {
|
||||
} : {},
|
||||
})
|
||||
|
||||
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)
|
||||
const apply = (cb, attrs) => {
|
||||
if (!editable) {
|
||||
return false
|
||||
}
|
||||
} else if (typeof value === 'object') {
|
||||
view.focus()
|
||||
return cb(attrs)(view.state, view.dispatch, view)
|
||||
}
|
||||
|
||||
const handle = (_name, _value) => {
|
||||
if (Array.isArray(_value)) {
|
||||
commands[_name] = attrs => _value.forEach(callback => apply(callback, attrs))
|
||||
} else if (typeof _value === 'function') {
|
||||
commands[_name] = attrs => apply(_value, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
handle(commandName, commandValue)
|
||||
})
|
||||
} else {
|
||||
handle(name, value)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
3
packages/tiptap/src/Utils/camelCase.js
Normal file
3
packages/tiptap/src/Utils/camelCase.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function (str) {
|
||||
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => (index === 0 ? word.toLowerCase() : word.toUpperCase())).replace(/\s+/g, '')
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export { default as camelCase } from './camelCase'
|
||||
export { default as ComponentView } from './ComponentView'
|
||||
export { default as Emitter } from './Emitter'
|
||||
export { default as Extension } from './Extension'
|
||||
export { default as ExtensionManager } from './ExtensionManager'
|
||||
export { default as Mark } from './Mark'
|
||||
|
||||
Reference in New Issue
Block a user