add extension demos

This commit is contained in:
Philipp Kühn 2021-08-25 18:03:42 +02:00
parent 6ab708b1a2
commit 88804668ff
44 changed files with 1695 additions and 1 deletions

View File

@ -81,7 +81,7 @@ export default {
}
</script>
<style lang="scss" scoped>
<style lang="scss">
.items {
position: relative;
border-radius: 0.25rem;

View 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/react.tsx'
import source from '@source'
setup('Extensions/BubbleMenu', source)
</script>
</body>
</html>

View File

@ -0,0 +1,43 @@
import React from 'react'
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import './styles.scss'
export default () => {
const editor = useEditor({
extensions: [
StarterKit,
],
content: `
<p>
Hey, try to select some text here. There will popup a menu for selecting some inline styles. Remember: you have full control about content and styling of this menu.
</p>
`,
})
return (
<>
{editor && <BubbleMenu editor={editor}>
<button
onClick={() => editor.chain().focus().toggleBold().run()}
className={editor.isActive('bold') ? 'is-active' : ''}
>
bold
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
className={editor.isActive('italic') ? 'is-active' : ''}
>
italic
</button>
<button
onClick={() => editor.chain().focus().toggleStrike().run()}
className={editor.isActive('strike') ? 'is-active' : ''}
>
strike
</button>
</BubbleMenu>}
<EditorContent editor={editor} />
</>
)
}

View File

@ -0,0 +1,5 @@
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}

View 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('Extensions/BubbleMenu', source)
</script>
</body>
</html>

View File

@ -0,0 +1,60 @@
<template>
<div>
<bubble-menu :editor="editor" v-if="editor">
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold
</button>
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic
</button>
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike
</button>
</bubble-menu>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
export default {
components: {
EditorContent,
BubbleMenu,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
StarterKit,
],
content: `
<p>
Hey, try to select some text here. Youll see a formatting menu pop up. And as always, you are in full control about content and styling of this menu.
</p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
</style>

View 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('Extensions/CharacterCount', source)
</script>
</body>
</html>

View File

@ -0,0 +1,66 @@
<template>
<div>
<editor-content :editor="editor" />
<div class="character-count" v-if="editor">
{{ editor.getCharacterCount() }}/{{ limit }} characters
</div>
</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 CharacterCount from '@tiptap/extension-character-count'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
limit: 280,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
CharacterCount.configure({
limit: this.limit,
}),
],
content: `
<p>
Lets make sure people cant write more than 280 characters. I bet you could build one of the biggest social networks on that idea.
</p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
.character-count {
margin-top: 1rem;
color: #868e96;
}
</style>

View 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('Extensions/Collaboration', source)
</script>
</body>
</html>

View File

@ -0,0 +1,7 @@
context('/demos/Extensions/Collaboration', () => {
before(() => {
cy.visit('/demos/Extensions/Collaboration')
})
// TODO: Write tests
})

View File

@ -0,0 +1,47 @@
<template>
<editor-content :editor="editor" />
</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 Collaboration from '@tiptap/extension-collaboration'
import * as Y from 'yjs'
import { WebrtcProvider } from 'y-webrtc'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
provider: null,
}
},
mounted() {
const ydoc = new Y.Doc()
this.provider = new WebrtcProvider('tiptap-collaboration-extension', ydoc)
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Collaboration.configure({
document: ydoc,
}),
],
})
},
beforeDestroy() {
this.editor.destroy()
this.provider.destroy()
},
}
</script>

View 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('Extensions/CollaborationCursor', source)
</script>
</body>
</html>

View File

@ -0,0 +1,7 @@
context('/demos/Extensions/CollaborationCursor', () => {
before(() => {
cy.visit('/demos/Extensions/CollaborationCursor')
})
// TODO: Write tests
})

View File

