Merge branch 'main' into feature/vue-node-views

This commit is contained in:
Philipp Kühn
2020-11-25 10:00:13 +01:00
58 changed files with 483 additions and 275 deletions

View File

@@ -0,0 +1,22 @@
context('/examples/basic', () => {
before(() => {
cy.visit('/examples/basic')
})
beforeEach(() => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.setContent('<h1>Example Text</h1>')
editor.commands.selectAll()
})
})
it('should apply the paragraph style when the keyboard shortcut is pressed', () => {
cy.get('.ProseMirror h1').should('exist')
cy.get('.ProseMirror p').should('not.exist')
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, altKey: true, key: '0' })
.find('p')
.should('contain', 'Example Text')
})
})

View File

@@ -2,4 +2,6 @@ context('/examples/collaborative-editing', () => {
before(() => {
cy.visit('/examples/collaborative-editing')
})
// TODO: Write tests
})

View File

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

View File

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

View File

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

View File

@@ -2,4 +2,6 @@ context('/examples/minimalist', () => {
before(() => {
cy.visit('/examples/minimalist')
})
// TODO: Write tests
})

View File

@@ -2,4 +2,6 @@ context('/examples/todo-app', () => {
before(() => {
cy.visit('/examples/todo-app')
})
// TODO: Write tests
})

View File

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

View File

@@ -1,41 +0,0 @@
<template>
<div class="editor">
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Bold from '@tiptap/extension-bold'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
content: '<p>Im running tiptap with Vue.js. This demo is interactive, try to edit the text.</p>',
extensions: [
Document,
Paragraph,
Text,
Bold,
],
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View File

@@ -2,4 +2,6 @@ context('/api/extensions/collaboration', () => {
before(() => {
cy.visit('/api/extensions/collaboration')
})
// TODO: Write tests
})

View File

@@ -2,4 +2,6 @@ context('/api/extensions/collaboration-cursor', () => {
before(() => {
cy.visit('/api/extensions/collaboration-cursor')
})
// TODO: Write tests
})

View File

@@ -2,4 +2,6 @@ context('/examples/dropcursor', () => {
before(() => {
cy.visit('/examples/dropcursor')
})
// TODO: Write tests
})

View File

@@ -2,4 +2,6 @@ context('/api/extensions/font-family', () => {
before(() => {
cy.visit('/api/extensions/font-family')
})
// TODO: Write tests
})

View File

@@ -2,4 +2,6 @@ context('/examples/gapcursor', () => {
before(() => {
cy.visit('/examples/gapcursor')
})
// TODO: Write tests
})

View File

