fix: preserve attributes of toggled node #3644 (#5489)

I only feel comfortable copying the attributes of the current node if the selection is only within a single node (I don't know what is expected if you had a selection of multiple nodes, the intersection of the attributes maybe?)

---------

Co-authored-by: Dominik Biedebach <dominik.biedebach@tiptap.dev>
This commit is contained in:
Nick Perez 2024-08-21 14:03:57 +02:00 committed by GitHub
parent dcffe441c5
commit 07fa49d026
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 62 additions and 4 deletions

View File

@ -0,0 +1,5 @@
---
"@tiptap/core": patch
---
Copy over node attributes on node toggling (for example to keep text styles while toggling a headline)

View File

@ -58,7 +58,21 @@ export default () => {
>
Justify
</button>
<button onClick={() => editor.chain().focus().unsetTextAlign().run()}>Unset text align</button>
<button onClick={() => editor.chain().focus().unsetTextAlign().run()}>
Unset text align
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive({ level: 1 }) ? 'is-active' : ''}
>
Toggle H1
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive({ level: 2 }) ? 'is-active' : ''}
>
Toggle H2
</button>
</div>
</div>
<EditorContent editor={editor} />

View File

@ -37,6 +37,21 @@ context('/src/Extensions/TextAlign/React/', () => {
})
})
it('should keep the text aligned when toggling headings', () => {
const alignments = ['center', 'right', 'justify']
const headings = [1, 2]
cy.get('.tiptap').then(([{ editor }]) => {
alignments.forEach(alignment => {
headings.forEach(level => {
editor.commands.setContent(`<p style="text-align: ${alignment}">Example Text</p>`)
editor.commands.toggleHeading({ level })
expect(editor.getHTML()).to.eq(`<h${level} style="text-align: ${alignment}">Example Text</h${level}>`)
})
})
})
})
it('aligns the text left on the 1st button', () => {
cy.get('button:nth-child(1)').click()

View File

@ -37,6 +37,21 @@ context('/src/Extensions/TextAlign/Vue/', () => {
})
})
it('should keep the text aligned when toggling headings', () => {
const alignments = ['center', 'right', 'justify']
const headings = [1, 2]
cy.get('.tiptap').then(([{ editor }]) => {
alignments.forEach(alignment => {
headings.forEach(level => {
editor.commands.setContent(`<p style="text-align: ${alignment}">Example Text</p>`)
editor.commands.toggleHeading({ level })
expect(editor.getHTML()).to.eq(`<h${level} style="text-align: ${alignment}">Example Text</h${level}>`)
})
})
})
})
it('aligns the text left on the 1st button', () => {
cy.get('button:nth-child(1)')
.click()

View File

@ -28,9 +28,18 @@ export const toggleNode: RawCommands['toggleNode'] = (typeOrName, toggleTypeOrNa
const toggleType = getNodeType(toggleTypeOrName, state.schema)
const isActive = isNodeActive(state, type, attributes)
if (isActive) {
return commands.setNode(toggleType)
let attributesToCopy: Record<string, any> | undefined
if (state.selection.$anchor.sameParent(state.selection.$head)) {
// only copy attributes if the selection is pointing to a node of the same type
attributesToCopy = state.selection.$anchor.parent.attrs
}
return commands.setNode(type, attributes)
if (isActive) {
return commands.setNode(toggleType, attributesToCopy)
}
// If the node is not active, we want to set the new node type with the given attributes
// Copying over the attributes from the current node if the selection is pointing to a node of the same type
return commands.setNode(type, { ...attributesToCopy, ...attributes })
}