mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-11-23 19:19:03 +08:00
fix(core): getMarkRange match only the current mark of a type #3872 (#5826)
Some checks failed
build / lint (20) (push) Has been cancelled
build / test (20, map[name:Demos/Examples spec:./demos/src/Examples/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/Experiments spec:./demos/src/Experiments/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/Extensions spec:./demos/src/Extensions/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/GuideContent spec:./demos/src/GuideContent/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/GuideGettingStarted spec:./demos/src/GuideGettingStarted/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/Marks spec:./demos/src/Marks/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/Nodes spec:./demos/src/Nodes/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Integration spec:./tests/cypress/integration/**/*.spec.{js,ts}]) (push) Has been cancelled
Publish / Release (20) (push) Has been cancelled
build / build (20) (push) Has been cancelled
Some checks failed
build / lint (20) (push) Has been cancelled
build / test (20, map[name:Demos/Examples spec:./demos/src/Examples/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/Experiments spec:./demos/src/Experiments/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/Extensions spec:./demos/src/Extensions/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/GuideContent spec:./demos/src/GuideContent/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/GuideGettingStarted spec:./demos/src/GuideGettingStarted/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/Marks spec:./demos/src/Marks/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Demos/Nodes spec:./demos/src/Nodes/**/*.spec.{js,ts}]) (push) Has been cancelled
build / test (20, map[name:Integration spec:./tests/cypress/integration/**/*.spec.{js,ts}]) (push) Has been cancelled
Publish / Release (20) (push) Has been cancelled
build / build (20) (push) Has been cancelled
This commit is contained in:
parent
d91f774e11
commit
2ea807d1db
5
.changeset/wicked-meals-shop.md
Normal file
5
.changeset/wicked-meals-shop.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@tiptap/core": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
getMarkRange would greedily match more content than it should have if it was the same type of mark, now it will match only the mark at the position #3872
|
@ -9,7 +9,14 @@ function findMarkInSet(
|
|||||||
attributes: Record<string, any> = {},
|
attributes: Record<string, any> = {},
|
||||||
): ProseMirrorMark | undefined {
|
): ProseMirrorMark | undefined {
|
||||||
return marks.find(item => {
|
return marks.find(item => {
|
||||||
return item.type === type && objectIncludes(item.attrs, attributes)
|
return (
|
||||||
|
item.type === type
|
||||||
|
&& objectIncludes(
|
||||||
|
// Only check equality for the attributes that are provided
|
||||||
|
Object.fromEntries(Object.keys(attributes).map(k => [k, item.attrs[k]])),
|
||||||
|
attributes,
|
||||||
|
)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,10 +28,23 @@ function isMarkInSet(
|
|||||||
return !!findMarkInSet(marks, type, attributes)
|
return !!findMarkInSet(marks, type, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the range of a mark at a resolved position.
|
||||||
|
*/
|
||||||
export function getMarkRange(
|
export function getMarkRange(
|
||||||
|
/**
|
||||||
|
* The position to get the mark range for.
|
||||||
|
*/
|
||||||
$pos: ResolvedPos,
|
$pos: ResolvedPos,
|
||||||
|
/**
|
||||||
|
* The mark type to get the range for.
|
||||||
|
*/
|
||||||
type: MarkType,
|
type: MarkType,
|
||||||
attributes: Record<string, any> = {},
|
/**
|
||||||
|
* The attributes to match against.
|
||||||
|
* If not provided, only the first mark at the position will be matched.
|
||||||
|
*/
|
||||||
|
attributes?: Record<string, any>,
|
||||||
): Range | void {
|
): Range | void {
|
||||||
if (!$pos || !type) {
|
if (!$pos || !type) {
|
||||||
return
|
return
|
||||||
@ -41,6 +61,9 @@ export function getMarkRange(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default to only matching against the first mark's attributes
|
||||||
|
attributes = attributes || start.node.marks[0]?.attrs
|
||||||
|
|
||||||
// We now know that the cursor is either at the start, middle or end of a text node with the specified mark
|
// We now know that the cursor is either at the start, middle or end of a text node with the specified mark
|
||||||
// so we can look it up on the targeted mark
|
// so we can look it up on the targeted mark
|
||||||
const mark = findMarkInSet([...start.node.marks], type, attributes)
|
const mark = findMarkInSet([...start.node.marks], type, attributes)
|
||||||
@ -54,9 +77,10 @@ export function getMarkRange(
|
|||||||
let endIndex = startIndex + 1
|
let endIndex = startIndex + 1
|
||||||
let endPos = startPos + start.node.nodeSize
|
let endPos = startPos + start.node.nodeSize
|
||||||
|
|
||||||
findMarkInSet([...start.node.marks], type, attributes)
|
while (
|
||||||
|
startIndex > 0
|
||||||
while (startIndex > 0 && mark.isInSet($pos.parent.child(startIndex - 1).marks)) {
|
&& isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)
|
||||||
|
) {
|
||||||
startIndex -= 1
|
startIndex -= 1
|
||||||
startPos -= $pos.parent.child(startIndex).nodeSize
|
startPos -= $pos.parent.child(startIndex).nodeSize
|
||||||
}
|
}
|
||||||
|
@ -140,4 +140,38 @@ describe('getMarkRange', () => {
|
|||||||
to: 39,
|
to: 39,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
it('can distinguish mark boundaries', () => {
|
||||||
|
const testDocument = {
|
||||||
|
type: 'doc',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: [
|
||||||
|
{ type: 'text', text: 'This is a text with a ' },
|
||||||
|
{ type: 'text', text: 'link.', marks: [{ type: 'link', attrs: { href: 'https://tiptap.dev' } }] },
|
||||||
|
{ type: 'text', text: 'another link', marks: [{ type: 'link', attrs: { href: 'https://tiptap.dev/invalid' } }] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: [
|
||||||
|
{ type: 'text', text: 'This is a text without a link.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = Node.fromJSON(schema, testDocument)
|
||||||
|
const $pos = doc.resolve(27)
|
||||||
|
const range = getMarkRange($pos, schema.marks.link, { href: 'https://tiptap.dev' })
|
||||||
|
|
||||||
|
expect(range).to.deep.eq({
|
||||||
|
from: 23,
|
||||||
|
to: 28,
|
||||||
|
})
|
||||||
|
|
||||||
|
const nextRange = getMarkRange(doc.resolve(28), schema.marks.link)
|
||||||
|
|
||||||
|
expect(nextRange).to.deep.eq({ from: 28, to: 40 })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user