Skip to content

Commit

Permalink
fix: NodePos querySelectorAll function (#5094)
Browse files Browse the repository at this point in the history
* fix nodepos queryselector

* fix NodePos querySelectorAll function

* tests

* remove test variable

* test fix
  • Loading branch information
Dalcvi authored May 8, 2024
1 parent 2b24986 commit 4900a27
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 33 deletions.
44 changes: 42 additions & 2 deletions demos/src/Examples/NodePos/React/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default () => {
<p>
This is a <strong>simple</strong> paragraph.
</p>
<img src="https://unsplash.it/200/200" alt="A 200x200 thumbnail from unsplash." />
<img src="https://unsplash.it/200/200" alt="A 200x200 square thumbnail from unsplash." />
<p>
Here is another paragraph inside this document.
</p>
Expand Down Expand Up @@ -69,10 +69,14 @@ export default () => {
<p>Sorted 3</p>
</li>
</ol>
<img src="https://unsplash.it/260/200" alt="A 260x200 thumbnail from unsplash." />
<blockquote>
<p>Here we have another paragraph inside a blockquote.</p>
<blockquote>
<img src="https://unsplash.it/260/200" alt="A 260x200 landscape thumbnail from unsplash." />
<img src="https://unsplash.it/100/200" alt="A 100x200 portrait thumbnail from unsplash." />
</blockquote>
</blockquote>
<img src="https://unsplash.it/260/200" alt="A 260x200 landscape thumbnail from unsplash." />
`,
})

Expand Down Expand Up @@ -177,6 +181,39 @@ export default () => {
setFoundNodes([nodePosition])
}, [editor])

const findAllLandscapeImages = useCallback(() => {
const nodePosition = editor.$doc.querySelectorAll('image', { src: 'https://unsplash.it/260/200' })

if (!nodePosition) {
setFoundNodes(null)
return
}

setFoundNodes(nodePosition)
}, [editor])

const findFirstLandscapeImageWithAllQuery = useCallback(() => {
const nodePosition = editor.$doc.querySelectorAll('image', { src: 'https://unsplash.it/260/200' }, true)

if (!nodePosition) {
setFoundNodes(null)
return
}

setFoundNodes(nodePosition)
}, [editor])

const findPortraitImageInBlockquote = useCallback(() => {
const nodePosition = editor.$doc.querySelector('image', { src: 'https://unsplash.it/100/200' })

if (!nodePosition) {
setFoundNodes(null)
return
}

setFoundNodes([nodePosition])
}, [editor])

const findFirstNode = useCallback(() => {
const nodePosition = editor.$doc.firstChild

Expand Down Expand Up @@ -235,6 +272,9 @@ export default () => {
<button data-testid="find-first-blockquote" onClick={findFirstBlockquote}>Find first blockquote</button>
<button data-testid="find-squared-image" onClick={findSquaredImage}>Find squared image</button>
<button data-testid="find-landscape-image" onClick={findLandscapeImage}>Find landscape image</button>
<button data-testid="find-all-landscape-images" onClick={findAllLandscapeImages}>Find all landscape images</button>
<button data-testid="find-first-landscape-image-with-all-query" onClick={findFirstLandscapeImageWithAllQuery}>Find first landscape image with all query</button>
<button data-testid="find-portrait-image-inside-blockquote" onClick={findPortraitImageInBlockquote}>Find portrait image in blockquote</button>
</div>
<div>
<button data-testid="find-first-node" onClick={findFirstNode}>Find first node</button>
Expand Down
60 changes: 47 additions & 13 deletions demos/src/Examples/NodePos/React/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ context('/src/Examples/NodePos/React/', () => {
cy.get('.tiptap').then(() => {
cy.get('button[data-testid="find-blockquotes"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 2)
cy.get('div[data-testid="found-node"]').should('have.length', 3)
})
})

it('should get images', () => {
cy.get('.tiptap').then(() => {
cy.get('button[data-testid="find-images"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 2)
cy.get('div[data-testid="found-node"]').should('have.length', 4)
})
})

Expand All @@ -60,17 +60,51 @@ context('/src/Examples/NodePos/React/', () => {
})
})

it('should get images by attributes', () => {
cy.get('.tiptap').then(() => {
cy.get('button[data-testid="find-squared-image"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/200/200')
describe('when querying by attribute', () => {
it('should get square image', () => {
cy.get('.tiptap').then(() => {
cy.get('button[data-testid="find-squared-image"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/200/200')
})
})

cy.get('button[data-testid="find-landscape-image"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/260/200')
it('should get landsape image', () => {
cy.get('.tiptap').then(() => {
cy.get('button[data-testid="find-landscape-image"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/260/200')
})
})

it('should get all landscape images', () => {
cy.get('.tiptap').then(() => {
cy.get('button[data-testid="find-all-landscape-images"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 2)
cy.get('div[data-testid="found-node"]').eq(0).should('contain', 'https://unsplash.it/260/200')
cy.get('div[data-testid="found-node"]').eq(1).should('contain', 'https://unsplash.it/260/200')
})
})

it('should get first landscape image with querySelectorAll', () => {
cy.get('.tiptap').then(() => {
cy.get('button[data-testid="find-first-landscape-image-with-all-query"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/260/200')
})
})

it('should get portrait image inside blockquote', () => {
cy.get('.tiptap').then(() => {
cy.get('button[data-testid="find-portrait-image-inside-blockquote"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/100/200')
})
})
})

Expand All @@ -84,7 +118,7 @@ context('/src/Examples/NodePos/React/', () => {
cy.get('button[data-testid="find-last-node"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'blockquote')
cy.get('div[data-testid="found-node"]').should('contain', 'image')

cy.get('button[data-testid="find-last-node-of-first-bullet-list"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist')
Expand Down
37 changes: 19 additions & 18 deletions packages/core/src/NodePos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class NodePos {
this.editor.commands.insertContentAt({ from, to }, content)
}

get attributes() : { [key: string]: any } {
get attributes(): { [key: string]: any } {
return this.node.attrs
}

Expand Down Expand Up @@ -200,34 +200,35 @@ export class NodePos {
querySelectorAll(selector: string, attributes: { [key: string]: any } = {}, firstItemOnly = false): NodePos[] {
let nodes: NodePos[] = []

// iterate through children recursively finding all nodes which match the selector with the node name
if (!this.children || this.children.length === 0) {
return nodes
}
const attrKeys = Object.keys(attributes)

/**
* Finds all children recursively that match the selector and attributes
* If firstItemOnly is true, it will return the first item found
*/
this.children.forEach(childPos => {
if (childPos.node.type.name === selector) {
if (Object.keys(attributes).length > 0) {
const nodeAttributes = childPos.node.attrs
const attrKeys = Object.keys(attributes)
// If we already found a node and we only want the first item, we dont need to keep going
if (firstItemOnly && nodes.length > 0) {
return
}

for (let index = 0; index < attrKeys.length; index += 1) {
const key = attrKeys[index]
if (childPos.node.type.name === selector) {
const doesAllAttributesMatch = attrKeys.every(key => attributes[key] === childPos.node.attrs[key])

if (nodeAttributes[key] !== attributes[key]) {
return
}
}
if (doesAllAttributesMatch) {
nodes.push(childPos)
}
}

nodes.push(childPos)

if (firstItemOnly) {
return
}
// If we already found a node and we only want the first item, we can stop here and skip the recursion
if (firstItemOnly && nodes.length > 0) {
return
}

nodes = nodes.concat(childPos.querySelectorAll(selector))
nodes = nodes.concat(childPos.querySelectorAll(selector, attributes, firstItemOnly))
})

return nodes
Expand Down

0 comments on commit 4900a27

Please sign in to comment.