add node demos
This commit is contained in:
15
demos/src/Nodes/Blockquote/Vue/index.html
Normal file
15
demos/src/Nodes/Blockquote/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/Blockquote', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
104
demos/src/Nodes/Blockquote/Vue/index.spec.js
Normal file
104
demos/src/Nodes/Blockquote/Vue/index.spec.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
context('/demos/Nodes/Blockquote', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/Blockquote')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse blockquote tags correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<blockquote><p>Example Text</p></blockquote>')
|
||||||
|
expect(editor.getHTML()).to.eq('<blockquote><p>Example Text</p></blockquote>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse blockquote tags without paragraphs correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<blockquote>Example Text</blockquote>')
|
||||||
|
expect(editor.getHTML()).to.eq('<blockquote><p>Example Text</p></blockquote>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should make the selected line a blockquote', () => {
|
||||||
|
cy.get('.ProseMirror blockquote')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('blockquote')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should wrap all nodes in one blockquote', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p><p>Example Text</p>')
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('blockquote')
|
||||||
|
.should('have.length', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should toggle the blockquote', () => {
|
||||||
|
cy.get('.ProseMirror blockquote')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('blockquote')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{selectall}')
|
||||||
|
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror blockquote')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make the selected line a blockquote when the keyboard shortcut is pressed', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { shiftKey: true, modKey: true, key: 'b' })
|
||||||
|
.find('blockquote')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should toggle the blockquote when the keyboard shortcut is pressed', () => {
|
||||||
|
cy.get('.ProseMirror blockquote')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { shiftKey: true, modKey: true, key: 'b' })
|
||||||
|
.find('blockquote')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{selectall}')
|
||||||
|
.trigger('keydown', { shiftKey: true, modKey: true, key: 'b' })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror blockquote')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a blockquote from markdown shortcuts', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('> Quote')
|
||||||
|
.find('blockquote')
|
||||||
|
.should('contain', 'Quote')
|
||||||
|
})
|
||||||
|
})
|
||||||
64
demos/src/Nodes/Blockquote/Vue/index.vue
Normal file
64
demos/src/Nodes/Blockquote/Vue/index.vue
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
|
||||||
|
blockquote
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import Blockquote from '@tiptap/extension-blockquote'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
Blockquote,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<blockquote>
|
||||||
|
Life is like riding a bycicle. To keep your balance, you must keep moving.
|
||||||
|
</blockquote>
|
||||||
|
<p>Albert Einstein</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/BulletList/Vue/index.html
Normal file
15
demos/src/Nodes/BulletList/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/BulletList', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
152
demos/src/Nodes/BulletList/Vue/index.spec.js
Normal file
152
demos/src/Nodes/BulletList/Vue/index.spec.js
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
context('/demos/Nodes/BulletList', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/BulletList')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse unordered lists correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<ul><li><p>Example Text</p></li></ul>')
|
||||||
|
expect(editor.getHTML()).to.eq('<ul><li><p>Example Text</p></li></ul>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse unordered lists without paragraphs correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<ul><li>Example Text</li></ul>')
|
||||||
|
expect(editor.getHTML()).to.eq('<ul><li><p>Example Text</p></li></ul>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should make the selected line a bullet list item', () => {
|
||||||
|
cy.get('.ProseMirror ul')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror ul li')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ul')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ul li')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should toggle the bullet list', () => {
|
||||||
|
cy.get('.ProseMirror ul')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ul')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror ul')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should leave the list with double enter', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('- List Item 1{enter}{enter}Paragraph')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li')
|
||||||
|
.its('length')
|
||||||
|
.should('eq', 1)
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('p')
|
||||||
|
.should('contain', 'Paragraph')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make the paragraph a bullet list keyboard shortcut is pressed', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, shiftKey: true, key: '8' })
|
||||||
|
.find('ul li')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a bullet list from an asterisk', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('* List Item 1{enter}List Item 2')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(1)')
|
||||||
|
.should('contain', 'List Item 1')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(2)')
|
||||||
|
.should('contain', 'List Item 2')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a bullet list from a dash', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('- List Item 1{enter}List Item 2')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(1)')
|
||||||
|
.should('contain', 'List Item 1')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(2)')
|
||||||
|
.should('contain', 'List Item 2')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a bullet list from a plus', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('+ List Item 1{enter}List Item 2')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(1)')
|
||||||
|
.should('contain', 'List Item 1')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(2)')
|
||||||
|
.should('contain', 'List Item 2')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove the bullet list after pressing backspace', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('* {backspace}Example')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('p')
|
||||||
|
.should('contain', '* Example')
|
||||||
|
})
|
||||||
|
})
|
||||||
66
demos/src/Nodes/BulletList/Vue/index.vue
Normal file
66
demos/src/Nodes/BulletList/Vue/index.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
|
||||||
|
bullet list
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import BulletList from '@tiptap/extension-bullet-list'
|
||||||
|
import ListItem from '@tiptap/extension-list-item'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
BulletList,
|
||||||
|
ListItem,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<ul>
|
||||||
|
<li>A list item</li>
|
||||||
|
<li>And another one</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/CodeBlock/Vue/index.html
Normal file
15
demos/src/Nodes/CodeBlock/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/CodeBlock', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
185
demos/src/Nodes/CodeBlock/Vue/index.spec.js
Normal file
185
demos/src/Nodes/CodeBlock/Vue/index.spec.js
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
context('/demos/Nodes/CodeBlock', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/CodeBlock')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse code blocks correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<pre><code>Example Text</code></pre>')
|
||||||
|
expect(editor.getHTML()).to.eq('<pre><code>Example Text</code></pre>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse code blocks with language correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<pre><code class="language-css">Example Text</code></pre>')
|
||||||
|
expect(editor.getHTML()).to.eq('<pre><code class="language-css">Example Text</code></pre>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should make the selected line a code block', () => {
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('pre')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should toggle the code block', () => {
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('pre')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{selectall}')
|
||||||
|
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror pre')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the keyboard shortcut should make the selected line a code block', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, altKey: true, key: 'c' })
|
||||||
|
.find('pre')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the keyboard shortcut should toggle the code block', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, altKey: true, key: 'c' })
|
||||||
|
.find('pre')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{selectall}')
|
||||||
|
.trigger('keydown', { modKey: true, altKey: true, key: 'c' })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror pre')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse the language from a HTML code block', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<pre><code class="language-css">body { display: none; }</code></pre>')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('pre>code.language-css')
|
||||||
|
.should('have.length', 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a code block from backtick markdown shortcuts', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('``` Code')
|
||||||
|
.find('pre>code')
|
||||||
|
.should('contain', 'Code')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a code block from tilde markdown shortcuts', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('~~~ Code')
|
||||||
|
.find('pre>code')
|
||||||
|
.should('contain', 'Code')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a code block for js with backticks', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('```js Code')
|
||||||
|
.find('pre>code.language-js')
|
||||||
|
.should('contain', 'Code')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a code block for js with tildes', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('~~~js Code')
|
||||||
|
.find('pre>code.language-js')
|
||||||
|
.should('contain', 'Code')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('reverts the markdown shortcut when pressing backspace', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('``` {backspace}')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror pre')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes the code block when pressing backspace', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror pre')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('Paragraph{enter}``` A{backspace}{backspace}')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror pre')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes the code block when pressing backspace, even with blank lines', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror pre')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('Paragraph{enter}{enter}``` A{backspace}{backspace}')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror pre')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes the code block when pressing backspace, even at start of document', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror pre')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('``` A{leftArrow}{backspace}')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror pre')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
87
demos/src/Nodes/CodeBlock/Vue/index.vue
Normal file
87
demos/src/Nodes/CodeBlock/Vue/index.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
|
||||||
|
code block
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import CodeBlock from '@tiptap/extension-code-block'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
CodeBlock,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>
|
||||||
|
That’s a boring paragraph followed by a fenced code block:
|
||||||
|
</p>
|
||||||
|
<pre><code>for (var i=1; i <= 20; i++)
|
||||||
|
{
|
||||||
|
if (i % 15 == 0)
|
||||||
|
console.log("FizzBuzz");
|
||||||
|
else if (i % 3 == 0)
|
||||||
|
console.log("Fizz");
|
||||||
|
else if (i % 5 == 0)
|
||||||
|
console.log("Buzz");
|
||||||
|
else
|
||||||
|
console.log(i);
|
||||||
|
}</code></pre>
|
||||||
|
<p>
|
||||||
|
Press Command/Ctrl + Enter to leave the fenced code block and continue typing in boring paragraphs.
|
||||||
|
</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/CodeBlockLowlight/Vue/index.html
Normal file
15
demos/src/Nodes/CodeBlockLowlight/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/CodeBlockLowlight', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
149
demos/src/Nodes/CodeBlockLowlight/Vue/index.vue
Normal file
149
demos/src/Nodes/CodeBlockLowlight/Vue/index.vue
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
|
||||||
|
code block
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||||
|
|
||||||
|
// load all highlight.js languages
|
||||||
|
import lowlight from 'lowlight'
|
||||||
|
|
||||||
|
// load specific languages only
|
||||||
|
// import lowlight from 'lowlight/lib/core'
|
||||||
|
// import javascript from 'highlight.js/lib/languages/javascript'
|
||||||
|
// lowlight.registerLanguage('javascript', javascript)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
CodeBlockLowlight.configure({
|
||||||
|
lowlight,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>
|
||||||
|
That’s a boring paragraph followed by a fenced code block:
|
||||||
|
</p>
|
||||||
|
<pre><code class="language-javascript">for (var i=1; i <= 20; i++)
|
||||||
|
{
|
||||||
|
if (i % 15 == 0)
|
||||||
|
console.log("FizzBuzz");
|
||||||
|
else if (i % 3 == 0)
|
||||||
|
console.log("Fizz");
|
||||||
|
else if (i % 5 == 0)
|
||||||
|
console.log("Buzz");
|
||||||
|
else
|
||||||
|
console.log(i);
|
||||||
|
}</code></pre>
|
||||||
|
<p>
|
||||||
|
Press Command/Ctrl + Enter to leave the fenced code block and continue typing in boring paragraphs.
|
||||||
|
</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #616161;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-tag,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-link,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class {
|
||||||
|
color: #F98181;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-builtin-name,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-params {
|
||||||
|
color: #FBBC88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet {
|
||||||
|
color: #B9F18D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-section {
|
||||||
|
color: #FAF594;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
color: #70CFF8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/Document/Vue/index.html
Normal file
15
demos/src/Nodes/Document/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/Document', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
26
demos/src/Nodes/Document/Vue/index.spec.js
Normal file
26
demos/src/Nodes/Document/Vue/index.spec.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
context('/demos/Nodes/Document', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/Document')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p></p>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the document in as json', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
const json = editor.getJSON()
|
||||||
|
|
||||||
|
expect(json).to.deep.equal({
|
||||||
|
type: 'doc',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
41
demos/src/Nodes/Document/Vue/index.vue
Normal file
41
demos/src/Nodes/Document/Vue/index.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>The Document extension is required. Though, you can write your own implementation, e. g. to give it custom name.</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
15
demos/src/Nodes/HardBreak/Vue/index.html
Normal file
15
demos/src/Nodes/HardBreak/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/HardBreak', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
58
demos/src/Nodes/HardBreak/Vue/index.spec.js
Normal file
58
demos/src/Nodes/HardBreak/Vue/index.spec.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
context('/demos/Nodes/HardBreak', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/HardBreak')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse hard breaks correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example<br>Text</p>')
|
||||||
|
expect(editor.getHTML()).to.eq('<p>Example<br>Text</p>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse hard breaks with self-closing tag correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example<br />Text</p>')
|
||||||
|
expect(editor.getHTML()).to.eq('<p>Example<br>Text</p>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should add a line break', () => {
|
||||||
|
cy.get('.ProseMirror br')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror br')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the default keyboard shortcut should add a line break', () => {
|
||||||
|
cy.get('.ProseMirror br')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { shiftKey: true, key: 'Enter' })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror br')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the alternative keyboard shortcut should add a line break', () => {
|
||||||
|
cy.get('.ProseMirror br')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, key: 'Enter' })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror br')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
56
demos/src/Nodes/HardBreak/Vue/index.vue
Normal file
56
demos/src/Nodes/HardBreak/Vue/index.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().setHardBreak().run()">
|
||||||
|
hardBreak
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import HardBreak from '@tiptap/extension-hard-break'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
HardBreak,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>
|
||||||
|
This<br>
|
||||||
|
is<br>
|
||||||
|
a<br>
|
||||||
|
single<br>
|
||||||
|
paragraph<br>
|
||||||
|
with<br>
|
||||||
|
line<br>
|
||||||
|
breaks.
|
||||||
|
</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
15
demos/src/Nodes/Heading/Vue/index.html
Normal file
15
demos/src/Nodes/Heading/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/Heading', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
142
demos/src/Nodes/Heading/Vue/index.spec.js
Normal file
142
demos/src/Nodes/Heading/Vue/index.spec.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
context('/demos/Nodes/Heading', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/Heading')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const headings = [
|
||||||
|
'<h1>Example Text</h1>',
|
||||||
|
'<h2>Example Text</h2>',
|
||||||
|
'<h3>Example Text</h3>',
|
||||||
|
]
|
||||||
|
|
||||||
|
headings.forEach(html => {
|
||||||
|
it(`should parse headings correctly (${html})`, () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent(html)
|
||||||
|
expect(editor.getHTML()).to.eq(html)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should omit disabled heading levels', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<h4>Example Text</h4>')
|
||||||
|
expect(editor.getHTML()).to.eq('<p>Example Text</p>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should make the selected line a h1', () => {
|
||||||
|
cy.get('.ProseMirror h1')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('h1')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should make the selected line a h2', () => {
|
||||||
|
cy.get('.ProseMirror h2')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(2)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('h2')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should make the selected line a h3', () => {
|
||||||
|
cy.get('.ProseMirror h3')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(3)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('h3')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should toggle the heading', () => {
|
||||||
|
cy.get('.ProseMirror h1')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('h1')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror h1')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make the paragraph a h1 keyboard shortcut is pressed', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, altKey: true, key: '1' })
|
||||||
|
.find('h1')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make the paragraph a h2 keyboard shortcut is pressed', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, altKey: true, key: '2' })
|
||||||
|
.find('h2')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make the paragraph a h3 keyboard shortcut is pressed', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, altKey: true, key: '3' })
|
||||||
|
.find('h3')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a h1 from the default markdown shortcut', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('# Headline')
|
||||||
|
.find('h1')
|
||||||
|
.should('contain', 'Headline')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a h2 from the default markdown shortcut', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('## Headline')
|
||||||
|
.find('h2')
|
||||||
|
.should('contain', 'Headline')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a h3 from the default markdown shortcut', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('### Headline')
|
||||||
|
.find('h3')
|
||||||
|
.should('contain', 'Headline')
|
||||||
|
})
|
||||||
|
})
|
||||||
58
demos/src/Nodes/Heading/Vue/index.vue
Normal file
58
demos/src/Nodes/Heading/Vue/index.vue
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
|
||||||
|
h1
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
|
||||||
|
h2
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
|
||||||
|
h3
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import Heading from '@tiptap/extension-heading'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
Heading.configure({
|
||||||
|
levels: [1, 2, 3],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<h1>This is a 1st level heading</h1>
|
||||||
|
<h2>This is a 2nd level heading</h2>
|
||||||
|
<h3>This is a 3rd level heading</h3>
|
||||||
|
<h4>This 4th level heading will be converted to a paragraph, because levels are configured to be only 1, 2 or 3.</h4>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
15
demos/src/Nodes/HorizontalRule/Vue/index.html
Normal file
15
demos/src/Nodes/HorizontalRule/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/HorizontalRule', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
66
demos/src/Nodes/HorizontalRule/Vue/index.spec.js
Normal file
66
demos/src/Nodes/HorizontalRule/Vue/index.spec.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
context('/demos/Nodes/HorizontalRule', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/HorizontalRule')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse horizontal rules correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p><hr>')
|
||||||
|
expect(editor.getHTML()).to.eq('<p>Example Text</p><hr>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse horizontal rules with self-closing tag correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p><hr />')
|
||||||
|
expect(editor.getHTML()).to.eq('<p>Example Text</p><hr>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should add a horizontal rule', () => {
|
||||||
|
cy.get('.ProseMirror hr')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror hr')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the default markdown shortcut should add a horizontal rule', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror hr')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('---')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror hr')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the alternative markdown shortcut should add a horizontal rule', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror hr')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('___ ')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror hr')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
51
demos/src/Nodes/HorizontalRule/Vue/index.vue
Normal file
51
demos/src/Nodes/HorizontalRule/Vue/index.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().setHorizontalRule().run()">
|
||||||
|
horizontalRule
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import HorizontalRule from '@tiptap/extension-horizontal-rule'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
HorizontalRule,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>This is a paragraph.</p>
|
||||||
|
<hr>
|
||||||
|
<p>And this is another paragraph.</p>
|
||||||
|
<hr>
|
||||||
|
<p>But between those paragraphs are horizontal rules.</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
15
demos/src/Nodes/Image/Vue/index.html
Normal file
15
demos/src/Nodes/Image/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/Image', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
demos/src/Nodes/Image/Vue/index.spec.js
Normal file
27
demos/src/Nodes/Image/Vue/index.spec.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
context('/demos/Nodes/Image', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/Image')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add an img tag with the correct URL', () => {
|
||||||
|
cy.window().then(win => {
|
||||||
|
cy.stub(win, 'prompt').returns('foobar.png')
|
||||||
|
|
||||||
|
cy.get('button:first')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.window().its('prompt').should('be.called')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('img')
|
||||||
|
.should('have.attr', 'src', 'foobar.png')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
78
demos/src/Nodes/Image/Vue/index.vue
Normal file
78
demos/src/Nodes/Image/Vue/index.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="addImage">
|
||||||
|
image
|
||||||
|
</button>
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import Image from '@tiptap/extension-image'
|
||||||
|
import Dropcursor from '@tiptap/extension-dropcursor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
addImage() {
|
||||||
|
const url = window.prompt('URL')
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
this.editor.chain().focus().setImage({ src: url }).run()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
Image,
|
||||||
|
Dropcursor,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>This is a basic example of implementing images. Drag to re-order.</p>
|
||||||
|
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" />
|
||||||
|
<img src="https://source.unsplash.com/K9QHL52rE2k/800x400" />
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
&.ProseMirror-selectednode {
|
||||||
|
outline: 3px solid #68CEF8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/ListItem/Vue/index.html
Normal file
15
demos/src/Nodes/ListItem/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/ListItem', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
49
demos/src/Nodes/ListItem/Vue/index.spec.js
Normal file
49
demos/src/Nodes/ListItem/Vue/index.spec.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
context('/demos/Nodes/ListItem', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/ListItem')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<ul><li>Example Text</li></ul>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a new list item on Enter', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{enter}2nd Item')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(1)')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(2)')
|
||||||
|
.should('contain', '2nd Item')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should sink the list item on Tab', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{enter}')
|
||||||
|
.trigger('keydown', { key: 'Tab' })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror').type('2nd Level')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(1) li')
|
||||||
|
.should('contain', '2nd Level')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should lift the list item on Shift+Tab', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{enter}')
|
||||||
|
.trigger('keydown', { key: 'Tab' })
|
||||||
|
.trigger('keydown', { shiftKey: true, key: 'Tab' })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror').type('1st Level')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(2)')
|
||||||
|
.should('contain', '1st Level')
|
||||||
|
})
|
||||||
|
})
|
||||||
86
demos/src/Nodes/ListItem/Vue/index.vue
Normal file
86
demos/src/Nodes/ListItem/Vue/index.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
|
||||||
|
bullet list
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
|
||||||
|
ordered list
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import BulletList from '@tiptap/extension-bullet-list'
|
||||||
|
import OrderedList from '@tiptap/extension-ordered-list'
|
||||||
|
import ListItem from '@tiptap/extension-list-item'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
BulletList,
|
||||||
|
OrderedList,
|
||||||
|
ListItem,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>
|
||||||
|
I like lists. Let’s add one:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>This is a bullet list.</li>
|
||||||
|
<li>And it has three list items.</li>
|
||||||
|
<li>Here is the third one.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Do you want to see one more? I bet! Here is another one:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>That’s a different list, actually it’s an ordered list.</li>
|
||||||
|
<li>It also has three list items.</li>
|
||||||
|
<li>And all of them are numbered.</li>
|
||||||
|
</ol>
|
||||||
|
<p>
|
||||||
|
Lists would be nothing without list items.
|
||||||
|
</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
112
demos/src/Nodes/Mention/Vue/MentionList.vue
Normal file
112
demos/src/Nodes/Mention/Vue/MentionList.vue
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<div class="items">
|
||||||
|
<button
|
||||||
|
class="item"
|
||||||
|
:class="{ 'is-selected': index === selectedIndex }"
|
||||||
|
v-for="(item, index) in items"
|
||||||
|
:key="index"
|
||||||
|
@click="selectItem(index)"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
command: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedIndex: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
items() {
|
||||||
|
this.selectedIndex = 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onKeyDown({ event }) {
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
this.upHandler()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
this.downHandler()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
this.enterHandler()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
upHandler() {
|
||||||
|
this.selectedIndex = ((this.selectedIndex + this.items.length) - 1) % this.items.length
|
||||||
|
},
|
||||||
|
|
||||||
|
downHandler() {
|
||||||
|
this.selectedIndex = (this.selectedIndex + 1) % this.items.length
|
||||||
|
},
|
||||||
|
|
||||||
|
enterHandler() {
|
||||||
|
this.selectItem(this.selectedIndex)
|
||||||
|
},
|
||||||
|
|
||||||
|
selectItem(index) {
|
||||||
|
const item = this.items[index]
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
this.command({ id: item })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.items {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background: white;
|
||||||
|
color: rgba(black, 0.8);
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.1),
|
||||||
|
0px 10px 20px rgba(0, 0, 0, 0.1),
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
|
||||||
|
&.is-selected,
|
||||||
|
&:hover {
|
||||||
|
color: #A975FF;
|
||||||
|
background: rgba(#A975FF, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/Mention/Vue/index.html
Normal file
15
demos/src/Nodes/Mention/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/Mention', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
demos/src/Nodes/Mention/Vue/index.spec.js
Normal file
7
demos/src/Nodes/Mention/Vue/index.spec.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
context('/demos/Nodes/Mention', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/Mention')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: Write tests
|
||||||
|
})
|
||||||
121
demos/src/Nodes/Mention/Vue/index.vue
Normal file
121
demos/src/Nodes/Mention/Vue/index.vue
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tippy from 'tippy.js'
|
||||||
|
import { Editor, EditorContent, VueRenderer } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import Mention from '@tiptap/extension-mention'
|
||||||
|
import MentionList from './MentionList.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
Mention.configure({
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'mention',
|
||||||
|
},
|
||||||
|
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 component
|
||||||
|
let popup
|
||||||
|
|
||||||
|
return {
|
||||||
|
onStart: props => {
|
||||||
|
component = new VueRenderer(MentionList, {
|
||||||
|
// using vue 2:
|
||||||
|
// parent: this,
|
||||||
|
// propsData: props,
|
||||||
|
props,
|
||||||
|
editor: props.editor,
|
||||||
|
})
|
||||||
|
|
||||||
|
popup = tippy('body', {
|
||||||
|
getReferenceClientRect: props.clientRect,
|
||||||
|
appendTo: () => document.body,
|
||||||
|
content: component.element,
|
||||||
|
showOnCreate: true,
|
||||||
|
interactive: true,
|
||||||
|
trigger: 'manual',
|
||||||
|
placement: 'bottom-start',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onUpdate(props) {
|
||||||
|
component.updateProps(props)
|
||||||
|
|
||||||
|
popup[0].setProps({
|
||||||
|
getReferenceClientRect: props.clientRect,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onKeyDown(props) {
|
||||||
|
if (props.event.key === 'Escape') {
|
||||||
|
popup[0].hide()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return component.ref?.onKeyDown(props)
|
||||||
|
},
|
||||||
|
onExit() {
|
||||||
|
popup[0].destroy()
|
||||||
|
component.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>Hi everyone! Don’t forget the daily stand up at 8 AM.</p>
|
||||||
|
<p><span data-mention data-id="Jennifer Grey"></span> Would you mind to share what you’ve been working on lately? We fear not much happened since Dirty Dancing.
|
||||||
|
<p><span data-mention data-id="Winona Ryder"></span> <span data-mention data-id="Axl Rose"></span> Let’s go through your most important points quickly.</p>
|
||||||
|
<p>I have a meeting with <span data-mention data-id="Christina Applegate"></span> and don’t want to come late.</p>
|
||||||
|
<p>– Thanks, your big boss</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mention {
|
||||||
|
color: #A975FF;
|
||||||
|
background-color: rgba(#A975FF, 0.1);
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
padding: 0.1rem 0.3rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/OrderedList/Vue/index.html
Normal file
15
demos/src/Nodes/OrderedList/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/OrderedList', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
131
demos/src/Nodes/OrderedList/Vue/index.spec.js
Normal file
131
demos/src/Nodes/OrderedList/Vue/index.spec.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
context('/demos/Nodes/OrderedList', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/OrderedList')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse ordered lists correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<ol><li><p>Example Text</p></li></ol>')
|
||||||
|
expect(editor.getHTML()).to.eq('<ol><li><p>Example Text</p></li></ol>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse ordered lists without paragraphs correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<ol><li>Example Text</li></ol>')
|
||||||
|
expect(editor.getHTML()).to.eq('<ol><li><p>Example Text</p></li></ol>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should make the selected line a ordered list item', () => {
|
||||||
|
cy.get('.ProseMirror ol')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror ol li')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ol')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ol li')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should toggle the ordered list', () => {
|
||||||
|
cy.get('.ProseMirror ol')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ol')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror ol')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make the paragraph an ordered list keyboard shortcut is pressed', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, shiftKey: true, key: '7' })
|
||||||
|
.find('ol li')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should leave the list with double enter', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('1. List Item 1{enter}{enter}Paragraph')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li')
|
||||||
|
.its('length')
|
||||||
|
.should('eq', 1)
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('p')
|
||||||
|
.should('contain', 'Paragraph')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a ordered list from a number', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('1. List Item 1{enter}List Item 2')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(1)')
|
||||||
|
.should('contain', 'List Item 1')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(2)')
|
||||||
|
.should('contain', 'List Item 2')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a ordered list from a number other than number one', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('2. List Item 1{enter}List Item 2')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ol')
|
||||||
|
.should('have.attr', 'start', '2')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove the ordered list after pressing backspace', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('1. {backspace}Example')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('p')
|
||||||
|
.should('contain', '1. Example')
|
||||||
|
})
|
||||||
|
})
|
||||||
71
demos/src/Nodes/OrderedList/Vue/index.vue
Normal file
71
demos/src/Nodes/OrderedList/Vue/index.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
|
||||||
|
ordered list
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import OrderedList from '@tiptap/extension-ordered-list'
|
||||||
|
import ListItem from '@tiptap/extension-list-item'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
OrderedList,
|
||||||
|
ListItem,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<ol>
|
||||||
|
<li>A list item</li>
|
||||||
|
<li>And another one</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<ol start="5">
|
||||||
|
<li>This item starts at 5</li>
|
||||||
|
<li>And another one</li>
|
||||||
|
</ol>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/Paragraph/Vue/index.html
Normal file
15
demos/src/Nodes/Paragraph/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/Paragraph', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
58
demos/src/Nodes/Paragraph/Vue/index.spec.js
Normal file
58
demos/src/Nodes/Paragraph/Vue/index.spec.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
context('/demos/Nodes/Paragraph', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/Paragraph')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse paragraphs correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
expect(editor.getHTML()).to.eq('<p>Example Text</p>')
|
||||||
|
|
||||||
|
editor.commands.setContent('<p><x-unknown>Example Text</x-unknown></p>')
|
||||||
|
expect(editor.getHTML()).to.eq('<p>Example Text</p>')
|
||||||
|
|
||||||
|
editor.commands.setContent('<p style="display: block;">Example Text</p>')
|
||||||
|
expect(editor.getHTML()).to.eq('<p>Example Text</p>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('text should be wrapped in a paragraph by default', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('Example Text')
|
||||||
|
.find('p')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('enter should make a new paragraph', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('First Paragraph{enter}Second Paragraph')
|
||||||
|
.find('p')
|
||||||
|
.should('have.length', 2)
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('p:first')
|
||||||
|
.should('contain', 'First Paragraph')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('p:nth-child(2)')
|
||||||
|
.should('contain', 'Second Paragraph')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('backspace should remove the second paragraph', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{enter}')
|
||||||
|
.find('p')
|
||||||
|
.should('have.length', 2)
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('{backspace}')
|
||||||
|
.find('p')
|
||||||
|
.should('have.length', 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
41
demos/src/Nodes/Paragraph/Vue/index.vue
Normal file
41
demos/src/Nodes/Paragraph/Vue/index.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>The Paragraph extension is not required, but it’s very likely you want to use it. It’s needed to write paragraphs of text. 🤓</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
15
demos/src/Nodes/Table/Vue/index.html
Normal file
15
demos/src/Nodes/Table/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/Table', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
77
demos/src/Nodes/Table/Vue/index.spec.js
Normal file
77
demos/src/Nodes/Table/Vue/index.spec.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
context('/demos/Nodes/Table', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/Table')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a table (1x1)', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror').find('td').its('length').should('eq', 1)
|
||||||
|
cy.get('.ProseMirror').find('tr').its('length').should('eq', 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a table (3x1)', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.insertTable({ cols: 3, rows: 1, withHeaderRow: false })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror').find('td').its('length').should('eq', 3)
|
||||||
|
cy.get('.ProseMirror').find('tr').its('length').should('eq', 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a table (1x3)', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.insertTable({ cols: 1, rows: 3, withHeaderRow: false })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror').find('td').its('length').should('eq', 3)
|
||||||
|
cy.get('.ProseMirror').find('tr').its('length').should('eq', 3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a table with header row (1x3)', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.insertTable({ cols: 1, rows: 3, withHeaderRow: true })
|
||||||
|
|
||||||
|
cy.get('.ProseMirror').find('th').its('length').should('eq', 1)
|
||||||
|
cy.get('.ProseMirror').find('td').its('length').should('eq', 2)
|
||||||
|
cy.get('.ProseMirror').find('tr').its('length').should('eq', 3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a table with correct defaults (3x3, th)', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.insertTable()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror').find('th').its('length').should('eq', 3)
|
||||||
|
cy.get('.ProseMirror').find('td').its('length').should('eq', 6)
|
||||||
|
cy.get('.ProseMirror').find('tr').its('length').should('eq', 3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('generates correct markup for a table (1x1)', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false })
|
||||||
|
|
||||||
|
const html = editor.getHTML()
|
||||||
|
expect(html).to.equal('<table><tbody><tr><td colspan="1" rowspan="1"><p></p></td></tr></tbody></table>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('generates correct markup for a table (1x1, th)', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: true })
|
||||||
|
|
||||||
|
const html = editor.getHTML()
|
||||||
|
expect(html).to.equal('<table><tbody><tr><th colspan="1" rowspan="1"><p></p></th></tr></tbody></table>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
181
demos/src/Nodes/Table/Vue/index.vue
Normal file
181
demos/src/Nodes/Table/Vue/index.vue
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()">
|
||||||
|
insertTable
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().addColumnBefore().run()">
|
||||||
|
addColumnBefore
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().addColumnAfter().run()">
|
||||||
|
addColumnAfter
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().deleteColumn().run()">
|
||||||
|
deleteColumn
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().addRowBefore().run()">
|
||||||
|
addRowBefore
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().addRowAfter().run()">
|
||||||
|
addRowAfter
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().deleteRow().run()">
|
||||||
|
deleteRow
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().deleteTable().run()">
|
||||||
|
deleteTable
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().mergeCells().run()">
|
||||||
|
mergeCells
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().splitCell().run()">
|
||||||
|
splitCell
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeaderColumn().run()">
|
||||||
|
toggleHeaderColumn
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeaderRow().run()">
|
||||||
|
toggleHeaderRow
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().toggleHeaderCell().run()">
|
||||||
|
toggleHeaderCell
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().mergeOrSplit().run()">
|
||||||
|
mergeOrSplit
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().setCellAttribute('colspan', 2).run()">
|
||||||
|
setCellAttribute
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().fixTables().run()">
|
||||||
|
fixTables
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().goToNextCell().run()">
|
||||||
|
goToNextCell
|
||||||
|
</button>
|
||||||
|
<button @click="editor.chain().focus().goToPreviousCell().run()">
|
||||||
|
goToPreviousCell
|
||||||
|
</button>
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import Table from '@tiptap/extension-table'
|
||||||
|
import TableRow from '@tiptap/extension-table-row'
|
||||||
|
import TableCell from '@tiptap/extension-table-cell'
|
||||||
|
import TableHeader from '@tiptap/extension-table-header'
|
||||||
|
import Gapcursor from '@tiptap/extension-gapcursor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
Gapcursor,
|
||||||
|
Table.configure({
|
||||||
|
resizable: true,
|
||||||
|
}),
|
||||||
|
TableRow,
|
||||||
|
TableHeader,
|
||||||
|
TableCell,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th colspan="3">Description</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Cyndi Lauper</td>
|
||||||
|
<td>singer</td>
|
||||||
|
<td>songwriter</td>
|
||||||
|
<td>actress</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ProseMirror {
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
min-width: 1em;
|
||||||
|
border: 2px solid #ced4da;
|
||||||
|
padding: 3px 5px;
|
||||||
|
vertical-align: top;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #f1f3f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedCell:after {
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
|
background: rgba(200, 200, 255, 0.4);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
right: -2px;
|
||||||
|
top: 0;
|
||||||
|
bottom: -2px;
|
||||||
|
width: 4px;
|
||||||
|
background-color: #adf;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableWrapper {
|
||||||
|
padding: 1rem 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-cursor {
|
||||||
|
cursor: ew-resize;
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/TaskItem/Vue/index.html
Normal file
15
demos/src/Nodes/TaskItem/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/TaskItem', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
demos/src/Nodes/TaskItem/Vue/index.spec.js
Normal file
7
demos/src/Nodes/TaskItem/Vue/index.spec.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
context('/demos/Nodes/TaskItem', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/TaskItem')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: Write tests
|
||||||
|
})
|
||||||
65
demos/src/Nodes/TaskItem/Vue/index.vue
Normal file
65
demos/src/Nodes/TaskItem/Vue/index.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import TaskList from '@tiptap/extension-task-list'
|
||||||
|
import TaskItem from '@tiptap/extension-task-item'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
TaskList,
|
||||||
|
TaskItem,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<ul data-type="taskList">
|
||||||
|
<li data-type="taskItem" data-checked="true">A list item</li>
|
||||||
|
<li data-type="taskItem" data-checked="false">And another one</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
ul[data-type="taskList"] {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/TaskList/Vue/index.html
Normal file
15
demos/src/Nodes/TaskList/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/TaskList', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
126
demos/src/Nodes/TaskList/Vue/index.spec.js
Normal file
126
demos/src/Nodes/TaskList/Vue/index.spec.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
context('/demos/Nodes/TaskList', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/TaskList')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<p>Example Text</p>')
|
||||||
|
cy.get('.ProseMirror').type('{selectall}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse unordered lists correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<ul data-type="taskList"><li data-checked="true" data-type="taskItem"><p>Example Text</p></li></ul>')
|
||||||
|
expect(editor.getHTML()).to.eq('<ul data-type="taskList"><li data-checked="true" data-type="taskItem"><p>Example Text</p></li></ul>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should parse unordered lists without paragraphs correctly', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.setContent('<ul data-type="taskList"><li data-checked="false" data-type="taskItem">Example Text</li></ul>')
|
||||||
|
expect(editor.getHTML()).to.eq('<ul data-type="taskList"><li data-checked="false" data-type="taskItem"><p>Example Text</p></li></ul>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should make the selected line a task list item', () => {
|
||||||
|
cy.get('.ProseMirror ul')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror ul li')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ul[data-type="taskList"]')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ul[data-type="taskList"] li')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the button should toggle the task list', () => {
|
||||||
|
cy.get('.ProseMirror ul')
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('ul[data-type="taskList"]')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
|
||||||
|
cy.get('button:nth-child(1)')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.get('.ProseMirror ul')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make the paragraph a task list when the keyboard shortcut is pressed', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.trigger('keydown', { modKey: true, shiftKey: true, key: '9' })
|
||||||
|
.find('ul li')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should leave the list with double enter', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('[ ] List Item 1{enter}{enter}Paragraph')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li')
|
||||||
|
.its('length')
|
||||||
|
.should('eq', 1)
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('p')
|
||||||
|
.should('contain', 'Paragraph')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a task list from square brackets', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('[ ] List Item 1{enter}List Item 2')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(1)')
|
||||||
|
.should('contain', 'List Item 1')
|
||||||
|
.should('have.attr', 'data-checked', 'false')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(2)')
|
||||||
|
.should('contain', 'List Item 2')
|
||||||
|
.should('have.attr', 'data-checked', 'false')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should make a task list from checked square brackets', () => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('[x] List Item 1{enter}List Item 2')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(1)')
|
||||||
|
.should('contain', 'List Item 1')
|
||||||
|
.should('have.attr', 'data-checked', 'true')
|
||||||
|
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.find('li:nth-child(2)')
|
||||||
|
.should('contain', 'List Item 2')
|
||||||
|
.should('have.attr', 'data-checked', 'false')
|
||||||
|
})
|
||||||
|
})
|
||||||
69
demos/src/Nodes/TaskList/Vue/index.vue
Normal file
69
demos/src/Nodes/TaskList/Vue/index.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<button @click="editor.chain().focus().toggleTaskList().run()" :class="{ 'is-active': editor.isActive('taskList') }">
|
||||||
|
task list
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
import TaskList from '@tiptap/extension-task-list'
|
||||||
|
import TaskItem from '@tiptap/extension-task-item'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
TaskList,
|
||||||
|
TaskItem,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<ul data-type="taskList">
|
||||||
|
<li data-type="taskItem" data-checked="true">A list item</li>
|
||||||
|
<li data-type="taskItem" data-checked="false">And another one</li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
ul[data-type="taskList"] {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
demos/src/Nodes/Text/Vue/index.html
Normal file
15
demos/src/Nodes/Text/Vue/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
import setup from '../../../../setup/vue.ts'
|
||||||
|
import source from '@source'
|
||||||
|
setup('Nodes/Text', source)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
demos/src/Nodes/Text/Vue/index.spec.js
Normal file
18
demos/src/Nodes/Text/Vue/index.spec.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
context('/demos/Nodes/Text', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/demos/Nodes/Text')
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||||
|
editor.commands.clearContent()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('text should be wrapped in a paragraph by default', () => {
|
||||||
|
cy.get('.ProseMirror')
|
||||||
|
.type('Example Text')
|
||||||
|
.find('p')
|
||||||
|
.should('contain', 'Example Text')
|
||||||
|
})
|
||||||
|
})
|
||||||
41
demos/src/Nodes/Text/Vue/index.vue
Normal file
41
demos/src/Nodes/Text/Vue/index.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="editor">
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import Document from '@tiptap/extension-document'
|
||||||
|
import Paragraph from '@tiptap/extension-paragraph'
|
||||||
|
import Text from '@tiptap/extension-text'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<p>The Text extension is required, at least if you want to have text in your text editor and that’s very likely.</p>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user