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