@@ -81,4 +81,32 @@ context('/api/extensions/text-align', () => {
.find('p')
.should('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('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

@@ -1,75 +0,0 @@
<template>
<div>
<div v-if="editor">
<button @click="editor.chain().focus().unsetAllMarks().run()">
clear formatting
</button>
<button @click="editor.chain().focus().undo().run()">
undo
</button>
<button @click="editor.chain().focus().redo().run()">
redo
</button>
<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().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>
</div>
<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 History from '@tiptap/extension-history'
import Bold from '@tiptap/extension-bold'
import Italic from '@tiptap/extension-italic'
import Code from '@tiptap/extension-code'
import CodeBlock from '@tiptap/extension-code-block'
import Heading from '@tiptap/extension-heading'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
content: '<h2>Hey there!</h2><p>This editor is based on Prosemirror, fully extendable and headless. You can easily add custom nodes as Vue components.</p>',
extensions: [
Document,
Paragraph,
Text,
CodeBlock,
History,
Bold,
Italic,
Code,
Heading,
],
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View File

@@ -1,30 +0,0 @@
<template>
<editor-content :editor="editor" />
</template>
<script>
import { Editor, EditorContent, defaultExtensions } from '@tiptap/vue-starter-kit'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
content: '<p>Im running tiptap with Vue.js. 🎉</p>',
extensions: defaultExtensions(),
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View File

@@ -47,31 +47,23 @@ context('/api/marks/link', () => {
})
})
const validUrls = [
'https://example.com',
'https://example.com/with-path',
'http://example.com/with-http',
'https://www.example.com/with-www',
'https://www.example.com/with-numbers-123',
'https://www.example.com/with-parameters?var=true',
'https://www.example.com/with-multiple-parameters?var=true&foo=bar',
'https://www.example.com/with-spaces?var=true&foo=bar+3',
// TODO: 'https://www.example.com/with,comma',
// TODO: 'https://www.example.com/with(brackets)',
// TODO: 'https://www.example.com/with!exclamation!marks',
'http://thelongestdomainnameintheworldandthensomeandthensomemoreandmore.com/',
'https://example.longtopleveldomain',
'https://example-with-dashes.com',
]
validUrls.forEach(url => {
it(`url should be detected: ${url}`, () => {
cy.get('.ProseMirror').paste({ pastePayload: url, pasteType: 'text/plain' })
.find('a')
.should('contain', url)
.should('have.attr', 'href', url)
})
it('detects a pasted URL', () => {
cy.get('.ProseMirror').paste({ pastePayload: 'https://example.com', pasteType: 'text/plain' })
.find('a')
.should('contain', 'https://example.com')
.should('have.attr', 'href', 'https://example.com')
})
// TODO: Test invalid URLs
it('correctly detects multiple pasted URLs', () => {
cy.get('.ProseMirror').paste({ pastePayload: 'https://example1.com, https://example2.com/foobar, (http://example3.com/foobar)', pasteType: 'text/plain' })
cy.get('.ProseMirror').find('a[href="https://example1.com"]')
.should('contain', 'https://example1.com')
cy.get('.ProseMirror').find('a[href="https://example2.com/foobar"]')
.should('contain', 'https://example2.com/foobar')
cy.get('.ProseMirror').find('a[href="http://example3.com/foobar"]')
.should('contain', 'http://example3.com/foobar')
})
})

View File

@@ -61,3 +61,16 @@ export default {
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
a {
color: #68CEF8;
}
}
</style>

View File

@@ -2,4 +2,6 @@ context('/api/marks/text-style', () => {
before(() => {
cy.visit('/api/marks/text-style')
})
// TODO: Write tests
})

View File

@@ -79,6 +79,13 @@ context('/api/nodes/bullet-list', () => {
.should('contain', 'Paragraph')
})
it('should make the paragraph a bullet list keyboard shortcut is pressed', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, shiftKey: true, key: '8' })
.find('ul li')
.should('contain', 'Example Text')
})
it('should make a bullet list from an asterisk', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()

View File

@@ -86,7 +86,28 @@ context('/api/nodes/heading', () => {
.should('not.exist')
})
it('should make a heading from the default markdown shortcut', () => {
it('should make the paragraph a h1 keyboard shortcut is pressed', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, altKey: true, key: '1' })
.find('h1')
.should('contain', 'Example Text')
})
it('should make the paragraph a h2 keyboard shortcut is pressed', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, altKey: true, key: '2' })
.find('h2')
.should('contain', 'Example Text')
})
it('should make the paragraph a h3 keyboard shortcut is pressed', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, altKey: true, key: '3' })
.find('h3')
.should('contain', 'Example Text')
})
it('should make a h1 from the default markdown shortcut', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()
})
@@ -96,4 +117,26 @@ context('/api/nodes/heading', () => {
.find('h1')
.should('contain', 'Headline')
})
it('should make a h2 from the default markdown shortcut', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()
})
cy.get('.ProseMirror')
.type('## Headline')
.find('h2')
.should('contain', 'Headline')
})
it('should make a h3 from the default markdown shortcut', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()
})
cy.get('.ProseMirror')
.type('### Headline')
.find('h3')
.should('contain', 'Headline')
})
})

View File

@@ -2,4 +2,48 @@ context('/api/nodes/list-item', () => {
before(() => {
cy.visit('/api/nodes/list-item')
})
beforeEach(() => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.setContent('<ul><li>Example Text</li></ul>')
})
})
it('should add a new list item on Enter', () => {
cy.get('.ProseMirror')
.type('{enter}2nd Item')
cy.get('.ProseMirror')
.find('li:nth-child(1)')
.should('contain', 'Example Text')
cy.get('.ProseMirror')
.find('li:nth-child(2)')
.should('contain', '2nd Item')
})
it('should sink the list item on Tab', () => {
cy.get('.ProseMirror')
.type('{enter}')
.trigger('keydown', { key: 'Tab' })
cy.get('.ProseMirror').type('2nd Level')
cy.get('.ProseMirror')
.find('li:nth-child(1) li')
.should('contain', '2nd Level')
})
it('should lift the list item on Shift+Tab', () => {
cy.get('.ProseMirror')
.type('{enter}')
.trigger('keydown', { key: 'Tab' })
.trigger('keydown', { shiftKey: true, key: 'Tab' })
cy.get('.ProseMirror').type('1st Level')
cy.get('.ProseMirror')
.find('li:nth-child(2)')
.should('contain', '1st Level')
})
})

View File

@@ -71,3 +71,50 @@ export default {
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
background: none;
font-size: 0.8rem;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
}
</style>

View File

