Merge branch 'feature/landingpage' of https://github.com/ueberdosis/tiptap-next into feature/landingpage

This commit is contained in:
Philipp Kühn
2021-02-03 18:36:58 +01:00
41 changed files with 574 additions and 31 deletions

View File

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

View File

@@ -0,0 +1,79 @@
<template>
<div v-if="editor">
<button @click="addImage">
add image from URL
</button>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor } from '@tiptap/core'
import { EditorContent } from '@tiptap/vue'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Image from '@tiptap/extension-image'
import Dropcursor from '@tiptap/extension-dropcursor'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
methods: {
addImage() {
const url = window.prompt('URL')
if (url) {
this.editor.chain().focus().setImage({ src: url }).run()
}
},
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Image,
Dropcursor,
],
content: `
<p>This is a basic example of implementing images. Drag to re-order.</p>
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" />
<img src="https://source.unsplash.com/K9QHL52rE2k/800x400" />
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
img {
max-width: 100%;
height: auto;
&.ProseMirror-selectednode {
outline: 3px solid #68CEF8;
}
}
}
</style>

View File

@@ -0,0 +1,70 @@
import { Node, Command } from '@tiptap/core'
export interface IframeOptions {
allowFullscreen: boolean,
HTMLAttributes: {
[key: string]: any
},
}
export default Node.create({
name: 'iframe',
group: 'block',
// selectable: false,
defaultOptions: <IframeOptions>{
allowFullscreen: true,
HTMLAttributes: {
class: 'iframe-wrapper',
},
},
addAttributes() {
return {
src: {
default: null,
},
frameborder: {
default: 0,
},
allowfullscreen: {
default: this.options.allowFullscreen,
parseHTML: () => {
return {
allowfullscreen: this.options.allowFullscreen,
}
},
},
}
},
parseHTML() {
return [{
tag: 'iframe',
}]
},
renderHTML({ HTMLAttributes }) {
return ['div', this.options.HTMLAttributes, ['iframe', HTMLAttributes, 0]]
},
addCommands() {
return {
/**
* Add an iframe
*/
setIframe: (options: { src: string }): Command => ({ tr, dispatch }) => {
const { selection } = tr
const node = this.type.create(options)
if (dispatch) {
tr.replaceRangeWith(selection.from, selection.to, node)
}
return true
},
}
},
})

View File

@@ -0,0 +1,84 @@
<template>
<div v-if="editor">
<button @click="addImage">
add iframe
</button>
<editor-content :editor="editor" />
</div>
</template>
<script>
import {
Editor, EditorContent, defaultExtensions,
} from '@tiptap/vue-starter-kit'
import Iframe from './iframe'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
...defaultExtensions(),
Iframe,
],
content: `
<p>Here is an exciting video:</p>
<iframe src="https://www.youtube.com/embed/XIMLoLxmTDw" frameborder="0" allowfullscreen></iframe>
`,
})
},
methods: {
addImage() {
const url = window.prompt('URL')
if (url) {
this.editor.chain().focus().setIframe({ src: url }).run()
}
},
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
.iframe-wrapper {
position: relative;
padding-bottom: 100/16*9%;
height: 0;
overflow: hidden;
width: 100%;
height: auto;
&.ProseMirror-selectednode {
outline: 3px solid #68CEF8;
}
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,67 @@
import { Extension } from '@tiptap/core'
import { Decoration, DecorationSet } from 'prosemirror-view'
import { Plugin } from 'prosemirror-state'
export interface PlaceholderOptions {
emptyEditorClass: string,
emptyNodeClass: string,
placeholder: string | Function,
showOnlyWhenEditable: boolean,
showOnlyCurrent: boolean,
}
export default Extension.create({
name: 'placeholder',
defaultOptions: <PlaceholderOptions>{
emptyEditorClass: 'is-editor-empty',
emptyNodeClass: 'is-empty',
placeholder: 'Write something …',
showOnlyWhenEditable: true,
showOnlyCurrent: true,
},
addProseMirrorPlugins() {
return [
new Plugin({
props: {
decorations: ({ doc, selection }) => {
const active = this.editor.isEditable || !this.options.showOnlyWhenEditable
const { anchor } = selection
const decorations: Decoration[] = []
const isEditorEmpty = doc.textContent.length === 0
if (!active) {
return
}
doc.descendants((node, pos) => {
const hasAnchor = anchor >= pos && anchor <= (pos + node.nodeSize)
const isNodeEmpty = node.content.size === 0
if ((hasAnchor || !this.options.showOnlyCurrent) && isNodeEmpty) {
const classes = [this.options.emptyNodeClass]
if (isEditorEmpty) {
classes.push(this.options.emptyEditorClass)
}
const decoration = Decoration.node(pos, pos + node.nodeSize, {
class: classes.join(' '),
'data-empty-text': typeof this.options.placeholder === 'function'
? this.options.placeholder(node)
: this.options.placeholder,
})
decorations.push(decoration)
}
return false
})
return DecorationSet.create(doc, decorations)
},
},
}),
]
},
})

View File

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

View File

@@ -31,7 +31,9 @@ export default {
addImage() {
const url = window.prompt('URL')
this.editor.chain().focus().setImage({ src: url }).run()
if (url) {
this.editor.chain().focus().setImage({ src: url }).run()
}
},
},
@@ -68,6 +70,10 @@ export default {
img {
max-width: 100%;
height: auto;
&.ProseMirror-selectednode {
outline: 3px solid #68CEF8;
}
}
}
</style>