-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: clear nodes when cursor at start of empty isolating parent (#3943)
* fix: clear nodes when cursor at start of empty isolating parent * fix: dont break backspace behavior when childCount is over 1 * fix: check if parent is textblock * fix: add strict pos check for parent isolating pos * demo: add isolation clear demo
- Loading branch information
Showing
6 changed files
with
443 additions
and
1 deletion.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import './styles.scss' | ||
|
||
import { Color } from '@tiptap/extension-color' | ||
import ListItem from '@tiptap/extension-list-item' | ||
import TextStyle from '@tiptap/extension-text-style' | ||
import { EditorContent, Node, useEditor } from '@tiptap/react' | ||
import React from 'react' | ||
|
||
import { content } from '../content' | ||
|
||
const WrapperBlock = Node.create({ | ||
name: 'wrapperBlock', | ||
|
||
group: 'block', | ||
|
||
isolating: true, | ||
|
||
content: 'block*', | ||
|
||
parseHTML() { | ||
return [{ tag: 'div[data-wrapper-block]' }] | ||
}, | ||
|
||
renderHTML({ HTMLAttributes }) { | ||
return ['div', { ...HTMLAttributes, 'data-wrapper-block': true }, 0] | ||
}, | ||
}) | ||
|
||
const MenuBar = ({ editor }) => { | ||
if (!editor) { | ||
return null | ||
} | ||
|
||
return ( | ||
<> | ||
<button | ||
onClick={() => editor.chain().focus().toggleBold().run()} | ||
disabled={ | ||
!editor.can() | ||
.chain() | ||
.focus() | ||
.toggleBold() | ||
.run() | ||
} | ||
className={editor.isActive('bold') ? 'is-active' : ''} | ||
> | ||
bold | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleItalic().run()} | ||
disabled={ | ||
!editor.can() | ||
.chain() | ||
.focus() | ||
.toggleItalic() | ||
.run() | ||
} | ||
className={editor.isActive('italic') ? 'is-active' : ''} | ||
> | ||
italic | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleStrike().run()} | ||
disabled={ | ||
!editor.can() | ||
.chain() | ||
.focus() | ||
.toggleStrike() | ||
.run() | ||
} | ||
className={editor.isActive('strike') ? 'is-active' : ''} | ||
> | ||
strike | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleCode().run()} | ||
disabled={ | ||
!editor.can() | ||
.chain() | ||
.focus() | ||
.toggleCode() | ||
.run() | ||
} | ||
className={editor.isActive('code') ? 'is-active' : ''} | ||
> | ||
code | ||
</button> | ||
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}> | ||
clear marks | ||
</button> | ||
<button onClick={() => editor.chain().focus().clearNodes().run()}> | ||
clear nodes | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().setParagraph().run()} | ||
className={editor.isActive('paragraph') ? 'is-active' : ''} | ||
> | ||
paragraph | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} | ||
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''} | ||
> | ||
h1 | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} | ||
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''} | ||
> | ||
h2 | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} | ||
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''} | ||
> | ||
h3 | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()} | ||
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''} | ||
> | ||
h4 | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()} | ||
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''} | ||
> | ||
h5 | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()} | ||
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''} | ||
> | ||
h6 | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleBulletList().run()} | ||
className={editor.isActive('bulletList') ? 'is-active' : ''} | ||
> | ||
bullet list | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleOrderedList().run()} | ||
className={editor.isActive('orderedList') ? 'is-active' : ''} | ||
> | ||
ordered list | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleCodeBlock().run()} | ||
className={editor.isActive('codeBlock') ? 'is-active' : ''} | ||
> | ||
code block | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().toggleBlockquote().run()} | ||
className={editor.isActive('blockquote') ? 'is-active' : ''} | ||
> | ||
blockquote | ||
</button> | ||
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}> | ||
horizontal rule | ||
</button> | ||
<button onClick={() => editor.chain().focus().setHardBreak().run()}> | ||
hard break | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().undo().run()} | ||
disabled={ | ||
!editor.can() | ||
.chain() | ||
.focus() | ||
.undo() | ||
.run() | ||
} | ||
> | ||
undo | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().redo().run()} | ||
disabled={ | ||
!editor.can() | ||
.chain() | ||
.focus() | ||
.redo() | ||
.run() | ||
} | ||
> | ||
redo | ||
</button> | ||
<button | ||
onClick={() => editor.chain().focus().setColor('#958DF1').run()} | ||
className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''} | ||
> | ||
purple | ||
</button> | ||
</> | ||
) | ||
} | ||
|
||
export default () => { | ||
const editor = useEditor({ | ||
extensions: [ | ||
WrapperBlock, | ||
Color.configure({ types: [TextStyle.name, ListItem.name] }), | ||
TextStyle.configure({ types: [ListItem.name] }), | ||
], | ||
content, | ||
}) | ||
|
||
return ( | ||
<div> | ||
<MenuBar editor={editor} /> | ||
<EditorContent editor={editor} /> | ||
</div> | ||
) | ||
} |
143 changes: 143 additions & 0 deletions
143
demos/src/Experiments/IsolatingClear/React/index.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
context('/src/Examples/Default/React/', () => { | ||
before(() => { | ||
cy.visit('/src/Examples/Default/React/') | ||
}) | ||
|
||
beforeEach(() => { | ||
cy.get('.ProseMirror').then(([{ editor }]) => { | ||
editor.commands.setContent('<h1>Example Text</h1>') | ||
cy.get('.ProseMirror').type('{selectall}') | ||
}) | ||
}) | ||
|
||
it('should apply the paragraph style when the keyboard shortcut is pressed', () => { | ||
cy.get('.ProseMirror h1').should('exist') | ||
cy.get('.ProseMirror p').should('not.exist') | ||
|
||
cy.get('.ProseMirror') | ||
.trigger('keydown', { modKey: true, altKey: true, key: '0' }) | ||
.find('p') | ||
.should('contain', 'Example Text') | ||
}) | ||
|
||
const buttonMarks = [ | ||
{ label: 'bold', tag: 'strong' }, | ||
{ label: 'italic', tag: 'em' }, | ||
{ label: 'strike', tag: 's' }, | ||
] | ||
|
||
buttonMarks.forEach(m => { | ||
it(`should disable ${m.label} when the code tag is enabled for cursor`, () => { | ||
cy.get('.ProseMirror').type('{selectall}Hello world') | ||
cy.get('button').contains('code').click() | ||
cy.get('button').contains(m.label).should('be.disabled') | ||
}) | ||
|
||
it(`should enable ${m.label} when the code tag is disabled for cursor`, () => { | ||
cy.get('.ProseMirror').type('{selectall}Hello world') | ||
cy.get('button').contains('code').click() | ||
cy.get('button').contains('code').click() | ||
cy.get('button').contains(m.label).should('not.be.disabled') | ||
}) | ||
|
||
it(`should disable ${m.label} when the code tag is enabled for selection`, () => { | ||
cy.get('.ProseMirror').type('{selectall}Hello world{selectall}') | ||
cy.get('button').contains('code').click() | ||
cy.get('button').contains(m.label).should('be.disabled') | ||
}) | ||
|
||
it(`should enable ${m.label} when the code tag is disabled for selection`, () => { | ||
cy.get('.ProseMirror').type('{selectall}Hello world{selectall}') | ||
cy.get('button').contains('code').click() | ||
cy.get('button').contains('code').click() | ||
cy.get('button').contains(m.label).should('not.be.disabled') | ||
}) | ||
|
||
it(`should apply ${m.label} when the button is pressed`, () => { | ||
cy.get('.ProseMirror').type('{selectall}Hello world') | ||
cy.get('button').contains('paragraph').click() | ||
cy.get('.ProseMirror').type('{selectall}') | ||
cy.get('button').contains(m.label).click() | ||
cy.get(`.ProseMirror ${m.tag}`).should('exist').should('have.text', 'Hello world') | ||
}) | ||
}) | ||
|
||
it('should clear marks when the button is pressed', () => { | ||
cy.get('.ProseMirror').type('{selectall}Hello world') | ||
cy.get('button').contains('paragraph').click() | ||
cy.get('.ProseMirror').type('{selectall}') | ||
cy.get('button').contains('bold').click() | ||
cy.get('.ProseMirror strong').should('exist').should('have.text', 'Hello world') | ||
cy.get('button').contains('clear marks').click() | ||
cy.get('.ProseMirror strong').should('not.exist') | ||
}) | ||
|
||
it('should clear nodes when the button is pressed', () => { | ||
cy.get('.ProseMirror').type('{selectall}Hello world') | ||
cy.get('button').contains('bullet list').click() | ||
cy.get('.ProseMirror ul').should('exist').should('have.text', 'Hello world') | ||
cy.get('.ProseMirror').type('{enter}A second item{enter}A third item{selectall}') | ||
cy.get('button').contains('clear nodes').click() | ||
cy.get('.ProseMirror ul').should('not.exist') | ||
cy.get('.ProseMirror p').should('have.length', 3) | ||
}) | ||
|
||
const buttonNodes = [ | ||
{ label: 'h1', tag: 'h1' }, | ||
{ label: 'h2', tag: 'h2' }, | ||
{ label: 'h3', tag: 'h3' }, | ||
{ label: 'h4', tag: 'h4' }, | ||
{ label: 'h5', tag: 'h5' }, | ||
{ label: 'h6', tag: 'h6' }, | ||
{ label: 'bullet list', tag: 'ul' }, | ||
{ label: 'ordered list', tag: 'ol' }, | ||
{ label: 'code block', tag: 'pre code' }, | ||
{ label: 'blockquote', tag: 'blockquote' }, | ||
] | ||
|
||
buttonNodes.forEach(n => { | ||
it(`should set ${n.label} when the button is pressed`, () => { | ||
cy.get('button').contains('paragraph').click() | ||
cy.get('.ProseMirror').type('{selectall}Hello world{selectall}') | ||
|
||
cy.get('button').contains(n.label).click() | ||
cy.get(`.ProseMirror ${n.tag}`).should('exist').should('have.text', 'Hello world') | ||
cy.get('button').contains(n.label).click() | ||
cy.get(`.ProseMirror ${n.tag}`).should('not.exist') | ||
}) | ||
}) | ||
|
||
it('should add a hr when on the same line as a node', () => { | ||
cy.get('.ProseMirror').type('{rightArrow}') | ||
cy.get('button').contains('horizontal rule').click() | ||
cy.get('.ProseMirror hr').should('exist') | ||
cy.get('.ProseMirror h1').should('exist') | ||
}) | ||
|
||
it('should add a hr when on a new line', () => { | ||
cy.get('.ProseMirror').type('{rightArrow}{enter}') | ||
cy.get('button').contains('horizontal rule').click() | ||
cy.get('.ProseMirror hr').should('exist') | ||
cy.get('.ProseMirror h1').should('exist') | ||
}) | ||
|
||
it('should add a br', () => { | ||
cy.get('.ProseMirror').type('{rightArrow}') | ||
cy.get('button').contains('hard break').click() | ||
cy.get('.ProseMirror h1 br').should('exist') | ||
}) | ||
|
||
it('should undo', () => { | ||
cy.get('.ProseMirror').type('{selectall}{backspace}') | ||
cy.get('button').contains('undo').click() | ||
cy.get('.ProseMirror').should('contain', 'Hello world') | ||
}) | ||
|
||
it('should redo', () => { | ||
cy.get('.ProseMirror').type('{selectall}{backspace}') | ||
cy.get('button').contains('undo').click() | ||
cy.get('.ProseMirror').should('contain', 'Hello world') | ||
cy.get('button').contains('redo').click() | ||
cy.get('.ProseMirror').should('not.contain', 'Hello world') | ||
}) | ||
}) |
Oops, something went wrong.