@ -0,0 +1,84 @@
<template>
<editor-content :editor="editor" />
</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 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 {
editor: null,
provider: null,
}
},
mounted() {
const ydoc = new Y.Doc()
this.provider = new WebrtcProvider('tiptap-collaboration-cursor-extension', ydoc)
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Collaboration.configure({
document: ydoc,
}),
CollaborationCursor.configure({
provider: this.provider,
user: {
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 #0D0D0D;
border-right: 1px solid #0D0D0D;
word-break: normal;
pointer-events: none;
}
/* Render the username above the caret */
.collaboration-cursor__label {
position: absolute;
top: -1.4em;
left: -1px;
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: normal;
user-select: none;
color: #0D0D0D;
padding: 0.1rem 0.3rem;
border-radius: 3px 3px 3px 0;
white-space: nowrap;
}
</style>

View 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('Extensions/Color', source)
</script>
</body>
</html>

View File

@ -0,0 +1,53 @@
context('/demos/Extensions/Color', () => {
before(() => {
cy.visit('/demos/Extensions/Color')
})
beforeEach(() => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.setContent('<p>Example Text</p>')
})
cy.get('.ProseMirror').type('{selectall}')
})
it('should set the color of the selected text', () => {
cy.get('button:first')
.should('not.have.class', 'is-active')
.click()
.should('have.class', 'is-active')
cy.get('.ProseMirror')
.find('span')
.should('have.attr', 'style', 'color: #958DF1')
})
it('should remove the color of the selected text', () => {
cy.get('button:first')
.click()
cy.get('.ProseMirror span').should('exist')
cy.get('button:last')
.click()
cy.get('.ProseMirror span').should('not.exist')
})
it('should change text color with color picker', () => {
cy.get('input[type=color]')
.invoke('val', '#ff0000')
.trigger('input')
cy.get('.ProseMirror')
.find('span')
.should('have.attr', 'style', 'color: #ff0000')
})
it('should match text and color picker color values', () => {
cy.get('button:first')
.click()
cy.get('input[type=color]')
.should('have.value', '#958df1')
})
})

View File

@ -0,0 +1,75 @@
<template>
<div v-if="editor">
<input
type="color"
@input="editor.chain().focus().setColor($event.target.value).run()"
:value="editor.getAttributes('textStyle').color"
>
<button @click="editor.chain().focus().setColor('#958DF1').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#958DF1' })}">
purple
</button>
<button @click="editor.chain().focus().setColor('#F98181').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#F98181' })}">
red
</button>
<button @click="editor.chain().focus().setColor('#FBBC88').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#FBBC88' })}">
orange
</button>
<button @click="editor.chain().focus().setColor('#FAF594').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#FAF594' })}">
yellow
</button>
<button @click="editor.chain().focus().setColor('#70CFF8').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#70CFF8' })}">
blue
</button>
<button @click="editor.chain().focus().setColor('#94FADB').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#94FADB' })}">
teal
</button>
<button @click="editor.chain().focus().setColor('#B9F18D').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#B9F18D' })}">
green
</button>
<button @click="editor.chain().focus().unsetColor().run()">
remove color
</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 TextStyle from '@tiptap/extension-text-style'
import { Color } from '@tiptap/extension-color'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
TextStyle,
Color,
],
content: `
<p><span style="color: #958DF1">Oh, for some reason thats purple.</span></p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View 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('Extensions/Dropcursor', source)
</script>
</body>
</html>

View File

@ -0,0 +1,7 @@
context('/demos/Examples/Dropcursor', () => {
before(() => {
cy.visit('/demos/Examples/Dropcursor')
})
// TODO: Write tests
})

View File

@ -0,0 +1,60 @@
<template>
<div>
<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 Dropcursor from '@tiptap/extension-dropcursor'
import Image from '@tiptap/extension-image'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Image,
Dropcursor,
],
content: `
<p>Try to drag around the image. While you drag, the editor should show a decoration under your cursor. The so called dropcursor.</p>
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" />
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
img {
max-width: 100%;
height: auto;
}
}
</style>

View File