@@ -61,6 +61,13 @@ context('/api/nodes/ordered-list', () => {
.should('not.exist')
})
it('should make the paragraph an ordered list keyboard shortcut is pressed', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, shiftKey: true, key: '7' })
.find('ol li')
.should('contain', 'Example Text')
})
it('should leave the list with double enter', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()

View File

@@ -2,4 +2,6 @@ context('/api/nodes/task-item', () => {
before(() => {
cy.visit('/api/nodes/task-item')
})
// TODO: Write tests
})

View File

@@ -61,6 +61,13 @@ context('/api/nodes/task-list', () => {
.should('not.exist')
})
it('should make the paragraph a task list when the keyboard shortcut is pressed', () => {
cy.get('.ProseMirror')
.trigger('keydown', { modKey: true, shiftKey: true, key: 'l' })
.find('ul li')
.should('contain', 'Example Text')
})
it('should leave the list with double enter', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()

View File

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

View File

@@ -1,44 +0,0 @@
<template>
<div class="editor">
<div class="menubar" v-if="editor">
<button
class="menubar__button"
:class="{ 'is-active': editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
>
Bold
</button>
</div>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent, defaultExtensions } from '@tiptap/vue-starter-kit'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
content: '<p>Hi! 👋 Im a text editor with just one button. Select some text and press the button to see what it does. Yes, its marking text <strong>bold</strong>. Amazing, isnt it?</p>',
extensions: defaultExtensions(),
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>

View File

@@ -48,7 +48,7 @@ Most of the core extensions register their own keyboard shortcuts. Depending on
| Center align | `Control`&nbsp;`Shift`&nbsp;`E` | `Cmd`&nbsp;`Shift`&nbsp;`E` |
| Right align | `Control`&nbsp;`Shift`&nbsp;`R` | `Cmd`&nbsp;`Shift`&nbsp;`R` |
| Justify | `Control`&nbsp;`Shift`&nbsp;`J` | `Cmd`&nbsp;`Shift`&nbsp;`J` |
| Task list | `Control`&nbsp;`Shift`&nbsp;`L` | `Cmd`&nbsp;`Shift`&nbsp;`L` |
| Task list | `Control`&nbsp;`Shift`&nbsp;`L` | `Cmd`&nbsp;`Shift`&nbsp;`L` (TODO: Conflict!) |
| Code block | `Control`&nbsp;`Alt`&nbsp;`C` | `Cmd`&nbsp;`Alt`&nbsp;`C` |
<!--| Toggle task| `Control`&nbsp;`Enter` | `Cmd`&nbsp;`Enter` | -->

View File

@@ -1,8 +1,10 @@
# Create your editor
# Create a new toolbar
## toc
## Introduction
TODO
<!-- ## Introduction
In its simplest version tiptap comes very raw. There is no menu, no buttons, no styling. Thats intended. See tiptap as your building blocks to build exactly the editor you would like to have.
## Adding a menu
@@ -24,4 +26,4 @@ Note that `Document`, `Paragraph` and `Text` are required. Otherwise you wont
<demo name="Guide/BuildYourEditor" highlight="10-13,30-33" />
Thats also the place where you can register custom extensions, which you or someone else built for tiptap.
Thats also the place where you can register custom extensions, which you or someone else built for tiptap. -->

View File

@@ -55,7 +55,38 @@ To actually start using tiptap, youll need to add a new component to your app
This is the fastest way to get tiptap up and running with Vue. It will give you a very basic version of tiptap, without any buttons. No worries, you will be able to add more functionality soon.
<demo name="Guide/GettingStarted" />
```html
<template>
<editor-content :editor="editor" />
</template>
<script>
import { Editor, EditorContent, defaultExtensions } from '@tiptap/vue-starter-kit'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
content: '<p>Im running tiptap with Vue.js. 🎉</p>',
extensions: defaultExtensions(),
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
```
## 5. Add it to your app
Now, lets replace the content of `src/App.vue` with the following example code to use our new `Tiptap` component in our app.

View File

@@ -57,8 +57,34 @@ Its also amazing for bug reports. Try to recreate a bug there and share it wi
* [Vue.js/tiptap on CodeSandbox](https://codesandbox.io/s/tiptap-issue-template-b83rr?file=/src/components/Tiptap.vue)
## Option 4: CDN
To pull in tiptap for quick demos or just giving it a spin, grab the latest build via CDN:
## Option 4: CDN (Draft)
To pull in tiptap for quick demos or just giving it a spin, grab the latest build via CDN. We use two different provides:
### Skypack (ES Modules)
Skypack enables you to use ES modules, which should be supported in all modern browsers. The packages are smaller, thats great, too. So here is how to use it:
```html
<!doctype html>
<head>
<meta charset="utf-8">
</head>
<body>
<div class="element"></div>
<script type="module">
import { Editor } from 'https://cdn.skypack.dev/@tiptap/core?min'
import { defaultExtensions } from 'https://cdn.skypack.dev/@tiptap/starter-kit?min'
const editor = new Editor({
element: document.querySelector('.element'),
extensions: defaultExtensions(),
content: '<p>Your content.</p>',
})
</script>
</body>
</html>
```
### Unpkg (UMD, deprecated)
We also have an UMD build on unpkg. Those UMD builds are larger, but should work even in older browsers. As tiptap doesnt work in older browsers anyway, we tend to remove those builds. What do you think? Anyway, heres how you can use it:
```html
<!doctype html>
@@ -81,4 +107,3 @@ To pull in tiptap for quick demos or just giving it a spin, grab the latest buil
</body>
</html>
```