Skip to content

Commit

Permalink
fix(core): getMarkRange match only the current mark of a type #3872 (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
nperez0111 authored Nov 17, 2024
1 parent d91f774 commit 2ea807d
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/wicked-meals-shop.md
Original file line number Diff line number Diff line change
@@ -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
34 changes: 29 additions & 5 deletions packages/core/src/helpers/getMarkRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ function findMarkInSet(
attributes: Record<string, any> = {},
): ProseMirrorMark | undefined {
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,
)
)
})
}

Expand All @@ -21,10 +28,23 @@ function isMarkInSet(
return !!findMarkInSet(marks, type, attributes)
}

/**
* Get the range of a mark at a resolved position.
*/
export function getMarkRange(
/**
* The position to get the mark range for.
*/
$pos: ResolvedPos,
/**
* The mark type to get the range for.
*/
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 {
if (!$pos || !type) {
return
Expand All @@ -41,6 +61,9 @@ export function getMarkRange(
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
// so we can look it up on the targeted mark
const mark = findMarkInSet([...start.node.marks], type, attributes)
Expand All @@ -54,9 +77,10 @@ export function getMarkRange(
let endIndex = startIndex + 1
let endPos = startPos + start.node.nodeSize

findMarkInSet([...start.node.marks], type, attributes)

while (startIndex > 0 && mark.isInSet($pos.parent.child(startIndex - 1).marks)) {
while (
startIndex > 0
&& isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)
) {
startIndex -= 1
startPos -= $pos.parent.child(startIndex).nodeSize
}
Expand Down
34 changes: 34 additions & 0 deletions tests/cypress/integration/core/getMarkRange.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,38 @@ describe('getMarkRange', () => {
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 })
})
})

0 comments on commit 2ea807d

Please sign in to comment.