-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Cut Command & Position Mapping for insertContentAt (#4141)
* feature(core): add cut commandd and map positions for insertContent * docs(core): added docs for cut command * chore(demos): added demo for cut command --------- Co-authored-by: bdbch <[email protected]>
- Loading branch information
Showing
10 changed files
with
326 additions
and
8 deletions.
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,77 @@ | ||
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, useEditor } from '@tiptap/react' | ||
import StarterKit from '@tiptap/starter-kit' | ||
import React from 'react' | ||
|
||
const MenuBar = ({ editor }) => { | ||
if (!editor) { | ||
return null | ||
} | ||
|
||
return ( | ||
<> | ||
<button onClick={() => editor.chain().cut({ from: editor.state.selection.$from.pos, to: editor.state.selection.$to.pos }, 1)}>Cut content to start of document</button> | ||
<button onClick={() => editor.chain().cut({ from: editor.state.selection.$from.pos, to: editor.state.selection.$to.pos }, editor.state.doc.nodeSize - 2)}>Cut content to end of document</button> | ||
</> | ||
) | ||
} | ||
|
||
export default () => { | ||
const editor = useEditor({ | ||
extensions: [ | ||
Color.configure({ types: [TextStyle.name, ListItem.name] }), | ||
TextStyle.configure({ types: [ListItem.name] }), | ||
StarterKit.configure({ | ||
bulletList: { | ||
keepMarks: true, | ||
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help | ||
}, | ||
orderedList: { | ||
keepMarks: true, | ||
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help | ||
}, | ||
}), | ||
], | ||
content: ` | ||
<h2> | ||
Hi there, | ||
</h2> | ||
<p> | ||
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists: | ||
</p> | ||
<ul> | ||
<li> | ||
That’s a bullet list with one … | ||
</li> | ||
<li> | ||
… or two list items. | ||
</li> | ||
</ul> | ||
<p> | ||
Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block: | ||
</p> | ||
<pre><code class="language-css">body { | ||
display: none; | ||
}</code></pre> | ||
<p> | ||
I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too. | ||
</p> | ||
<blockquote> | ||
Wow, that’s amazing. Good work, boy! 👏 | ||
<br /> | ||
— Mom | ||
</blockquote> | ||
`, | ||
}) | ||
|
||
return ( | ||
<div> | ||
<MenuBar editor={editor} /> | ||
<EditorContent editor={editor} /> | ||
</div> | ||
) | ||
} |
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('.tiptap').then(([{ editor }]) => { | ||
editor.commands.setContent('<h1>Example Text</h1>') | ||
cy.get('.tiptap').type('{selectall}') | ||
}) | ||
}) | ||
|
||
it('should apply the paragraph style when the keyboard shortcut is pressed', () => { | ||
cy.get('.tiptap h1').should('exist') | ||
cy.get('.tiptap p').should('not.exist') | ||
|
||
cy.get('.tiptap') | ||
.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('.tiptap').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('.tiptap').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('.tiptap').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('.tiptap').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('.tiptap').type('{selectall}Hello world') | ||
cy.get('button').contains('paragraph').click() | ||
cy.get('.tiptap').type('{selectall}') | ||
cy.get('button').contains(m.label).click() | ||
cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world') | ||
}) | ||
}) | ||
|
||
it('should clear marks when the button is pressed', () => { | ||
cy.get('.tiptap').type('{selectall}Hello world') | ||
cy.get('button').contains('paragraph').click() | ||
cy.get('.tiptap').type('{selectall}') | ||
cy.get('button').contains('bold').click() | ||
cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world') | ||
cy.get('button').contains('clear marks').click() | ||
cy.get('.tiptap strong').should('not.exist') | ||
}) | ||
|
||
it('should clear nodes when the button is pressed', () => { | ||
cy.get('.tiptap').type('{selectall}Hello world') | ||
cy.get('button').contains('bullet list').click() | ||
cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world') | ||
cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}') | ||
cy.get('button').contains('clear nodes').click() | ||
cy.get('.tiptap ul').should('not.exist') | ||
cy.get('.tiptap 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('.tiptap').type('{selectall}Hello world{selectall}') | ||
|
||
cy.get('button').contains(n.label).click() | ||
cy.get(`.tiptap ${n.tag}`).should('exist').should('have.text', 'Hello world') | ||
cy.get('button').contains(n.label).click() | ||
cy.get(`.tiptap ${n.tag}`).should('not.exist') | ||
}) | ||
}) | ||
|
||
it('should add a hr when on the same line as a node', () => { | ||
cy.get('.tiptap').type('{rightArrow}') | ||
cy.get('button').contains('horizontal rule').click() | ||
cy.get('.tiptap hr').should('exist') | ||
cy.get('.tiptap h1').should('exist') | ||
}) | ||
|
||
it('should add a hr when on a new line', () => { | ||
cy.get('.tiptap').type('{rightArrow}{enter}') | ||
cy.get('button').contains('horizontal rule').click() | ||
cy.get('.tiptap hr').should('exist') | ||
cy.get('.tiptap h1').should('exist') | ||
}) | ||
|
||
it('should add a br', () => { | ||
cy.get('.tiptap').type('{rightArrow}') | ||
cy.get('button').contains('hard break').click() | ||
cy.get('.tiptap h1 br').should('exist') | ||
}) | ||
|
||
it('should undo', () => { | ||
cy.get('.tiptap').type('{selectall}{backspace}') | ||
cy.get('button').contains('undo').click() | ||
cy.get('.tiptap').should('contain', 'Hello world') | ||
}) | ||
|
||
it('should redo', () => { | ||
cy.get('.tiptap').type('{selectall}{backspace}') | ||
cy.get('button').contains('undo').click() | ||
cy.get('.tiptap').should('contain', 'Hello world') | ||
cy.get('button').contains('redo').click() | ||
cy.get('.tiptap').should('not.contain', 'Hello world') | ||
}) | ||
}) |
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,56 @@ | ||
/* Basic editor styles */ | ||
.tiptap { | ||
> * + * { | ||
margin-top: 0.75em; | ||
} | ||
|
||
ul, | ||
ol { | ||
padding: 0 1rem; | ||
} | ||
|
||
h1, | ||
h2, | ||
h3, | ||
h4, | ||
h5, | ||
h6 { | ||
line-height: 1.1; | ||
} | ||
|
||
code { | ||
background-color: rgba(#616161, 0.1); | ||
color: #616161; | ||
} | ||
|
||
pre { | ||
background: #0D0D0D; | ||
color: #FFF; | ||
font-family: 'JetBrainsMono', monospace; | ||
padding: 0.75rem 1rem; | ||
border-radius: 0.5rem; | ||
|
||
code { | ||
color: inherit; | ||
padding: 0; | ||
background: none; | ||
font-size: 0.8rem; | ||
} | ||
} | ||
|
||
img { | ||
max-width: 100%; | ||
height: auto; | ||
} | ||
|
||
blockquote { | ||
padding-left: 1rem; | ||
border-left: 2px solid rgba(#0D0D0D, 0.1); | ||
} | ||
|
||
hr { | ||
border: none; | ||
border-top: 2px solid rgba(#0D0D0D, 0.1); | ||
margin: 2rem 0; | ||
} | ||
} |
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
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
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,16 @@ | ||
# cut | ||
This command cuts out content and places it into the given position. | ||
|
||
See also: [focus](/api/commands/cut) | ||
|
||
## Usage | ||
```js | ||
const from = editor.state.selection.from | ||
const to = editor.state.selection.to | ||
|
||
const endPos = editor.state.doc.nodeSize - 2 | ||
|
||
// Cut out content from range and put it at the end of the document | ||
editor.commands.cut({ from, to }, endPos) | ||
``` | ||
|
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,25 @@ | ||
import { RawCommands } from '../types.js' | ||
|
||
declare module '@tiptap/core' { | ||
interface Commands<ReturnType> { | ||
cut: { | ||
/** | ||
* Cuts content from a range and inserts it at a given position. | ||
*/ | ||
cut: ({ from, to }: { from: number, to: number }, targetPos: number) => ReturnType, | ||
} | ||
} | ||
} | ||
|
||
export const cut: RawCommands['cut'] = (originRange, targetPos) => ({ editor }) => { | ||
const { state } = editor | ||
|
||
const contentSlice = state.doc.slice(originRange.from, originRange.to) | ||
|
||
return editor | ||
.chain() | ||
.deleteRange(originRange) | ||
.insertContentAt(targetPos, contentSlice.content.toJSON()) | ||
.focus() | ||
.run() | ||
} |
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
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