Merge branch 'main' into feature/new-highlight-extension

# Conflicts:
#	docs/src/docPages/api/extensions.md
#	docs/src/links.yaml
#	packages/core/src/extensions/toggleMark.ts
This commit is contained in:
Philipp Kühn
2020-11-05 21:27:20 +01:00
245 changed files with 4197 additions and 2609 deletions

View File

@@ -1,9 +1,9 @@
<template>
<pre>{{ html }}</pre>
<pre><code>{{ html }}</code></pre>
</template>
<script>
import { generateHtml } from '@tiptap/core'
import { generateHTML } from '@tiptap/html'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
@@ -26,7 +26,7 @@ export default {
},
mounted() {
this.html = generateHtml(this.json, [
this.html = generateHTML(this.json, [
Document(),
Paragraph(),
Text(),

View File

@@ -14,7 +14,10 @@
code
</button>
<button @click="editor.chain().focus().removeMarks().run()">
clear format
clear marks
</button>
<button @click="editor.chain().focus().clearNodes().run()">
clear nodes
</button>
<button @click="editor.chain().focus().paragraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph
@@ -37,10 +40,10 @@
<button @click="editor.chain().focus().heading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
h6
</button>
<button @click="editor.chain().focus().bulletList().run()" :class="{ 'is-active': editor.isActive('bullet_list') }">
<button @click="editor.chain().focus().bulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list
</button>
<button @click="editor.chain().focus().orderedList().run()" :class="{ 'is-active': editor.isActive('ordered_list') }">
<button @click="editor.chain().focus().orderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list
</button>
<button @click="editor.chain().focus().codeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">

View File

@@ -1,10 +1,13 @@
<template>
<div>
<button @click="setName">
set username
Set Name
</button>
<button @click="changeName">
Random Name
</button>
<button @click="changeColor">
change color
Random Color
</button>
<div class="collaboration-status">
@@ -89,7 +92,16 @@ export default {
methods: {
setName() {
this.name = window.prompt('Name')
const name = window.prompt('Name')
if (name) {
this.name = name
return this.updateUser()
}
},
changeName() {
this.name = this.getRandomName()
this.updateUser()
},
@@ -108,28 +120,26 @@ export default {
},
getRandomColor() {
const colors = [
'#f03e3e',
'#d6336c',
'#ae3ec9',
'#7048e8',
'#4263eb',
'#1c7ed6',
'#1098ad',
'#0ca678',
'#37b24d',
'#74b816',
'#f59f00',
'#f76707',
]
return colors[Math.floor(Math.random() * colors.length)]
return this.getRandomElement([
'#616161',
'#A975FF',
'#FB5151',
'#fd9170',
'#FFCB6B',
'#68CEF8',
'#80cbc4',
'#9DEF8F',
])
},
getRandomName() {
const names = ['🙈', '🙉', '🙊', '💥', '💫', '💦', '💨', '🐵', '🐒', '🦍', '🦧', '🐶', '🐕', '🦮', '🐕‍🦺', '🐩', '🐺', '🦊', '🦝', '🐱', '🐈', '🦁', '🐯', '🐅', '🐆', '🐴', '🐎', '🦄', '🦓', '🦌', '🐮', '🐂', '🐃', '🐄', '🐷', '🐖', '🐗', '🐽', '🐏', '🐑', '🐐', '🐪', '🐫', '🦙', '🦒', '🐘', '🦏', '🦛', '🐭', '🐁', '🐀', '🐹', '🐰', '🐇', '🐿', '🦔', '🦇', '🐻', '🐨', '🐼', '🦥', '🦦', '🦨', '🦘', '🦡', '🐾', '🦃', '🐔', '🐓', '🐣', '🐤', '🐥', '🐦', '🐧', '🕊', '🦅', '🦆', '🦢', '🦉', '🦩', '🦚', '🦜', '🐸', '🐊', '🐢', '🦎', '🐍', '🐲', '🐉', '🦕', '🦖', '🐳', '🐋', '🐬', '🐟', '🐠', '🐡', '🦈', '🐙', '🐚', '🐌', '🦋', '🐛', '🐜', '🐝', '🐞', '🦗', '🕷', '🕸', '🦂', '🦟', '🦠']
return this.getRandomElement([
'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',
])
},
return names[Math.floor(Math.random() * names.length)]
getRandomElement(list) {
return list[Math.floor(Math.random() * list.length)]
},
updateState() {
@@ -152,6 +162,7 @@ export default {
</script>
<style lang="scss">
/* A list of all available users */
.collaboration-users {
margin-top: 0.5rem;
@@ -165,6 +176,7 @@ export default {
}
}
/* Some information about the status */
.collaboration-status {
background: #eee;
color: #666;
@@ -183,7 +195,7 @@ export default {
}
}
/* This gives the remote user caret */
/* Give a remote user a caret */
.collaboration-cursor__caret {
position: relative;
margin-left: -1px;
@@ -194,10 +206,10 @@ export default {
pointer-events: none;
}
/* This renders the username above the caret */
/* Render the username above the caret */
.collaboration-cursor__label {
position: absolute;
top: -1.6em;
top: -1.4em;
left: -1px;
font-size: 13px;
font-style: normal;

View File

@@ -1,12 +1,18 @@
<template>
<div>
<div class="actions">
<div class="actions" v-if="editor">
<button class="button" @click="setContent">
Set Content
</button>
<button class="button" @click="clearContent">
Clear Content
</button>
<button @click="editor.chain().focus().bold().run()" :class="{ 'is-active': editor.isActive('bold') }">
Bold
</button>
<button @click="editor.chain().focus().italic().run()" :class="{ 'is-active': editor.isActive('italic') }">
Italic
</button>
</div>
<editor-content :editor="editor" />
@@ -70,7 +76,7 @@ export default {
content: [
{
type: 'text',
text: 'This is some inserted text. 👋',
text: 'Its 19871. You cant turn on a radio, or go to a mall without hearing Olivia Newton-Johns hit song, Physical.',
},
],
}],
@@ -97,9 +103,12 @@ export default {
<style lang="scss">
.export {
padding: 1rem 0 0;
h3 {
margin: 0.5rem 0;
margin: 1rem 0 0.5rem;
}
pre {
border-radius: 5px;
color: #333;
@@ -109,7 +118,9 @@ export default {
display: block;
white-space: pre-wrap;
font-size: 0.8rem;
padding: 1rem;
padding: 0.75rem 1rem;
background-color:#e9ecef;
color: #495057;
}
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<div>
<div v-if="editor">
<button @click="editor.chain().focus().bold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold
</button>
<button @click="editor.chain().focus().italic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic
</button>
<button @click="editor.chain().focus().heading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
h1
</button>
<button @click="editor.chain().focus().heading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2
</button>
<button @click="editor.chain().focus().heading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3
</button>
<button @click="editor.chain().focus().paragraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph
</button>
<button @click="editor.chain().focus().textAlign('left').run()">
left
</button>
<button @click="editor.chain().focus().textAlign('center').run()">
center
</button>
<button @click="editor.chain().focus().textAlign('right').run()">
right
</button>
<button @click="editor.chain().focus().textAlign('justify').run()">
justify
</button>
</div>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-starter-kit'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Heading from '@tiptap/extension-heading'
import Bold from '@tiptap/extension-bold'
import Italic from '@tiptap/extension-italic'
import TextAlign from '@tiptap/extension-text-align'
import HardBreak from '@tiptap/extension-hard-break'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document(),
Paragraph(),
Text(),
Heading({
level: [1, 2, 3],
}),
Bold(),
Italic(),
TextAlign(),
HardBreak(),
],
content: `
<h3>Girls Just Want to Have Fun (Cyndi Lauper)</h2>
<p>I come home in the morning light<br>
My mother says, “When you gonna live your life right?”<br>
Oh mother dear were not the fortunate ones<br>
And girls, they wanna have fun<br>
Oh girls just want to have fun</p>
<p style="text-align: center">The phone rings in the middle of the night<br>
My father yells, "What you gonna do with your life?"<br>
Oh daddy dear, you know youre still number one<br>
But girls, they wanna have fun<br>
Oh girls just want to have</p>
<p style="text-align:right">Thats all they really want<br>
Some fun<br>
When the working day is done<br>
Oh girls, they wanna have fun<br>
Oh girls just wanna have fun<br>
(girls, they wanna, wanna have fun, girls wanna have)</p>
<p style="text-align:justify">Some boys take a beautiful girl
And hide her away from the rest of the world
I want to be the one to walk in the sun
Oh girls, they wanna have fun
Oh girls just wanna have</p>
<p style="text-align:justify">That's all they really want
Some fun
When the working day is done
Oh girls, they wanna have fun
Oh girls just want to have fun (girls, they wanna, wanna have fun, girls wanna have)
They just wanna, they just wanna (girls)
They just wanna, they just wanna, oh girl (girls just wanna have fun)
Girls just wanna have fun
They just wanna, they just wanna
They just wanna, they just wanna (girls)
They just wanna, they just wanna, oh girl (girls just wanna have fun)
Girls just want to have fun</p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View File

@@ -11,13 +11,19 @@
</template>
<script>
import { Editor } from '@tiptap/core'
import { Editor, mergeAttributes } from '@tiptap/core'
import { EditorContent } from '@tiptap/vue'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Link from '@tiptap/extension-link'
const CustomLink = Link.extend({
renderHTML({ attributes }) {
return ['strong', mergeAttributes(attributes, { rel: this.options.rel }), 0]
},
})
export default {
components: {
EditorContent,
@@ -35,7 +41,7 @@ export default {
Document(),
Paragraph(),
Text(),
Link(),
CustomLink(),
],
content: `
<p>

View File

@@ -22,13 +22,13 @@ export default {
this.editor = new Editor({
content: `
<p>
Start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels.
Markdown shortcuts make it easy to format the text while typing.
</p>
<p>
Those conventions are called <strong>input rules</strong> in tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code.
To test that, start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels.
</p>
<p>
You can add your own input rules to your Nodes and Marks or even to the default ones.
Those conventions are called input rules in tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code. You can add your own input rules to existing extensions, and to your custom nodes and marks.
</p>
`,
extensions: defaultExtensions(),

View File

@@ -0,0 +1,5 @@
context('/api/extensions/collaboration', () => {
before(() => {
cy.visit('/api/extensions/collaboration')
})
})

View File

@@ -0,0 +1,91 @@
<template>
<editor-content :editor="editor" />
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-starter-kit'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
import * as Y from 'yjs'
import { WebrtcProvider } from 'y-webrtc'
export default {
components: {
EditorContent,
},
data() {
return {
documentName: 'tiptap-collaboration-cursor-extension',
ydoc: null,
provider: null,
type: null,
editor: null,
}
},
mounted() {
this.ydoc = new Y.Doc()
this.provider = new WebrtcProvider(this.documentName, this.ydoc)
this.type = this.ydoc.getXmlFragment('prosemirror')
this.editor = new Editor({
// TODO: This is added by every new user.
// content: `
// <p>Example Text</p>
// `,
extensions: [
Document(),
Paragraph(),
Text(),
Collaboration({
provider: this.provider,
type: this.type,
}),
CollaborationCursor({
provider: this.provider,
name: 'Cyndi Lauper',
color: '#f783ac',
}),
],
})
},
beforeDestroy() {
this.editor.destroy()
this.provider.destroy()
},
}
</script>
<style lang="scss">
/* Give a remote user a caret */
.collaboration-cursor__caret {
position: relative;
margin-left: -1px;
margin-right: -1px;
border-left: 1px solid black;
border-right: 1px solid black;
word-break: normal;
pointer-events: none;
}
/* Render the username above the caret */
.collaboration-cursor__label {
position: absolute;
top: -1.4em;
left: -1px;
font-size: 13px;
font-style: normal;
font-weight: normal;
line-height: normal;
user-select: none;
color: white;
padding: 0.1rem 0.3rem;
border-radius: 3px;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,9 @@
context('/examples/focus', () => {
before(() => {
cy.visit('/examples/focus')
})
it('should have class', () => {
cy.get('.ProseMirror p:first').should('have.class', 'has-focus')
})
})

View File

@@ -0,0 +1,59 @@
<template>
<div>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-starter-kit'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Focus from '@tiptap/extension-focus'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document(),
Paragraph(),
Text(),
Focus({
className: 'has-focus',
nested: true,
}),
],
autoFocus: true,
content: `
<p>
The focus extension adds a class to the focused node only. That enables you to add a custom styling to just that node. By default, itll add <code>.has-focus</code>, even to nested nodes.
</p>
<p>
Nested elements will be focused with the default setting nested: true. Otherwise the whole item will get the focus class, even when just a single nested item is selected.
</p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
.has-focus {
border-radius: 3px;
box-shadow: 0 0 0 3px #68CEF8;
}
</style>

View File

@@ -0,0 +1,5 @@
context('/examples/gapcursor', () => {
before(() => {
cy.visit('/examples/gapcursor')
})
})

View File

@@ -0,0 +1,78 @@
<template>
<div>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-starter-kit'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Gapcursor from '@tiptap/extension-gapcursor'
import Image from '@tiptap/extension-image'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document(),
Paragraph(),
Text(),
Image(),
Gapcursor(),
],
content: `
<p>Try to set the cursor behind the image with your arrow keys! You should see big blinking cursor right from the image. This is the gapcursor.</p>
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" />
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Copied from the original prosemirror-gapcursor plugin by Marijn Haverbeke */
/* https://github.com/ProseMirror/prosemirror-gapcursor/blob/master/style/gapcursor.css */
.ProseMirror-gapcursor {
display: none;
pointer-events: none;
position: absolute;
border: 10px solid red;
}
.ProseMirror-gapcursor:after {
content: "";
display: block;
position: absolute;
top: -2px;
width: 20px;
border-top: 1px solid black;
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
}
@keyframes ProseMirror-cursor-blink {
to {
visibility: hidden;
}
}
.ProseMirror-focused .ProseMirror-gapcursor {
display: block;
}
</style>

View File

@@ -1,5 +0,0 @@
context('/api/extensions/list-item', () => {
before(() => {
cy.visit('/api/extensions/list-item')
})
})

View File

@@ -1,6 +1,6 @@
context('/api/extensions/text', () => {
context('/api/nodes/text', () => {
before(() => {
cy.visit('/api/extensions/text')
cy.visit('/api/nodes/text')
})
beforeEach(() => {

View File

@@ -9,6 +9,9 @@
<button @click="editor.chain().focus().textAlign('right').run()">
right
</button>
<button @click="editor.chain().focus().resetNodeAttributes(['textAlign']).run()">
set default
</button>
<editor-content :editor="editor" />
</div>
</template>

View File

@@ -1,6 +1,6 @@
context('/api/extensions/bold', () => {
context('/api/marks/bold', () => {
before(() => {
cy.visit('/api/extensions/bold')
cy.visit('/api/marks/bold')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/code', () => {
context('/api/marks/code', () => {
before(() => {
cy.visit('/api/extensions/code')
cy.visit('/api/marks/code')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/italic', () => {
context('/api/marks/italic', () => {
before(() => {
cy.visit('/api/extensions/italic')
cy.visit('/api/marks/italic')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/link', () => {
context('/api/marks/link', () => {
before(() => {
cy.visit('/api/extensions/link')
cy.visit('/api/marks/link')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/strike', () => {
context('/api/marks/strike', () => {
before(() => {
cy.visit('/api/extensions/strike')
cy.visit('/api/marks/strike')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/underline', () => {
context('/api/marks/underline', () => {
before(() => {
cy.visit('/api/extensions/underline')
cy.visit('/api/marks/underline')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/blockquote', () => {
context('/api/nodes/blockquote', () => {
before(() => {
cy.visit('/api/extensions/blockquote')
cy.visit('/api/nodes/blockquote')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/bullet-list', () => {
context('/api/nodes/bullet-list', () => {
before(() => {
cy.visit('/api/extensions/bullet-list')
cy.visit('/api/nodes/bullet-list')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
<template>
<div v-if="editor">
<button @click="editor.chain().focus().bulletList().run()" :class="{ 'is-active': editor.isActive('bullet_list') }">
<button @click="editor.chain().focus().bulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list
</button>

View File

@@ -1,6 +1,6 @@
context('/api/extensions/code-block', () => {
context('/api/nodes/code-block', () => {
before(() => {
cy.visit('/api/extensions/code-block')
cy.visit('/api/nodes/code-block')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/document', () => {
context('/api/nodes/document', () => {
before(() => {
cy.visit('/api/extensions/document')
cy.visit('/api/nodes/document')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/hard-break', () => {
context('/api/nodes/hard-break', () => {
before(() => {
cy.visit('/api/extensions/hard-break')
cy.visit('/api/nodes/hard-break')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/heading', () => {
context('/api/nodes/heading', () => {
before(() => {
cy.visit('/api/extensions/heading')
cy.visit('/api/nodes/heading')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
context('/api/extensions/horizontal-rule', () => {
context('/api/nodes/horizontal-rule', () => {
before(() => {
cy.visit('/api/extensions/horizontal-rule')
cy.visit('/api/nodes/horizontal-rule')
})
beforeEach(() => {

View File

@@ -0,0 +1,27 @@
context('/api/nodes/image', () => {
before(() => {
cy.visit('/api/nodes/image')
})
beforeEach(() => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.setContent('<p>Example Text</p>')
editor.selectAll()
})
})
it('should add an img tag with the correct URL', () => {
cy.window().then(win => {
cy.stub(win, 'prompt').returns('foobar.png')
cy.get('.demo__preview button:first')
.click()
cy.window().its('prompt').should('be.called')
cy.get('.ProseMirror')
.find('img')
.should('have.attr', 'src', 'foobar.png')
})
})
})

View File

@@ -1,5 +1,8 @@
<template>
<div v-if="editor">
<button @click="addImage">
image
</button>
<editor-content :editor="editor" />
</div>
</template>
@@ -11,6 +14,7 @@ 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: {
@@ -23,6 +27,14 @@ export default {
}
},
methods: {
addImage() {
const url = window.prompt('URL')
this.editor.chain().focus().image({ src: url }).run()
},
},
mounted() {
this.editor = new Editor({
extensions: [
@@ -30,10 +42,12 @@ export default {
Paragraph(),
Text(),
Image(),
Dropcursor(),
],
content: `
<p>This is basic example of implementing images. Try to drop new images here. Reordering also works.</p>
<img src="https://66.media.tumblr.com/dcd3d24b79d78a3ee0f9192246e727f1/tumblr_o00xgqMhPM1qak053o1_400.gif" />
<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" />
`,
})
},

View File

@@ -0,0 +1,5 @@
context('/api/nodes/list-item', () => {
before(() => {
cy.visit('/api/nodes/list-item')
})
})

View File

@@ -1,9 +1,9 @@
<template>
<div v-if="editor">
<button @click="editor.chain().focus().bulletList().run()" :class="{ 'is-active': editor.isActive('bullet_list') }">
<button @click="editor.chain().focus().bulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list
</button>
<button @click="editor.chain().focus().orderedList().run()" :class="{ 'is-active': editor.isActive('ordered_list') }">
<button @click="editor.chain().focus().orderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list
</button>

View File

@@ -1,6 +1,6 @@
context('/api/extensions/ordered-list', () => {
context('/api/nodes/ordered-list', () => {
before(() => {
cy.visit('/api/extensions/ordered-list')
cy.visit('/api/nodes/ordered-list')
})
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
<template>
<div v-if="editor">
<button @click="editor.chain().focus().orderedList().run()" :class="{ 'is-active': editor.isActive('ordered_list') }">
<button @click="editor.chain().focus().orderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list
</button>

View File

@@ -1,6 +1,6 @@
context('/api/extensions/paragraph', () => {
context('/api/nodes/paragraph', () => {
before(() => {
cy.visit('/api/extensions/paragraph')
cy.visit('/api/nodes/paragraph')
})
beforeEach(() => {

View File

@@ -0,0 +1,119 @@
context('/api/nodes/task-list', () => {
before(() => {
cy.visit('/api/nodes/task-list')
})
beforeEach(() => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.setContent('<p>Example Text</p>')
editor.selectAll()
})
})
it('should parse unordered lists correctly', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.setContent('<ul data-type="task_list"><li data-checked="true" data-type="taskItem"><p>Example Text</p></li></ul>')
expect(editor.getHTML()).to.eq('<ul data-type="task_list"><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.setContent('<ul data-type="task_list"><li data-checked="false" data-type="taskItem">Example Text</li></ul>')
expect(editor.getHTML()).to.eq('<ul data-type="task_list"><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('.demo__preview button:nth-child(1)')
.click()
cy.get('.ProseMirror')
.find('ul[data-type="task_list"]')
.should('contain', 'Example Text')
cy.get('.ProseMirror')
.find('ul[data-type="task_list"] li')
.should('contain', 'Example Text')
})
it('the button should toggle the task list', () => {
cy.get('.ProseMirror ul')
.should('not.exist')
cy.get('.demo__preview button:nth-child(1)')
.click()
cy.get('.ProseMirror')
.find('ul[data-type="task_list"]')
.should('contain', 'Example Text')
cy.get('.demo__preview 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.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.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.only('should make a task list from checked square brackets', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.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', 'true')
})
})

View File

@@ -0,0 +1,70 @@
<template>
<div v-if="editor">
<button @click="editor.chain().focus().taskList().run()" :class="{ 'is-active': editor.isActive('task_list') }">
task list
</button>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor } from '@tiptap/core'
import { EditorContent } from '@tiptap/vue'
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="task_list">
<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;
> input {
flex: 0 0 auto;
margin-right: 0.5rem;
}
}
}
</style>

View File

@@ -1,6 +1,6 @@
context('/api/extensions/text', () => {
context('/api/nodes/text', () => {
before(() => {
cy.visit('/api/extensions/text')
cy.visit('/api/nodes/text')
})
beforeEach(() => {

View File

@@ -18,7 +18,7 @@ export default {
mounted() {
this.editor = new Editor({
content: '<p>Hello, Im tiptap running in Vue.js! 👋</p>',
content: '<p>Hello, here is tiptap! 👋</p>',
extensions: defaultExtensions(),
})
},