@ -0,0 +1,44 @@
import React from 'react'
import { useEditor, EditorContent, FloatingMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import './styles.scss'
export default () => {
const editor = useEditor({
extensions: [
StarterKit,
],
content: `
<p>
This is an example of a Medium-like editor. Enter a new line and some buttons will appear.
</p>
<p></p>
`,
})
return (
<>
{editor && <FloatingMenu editor={editor}>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
>
h1
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
>
h2
</button>
<button
onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''}
>
bullet list
</button>
</FloatingMenu>}
<EditorContent editor={editor} />
</>
)
}

View File

@ -0,0 +1,10 @@
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
}

View 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('Extensions/FloatingMenu', source)
</script>
</body>
</html>

View File

@ -0,0 +1,66 @@
<template>
<div>
<floating-menu :editor="editor" 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().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list
</button>
</floating-menu>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent, FloatingMenu } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
export default {
components: {
EditorContent,
FloatingMenu,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
StarterKit,
],
content: `
<p>
This is an example of a Medium-like editor. Enter a new line and some buttons will appear.
</p>
<p></p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
}
</style>

View 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('Extensions/Focus', source)
</script>
</body>
</html>

View File

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

View File

@ -0,0 +1,88 @@
<template>
<div>
<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 Focus from '@tiptap/extension-focus'
import Code from '@tiptap/extension-code'
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,
Focus.configure({
className: 'has-focus',
mode: 'all',
}),
Code,
BulletList,
ListItem,
],
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>
<ul>
<li>Nested elements (like this list item) will be focused with the default setting of <code>mode: all</code>.</li>
<li>Otherwise the whole list will get the focus class, even when just a single list item is selected.</li>
</ul>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
.has-focus {
border-radius: 3px;
box-shadow: 0 0 0 3px #68CEF8;
}
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
}
</style>

View 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('Extensions/FontFamily', source)
</script>
</body>
</html>

View File

@ -0,0 +1,7 @@
context('/demos/Extensions/FontFamily', () => {
before(() => {
cy.visit('/demos/Extensions/FontFamily')
})
// TODO: Write tests
})

View File

@ -0,0 +1,68 @@
<template>
<div v-if="editor">
<button @click="editor.chain().focus().setFontFamily('Inter').run()" :class="{ 'is-active': editor.isActive('textStyle', { fontFamily: 'Inter' }) }">
Inter
</button>
<button @click="editor.chain().focus().setFontFamily('Comic Sans MS, Comic Sans').run()" :class="{ 'is-active': editor.isActive('textStyle', { fontFamily: 'Comic Sans MS, Comic Sans' }) }">
Comic Sans
</button>
<button @click="editor.chain().focus().setFontFamily('serif').run()" :class="{ 'is-active': editor.isActive('textStyle', { fontFamily: 'serif' }) }">
serif
</button>
<button @click="editor.chain().focus().setFontFamily('monospace').run()" :class="{ 'is-active': editor.isActive('textStyle', { fontFamily: 'monospace' }) }">
monospace
</button>
<button @click="editor.chain().focus().setFontFamily('cursive').run()" :class="{ 'is-active': editor.isActive('textStyle', { fontFamily: 'cursive' }) }">
cursive
</button>
<button @click="editor.chain().focus().unsetFontFamily().run()">
Remove font-family
</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 TextStyle from '@tiptap/extension-text-style'
import FontFamily from '@tiptap/extension-font-family'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
TextStyle,
FontFamily,
],
content: `
<p><span style="font-family: Inter">Did you know that Inter is a really nice font for interfaces?</span></p>
<p><span style="font-family: Comic Sans MS, Comic Sans">It doesnt look as professional as Comic Sans.</span></p>
<p><span style="font-family: serif">Serious people use serif fonts anyway.</span></p>
<p><span style="font-family: monospace">The cool kids can apply monospace fonts aswell.</span></p>
<p><span style="font-family: cursive">But hopefully we all can agree, that cursive fonts are the best.</span></p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View 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('Extensions/Gapcursor', source)
</script>
</body>
</html>

View File

@ -0,0 +1,7 @@
context('/demos/Examples/Gapcursor', () => {
before(() => {
cy.visit('/demos/Examples/Gapcursor')
})
// TODO: Write tests
})

View File

@ -0,0 +1,60 @@
<template>
<div>
<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 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 move the cursor after the image with your arrow keys! You should see a horizontal blinking cursor below the image. This is the gapcursor.</p>
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" />
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
img {
max-width: 100%;
height: auto;
}
}
</style>

