move commands again
This commit is contained in:
9
packages/core/src/commands/blur.ts
Normal file
9
packages/core/src/commands/blur.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (): Command => ({ view }) => {
|
||||
const element = view.dom as HTMLElement
|
||||
|
||||
element.blur()
|
||||
|
||||
return true
|
||||
}
|
||||
5
packages/core/src/commands/clearContent.ts
Normal file
5
packages/core/src/commands/clearContent.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (emitUpdate: Boolean = false): Command => ({ commands }) => {
|
||||
return commands.setContent('', emitUpdate)
|
||||
}
|
||||
29
packages/core/src/commands/clearNodes.ts
Normal file
29
packages/core/src/commands/clearNodes.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { liftTarget } from 'prosemirror-transform'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (): Command => ({ state, tr, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const { from, to } = selection
|
||||
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (!node.type.isText) {
|
||||
const fromPos = tr.doc.resolve(tr.mapping.map(pos + 1))
|
||||
const toPos = tr.doc.resolve(tr.mapping.map(pos + node.nodeSize - 1))
|
||||
const nodeRange = fromPos.blockRange(toPos)
|
||||
|
||||
if (nodeRange) {
|
||||
const targetLiftDepth = liftTarget(nodeRange)
|
||||
|
||||
if (node.type.isTextblock && dispatch) {
|
||||
tr.setNodeMarkup(nodeRange.start, state.schema.nodes.paragraph)
|
||||
}
|
||||
|
||||
if ((targetLiftDepth || targetLiftDepth === 0) && dispatch) {
|
||||
tr.lift(nodeRange, targetLiftDepth)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
6
packages/core/src/commands/deleteSelection.ts
Normal file
6
packages/core/src/commands/deleteSelection.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { deleteSelection } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (): Command => ({ state, dispatch }) => {
|
||||
return deleteSelection(state, dispatch)
|
||||
}
|
||||
58
packages/core/src/commands/focus.ts
Normal file
58
packages/core/src/commands/focus.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { TextSelection } from 'prosemirror-state'
|
||||
import { Editor, Command } from '../Editor'
|
||||
import minMax from '../utils/minMax'
|
||||
|
||||
type Position = 'start' | 'end' | number | boolean | null
|
||||
|
||||
interface ResolvedSelection {
|
||||
from: number,
|
||||
to: number,
|
||||
}
|
||||
|
||||
function resolveSelection(editor: Editor, position: Position = null): ResolvedSelection {
|
||||
if (position === null) {
|
||||
return editor.selection
|
||||
}
|
||||
|
||||
if (position === 'start' || position === true) {
|
||||
return {
|
||||
from: 0,
|
||||
to: 0,
|
||||
}
|
||||
}
|
||||
|
||||
if (position === 'end') {
|
||||
const { size } = editor.state.doc.content
|
||||
|
||||
return {
|
||||
from: size,
|
||||
to: size - 1, // TODO: -1 only for nodes with content
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
from: position as number,
|
||||
to: position as number,
|
||||
}
|
||||
}
|
||||
|
||||
export default (position: Position = null): Command => ({
|
||||
editor, view, tr, dispatch,
|
||||
}) => {
|
||||
if ((view.hasFocus() && position === null) || position === false) {
|
||||
return true
|
||||
}
|
||||
|
||||
const { from, to } = resolveSelection(editor, position)
|
||||
const { doc } = tr
|
||||
const resolvedFrom = minMax(from, 0, doc.content.size)
|
||||
const resolvedEnd = minMax(to, 0, doc.content.size)
|
||||
const selection = TextSelection.create(doc, resolvedFrom, resolvedEnd)
|
||||
|
||||
if (dispatch) {
|
||||
tr.setSelection(selection)
|
||||
view.focus()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { Selection, Transaction } from 'prosemirror-state'
|
||||
import { ReplaceStep, ReplaceAroundStep } from 'prosemirror-transform'
|
||||
import elementFromString from '../utils/elementFromString'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
// TODO: move to utils
|
||||
// https://github.com/ProseMirror/prosemirror-state/blob/master/src/selection.js#L466
|
||||
@@ -18,27 +17,15 @@ function selectionToInsertionEnd(tr: Transaction, startLen: number, bias: number
|
||||
tr.setSelection(Selection.near(tr.doc.resolve(end as unknown as number), bias))
|
||||
}
|
||||
|
||||
export const InsertHTML = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
insertHTML: (value: string): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const element = elementFromString(value)
|
||||
const slice = DOMParser.fromSchema(state.schema).parseSlice(element)
|
||||
export default (value: string): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const element = elementFromString(value)
|
||||
const slice = DOMParser.fromSchema(state.schema).parseSlice(element)
|
||||
|
||||
if (dispatch) {
|
||||
tr.insert(selection.anchor, slice.content)
|
||||
selectionToInsertionEnd(tr, tr.steps.length - 1, -1)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
InsertHTML: typeof InsertHTML,
|
||||
if (dispatch) {
|
||||
tr.insert(selection.anchor, slice.content)
|
||||
selectionToInsertionEnd(tr, tr.steps.length - 1, -1)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
9
packages/core/src/commands/insertText.ts
Normal file
9
packages/core/src/commands/insertText.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (value: string): Command => ({ tr, dispatch }) => {
|
||||
if (dispatch) {
|
||||
tr.insertText(value)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
10
packages/core/src/commands/liftListItem.ts
Normal file
10
packages/core/src/commands/liftListItem.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { liftListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export default (typeOrName: string | NodeType): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return liftListItem(type)(state, dispatch)
|
||||
}
|
||||
26
packages/core/src/commands/removeMark.ts
Normal file
26
packages/core/src/commands/removeMark.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
import getMarkRange from '../utils/getMarkRange'
|
||||
|
||||
export default (typeOrName: string | MarkType): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
let { from, to } = selection
|
||||
const { $from, empty } = selection
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRange($from, type)
|
||||
|
||||
if (range) {
|
||||
from = range.from
|
||||
to = range.to
|
||||
}
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
tr.removeMark(from, to, type)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
20
packages/core/src/commands/removeMarks.ts
Normal file
20
packages/core/src/commands/removeMarks.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const { from, to, empty } = selection
|
||||
|
||||
if (empty) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
Object
|
||||
.entries(state.schema.marks)
|
||||
.forEach(([, mark]) => {
|
||||
tr.removeMark(from, to, mark as any)
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
23
packages/core/src/commands/resetNodeAttributes.ts
Normal file
23
packages/core/src/commands/resetNodeAttributes.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (attributeNames: string[] = []): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const { from, to } = selection
|
||||
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (!node.type.isText) {
|
||||
attributeNames.forEach(name => {
|
||||
const attribute = node.type.spec.attrs?.[name]
|
||||
const defaultValue = attribute?.default
|
||||
|
||||
if (attribute && defaultValue !== undefined && dispatch) {
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
[name]: defaultValue,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
9
packages/core/src/commands/scrollIntoView.ts
Normal file
9
packages/core/src/commands/scrollIntoView.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (): Command => ({ tr, dispatch }) => {
|
||||
if (dispatch) {
|
||||
tr.scrollIntoView()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
6
packages/core/src/commands/selectAll.ts
Normal file
6
packages/core/src/commands/selectAll.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { selectAll } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (): Command => ({ state, dispatch }) => {
|
||||
return selectAll(state, dispatch)
|
||||
}
|
||||
6
packages/core/src/commands/selectParentNode.ts
Normal file
6
packages/core/src/commands/selectParentNode.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { selectParentNode } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (): Command => ({ state, dispatch }) => {
|
||||
return selectParentNode(state, dispatch)
|
||||
}
|
||||
10
packages/core/src/commands/setBlockType.ts
Normal file
10
packages/core/src/commands/setBlockType.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { setBlockType } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export default (typeOrName: string | NodeType, attrs = {}): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return setBlockType(type, attrs)(state, dispatch)
|
||||
}
|
||||
17
packages/core/src/commands/setContent.ts
Normal file
17
packages/core/src/commands/setContent.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { TextSelection } from 'prosemirror-state'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (content: string, emitUpdate: Boolean = false, parseOptions = {}): Command => ({ tr, editor, dispatch }) => {
|
||||
const { createDocument } = editor
|
||||
const { doc } = tr
|
||||
const document = createDocument(content, parseOptions)
|
||||
const selection = TextSelection.create(doc, 0, doc.content.size)
|
||||
|
||||
if (dispatch) {
|
||||
tr.setSelection(selection)
|
||||
.replaceSelectionWith(document, false)
|
||||
.setMeta('preventUpdate', !emitUpdate)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
14
packages/core/src/commands/setNodeAttributes.ts
Normal file
14
packages/core/src/commands/setNodeAttributes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (attributes: {}): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const { from, to } = selection
|
||||
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (!node.type.isText && dispatch) {
|
||||
tr.setNodeMarkup(pos, undefined, attributes)
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
10
packages/core/src/commands/sinkListItem.ts
Normal file
10
packages/core/src/commands/sinkListItem.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { sinkListItem as originalSinkListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export default (typeOrName: string | NodeType): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return originalSinkListItem(type)(state, dispatch)
|
||||
}
|
||||
117
packages/core/src/commands/splitBlock.ts
Normal file
117
packages/core/src/commands/splitBlock.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { canSplit } from 'prosemirror-transform'
|
||||
import { ContentMatch, Fragment } from 'prosemirror-model'
|
||||
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state'
|
||||
import { Command } from '../Editor'
|
||||
|
||||
function defaultBlockAt(match: ContentMatch) {
|
||||
for (let i = 0; i < match.edgeCount; i + 1) {
|
||||
const { type } = match.edge(i)
|
||||
// @ts-ignore
|
||||
if (type.isTextblock && !type.hasRequiredAttrs()) return type
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export interface SplitBlockOptions {
|
||||
withAttributes: boolean,
|
||||
withMarks: boolean,
|
||||
}
|
||||
|
||||
function keepMarks(state: EditorState) {
|
||||
const marks = state.storedMarks
|
||||
|| (state.selection.$to.parentOffset && state.selection.$from.marks())
|
||||
|
||||
if (marks) {
|
||||
state.tr.ensureMarks(marks)
|
||||
}
|
||||
}
|
||||
|
||||
export default (options: Partial<SplitBlockOptions> = {}): Command => ({ tr, state, dispatch }) => {
|
||||
const defaultOptions: SplitBlockOptions = {
|
||||
withAttributes: false,
|
||||
withMarks: true,
|
||||
}
|
||||
const config = { ...defaultOptions, ...options }
|
||||
const { selection, doc } = tr
|
||||
const { $from, $to } = selection
|
||||
|
||||
if (selection instanceof NodeSelection && selection.node.isBlock) {
|
||||
if (!$from.parentOffset || !canSplit(doc, $from.pos)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
if (config.withMarks) {
|
||||
keepMarks(state)
|
||||
}
|
||||
|
||||
tr.split($from.pos).scrollIntoView()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (!$from.parent.isBlock) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
const atEnd = $to.parentOffset === $to.parent.content.size
|
||||
|
||||
if (selection instanceof TextSelection) {
|
||||
tr.deleteSelection()
|
||||
}
|
||||
|
||||
const deflt = $from.depth === 0
|
||||
? undefined
|
||||
: defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)))
|
||||
|
||||
let types = atEnd && deflt
|
||||
? [{
|
||||
type: deflt,
|
||||
attrs: config.withAttributes
|
||||
? $from.node().attrs
|
||||
: {},
|
||||
}]
|
||||
: undefined
|
||||
|
||||
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types)
|
||||
|
||||
if (
|
||||
!types
|
||||
&& !can
|
||||
&& canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : undefined)
|
||||
) {
|
||||
can = true
|
||||
types = deflt
|
||||
? [{
|
||||
type: deflt,
|
||||
attrs: config.withAttributes
|
||||
? $from.node().attrs
|
||||
: {},
|
||||
}]
|
||||
: undefined
|
||||
}
|
||||
|
||||
if (can) {
|
||||
tr.split(tr.mapping.map($from.pos), 1, types)
|
||||
|
||||
if (
|
||||
!atEnd
|
||||
&& !$from.parentOffset
|
||||
&& $from.parent.type !== deflt
|
||||
&& $from.node(-1).canReplace($from.index(-1), $from.indexAfter(-1), Fragment.from(deflt?.create()))
|
||||
) {
|
||||
tr.setNodeMarkup(tr.mapping.map($from.before()), deflt || undefined)
|
||||
}
|
||||
}
|
||||
|
||||
if (config.withMarks) {
|
||||
keepMarks(state)
|
||||
}
|
||||
|
||||
tr.scrollIntoView()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
10
packages/core/src/commands/splitListItem.ts
Normal file
10
packages/core/src/commands/splitListItem.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { splitListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export default (typeOrName: string | NodeType): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return splitListItem(type)(state, dispatch)
|
||||
}
|
||||
16
packages/core/src/commands/toggleBlockType.ts
Normal file
16
packages/core/src/commands/toggleBlockType.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import nodeIsActive from '../utils/nodeIsActive'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export default (typeOrName: string | NodeType, toggleTypeOrName: string | NodeType, attrs = {}): Command => ({ state, commands }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
const toggleType = getNodeType(toggleTypeOrName, state.schema)
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
|
||||
if (isActive) {
|
||||
return commands.setBlockType(toggleType)
|
||||
}
|
||||
|
||||
return commands.setBlockType(type, attrs)
|
||||
}
|
||||
52
packages/core/src/commands/toggleList.ts
Normal file
52
packages/core/src/commands/toggleList.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { findParentNode } from 'prosemirror-utils'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
import isList from '../utils/isList'
|
||||
|
||||
export default (listTypeOrName: string | NodeType, itemTypeOrName: string | NodeType): Command => ({
|
||||
editor, tr, state, dispatch, chain, commands, can,
|
||||
}) => {
|
||||
const { extensions } = editor.options
|
||||
const listType = getNodeType(listTypeOrName, state.schema)
|
||||
const itemType = getNodeType(itemTypeOrName, state.schema)
|
||||
const { selection } = state
|
||||
const { $from, $to } = selection
|
||||
const range = $from.blockRange($to)
|
||||
|
||||
if (!range) {
|
||||
return false
|
||||
}
|
||||
|
||||
const parentList = findParentNode(node => isList(node.type.name, extensions))(selection)
|
||||
|
||||
if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
|
||||
// remove list
|
||||
if (parentList.node.type === listType) {
|
||||
return commands.liftListItem(itemType)
|
||||
}
|
||||
|
||||
// change list type
|
||||
if (
|
||||
isList(parentList.node.type.name, extensions)
|
||||
&& listType.validContent(parentList.node.content)
|
||||
&& dispatch
|
||||
) {
|
||||
tr.setNodeMarkup(parentList.pos, listType)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const canWrapInList = can().wrapInList(listType)
|
||||
|
||||
// try to convert node to paragraph if needed
|
||||
if (!canWrapInList) {
|
||||
return chain()
|
||||
.clearNodes()
|
||||
.wrapInList(listType)
|
||||
.run()
|
||||
}
|
||||
|
||||
return commands.wrapInList(listType)
|
||||
}
|
||||
10
packages/core/src/commands/toggleMark.ts
Normal file
10
packages/core/src/commands/toggleMark.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { toggleMark as originalToggleMark } from 'prosemirror-commands'
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
|
||||
export default (typeOrName: string | MarkType): Command => ({ state, dispatch }) => {
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
|
||||
return originalToggleMark(type)(state, dispatch)
|
||||
}
|
||||
16
packages/core/src/commands/toggleWrap.ts
Normal file
16
packages/core/src/commands/toggleWrap.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { wrapIn, lift } from 'prosemirror-commands'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import nodeIsActive from '../utils/nodeIsActive'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export default (typeOrName: string | NodeType, attrs = {}): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
|
||||
if (isActive) {
|
||||
return lift(state, dispatch)
|
||||
}
|
||||
|
||||
return wrapIn(type, attrs)(state, dispatch)
|
||||
}
|
||||
15
packages/core/src/commands/try.ts
Normal file
15
packages/core/src/commands/try.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Command } from '../Editor'
|
||||
|
||||
export default (commands: Command[] | ((props: Parameters<Command>[0]) => Command[])): Command => props => {
|
||||
const items = typeof commands === 'function'
|
||||
? commands(props)
|
||||
: commands
|
||||
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
if (items[i](props)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
32
packages/core/src/commands/updateMark.ts
Normal file
32
packages/core/src/commands/updateMark.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
import getMarkRange from '../utils/getMarkRange'
|
||||
|
||||
export default (typeOrName: string | MarkType, attrs: {}): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection, doc } = tr
|
||||
let { from, to } = selection
|
||||
const { $from, empty } = selection
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRange($from, type)
|
||||
|
||||
if (range) {
|
||||
from = range.from
|
||||
to = range.to
|
||||
}
|
||||
}
|
||||
|
||||
const hasMark = doc.rangeHasMark(from, to, type)
|
||||
|
||||
if (hasMark && dispatch) {
|
||||
tr.removeMark(from, to, type)
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
tr.addMark(from, to, type.create(attrs))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
10
packages/core/src/commands/wrapInList.ts
Normal file
10
packages/core/src/commands/wrapInList.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { wrapInList } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export default (typeOrName: string | NodeType, attrs?: {}): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return wrapInList(type, attrs)(state, dispatch)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const Blur = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
blur: (): Command => ({ view }) => {
|
||||
const element = view.dom as HTMLElement
|
||||
|
||||
element.blur()
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
Blur: typeof Blur,
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const ClearContent = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
clearContent: (emitUpdate: Boolean = false): Command => ({ commands }) => {
|
||||
return commands.setContent('', emitUpdate)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ClearContent: typeof ClearContent,
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { liftTarget } from 'prosemirror-transform'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const ClearNodes = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
clearNodes: (): Command => ({ state, tr, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const { from, to } = selection
|
||||
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (!node.type.isText) {
|
||||
const fromPos = tr.doc.resolve(tr.mapping.map(pos + 1))
|
||||
const toPos = tr.doc.resolve(tr.mapping.map(pos + node.nodeSize - 1))
|
||||
const nodeRange = fromPos.blockRange(toPos)
|
||||
|
||||
if (nodeRange) {
|
||||
const targetLiftDepth = liftTarget(nodeRange)
|
||||
|
||||
if (node.type.isTextblock && dispatch) {
|
||||
tr.setNodeMarkup(nodeRange.start, state.schema.nodes.paragraph)
|
||||
}
|
||||
|
||||
if ((targetLiftDepth || targetLiftDepth === 0) && dispatch) {
|
||||
tr.lift(nodeRange, targetLiftDepth)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ClearNodes: typeof ClearNodes,
|
||||
}
|
||||
}
|
||||
68
packages/core/src/extensions/commands.ts
Normal file
68
packages/core/src/extensions/commands.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { createExtension } from '../Extension'
|
||||
import blur from '../commands/blur'
|
||||
import clearContent from '../commands/clearContent'
|
||||
import clearNodes from '../commands/clearNodes'
|
||||
import deleteSelection from '../commands/deleteSelection'
|
||||
import focus from '../commands/focus'
|
||||
import insertHTML from '../commands/insertHTML'
|
||||
import insertText from '../commands/insertText'
|
||||
import liftListItem from '../commands/liftListItem'
|
||||
import removeMark from '../commands/removeMark'
|
||||
import removeMarks from '../commands/removeMarks'
|
||||
import resetNodeAttributes from '../commands/resetNodeAttributes'
|
||||
import scrollIntoView from '../commands/scrollIntoView'
|
||||
import selectAll from '../commands/selectAll'
|
||||
import selectParentNode from '../commands/selectParentNode'
|
||||
import setBlockType from '../commands/setBlockType'
|
||||
import setContent from '../commands/setContent'
|
||||
import setNodeAttributes from '../commands/setNodeAttributes'
|
||||
import sinkListItem from '../commands/sinkListItem'
|
||||
import splitBlock from '../commands/splitBlock'
|
||||
import splitListItem from '../commands/splitListItem'
|
||||
import toggleBlockType from '../commands/toggleBlockType'
|
||||
import toggleList from '../commands/toggleList'
|
||||
import toggleMark from '../commands/toggleMark'
|
||||
import toggleWrap from '../commands/toggleWrap'
|
||||
import tryCommand from '../commands/try'
|
||||
import updateMark from '../commands/updateMark'
|
||||
import wrapInList from '../commands/wrapInList'
|
||||
|
||||
export const Commands = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
blur,
|
||||
clearContent,
|
||||
clearNodes,
|
||||
deleteSelection,
|
||||
focus,
|
||||
insertHTML,
|
||||
insertText,
|
||||
liftListItem,
|
||||
removeMark,
|
||||
removeMarks,
|
||||
resetNodeAttributes,
|
||||
scrollIntoView,
|
||||
selectAll,
|
||||
selectParentNode,
|
||||
setBlockType,
|
||||
setContent,
|
||||
setNodeAttributes,
|
||||
sinkListItem,
|
||||
splitBlock,
|
||||
splitListItem,
|
||||
toggleBlockType,
|
||||
toggleList,
|
||||
toggleMark,
|
||||
toggleWrap,
|
||||
try: tryCommand,
|
||||
updateMark,
|
||||
wrapInList,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
Commands: typeof Commands,
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { deleteSelection } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const DeleteSelection = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
deleteSelection: (): Command => ({ state, dispatch }) => {
|
||||
return deleteSelection(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
DeleteSelection: typeof DeleteSelection,
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { TextSelection } from 'prosemirror-state'
|
||||
import { Editor, Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import minMax from '../utils/minMax'
|
||||
|
||||
type Position = 'start' | 'end' | number | boolean | null
|
||||
|
||||
interface ResolvedSelection {
|
||||
from: number,
|
||||
to: number,
|
||||
}
|
||||
|
||||
function resolveSelection(editor: Editor, position: Position = null): ResolvedSelection {
|
||||
if (position === null) {
|
||||
return editor.selection
|
||||
}
|
||||
|
||||
if (position === 'start' || position === true) {
|
||||
return {
|
||||
from: 0,
|
||||
to: 0,
|
||||
}
|
||||
}
|
||||
|
||||
if (position === 'end') {
|
||||
const { size } = editor.state.doc.content
|
||||
|
||||
return {
|
||||
from: size,
|
||||
to: size - 1, // TODO: -1 only for nodes with content
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
from: position as number,
|
||||
to: position as number,
|
||||
}
|
||||
}
|
||||
|
||||
export const Focus = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
focus: (position: Position = null): Command => ({
|
||||
editor, view, tr, dispatch,
|
||||
}) => {
|
||||
if ((view.hasFocus() && position === null) || position === false) {
|
||||
return true
|
||||
}
|
||||
|
||||
const { from, to } = resolveSelection(editor, position)
|
||||
const { doc } = tr
|
||||
const resolvedFrom = minMax(from, 0, doc.content.size)
|
||||
const resolvedEnd = minMax(to, 0, doc.content.size)
|
||||
const selection = TextSelection.create(doc, resolvedFrom, resolvedEnd)
|
||||
|
||||
if (dispatch) {
|
||||
tr.setSelection(selection)
|
||||
view.focus()
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
Focus: typeof Focus,
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,4 @@
|
||||
export { Blur } from './blur'
|
||||
export { ClearContent } from './clearContent'
|
||||
export { ClearNodes } from './clearNodes'
|
||||
export { DeleteSelection } from './deleteSelection'
|
||||
export { Commands } from './commands'
|
||||
export { Editable } from './editable'
|
||||
export { Focus } from './focus'
|
||||
export { FocusEvents } from './focusEvents'
|
||||
export { InsertHTML } from './insertHTML'
|
||||
export { InsertText } from './insertText'
|
||||
export { BaseKeymap } from './baseKeymap'
|
||||
export { LiftListItem } from './liftListItem'
|
||||
export { RemoveMark } from './removeMark'
|
||||
export { RemoveMarks } from './removeMarks'
|
||||
export { ScrollIntoView } from './scrollIntoView'
|
||||
export { SelectAll } from './selectAll'
|
||||
export { SelectParentNode } from './selectParentNode'
|
||||
export { ResetNodeAttributes } from './resetNodeAttributes'
|
||||
export { SetNodeAttributes } from './setNodeAttributes'
|
||||
export { SetBlockType } from './setBlockType'
|
||||
export { SetContent } from './setContent'
|
||||
export { SinkListItem } from './sinkListItem'
|
||||
export { SplitBlock } from './splitBlock'
|
||||
export { SplitListItem } from './splitListItem'
|
||||
export { ToggleBlockType } from './toggleBlockType'
|
||||
export { ToggleList } from './toggleList'
|
||||
export { ToggleMark } from './toggleMark'
|
||||
export { ToggleWrap } from './toggleWrap'
|
||||
export { Try } from './try'
|
||||
export { UpdateMark } from './updateMark'
|
||||
export { WrapInList } from './wrapInList'
|
||||
export { Keymap } from './keymap'
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const InsertText = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
insertText: (value: string): Command => ({ tr, dispatch }) => {
|
||||
if (dispatch) {
|
||||
tr.insertText(value)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
InsertText: typeof InsertText,
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { undoInputRule } from 'prosemirror-inputrules'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const BaseKeymap = createExtension({
|
||||
export const Keymap = createExtension({
|
||||
addKeyboardShortcuts() {
|
||||
const handleBackspace = () => this.editor.try(({ state, dispatch }) => [
|
||||
() => undoInputRule(state, dispatch),
|
||||
@@ -47,6 +47,6 @@ export const BaseKeymap = createExtension({
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
BaseKeymap: typeof BaseKeymap,
|
||||
Keymap: typeof Keymap,
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { liftListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const LiftListItem = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
liftListItem: (typeOrName: string | NodeType): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return liftListItem(type)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
LiftListItem: typeof LiftListItem,
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
import getMarkRange from '../utils/getMarkRange'
|
||||
|
||||
export const RemoveMark = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
removeMark: (typeOrName: string | MarkType): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
let { from, to } = selection
|
||||
const { $from, empty } = selection
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRange($from, type)
|
||||
|
||||
if (range) {
|
||||
from = range.from
|
||||
to = range.to
|
||||
}
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
tr.removeMark(from, to, type)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
RemoveMark: typeof RemoveMark,
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const RemoveMarks = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
removeMarks: (): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const { from, to, empty } = selection
|
||||
|
||||
if (empty) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
Object
|
||||
.entries(state.schema.marks)
|
||||
.forEach(([, mark]) => {
|
||||
tr.removeMark(from, to, mark as any)
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
RemoveMarks: typeof RemoveMarks,
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const ResetNodeAttributes = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
resetNodeAttributes: (attributeNames: string[] = []): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const { from, to } = selection
|
||||
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (!node.type.isText) {
|
||||
attributeNames.forEach(name => {
|
||||
const attribute = node.type.spec.attrs?.[name]
|
||||
const defaultValue = attribute?.default
|
||||
|
||||
if (attribute && defaultValue !== undefined && dispatch) {
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
[name]: defaultValue,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ResetNodeAttributes: typeof ResetNodeAttributes,
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const ScrollIntoView = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
scrollIntoView: (): Command => ({ tr, dispatch }) => {
|
||||
if (dispatch) {
|
||||
tr.scrollIntoView()
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ScrollIntoView: typeof ScrollIntoView,
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { selectAll } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const SelectAll = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
selectAll: (): Command => ({ state, dispatch }) => {
|
||||
return selectAll(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SelectAll: typeof SelectAll,
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { selectParentNode } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const SelectParentNode = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
selectParentNode: (): Command => ({ state, dispatch }) => {
|
||||
return selectParentNode(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SelectParentNode: typeof SelectParentNode,
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { setBlockType } from 'prosemirror-commands'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const SetBlockType = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
setBlockType: (typeOrName: string | NodeType, attrs = {}): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return setBlockType(type, attrs)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SetBlockType: typeof SetBlockType,
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { TextSelection } from 'prosemirror-state'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const SetContent = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
setContent: (content: string, emitUpdate: Boolean = false, parseOptions = {}): Command => ({ tr, editor, dispatch }) => {
|
||||
const { createDocument } = editor
|
||||
const { doc } = tr
|
||||
const document = createDocument(content, parseOptions)
|
||||
const selection = TextSelection.create(doc, 0, doc.content.size)
|
||||
|
||||
if (dispatch) {
|
||||
tr.setSelection(selection)
|
||||
.replaceSelectionWith(document, false)
|
||||
.setMeta('preventUpdate', !emitUpdate)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SetContent: typeof SetContent,
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const SetNodeAttributes = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
setNodeAttributes: (attributes: {}): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const { from, to } = selection
|
||||
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (!node.type.isText && dispatch) {
|
||||
tr.setNodeMarkup(pos, undefined, attributes)
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SetNodeAttributes: typeof SetNodeAttributes,
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { sinkListItem as originalSinkListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const SinkListItem = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
sinkListItem: (typeOrName: string | NodeType): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return originalSinkListItem(type)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SinkListItem: typeof SinkListItem,
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import { canSplit } from 'prosemirror-transform'
|
||||
import { ContentMatch, Fragment } from 'prosemirror-model'
|
||||
import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
function defaultBlockAt(match: ContentMatch) {
|
||||
for (let i = 0; i < match.edgeCount; i + 1) {
|
||||
const { type } = match.edge(i)
|
||||
// @ts-ignore
|
||||
if (type.isTextblock && !type.hasRequiredAttrs()) return type
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
interface SplitBlockOptions {
|
||||
withAttributes: boolean,
|
||||
withMarks: boolean,
|
||||
}
|
||||
|
||||
function keepMarks(state: EditorState) {
|
||||
const marks = state.storedMarks
|
||||
|| (state.selection.$to.parentOffset && state.selection.$from.marks())
|
||||
|
||||
if (marks) {
|
||||
state.tr.ensureMarks(marks)
|
||||
}
|
||||
}
|
||||
|
||||
export const SplitBlock = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
splitBlock: (options: Partial<SplitBlockOptions> = {}): Command => ({ tr, state, dispatch }) => {
|
||||
const defaultOptions: SplitBlockOptions = {
|
||||
withAttributes: false,
|
||||
withMarks: true,
|
||||
}
|
||||
const config = { ...defaultOptions, ...options }
|
||||
const { selection, doc } = tr
|
||||
const { $from, $to } = selection
|
||||
|
||||
if (selection instanceof NodeSelection && selection.node.isBlock) {
|
||||
if (!$from.parentOffset || !canSplit(doc, $from.pos)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
if (config.withMarks) {
|
||||
keepMarks(state)
|
||||
}
|
||||
|
||||
tr.split($from.pos).scrollIntoView()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (!$from.parent.isBlock) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
const atEnd = $to.parentOffset === $to.parent.content.size
|
||||
|
||||
if (selection instanceof TextSelection) {
|
||||
tr.deleteSelection()
|
||||
}
|
||||
|
||||
const deflt = $from.depth === 0
|
||||
? undefined
|
||||
: defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)))
|
||||
|
||||
let types = atEnd && deflt
|
||||
? [{
|
||||
type: deflt,
|
||||
attrs: config.withAttributes
|
||||
? $from.node().attrs
|
||||
: {},
|
||||
}]
|
||||
: undefined
|
||||
|
||||
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types)
|
||||
|
||||
if (
|
||||
!types
|
||||
&& !can
|
||||
&& canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : undefined)
|
||||
) {
|
||||
can = true
|
||||
types = deflt
|
||||
? [{
|
||||
type: deflt,
|
||||
attrs: config.withAttributes
|
||||
? $from.node().attrs
|
||||
: {},
|
||||
}]
|
||||
: undefined
|
||||
}
|
||||
|
||||
if (can) {
|
||||
tr.split(tr.mapping.map($from.pos), 1, types)
|
||||
|
||||
if (
|
||||
!atEnd
|
||||
&& !$from.parentOffset
|
||||
&& $from.parent.type !== deflt
|
||||
&& $from.node(-1).canReplace($from.index(-1), $from.indexAfter(-1), Fragment.from(deflt?.create()))
|
||||
) {
|
||||
tr.setNodeMarkup(tr.mapping.map($from.before()), deflt || undefined)
|
||||
}
|
||||
}
|
||||
|
||||
if (config.withMarks) {
|
||||
keepMarks(state)
|
||||
}
|
||||
|
||||
tr.scrollIntoView()
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SplitBlock: typeof SplitBlock,
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { splitListItem } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const SplitListItem = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
splitListItem: (typeOrName: string | NodeType): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return splitListItem(type)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
SplitListItem: typeof SplitListItem,
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import nodeIsActive from '../utils/nodeIsActive'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const ToggleBlockType = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
toggleBlockType: (typeOrName: string | NodeType, toggleTypeOrName: string | NodeType, attrs = {}): Command => ({ state, commands }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
const toggleType = getNodeType(toggleTypeOrName, state.schema)
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
|
||||
if (isActive) {
|
||||
return commands.setBlockType(toggleType)
|
||||
}
|
||||
|
||||
return commands.setBlockType(type, attrs)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ToggleBlockType: typeof ToggleBlockType,
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { findParentNode } from 'prosemirror-utils'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
import isList from '../utils/isList'
|
||||
|
||||
export const ToggleList = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
toggleList: (listTypeOrName: string | NodeType, itemTypeOrName: string | NodeType): Command => ({
|
||||
tr, state, dispatch, chain, commands, can,
|
||||
}) => {
|
||||
const { extensions } = this.editor.options
|
||||
const listType = getNodeType(listTypeOrName, state.schema)
|
||||
const itemType = getNodeType(itemTypeOrName, state.schema)
|
||||
const { selection } = state
|
||||
const { $from, $to } = selection
|
||||
const range = $from.blockRange($to)
|
||||
|
||||
if (!range) {
|
||||
return false
|
||||
}
|
||||
|
||||
const parentList = findParentNode(node => isList(node.type.name, extensions))(selection)
|
||||
|
||||
if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
|
||||
// remove list
|
||||
if (parentList.node.type === listType) {
|
||||
return commands.liftListItem(itemType)
|
||||
}
|
||||
|
||||
// change list type
|
||||
if (
|
||||
isList(parentList.node.type.name, extensions)
|
||||
&& listType.validContent(parentList.node.content)
|
||||
&& dispatch
|
||||
) {
|
||||
tr.setNodeMarkup(parentList.pos, listType)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const canWrapInList = can().wrapInList(listType)
|
||||
|
||||
// try to convert node to paragraph if needed
|
||||
if (!canWrapInList) {
|
||||
return chain()
|
||||
.clearNodes()
|
||||
.wrapInList(listType)
|
||||
.run()
|
||||
}
|
||||
|
||||
return commands.wrapInList(listType)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ToggleList: typeof ToggleList,
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { toggleMark as originalToggleMark } from 'prosemirror-commands'
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
|
||||
export const ToggleMark = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
toggleMark: (typeOrName: string | MarkType): Command => ({ state, dispatch }) => {
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
|
||||
return originalToggleMark(type)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ToggleMark: typeof ToggleMark,
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { wrapIn, lift } from 'prosemirror-commands'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import nodeIsActive from '../utils/nodeIsActive'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const ToggleWrap = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
toggleWrap: (typeOrName: string | NodeType, attrs = {}): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
|
||||
if (isActive) {
|
||||
return lift(state, dispatch)
|
||||
}
|
||||
|
||||
return wrapIn(type, attrs)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
ToggleWrap: typeof ToggleWrap,
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
|
||||
export const Try = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
try: (commands: Command[] | ((props: Parameters<Command>[0]) => Command[])): Command => props => {
|
||||
const items = typeof commands === 'function'
|
||||
? commands(props)
|
||||
: commands
|
||||
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
if (items[i](props)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
Try: typeof Try,
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { MarkType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getMarkType from '../utils/getMarkType'
|
||||
import getMarkRange from '../utils/getMarkRange'
|
||||
|
||||
export const UpdateMark = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
updateMark: (typeOrName: string | MarkType, attrs: {}): Command => ({ tr, state, dispatch }) => {
|
||||
const { selection, doc } = tr
|
||||
let { from, to } = selection
|
||||
const { $from, empty } = selection
|
||||
const type = getMarkType(typeOrName, state.schema)
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRange($from, type)
|
||||
|
||||
if (range) {
|
||||
from = range.from
|
||||
to = range.to
|
||||
}
|
||||
}
|
||||
|
||||
const hasMark = doc.rangeHasMark(from, to, type)
|
||||
|
||||
if (hasMark && dispatch) {
|
||||
tr.removeMark(from, to, type)
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
tr.addMark(from, to, type.create(attrs))
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
UpdateMark: typeof UpdateMark,
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { wrapInList } from 'prosemirror-schema-list'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import { Command } from '../Editor'
|
||||
import { createExtension } from '../Extension'
|
||||
import getNodeType from '../utils/getNodeType'
|
||||
|
||||
export const WrapInList = createExtension({
|
||||
addCommands() {
|
||||
return {
|
||||
wrapInList: (typeOrName: string | NodeType, attrs?: {}): Command => ({ state, dispatch }) => {
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
return wrapInList(type, attrs)(state, dispatch)
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
declare module '../Editor' {
|
||||
interface AllExtensions {
|
||||
WrapInList: typeof WrapInList,
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,11 @@ function getMarksBetween(start: number, end: number, state: EditorState) {
|
||||
return marks
|
||||
}
|
||||
|
||||
export default function (regexp: RegExp, markType: MarkType, getAttrs?: Function) {
|
||||
export default function (regexp: RegExp, markType: MarkType, getAttributes?: Function) {
|
||||
return new InputRule(regexp, (state, match, start, end) => {
|
||||
const attributes = getAttrs instanceof Function ? getAttrs(match) : getAttrs
|
||||
const attributes = getAttributes instanceof Function
|
||||
? getAttributes(match)
|
||||
: getAttributes
|
||||
const { tr } = state
|
||||
const captureGroup = match[match.length - 1]
|
||||
const fullMatch = match[0]
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { InputRule } from 'prosemirror-inputrules'
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
|
||||
export default function (regexp: RegExp, type: NodeType, getAttrs?: (match: any) => any): InputRule {
|
||||
export default function (regexp: RegExp, type: NodeType, getAttributes?: (match: any) => any): InputRule {
|
||||
return new InputRule(regexp, (state, match, start, end) => {
|
||||
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs
|
||||
const attributes = getAttributes instanceof Function
|
||||
? getAttributes(match)
|
||||
: getAttributes
|
||||
const { tr } = state
|
||||
|
||||
if (match[0]) {
|
||||
tr.replaceWith(start - 1, end, type.create(attrs))
|
||||
tr.replaceWith(start - 1, end, type.create(attributes))
|
||||
}
|
||||
|
||||
return tr
|
||||
|
||||
Reference in New Issue
Block a user