mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-01-18 06:03:22 +08:00
add extension demos
This commit is contained in:
parent
6ab708b1a2
commit
88804668ff
@ -81,7 +81,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.items {
|
||||
position: relative;
|
||||
border-radius: 0.25rem;
|
||||
|
15
demos/src/Extensions/BubbleMenu/React/index.html
Normal file
15
demos/src/Extensions/BubbleMenu/React/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/react.tsx'
|
||||
import source from '@source'
|
||||
setup('Extensions/BubbleMenu', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
43
demos/src/Extensions/BubbleMenu/React/index.jsx
Normal file
43
demos/src/Extensions/BubbleMenu/React/index.jsx
Normal 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} />
|
||||
</>
|
||||
)
|
||||
}
|
5
demos/src/Extensions/BubbleMenu/React/styles.scss
Normal file
5
demos/src/Extensions/BubbleMenu/React/styles.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
15
demos/src/Extensions/BubbleMenu/Vue/index.html
Normal file
15
demos/src/Extensions/BubbleMenu/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/BubbleMenu', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
60
demos/src/Extensions/BubbleMenu/Vue/index.vue
Normal file
60
demos/src/Extensions/BubbleMenu/Vue/index.vue
Normal 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. You’ll 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>
|
15
demos/src/Extensions/CharacterCount/Vue/index.html
Normal file
15
demos/src/Extensions/CharacterCount/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/CharacterCount', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
66
demos/src/Extensions/CharacterCount/Vue/index.vue
Normal file
66
demos/src/Extensions/CharacterCount/Vue/index.vue
Normal 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>
|
||||
Let‘s make sure people can’t 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>
|
15
demos/src/Extensions/Collaboration/Vue/index.html
Normal file
15
demos/src/Extensions/Collaboration/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/Collaboration', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
7
demos/src/Extensions/Collaboration/Vue/index.spec.js
Normal file
7
demos/src/Extensions/Collaboration/Vue/index.spec.js
Normal file
@ -0,0 +1,7 @@
|
||||
context('/demos/Extensions/Collaboration', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Extensions/Collaboration')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
47
demos/src/Extensions/Collaboration/Vue/index.vue
Normal file
47
demos/src/Extensions/Collaboration/Vue/index.vue
Normal 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>
|
15
demos/src/Extensions/CollaborationCursor/Vue/index.html
Normal file
15
demos/src/Extensions/CollaborationCursor/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/CollaborationCursor', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,7 @@
|
||||
context('/demos/Extensions/CollaborationCursor', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Extensions/CollaborationCursor')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
84
demos/src/Extensions/CollaborationCursor/Vue/index.vue
Normal file
84
demos/src/Extensions/CollaborationCursor/Vue/index.vue
Normal 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>
|
15
demos/src/Extensions/Color/Vue/index.html
Normal file
15
demos/src/Extensions/Color/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/Color', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
53
demos/src/Extensions/Color/Vue/index.spec.js
Normal file
53
demos/src/Extensions/Color/Vue/index.spec.js
Normal 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')
|
||||
})
|
||||
})
|
75
demos/src/Extensions/Color/Vue/index.vue
Normal file
75
demos/src/Extensions/Color/Vue/index.vue
Normal 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 that’s purple.</span></p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
15
demos/src/Extensions/Dropcursor/Vue/index.html
Normal file
15
demos/src/Extensions/Dropcursor/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/Dropcursor', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
7
demos/src/Extensions/Dropcursor/Vue/index.spec.js
Normal file
7
demos/src/Extensions/Dropcursor/Vue/index.spec.js
Normal file
@ -0,0 +1,7 @@
|
||||
context('/demos/Examples/Dropcursor', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Dropcursor')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
60
demos/src/Extensions/Dropcursor/Vue/index.vue
Normal file
60
demos/src/Extensions/Dropcursor/Vue/index.vue
Normal 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>
|
44
demos/src/Extensions/FloatingMenu/React/index.jsx
Normal file
44
demos/src/Extensions/FloatingMenu/React/index.jsx
Normal 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} />
|
||||
</>
|
||||
)
|
||||
}
|
10
demos/src/Extensions/FloatingMenu/React/styles.scss
Normal file
10
demos/src/Extensions/FloatingMenu/React/styles.scss
Normal file
@ -0,0 +1,10 @@
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
15
demos/src/Extensions/FloatingMenu/Vue/index.html
Normal file
15
demos/src/Extensions/FloatingMenu/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/FloatingMenu', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
66
demos/src/Extensions/FloatingMenu/Vue/index.vue
Normal file
66
demos/src/Extensions/FloatingMenu/Vue/index.vue
Normal 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>
|
15
demos/src/Extensions/Focus/Vue/index.html
Normal file
15
demos/src/Extensions/Focus/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/Focus', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
9
demos/src/Extensions/Focus/Vue/index.spec.js
Normal file
9
demos/src/Extensions/Focus/Vue/index.spec.js
Normal 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')
|
||||
})
|
||||
})
|
88
demos/src/Extensions/Focus/Vue/index.vue
Normal file
88
demos/src/Extensions/Focus/Vue/index.vue
Normal 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, it’ll 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>
|
15
demos/src/Extensions/FontFamily/Vue/index.html
Normal file
15
demos/src/Extensions/FontFamily/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/FontFamily', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
7
demos/src/Extensions/FontFamily/Vue/index.spec.js
Normal file
7
demos/src/Extensions/FontFamily/Vue/index.spec.js
Normal file
@ -0,0 +1,7 @@
|
||||
context('/demos/Extensions/FontFamily', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Extensions/FontFamily')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
68
demos/src/Extensions/FontFamily/Vue/index.vue
Normal file
68
demos/src/Extensions/FontFamily/Vue/index.vue
Normal 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 doesn’t 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>
|
15
demos/src/Extensions/Gapcursor/Vue/index.html
Normal file
15
demos/src/Extensions/Gapcursor/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/Gapcursor', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
7
demos/src/Extensions/Gapcursor/Vue/index.spec.js
Normal file
7
demos/src/Extensions/Gapcursor/Vue/index.spec.js
Normal file
@ -0,0 +1,7 @@
|
||||
context('/demos/Examples/Gapcursor', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Gapcursor')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
60
demos/src/Extensions/Gapcursor/Vue/index.vue
Normal file
60
demos/src/Extensions/Gapcursor/Vue/index.vue
Normal 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>
|
15
demos/src/Extensions/History/Vue/index.html
Normal file
15
demos/src/Extensions/History/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/History', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
123
demos/src/Extensions/History/Vue/index.spec.js
Normal file
123
demos/src/Extensions/History/Vue/index.spec.js
Normal 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')
|
||||
})
|
||||
})
|
61
demos/src/Extensions/History/Vue/index.vue
Normal file
61
demos/src/Extensions/History/Vue/index.vue
Normal 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>
|
15
demos/src/Extensions/Placeholder/Vue/index.html
Normal file
15
demos/src/Extensions/Placeholder/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/Placeholder', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
61
demos/src/Extensions/Placeholder/Vue/index.vue
Normal file
61
demos/src/Extensions/Placeholder/Vue/index.vue
Normal 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>
|
15
demos/src/Extensions/TextAlign/Vue/index.html
Normal file
15
demos/src/Extensions/TextAlign/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/TextAlign', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
112
demos/src/Extensions/TextAlign/Vue/index.spec.js
Normal file
112
demos/src/Extensions/TextAlign/Vue/index.spec.js
Normal 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')
|
||||
})
|
||||
})
|
64
demos/src/Extensions/TextAlign/Vue/index.vue
Normal file
64
demos/src/Extensions/TextAlign/Vue/index.vue
Normal 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>
|
15
demos/src/Extensions/Typography/Vue/index.html
Normal file
15
demos/src/Extensions/Typography/Vue/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from '../../../../setup/vue.ts'
|
||||
import source from '@source'
|
||||
setup('Extensions/Typography', source)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
131
demos/src/Extensions/Typography/Vue/index.spec.js
Normal file
131
demos/src/Extensions/Typography/Vue/index.spec.js
Normal 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')
|
||||
})
|
||||
})
|
44
demos/src/Extensions/Typography/Vue/index.vue
Normal file
44
demos/src/Extensions/Typography/Vue/index.vue
Normal 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>
|
Loading…
Reference in New Issue
Block a user