View 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('Extensions/History', source)
</script>
</body>
</html>

View File

@ -0,0 +1,123 @@
context('/demos/Extensions/History', () => {
beforeEach(() => {
cy.visit('/demos/Extensions/History')
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.setContent('<p>Mistake</p>')
})
})
it('should make the last change undone', () => {
cy.get('.ProseMirror')
.should('contain', 'Mistake')
cy.get('button:first')
.should('not.have.attr', 'disabled')
cy.get('button:first')
.click()
cy.get('.ProseMirror')
.should('not.contain', 'Mistake')
})
it('should make the last change undone with the keyboard shortcut', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, key: 'z' })
cy.get('.ProseMirror')
.should('not.contain', 'Mistake')
})
it('should make the last change undone with the keyboard shortcut (russian)', () => {
cy.get('.ProseMirror')
.should('contain', 'Mistake')
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, key: 'я' })
cy.get('.ProseMirror')
.should('not.contain', 'Mistake')
})
it('should apply the last undone change again with the keyboard shortcut', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, key: 'z' })
cy.get('.ProseMirror')
.should('not.contain', 'Mistake')
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, shiftKey: true, key: 'z' })
cy.get('.ProseMirror')
.should('contain', 'Mistake')
})
it('should apply the last undone change again with the keyboard shortcut (russian)', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, key: 'я' })
cy.get('.ProseMirror')
.should('not.contain', 'Mistake')
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, shiftKey: true, key: 'я' })
cy.get('.ProseMirror')
.should('contain', 'Mistake')
})
it('should apply the last undone change again', () => {
cy.get('.ProseMirror')
.should('contain', 'Mistake')
cy.get('button:first')
.should('not.have.attr', 'disabled')
cy.get('button:first')
.click()
cy.get('.ProseMirror')
.should('not.contain', 'Mistake')
cy.get('button:first')
.should('have.attr', 'disabled')
cy.get('button:nth-child(2)')
.click()
cy.get('.ProseMirror')
.should('contain', 'Mistake')
})
it('should disable undo button when there are no more changes to undo', () => {
cy.get('.ProseMirror')
.should('contain', 'Mistake')
cy.get('button:first')
.should('not.have.attr', 'disabled')
cy.get('button:first')
.click()
cy.get('button:first')
.should('have.attr', 'disabled')
})
it('should disable redo button when there are no more changes to redo', () => {
cy.get('.ProseMirror')
.should('contain', 'Mistake')
cy.get('button:nth-child(2)')
.should('have.attr', 'disabled')
cy.get('button:first')
.should('not.have.attr', 'disabled')
cy.get('button:first')
.click()
cy.get('button:nth-child(2)')
.should('not.have.attr', 'disabled')
})
})

View File

