Merge branch 'feature/invert-isactive' into main
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import { toggleMark as originalToggleMark } from 'prosemirror-commands'
|
|
||||||
import { MarkType } from 'prosemirror-model'
|
import { MarkType } from 'prosemirror-model'
|
||||||
import { Command } from '../types'
|
import { Command } from '../types'
|
||||||
import getMarkType from '../helpers/getMarkType'
|
import getMarkType from '../helpers/getMarkType'
|
||||||
@@ -7,16 +6,13 @@ import markIsActive from '../helpers/markIsActive'
|
|||||||
/**
|
/**
|
||||||
* Toggle a mark on and off.
|
* Toggle a mark on and off.
|
||||||
*/
|
*/
|
||||||
export const toggleMark = (typeOrName: string | MarkType, attributes?: {}): Command => ({ state, dispatch, commands }) => {
|
export const toggleMark = (typeOrName: string | MarkType, attributes?: {}): Command => ({ state, commands }) => {
|
||||||
const type = getMarkType(typeOrName, state.schema)
|
const type = getMarkType(typeOrName, state.schema)
|
||||||
|
const isActive = markIsActive(state, type, attributes)
|
||||||
|
|
||||||
const hasMarkWithDifferentAttributes = attributes
|
if (isActive) {
|
||||||
&& markIsActive(state, type)
|
return commands.unsetMark(type)
|
||||||
&& !markIsActive(state, type, attributes)
|
}
|
||||||
|
|
||||||
if (attributes && hasMarkWithDifferentAttributes) {
|
|
||||||
return commands.setMark(type, attributes)
|
return commands.setMark(type, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalToggleMark(type, attributes)(state, dispatch)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,23 +3,20 @@ import { Mark, MarkType } from 'prosemirror-model'
|
|||||||
import objectIncludes from '../utilities/objectIncludes'
|
import objectIncludes from '../utilities/objectIncludes'
|
||||||
import getMarkType from '../helpers/getMarkType'
|
import getMarkType from '../helpers/getMarkType'
|
||||||
|
|
||||||
|
type MarkRange = {
|
||||||
|
mark: Mark,
|
||||||
|
from: number,
|
||||||
|
to: number,
|
||||||
|
}
|
||||||
|
|
||||||
export default function markIsActive(state: EditorState, typeOrName: MarkType | string | null, attributes = {}) {
|
export default function markIsActive(state: EditorState, typeOrName: MarkType | string | null, attributes = {}) {
|
||||||
const { from, to, empty } = state.selection
|
const { from, to, empty } = state.selection
|
||||||
const type = typeOrName
|
const type = typeOrName
|
||||||
? getMarkType(typeOrName, state.schema)
|
? getMarkType(typeOrName, state.schema)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
let marks: Mark[] = []
|
|
||||||
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
marks = state.selection.$head.marks()
|
return !!state.selection.$head.marks()
|
||||||
} else {
|
|
||||||
state.doc.nodesBetween(from, to, node => {
|
|
||||||
marks = [...marks, ...node.marks]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const markWithAttributes = marks
|
|
||||||
.filter(mark => {
|
.filter(mark => {
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return true
|
return true
|
||||||
@@ -28,6 +25,40 @@ export default function markIsActive(state: EditorState, typeOrName: MarkType |
|
|||||||
return type.name === mark.type.name
|
return type.name === mark.type.name
|
||||||
})
|
})
|
||||||
.find(mark => objectIncludes(mark.attrs, attributes))
|
.find(mark => objectIncludes(mark.attrs, attributes))
|
||||||
|
}
|
||||||
return !!markWithAttributes
|
|
||||||
|
let selectionRange = 0
|
||||||
|
let markRanges: MarkRange[] = []
|
||||||
|
|
||||||
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||||
|
if (node.isInline) {
|
||||||
|
const relativeFrom = Math.max(from, pos)
|
||||||
|
const relativeTo = Math.min(to, pos + node.nodeSize)
|
||||||
|
const range = relativeTo - relativeFrom
|
||||||
|
|
||||||
|
selectionRange += range
|
||||||
|
|
||||||
|
markRanges = [...markRanges, ...node.marks.map(mark => ({
|
||||||
|
mark,
|
||||||
|
from: relativeFrom,
|
||||||
|
to: relativeTo,
|
||||||
|
}))]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const range = markRanges
|
||||||
|
.filter(markRange => {
|
||||||
|
if (!type) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.name === markRange.mark.type.name
|
||||||
|
})
|
||||||
|
.filter(markRange => objectIncludes(markRange.mark.attrs, attributes))
|
||||||
|
.reduce((sum, markRange) => {
|
||||||
|
const size = markRange.to - markRange.from
|
||||||
|
return sum + size
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return selectionRange <= range
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,27 +3,60 @@ import { Node, NodeType } from 'prosemirror-model'
|
|||||||
import objectIncludes from '../utilities/objectIncludes'
|
import objectIncludes from '../utilities/objectIncludes'
|
||||||
import getNodeType from '../helpers/getNodeType'
|
import getNodeType from '../helpers/getNodeType'
|
||||||
|
|
||||||
|
type NodeRange = {
|
||||||
|
node: Node,
|
||||||
|
from: number,
|
||||||
|
to: number,
|
||||||
|
}
|
||||||
|
|
||||||
export default function nodeIsActive(state: EditorState, typeOrName: NodeType | string | null, attributes = {}) {
|
export default function nodeIsActive(state: EditorState, typeOrName: NodeType | string | null, attributes = {}) {
|
||||||
const { from, to } = state.selection
|
const { from, to, empty } = state.selection
|
||||||
const type = typeOrName
|
const type = typeOrName
|
||||||
? getNodeType(typeOrName, state.schema)
|
? getNodeType(typeOrName, state.schema)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
let nodes: Node[] = []
|
let nodeRanges: NodeRange[] = []
|
||||||
|
|
||||||
state.doc.nodesBetween(from, to, node => {
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||||
nodes = [...nodes, node]
|
if (!node.isText) {
|
||||||
|
const relativeFrom = Math.max(from, pos)
|
||||||
|
const relativeTo = Math.min(to, pos + node.nodeSize)
|
||||||
|
|
||||||
|
nodeRanges = [...nodeRanges, {
|
||||||
|
node,
|
||||||
|
from: relativeFrom,
|
||||||
|
to: relativeTo,
|
||||||
|
}]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const nodeWithAttributes = nodes
|
if (empty) {
|
||||||
.filter(node => {
|
return !!nodeRanges
|
||||||
|
.filter(nodeRange => {
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return type.name === node.type.name
|
return type.name === nodeRange.node.type.name
|
||||||
})
|
})
|
||||||
.find(node => objectIncludes(node.attrs, attributes))
|
.find(nodeRange => objectIncludes(nodeRange.node.attrs, attributes))
|
||||||
|
}
|
||||||
return !!nodeWithAttributes
|
|
||||||
|
const range = nodeRanges
|
||||||
|
.filter(nodeRange => {
|
||||||
|
if (!type) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.name === nodeRange.node.type.name
|
||||||
|
})
|
||||||
|
.filter(nodeRange => objectIncludes(nodeRange.node.attrs, attributes))
|
||||||
|
.reduce((sum, nodeRange) => {
|
||||||
|
const size = nodeRange.to - nodeRange.from
|
||||||
|
return sum + size
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
const selectionRange = to - from
|
||||||
|
|
||||||
|
return selectionRange <= range
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user