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:
@@ -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(),
|
||||
@@ -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') }">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: 'It’s 19871. You can’t turn on a radio, or go to a mall without hearing Olivia Newton-John’s 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>
|
||||
|
||||
118
docs/src/demos/Examples/Formatting/index.vue
Normal file
118
docs/src/demos/Examples/Formatting/index.vue
Normal 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 we’re 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 you’re still number one<br>
|
||||
But girls, they wanna have fun<br>
|
||||
Oh girls just want to have</p>
|
||||
<p style="text-align:right">That’s 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
context('/api/extensions/collaboration', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/collaboration')
|
||||
})
|
||||
})
|
||||
91
docs/src/demos/Extensions/CollaborationCursor/index.vue
Normal file
91
docs/src/demos/Extensions/CollaborationCursor/index.vue
Normal 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>
|
||||
9
docs/src/demos/Extensions/Focus/index.spec.js
Normal file
9
docs/src/demos/Extensions/Focus/index.spec.js
Normal 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')
|
||||
})
|
||||
})
|
||||
59
docs/src/demos/Extensions/Focus/index.vue
Normal file
59
docs/src/demos/Extensions/Focus/index.vue
Normal 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, it’ll 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>
|
||||
5
docs/src/demos/Extensions/Gapcursor/index.spec.js
Normal file
5
docs/src/demos/Extensions/Gapcursor/index.spec.js
Normal file
@@ -0,0 +1,5 @@
|
||||
context('/examples/gapcursor', () => {
|
||||
before(() => {
|
||||
cy.visit('/examples/gapcursor')
|
||||
})
|
||||
})
|
||||
78
docs/src/demos/Extensions/Gapcursor/index.vue
Normal file
78
docs/src/demos/Extensions/Gapcursor/index.vue
Normal 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>
|
||||
@@ -1,5 +0,0 @@
|
||||
context('/api/extensions/list-item', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/list-item')
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/text', () => {
|
||||
context('/api/nodes/text', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/text')
|
||||
cy.visit('/api/nodes/text')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/bold', () => {
|
||||
context('/api/marks/bold', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/bold')
|
||||
cy.visit('/api/marks/bold')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/code', () => {
|
||||
context('/api/marks/code', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/code')
|
||||
cy.visit('/api/marks/code')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/italic', () => {
|
||||
context('/api/marks/italic', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/italic')
|
||||
cy.visit('/api/marks/italic')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/link', () => {
|
||||
context('/api/marks/link', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/link')
|
||||
cy.visit('/api/marks/link')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/strike', () => {
|
||||
context('/api/marks/strike', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/strike')
|
||||
cy.visit('/api/marks/strike')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/underline', () => {
|
||||
context('/api/marks/underline', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/underline')
|
||||
cy.visit('/api/marks/underline')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/blockquote', () => {
|
||||
context('/api/nodes/blockquote', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/blockquote')
|
||||
cy.visit('/api/nodes/blockquote')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -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(() => {
|
||||
@@ -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>
|
||||
|
||||
@@ -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(() => {
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/document', () => {
|
||||
context('/api/nodes/document', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/document')
|
||||
cy.visit('/api/nodes/document')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -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(() => {
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/heading', () => {
|
||||
context('/api/nodes/heading', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/heading')
|
||||
cy.visit('/api/nodes/heading')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -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(() => {
|
||||
27
docs/src/demos/Nodes/Image/index.spec.js
Normal file
27
docs/src/demos/Nodes/Image/index.spec.js
Normal 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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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" />
|
||||
`,
|
||||
})
|
||||
},
|
||||
5
docs/src/demos/Nodes/ListItem/index.spec.js
Normal file
5
docs/src/demos/Nodes/ListItem/index.spec.js
Normal file
@@ -0,0 +1,5 @@
|
||||
context('/api/nodes/list-item', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/nodes/list-item')
|
||||
})
|
||||
})
|
||||
@@ -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>
|
||||
|
||||
@@ -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(() => {
|
||||
@@ -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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/paragraph', () => {
|
||||
context('/api/nodes/paragraph', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/paragraph')
|
||||
cy.visit('/api/nodes/paragraph')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
119
docs/src/demos/Nodes/TaskList/index.spec.js
Normal file
119
docs/src/demos/Nodes/TaskList/index.spec.js
Normal 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')
|
||||
})
|
||||
})
|
||||
70
docs/src/demos/Nodes/TaskList/index.vue
Normal file
70
docs/src/demos/Nodes/TaskList/index.vue
Normal 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>
|
||||
@@ -1,6 +1,6 @@
|
||||
context('/api/extensions/text', () => {
|
||||
context('/api/nodes/text', () => {
|
||||
before(() => {
|
||||
cy.visit('/api/extensions/text')
|
||||
cy.visit('/api/nodes/text')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -18,7 +18,7 @@ export default {
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
content: '<p>Hello, I’m tiptap running in Vue.js! 👋</p>',
|
||||
content: '<p>Hello, here is tiptap! 👋</p>',
|
||||
extensions: defaultExtensions(),
|
||||
})
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user