@ -0,0 +1,61 @@
<template>
<div v-if="editor">
<button
@click="editor.chain().focus().undo().run()"
:disabled="!editor.can().undo()"
>
undo
</button>
<button
@click="editor.chain().focus().redo().run()"
:disabled="!editor.can().redo()"
>
redo
</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 History from '@tiptap/extension-history'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
History,
],
content: `
<p>
With the History extension the Editor will keep track of your changes. And if you think you made a mistake, you can redo your changes. Try it out, change the content and hit the undo button!
</p>
<p>
And yes, you can also use a keyboard shortcut to undo changes (Control/Cmd Z) or redo changes (Control/Cmd Shift Z).
</p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View 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('Extensions/Placeholder', source)
</script>
</body>
</html>

View File

@ -0,0 +1,61 @@
<template>
<editor-content :editor="editor" />
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extension-placeholder'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
StarterKit,
Placeholder,
],
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
/* Placeholder (at the top) */
.ProseMirror p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}
/* Placeholder (on every new line) */
/*.ProseMirror p.is-empty::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}*/
</style>

View 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('Extensions/TextAlign', source)
</script>
</body>
</html>

View File

@ -0,0 +1,112 @@
context('/demos/Extensions/TextAlign', () => {
before(() => {
cy.visit('/demos/Extensions/TextAlign')
})
beforeEach(() => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.setContent('<p>Example Text</p>')
})
})
it('should parse left align text correctly (and not render)', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.setContent('<p style="text-align: left">Example Text</p>')
expect(editor.getHTML()).to.eq('<p>Example Text</p>')
})
})
it('should parse center align text correctly', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.setContent('<p style="text-align: center">Example Text</p>')
expect(editor.getHTML()).to.eq('<p style="text-align: center">Example Text</p>')
})
})
it('should parse right align text correctly', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.setContent('<p style="text-align: right">Example Text</p>')
expect(editor.getHTML()).to.eq('<p style="text-align: right">Example Text</p>')
})
})
it('should parse left justify text correctly', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.setContent('<p style="text-align: justify">Example Text</p>')
expect(editor.getHTML()).to.eq('<p style="text-align: justify">Example Text</p>')
})
})
it('aligns the text left on the 1st button', () => {
cy.get('button:nth-child(1)')
.click()
cy.get('.ProseMirror')
.find('p')
.should('not.have.css', 'text-align', 'left')
})
it('aligns the text center on the 2nd button', () => {
cy.get('button:nth-child(2)')
.click()
cy.get('.ProseMirror')
.find('p')
.should('have.css', 'text-align', 'center')
})
it('aligns the text right on the 3rd button', () => {
cy.get('button:nth-child(3)')
.click()
cy.get('.ProseMirror')
.find('p')
.should('have.css', 'text-align', 'right')
})
it('aligns the text justified on the 4th button', () => {
cy.get('button:nth-child(4)')
.click()
cy.get('.ProseMirror')
.find('p')
.should('have.css', 'text-align', 'justify')
})
it('aligns the text default on the 5th button', () => {
cy.get('button:nth-child(5)')
.click()
cy.get('.ProseMirror')
.find('p')
.should('not.have.css', 'text-align', 'left')
})
it('aligns the text left when pressing the keyboard shortcut', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, shiftKey: true, key: 'l' })
.find('p')
.should('not.have.css', 'text-align', 'left')
})
it('aligns the text center when pressing the keyboard shortcut', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, shiftKey: true, key: 'e' })
.find('p')
.should('have.css', 'text-align', 'center')
})
it('aligns the text right when pressing the keyboard shortcut', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, shiftKey: true, key: 'r' })
.find('p')
.should('have.css', 'text-align', 'right')
})
it('aligns the text justified when pressing the keyboard shortcut', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, shiftKey: true, key: 'j' })
.find('p')
.should('have.css', 'text-align', 'justify')
})
})

View File

@ -0,0 +1,64 @@
<template>
<div v-if="editor">
<button @click="editor.chain().focus().setTextAlign('left').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'left' }) }">
left
</button>
<button @click="editor.chain().focus().setTextAlign('center').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'center' }) }">
center
</button>
<button @click="editor.chain().focus().setTextAlign('right').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'right' }) }">
right
</button>
<button @click="editor.chain().focus().setTextAlign('justify').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'justify' }) }">
justify
</button>
<button @click="editor.chain().focus().unsetTextAlign().run()">
set default
</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 Heading from '@tiptap/extension-heading'
import Text from '@tiptap/extension-text'
import TextAlign from '@tiptap/extension-text-align'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Heading,
TextAlign.configure({
types: ['heading', 'paragraph'],
}),
],
content: `
<h2>Heading</h2>
<p style="text-align: center">first paragraph</p>
<p style="text-align: right">second paragraph</p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View 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('Extensions/Typography', source)
</script>
</body>
</html>

