diff --git a/docs/src/demos/Examples/Default/React/index.jsx b/docs/src/demos/Examples/Default/React/index.jsx
index 085713b2..3f8d89df 100644
--- a/docs/src/demos/Examples/Default/React/index.jsx
+++ b/docs/src/demos/Examples/Default/React/index.jsx
@@ -1,12 +1,7 @@
import React from 'react'
-import tippy from 'tippy.js'
-import { useEditor, EditorContent, ReactRenderer, ReactNodeViewRenderer, NodeViewWrapper, NodeViewContent } from '@tiptap/react'
+import { useEditor, EditorContent } from '@tiptap/react'
import { defaultExtensions } from '@tiptap/starter-kit'
-import Heading from '@tiptap/extension-heading'
-import Paragraph from '@tiptap/extension-paragraph'
-import Mention from '@tiptap/extension-mention'
import './styles.scss'
-import { render } from 'react-dom'
const MenuBar = ({ editor }) => {
if (!editor) {
@@ -127,162 +122,45 @@ const MenuBar = ({ editor }) => {
)
}
-const MentionList = (props) => {
- console.log({props})
-
- return (
-
- mentions
- {props.items.map((item) => (
-
- {item}
-
- ))}
-
- )
-}
-
-class MentionList2 extends React.Component {
-
- onKeyDown(props) {
- console.log('onKeyDown', props)
- }
-
- render() {
- return (
-
- mentions
- {this.props.items.map((item, index) => (
-
- {item}
-
- ))}
-
- )
- }
-}
-
export default () => {
const editor = useEditor({
- // onTransaction({ editor }) {
- // console.log('anchor', editor.state.selection.anchor)
- // },
extensions: [
- ...defaultExtensions().filter(item => item.config.name !== 'heading'),
- Heading.extend({
- draggable: true,
- addNodeView() {
- return ReactNodeViewRenderer((props) => {
- return (
-
-
- ⠿
- level: {props.node.attrs.level}
- props.updateAttributes({ level: 1 })}>
- set level 1
-
-
-
-
- )
- })
- }
- }),
- Mention.configure({
- suggestion: {
- items: query => {
- return [
- 'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet',
- ].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 10)
- },
- render: () => {
- let reactRenderer
- let popup
-
- return {
- onStart: props => {
- reactRenderer = new ReactRenderer(MentionList2, {
- props,
- editor: props.editor,
- })
-
- popup = tippy('body', {
- getReferenceClientRect: props.clientRect,
- appendTo: () => document.body,
- content: reactRenderer.element,
- showOnCreate: true,
- interactive: true,
- trigger: 'manual',
- placement: 'bottom-start',
- })
- },
- onUpdate(props) {
- reactRenderer.updateProps(props)
-
- popup[0].setProps({
- getReferenceClientRect: props.clientRect,
- })
- },
- onKeyDown(props) {
- return reactRenderer.ref.onKeyDown(props)
- },
- onExit() {
- popup[0].destroy()
- reactRenderer.destroy()
- },
- }
- }
- },
- })
+ ...defaultExtensions(),
],
content: `
- heading
- heading
- paragraph
+
+ Hi there,
+
+
+ this is a basic basic example of tiptap . Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists:
+
+
+
+ That’s a bullet list with one …
+
+
+ … or two list items.
+
+
+
+ Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
+
+ body {
+ display: none;
+}
+
+ 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.
+
+
+ Wow, that’s amazing. Good work, boy! 👏
+
+ — Mom
+
`,
-// content: `
-//
-// Hi there,
-//
-//
-// this is a basic basic example of tiptap . Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists:
-//
-//
-//
-// That’s a bullet list with one …
-//
-//
-// … or two list items.
-//
-//
-//
-// Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
-//
-// body {
-// display: none;
-// }
-//
-// 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.
-//
-//
-// Wow, that’s amazing. Good work, boy! 👏
-//
-// — Mom
-//
-// `,
})
return (
-
- editor.setEditable(true)}>editable
- editor.setEditable(false)}>readonly
-
diff --git a/docs/src/demos/ReactPlayground/index.jsx b/docs/src/demos/ReactPlayground/index.jsx
new file mode 100644
index 00000000..085713b2
--- /dev/null
+++ b/docs/src/demos/ReactPlayground/index.jsx
@@ -0,0 +1,290 @@
+import React from 'react'
+import tippy from 'tippy.js'
+import { useEditor, EditorContent, ReactRenderer, ReactNodeViewRenderer, NodeViewWrapper, NodeViewContent } from '@tiptap/react'
+import { defaultExtensions } from '@tiptap/starter-kit'
+import Heading from '@tiptap/extension-heading'
+import Paragraph from '@tiptap/extension-paragraph'
+import Mention from '@tiptap/extension-mention'
+import './styles.scss'
+import { render } from 'react-dom'
+
+const MenuBar = ({ editor }) => {
+ if (!editor) {
+ return null
+ }
+
+ return (
+ <>
+ editor.chain().focus().toggleBold().run()}
+ className={editor.isActive('bold') ? 'is-active' : ''}
+ >
+ bold
+
+ editor.chain().focus().toggleItalic().run()}
+ className={editor.isActive('italic') ? 'is-active' : ''}
+ >
+ italic
+
+ editor.chain().focus().toggleStrike().run()}
+ className={editor.isActive('strike') ? 'is-active' : ''}
+ >
+ strike
+
+ editor.chain().focus().toggleCode().run()}
+ className={editor.isActive('code') ? 'is-active' : ''}
+ >
+ code
+
+ editor.chain().focus().unsetAllMarks().run()}>
+ clear marks
+
+ editor.chain().focus().clearNodes().run()}>
+ clear nodes
+
+ editor.chain().focus().setParagraph().run()}
+ className={editor.isActive('paragraph') ? 'is-active' : ''}
+ >
+ paragraph
+
+ editor.chain().focus().toggleHeading({ level: 1 }).run()}
+ className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
+ >
+ h1
+
+ editor.chain().focus().toggleHeading({ level: 2 }).run()}
+ className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
+ >
+ h2
+
+ editor.chain().focus().toggleHeading({ level: 3 }).run()}
+ className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
+ >
+ h3
+
+ editor.chain().focus().toggleHeading({ level: 4 }).run()}
+ className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
+ >
+ h4
+
+ editor.chain().focus().toggleHeading({ level: 5 }).run()}
+ className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
+ >
+ h5
+
+ editor.chain().focus().toggleHeading({ level: 6 }).run()}
+ className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
+ >
+ h6
+
+ editor.chain().focus().toggleBulletList().run()}
+ className={editor.isActive('bulletList') ? 'is-active' : ''}
+ >
+ bullet list
+
+ editor.chain().focus().toggleOrderedList().run()}
+ className={editor.isActive('orderedList') ? 'is-active' : ''}
+ >
+ ordered list
+
+ editor.chain().focus().toggleCodeBlock().run()}
+ className={editor.isActive('codeBlock') ? 'is-active' : ''}
+ >
+ code block
+
+ editor.chain().focus().toggleBlockquote().run()}
+ className={editor.isActive('blockquote') ? 'is-active' : ''}
+ >
+ blockquote
+
+ editor.chain().focus().setHorizontalRule().run()}>
+ horizontal rule
+
+ editor.chain().focus().setHardBreak().run()}>
+ hard break
+
+ editor.chain().focus().undo().run()}>
+ undo
+
+ editor.chain().focus().redo().run()}>
+ redo
+
+ >
+ )
+}
+
+const MentionList = (props) => {
+ console.log({props})
+
+ return (
+
+ mentions
+ {props.items.map((item) => (
+
+ {item}
+
+ ))}
+
+ )
+}
+
+class MentionList2 extends React.Component {
+
+ onKeyDown(props) {
+ console.log('onKeyDown', props)
+ }
+
+ render() {
+ return (
+
+ mentions
+ {this.props.items.map((item, index) => (
+
+ {item}
+
+ ))}
+
+ )
+ }
+}
+
+export default () => {
+ const editor = useEditor({
+ // onTransaction({ editor }) {
+ // console.log('anchor', editor.state.selection.anchor)
+ // },
+ extensions: [
+ ...defaultExtensions().filter(item => item.config.name !== 'heading'),
+ Heading.extend({
+ draggable: true,
+ addNodeView() {
+ return ReactNodeViewRenderer((props) => {
+ return (
+
+
+ ⠿
+ level: {props.node.attrs.level}
+ props.updateAttributes({ level: 1 })}>
+ set level 1
+
+
+
+
+ )
+ })
+ }
+ }),
+ Mention.configure({
+ suggestion: {
+ items: query => {
+ return [
+ 'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet',
+ ].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 10)
+ },
+ render: () => {
+ let reactRenderer
+ let popup
+
+ return {
+ onStart: props => {
+ reactRenderer = new ReactRenderer(MentionList2, {
+ props,
+ editor: props.editor,
+ })
+
+ popup = tippy('body', {
+ getReferenceClientRect: props.clientRect,
+ appendTo: () => document.body,
+ content: reactRenderer.element,
+ showOnCreate: true,
+ interactive: true,
+ trigger: 'manual',
+ placement: 'bottom-start',
+ })
+ },
+ onUpdate(props) {
+ reactRenderer.updateProps(props)
+
+ popup[0].setProps({
+ getReferenceClientRect: props.clientRect,
+ })
+ },
+ onKeyDown(props) {
+ return reactRenderer.ref.onKeyDown(props)
+ },
+ onExit() {
+ popup[0].destroy()
+ reactRenderer.destroy()
+ },
+ }
+ }
+ },
+ })
+ ],
+ content: `
+ heading
+ heading
+ paragraph
+ `,
+// content: `
+//
+// Hi there,
+//
+//
+// this is a basic basic example of tiptap . Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists:
+//
+//
+//
+// That’s a bullet list with one …
+//
+//
+// … or two list items.
+//
+//
+//
+// Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
+//
+// body {
+// display: none;
+// }
+//
+// 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.
+//
+//
+// Wow, that’s amazing. Good work, boy! 👏
+//
+// — Mom
+//
+// `,
+ })
+
+ return (
+
+
+ editor.setEditable(true)}>editable
+ editor.setEditable(false)}>readonly
+
+
+
+
+ )
+}
diff --git a/docs/src/demos/ReactPlayground/index.spec.js b/docs/src/demos/ReactPlayground/index.spec.js
new file mode 100644
index 00000000..350c2d41
--- /dev/null
+++ b/docs/src/demos/ReactPlayground/index.spec.js
@@ -0,0 +1,22 @@
+context('/demos/Examples/Default/React', () => {
+ before(() => {
+ cy.visit('/demos/Examples/Default/React')
+ })
+
+ beforeEach(() => {
+ cy.get('.ProseMirror').then(([{ editor }]) => {
+ editor.commands.setContent('Example Text ')
+ 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')
+ })
+})
diff --git a/docs/src/demos/ReactPlayground/styles.scss b/docs/src/demos/ReactPlayground/styles.scss
new file mode 100644
index 00000000..dc996245
--- /dev/null
+++ b/docs/src/demos/ReactPlayground/styles.scss
@@ -0,0 +1,55 @@
+/* Basic editor styles */
+.ProseMirror {
+ > * + * {
+ 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;
+ 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;
+ }
+}