View File

@ -0,0 +1,131 @@
context('/demos/Extensions/Typography', () => {
before(() => {
cy.visit('/demos/Extensions/Typography')
})
beforeEach(() => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()
})
})
it('should make an em dash from two dashes', () => {
cy.get('.ProseMirror')
.type('-- emDash')
.should('contain', '— emDash')
})
it('should make an ellipsis from three dots', () => {
cy.get('.ProseMirror')
.type('... ellipsis')
.should('contain', '… ellipsis')
})
it('should make a correct open double quote', () => {
cy.get('.ProseMirror')
.type('"openDoubleQuote"')
.should('contain', '“openDoubleQuote')
})
it('should make a correct close double quote', () => {
cy.get('.ProseMirror')
.type('"closeDoubleQuote"')
.should('contain', 'closeDoubleQuote”')
})
it('should make a correct open single quote', () => {
cy.get('.ProseMirror')
.type("'openSingleQuote'")
.should('contain', 'openSingleQuote')
})
it('should make a correct close single quote', () => {
cy.get('.ProseMirror')
.type("'closeSingleQuote'")
.should('contain', 'closeSingleQuote')
})
it('should make a left arrow', () => {
cy.get('.ProseMirror')
.type('<- leftArrow')
.should('contain', '← leftArrow')
})
it('should make a right arrow', () => {
cy.get('.ProseMirror')
.type('-> rightArrow')
.should('contain', '→ rightArrow')
})
it('should make a copyright sign', () => {
cy.get('.ProseMirror')
.type('(c) copyright')
.should('contain', '© copyright')
})
it('should make a registered trademark sign', () => {
cy.get('.ProseMirror')
.type('(r) registeredTrademark')
.should('contain', '® registeredTrademark')
})
it('should make a trademark sign', () => {
cy.get('.ProseMirror')
.type('(tm) trademark')
.should('contain', '™ trademark')
})
it('should make a one half', () => {
cy.get('.ProseMirror')
.type('1/2 oneHalf')
.should('contain', '½ oneHalf')
})
it('should make a plus/minus sign', () => {
cy.get('.ProseMirror')
.type('+/- plusMinus')
.should('contain', '± plusMinus')
})
it('should make a not equal sign', () => {
cy.get('.ProseMirror')
.type('!= notEqual')
.should('contain', '≠ notEqual')
})
it('should make a laquo', () => {
cy.get('.ProseMirror')
.type('<< laquorow')
.should('contain', '« laquo')
})
it('should make a raquo', () => {
cy.get('.ProseMirror')
.type('>> raquorow')
.should('contain', '» raquo')
})
it('should make a multiplication sign from an asterisk', () => {
cy.get('.ProseMirror')
.type('1*1 multiplication')
.should('contain', '1×1 multiplication')
})
it('should make a multiplication sign from an x', () => {
cy.get('.ProseMirror')
.type('1x1 multiplication')
.should('contain', '1×1 multiplication')
})
it('should make a multiplication sign from an asterisk with spaces', () => {
cy.get('.ProseMirror')
.type('1 * 1 multiplication')
.should('contain', '1 × 1 multiplication')
})
it('should make a multiplication sign from an x with spaces', () => {
cy.get('.ProseMirror')
.type('1 x 1 multiplication')
.should('contain', '1 × 1 multiplication')
})
})

View File

@ -0,0 +1,44 @@
<template>
<div>
<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 Typography from '@tiptap/extension-typography'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Typography,
],
content: `
<p>I have been suffering from Typomania all my life, a sickness that is incurable but not lethal.</p>
<p> Erik Spiekermann, December 2008</p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>