mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-11-27 14:59:27 +08:00
remove demos from gridsome
This commit is contained in:
parent
684904ca06
commit
97fdd484e7
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -92,7 +92,7 @@ jobs:
|
||||
uses: cypress-io/github-action@v2
|
||||
with:
|
||||
cache-key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
|
||||
start: yarn start
|
||||
start: yarn start:demos
|
||||
wait-on: 'http://localhost:3000'
|
||||
project: ./tests
|
||||
browser: chrome
|
||||
|
@ -1,88 +0,0 @@
|
||||
<template>
|
||||
<client-only>
|
||||
<demo-frame v-if="inline && mainFile" v-bind="props" />
|
||||
<div class="demo" v-else>
|
||||
<template v-if="mainFile">
|
||||
<demo-frame class="demo__preview" v-bind="props" />
|
||||
<div class="demo__source" v-if="!hideSource">
|
||||
<div class="demo__scroller" v-if="showFileNames">
|
||||
<div class="demo__tabs">
|
||||
<button
|
||||
class="demo__tab"
|
||||
:class="{ 'is-active': currentIndex === index}"
|
||||
v-for="(file, index) in files"
|
||||
:key="index"
|
||||
@click="currentIndex = index"
|
||||
>
|
||||
{{ file.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo__code" v-if="activeFile" :key="activeFile.path">
|
||||
<!-- eslint-disable-next-line -->
|
||||
<prism :language="activeFile.highlight" :highlight="highlight">{{ activeFile.content }}</prism>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo__meta">
|
||||
<g-link class="demo__name" :to="`/demos/${name}`" v-if="isDevelopment">
|
||||
Demo/{{ name }}
|
||||
</g-link>
|
||||
<div class="demo__name" v-else>
|
||||
Demo/{{ name }}
|
||||
</div>
|
||||
<a class="demo__link" :href="githubUrl" target="_blank">
|
||||
Edit on GitHub →
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="demo__error">
|
||||
Could not find a demo called “{{ name }}”.
|
||||
</div>
|
||||
</div>
|
||||
</client-only>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Prism from '~/components/Prism'
|
||||
import DemoFrame from '~/components/DemoFrame'
|
||||
import DemoMixin from '~/components/DemoMixin'
|
||||
|
||||
export default {
|
||||
mixins: [DemoMixin],
|
||||
|
||||
components: {
|
||||
DemoFrame,
|
||||
Prism,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
showFileNames() {
|
||||
return this.files.length > 1
|
||||
},
|
||||
|
||||
activeFile() {
|
||||
return this.files[this.currentIndex]
|
||||
},
|
||||
|
||||
isDevelopment() {
|
||||
return process.env.NODE_ENV === 'development'
|
||||
},
|
||||
|
||||
githubUrl() {
|
||||
if (this.isDevelopment) {
|
||||
return `vscode://file${this.cwd}/src/demos/${this.name}/${this.files[0].name}`
|
||||
}
|
||||
|
||||
return `https://github.com/ueberdosis/tiptap/tree/main/docs/src/demos/${this.name}`
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="./style.scss" scoped />
|
@ -1,109 +0,0 @@
|
||||
.demo {
|
||||
overflow: hidden;
|
||||
border-radius: 0.75rem;
|
||||
|
||||
&__preview {
|
||||
border-top-left-radius: inherit;
|
||||
border-top-right-radius: inherit;
|
||||
border-bottom-width: 0;
|
||||
color: $colorBlack;
|
||||
background-color: $colorWhite;
|
||||
max-height: unquote("max(300px, 60vh)");
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border: 3px solid $colorBlack;
|
||||
}
|
||||
|
||||
&__source {
|
||||
background-color: $colorBlack;
|
||||
}
|
||||
|
||||
&__scroller {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 1.25rem 0 1.25rem;
|
||||
border-bottom: 2px solid rgba($colorWhite, 0.1);
|
||||
}
|
||||
|
||||
&__tab {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
color: rgba($colorWhite, 0.7);
|
||||
padding: 0.3rem 0 calc(0.3rem + 2px) 0;
|
||||
font: inherit;
|
||||
font-family: "JetBrainsMono", monospace;
|
||||
font-size: 0.85rem;
|
||||
border: none;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: -2px;
|
||||
border-bottom: 2px solid transparent;
|
||||
|
||||
&.is-active,
|
||||
&:hover {
|
||||
color: $colorWhite;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
font-weight: 700;
|
||||
border-bottom-color: $colorWhite
|
||||
}
|
||||
}
|
||||
|
||||
&__code {
|
||||
pre {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0.5rem 1.25rem;
|
||||
font-size: 0.85rem;
|
||||
border-top: 1px solid rgba($colorWhite, 0.1);
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
background-color: $colorBlack;
|
||||
white-space: nowrap;
|
||||
color: rgba($colorWhite, 0.5);
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: none;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
margin-left: auto;
|
||||
|
||||
&:hover {
|
||||
color: $colorWhite;
|
||||
}
|
||||
}
|
||||
|
||||
&__error {
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: 0.75rem;
|
||||
border: 3px solid $colorBlack;
|
||||
background-color: $colorRed;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<component :is="mainFile" v-if="mainFile && mode === 'vue'" />
|
||||
<react-renderer :component="mainFile" v-else-if="mainFile && mode === 'react'" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DemoMixin from '~/components/DemoMixin'
|
||||
|
||||
export default {
|
||||
mixins: [DemoMixin],
|
||||
|
||||
components: {
|
||||
ReactRenderer: () => import(/* webpackChunkName: "react-renderer" */ '~/components/ReactRenderer'),
|
||||
},
|
||||
}
|
||||
</script>
|
@ -1,142 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="demo-frame"
|
||||
:class="{ 'is-inline': inline, 'is-loading': isLoading }"
|
||||
v-observe-visibility="{
|
||||
callback: visibilityChanged,
|
||||
once: true,
|
||||
}"
|
||||
>
|
||||
<div class="demo-frame__loader-wrapper" v-if="isLoading">
|
||||
<div class="demo-frame__loader" />
|
||||
</div>
|
||||
<iframe
|
||||
class="demo-frame__iframe"
|
||||
v-resize.quiet="{ scrolling: 'omit' }"
|
||||
:src="`/demos/${name}?${query}`"
|
||||
width="100%"
|
||||
height="0"
|
||||
frameborder="0"
|
||||
@load="onLoad"
|
||||
v-if="isVisible"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ObserveVisibility } from 'vue-observe-visibility'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
highlight: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
|
||||
showSource: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
directives: {
|
||||
ObserveVisibility,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
isVisible: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
query() {
|
||||
return `inline=${this.inline}&highlight=${this.highlight}&showSource=${this.showSource}`
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onLoad() {
|
||||
this.isLoading = false
|
||||
},
|
||||
|
||||
visibilityChanged(isVisible) {
|
||||
this.isVisible = isVisible
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.demo-frame {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
min-height: 5rem;
|
||||
|
||||
&__iframe {
|
||||
background-color: transparent;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
&.is-inline {
|
||||
border-radius: 0.75rem;
|
||||
background-color: rgba($colorBlack, 0.03);
|
||||
margin: -1.25rem;
|
||||
}
|
||||
|
||||
&__loader-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&__loader {
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
text-indent: -9999rem;
|
||||
border-top: 2px solid rgba($colorBlack, 0.2);
|
||||
border-right: 2px solid rgba($colorBlack, 0.2);
|
||||
border-bottom: 2px solid rgba($colorBlack, 0.2);
|
||||
border-left: 2px solid $colorBlack;
|
||||
transform: translateZ(0);
|
||||
animation: load8 1.1s infinite linear;
|
||||
|
||||
&,
|
||||
&::after {
|
||||
border-radius: 50%;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load8 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,92 +0,0 @@
|
||||
import collect from 'collect.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
highlight: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
|
||||
hideSource: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
files: [],
|
||||
syntax: {
|
||||
vue: 'html',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
props() {
|
||||
return {
|
||||
name: this.name,
|
||||
inline: this.inline,
|
||||
highlight: this.highlight,
|
||||
hideSource: this.hideSource,
|
||||
}
|
||||
},
|
||||
|
||||
mainFile() {
|
||||
if (!this.mainFilePath) {
|
||||
return false
|
||||
}
|
||||
|
||||
return require(`~/demos/${this.mainFilePath}`).default
|
||||
},
|
||||
|
||||
mainFilePath() {
|
||||
const file = this.files.find(item => item.path.endsWith('index.vue') || item.path.endsWith('index.jsx'))
|
||||
|
||||
if (file) {
|
||||
return file.path
|
||||
}
|
||||
},
|
||||
|
||||
mode() {
|
||||
if (this.mainFilePath?.endsWith('.jsx')) {
|
||||
return 'react'
|
||||
}
|
||||
|
||||
return 'vue'
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.files = collect(require.context('~/demos/', true, /.+\..+$/).keys())
|
||||
.filter(path => path.startsWith(`./${this.name}/`))
|
||||
.filter(path => !path.endsWith('.spec.js') && !path.endsWith('.spec.ts'))
|
||||
.map(path => path.replace('./', ''))
|
||||
.map(path => {
|
||||
const extension = path.split('.').pop()
|
||||
|
||||
return {
|
||||
path,
|
||||
name: path.replace(`${this.name}/`, ''),
|
||||
content: require(`!!raw-loader!~/demos/${path}`).default,
|
||||
extension,
|
||||
highlight: this.syntax[extension] || extension,
|
||||
}
|
||||
})
|
||||
.filter(item => {
|
||||
return ['vue', 'ts', 'js', 'jsx', 'scss'].includes(item.extension)
|
||||
})
|
||||
.sortBy(item => item.path.split('/').length && !item.path.endsWith('index.vue') && !item.path.endsWith('index.jsx'))
|
||||
.toArray()
|
||||
},
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
<template>
|
||||
<div class="demos" :class="{ 'is-first-active': selectedIndex === 0 }">
|
||||
<button
|
||||
class="demos__tab"
|
||||
:class="{ 'is-active': selectedIndex === index }"
|
||||
v-for="(item, index) in formattedItems"
|
||||
:key="index"
|
||||
@click="selectedIndex = index"
|
||||
>
|
||||
{{ item.title }}
|
||||
</button>
|
||||
<demo
|
||||
:name="selectedItem.name"
|
||||
:key="selectedItem.title"
|
||||
:hide-source="hideSource"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Demo from '@/components/Demo'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Demo,
|
||||
},
|
||||
|
||||
props: {
|
||||
items: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
hideSource: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: 0,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
formattedItems() {
|
||||
return Object
|
||||
.entries(this.items)
|
||||
.map(([title, name]) => {
|
||||
return {
|
||||
title,
|
||||
name,
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
selectedItem() {
|
||||
return this.formattedItems[this.selectedIndex]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.demos {
|
||||
&__tab {
|
||||
border-radius: 0.4rem 0.4rem 0 0;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0.3rem 1.25rem;
|
||||
margin-bottom: -1px;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.025rem;
|
||||
|
||||
&.is-active {
|
||||
background-color: $colorBlack;
|
||||
color: $colorWhite;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-first-active ::v-deep .demo {
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,145 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { content } from '../content.js'
|
||||
import './styles.scss'
|
||||
|
||||
const MenuBar = ({ editor }) => {
|
||||
if (!editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||
className={editor.isActive('code') ? 'is-active' : ''}
|
||||
>
|
||||
code
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
|
||||
clear marks
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().clearNodes().run()}>
|
||||
clear nodes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().setParagraph().run()}
|
||||
className={editor.isActive('paragraph') ? 'is-active' : ''}
|
||||
>
|
||||
paragraph
|
||||
</button>
|
||||
<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().toggleHeading({ level: 3 }).run()}
|
||||
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
|
||||
>
|
||||
h3
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
|
||||
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
|
||||
>
|
||||
h4
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
|
||||
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
|
||||
>
|
||||
h5
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
|
||||
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
|
||||
>
|
||||
h6
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
className={editor.isActive('bulletList') ? 'is-active' : ''}
|
||||
>
|
||||
bullet list
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
className={editor.isActive('orderedList') ? 'is-active' : ''}
|
||||
>
|
||||
ordered list
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||
className={editor.isActive('codeBlock') ? 'is-active' : ''}
|
||||
>
|
||||
code block
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||
className={editor.isActive('blockquote') ? 'is-active' : ''}
|
||||
>
|
||||
blockquote
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
|
||||
horizontal rule
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setHardBreak().run()}>
|
||||
hard break
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().undo().run()}>
|
||||
undo
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().redo().run()}>
|
||||
redo
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
],
|
||||
content,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
spellcheck: 'false',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MenuBar editor={editor} />
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/Book/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Book/Vue')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,163 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div 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>
|
||||
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
|
||||
code
|
||||
</button>
|
||||
<button @click="editor.chain().focus().unsetAllMarks().run()">
|
||||
clear marks
|
||||
</button>
|
||||
<button @click="editor.chain().focus().clearNodes().run()">
|
||||
clear nodes
|
||||
</button>
|
||||
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
|
||||
paragraph
|
||||
</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>
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
|
||||
h3
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
|
||||
h4
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
|
||||
h5
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
|
||||
h6
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
|
||||
bullet list
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
|
||||
ordered list
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
|
||||
code block
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
|
||||
blockquote
|
||||
</button>
|
||||
<button @click="editor.chain().focus().setHorizontalRule().run()">
|
||||
horizontal rule
|
||||
</button>
|
||||
<button @click="editor.chain().focus().setHardBreak().run()">
|
||||
hard break
|
||||
</button>
|
||||
<button @click="editor.chain().focus().undo().run()">
|
||||
undo
|
||||
</button>
|
||||
<button @click="editor.chain().focus().redo().run()">
|
||||
redo
|
||||
</button>
|
||||
</div>
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { content } from '../content.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
],
|
||||
content,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
spellcheck: 'false',
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
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>
|
@ -1,252 +0,0 @@
|
||||
export const content = `
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras turpis leo, placerat vitae ultricies interdum, semper quis urna. In dolor est, placerat vestibulum velit eleifend, finibus viverra risus. Vestibulum eleifend massa vitae ultricies laoreet. Integer maximus dolor sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus metus magna, tincidunt in libero et, interdum tempus ligula. Curabitur nunc urna, sodales vel vestibulum quis, sodales non nunc. Quisque pharetra urna ultrices, accumsan augue eu, aliquet elit. Cras ut venenatis justo, eget sagittis risus. Duis vehicula enim non varius sollicitudin.</p>
|
||||
<p>Ut nulla sapien, rhoncus ac erat ac, dignissim fermentum nibh. Nunc varius turpis at augue interdum efficitur pellentesque a nisi. Integer in aliquam tellus, in condimentum libero. Aenean maximus dolor sit amet urna sodales, at facilisis augue tempor. Ut pretium nisi in dui pellentesque vulputate. Nulla nec elementum odio. Etiam finibus, massa in porta convallis, diam tellus efficitur mauris, ac luctus justo odio a ex. In varius lacus elit, ac dapibus nisl dictum non. Aenean a erat vitae erat porta congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla nec arcu sem. Sed pulvinar mi eu venenatis auctor. In aliquet, neque in mattis auctor, nunc nunc pellentesque leo, eget dapibus velit libero sit amet magna. Morbi vestibulum, odio vel elementum cursus, eros mi tempus nulla, id faucibus lorem eros vel eros. Aliquam erat volutpat. Pellentesque laoreet quis orci in porttitor.</p>
|
||||
<p>Nam vitae ultrices ex. Sed rutrum purus et enim pharetra, ut aliquam nunc accumsan. Ut eu aliquam nunc, ac elementum tellus. Vivamus porttitor mi augue, sed rutrum elit dictum in. Vestibulum porta sodales orci, non dictum lacus. Maecenas a est eu quam imperdiet tincidunt. Ut posuere, lorem sit amet hendrerit tempor, turpis ante sagittis ante, non bibendum ipsum diam sed tellus.</p>
|
||||
<p>Vivamus fringilla arcu sit amet sapien dapibus pulvinar. Sed tempus nec lacus in scelerisque. Suspendisse vel vestibulum mauris, condimentum porttitor lacus. Nunc finibus vel sapien eget lobortis. Praesent auctor, neque vel eleifend vestibulum, mi risus facilisis leo, vitae dictum ligula tellus quis erat. Quisque volutpat tempor sapien a commodo. Mauris vehicula, diam ac vulputate semper, justo tortor vestibulum nibh, nec interdum lacus lorem et massa. Donec eu ex urna. Praesent scelerisque feugiat nisl non consequat. Vivamus in pretium nibh. Vivamus fringilla purus at nisi varius, sit amet congue nisl pharetra. Aliquam erat volutpat.</p>
|
||||
<p>Nullam ultrices eros dolor, ut consequat ex eleifend at. Cras eleifend venenatis ligula in volutpat. Sed mollis felis a eleifend porttitor. Proin a diam sem. Etiam eu velit mi. Proin quis elit eget urna maximus gravida sed nec nunc. In blandit efficitur neque eget feugiat.</p>
|
||||
<p>Integer sit amet consequat sem. Curabitur suscipit arcu a ex tristique rhoncus. Ut id tincidunt augue. Suspendisse volutpat iaculis elit, ut fringilla mauris laoreet hendrerit. Aenean malesuada purus commodo leo maximus rutrum. Donec id sapien augue. Donec in aliquam nisl. Phasellus eget cursus augue. Pellentesque tincidunt lacus urna, varius dignissim dolor dignissim nec. Sed at laoreet dui. Ut varius lectus sit amet ligula euismod, ac eleifend felis efficitur. Proin a nunc sed eros dignissim mattis ut vitae libero.</p>
|
||||
<p>Maecenas et hendrerit mi. Nam id sodales leo. In et semper dolor. Morbi pellentesque, justo a condimentum aliquam, nunc purus mollis urna, ac viverra augue tellus sed urna. Mauris vestibulum rutrum erat, pharetra ultricies quam finibus sed. Mauris a laoreet ante, ut dictum augue. Duis dolor neque, consectetur eleifend elementum in, lacinia id sapien.</p>
|
||||
<p>Maecenas ullamcorper vehicula sapien at tristique. Mauris eget nisi a sapien laoreet blandit a eu velit. Curabitur pellentesque sollicitudin purus. Proin eu commodo ligula, a sagittis lorem. Suspendisse aliquet sollicitudin diam, ac placerat lectus. In sit amet urna eget turpis iaculis scelerisque nec ac lorem. Etiam at magna rhoncus, molestie tellus a, ornare sapien. Phasellus quis velit in turpis vulputate pretium in a mi.</p>
|
||||
<p>Morbi eget nunc at elit ultrices rutrum quis a dolor. Curabitur id quam vel elit rhoncus molestie vitae in purus. Proin vehicula magna a tellus imperdiet, non ultrices dolor tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed commodo ex ac tortor suscipit ultrices non vel mauris. Fusce a molestie felis, ut eleifend felis. Nunc odio urna, vulputate vitae dignissim ut, mattis accumsan lectus. Curabitur vel velit purus. Nunc volutpat, augue sed pretium euismod, magna sapien elementum purus, eget tincidunt leo purus eget leo. Integer iaculis arcu sit amet venenatis sagittis. Aenean vitae massa a mauris sodales dignissim.</p>
|
||||
<p>Curabitur tempor ornare sagittis. Quisque maximus, elit ac lacinia vehicula, mi elit pretium elit, in eleifend massa turpis non arcu. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras molestie finibus metus at porttitor. Pellentesque a facilisis justo. Vivamus molestie ante elit, id suscipit sapien rutrum eget. Maecenas tellus ligula, pretium quis nisi sed, finibus egestas nisl. Sed non metus maximus, blandit ex eget, aliquet tellus. Sed a blandit purus, ut aliquet erat. Fusce tincidunt tortor sapien, quis porta ante venenatis quis. In varius, purus id malesuada finibus, risus lorem lobortis orci, vitae congue lectus ante ut nunc. Mauris hendrerit mollis condimentum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus non urna id sapien finibus mattis. Integer vitae eros dolor.</p>
|
||||
<p>Phasellus scelerisque mi non ultricies sagittis. Proin in fringilla mauris. Duis quis porta tortor, at fringilla nisl. Sed at varius urna. Donec vitae luctus lectus. Suspendisse blandit massa ut porta convallis. Mauris lobortis lobortis sem, et congue elit consectetur et. Ut posuere eu urna id laoreet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec at sapien vel ante semper rutrum vitae nec justo. Vivamus pulvinar magna a leo hendrerit, vitae aliquam magna vulputate. Suspendisse potenti.</p>
|
||||
<p>Suspendisse quis sapien quam. Nam fermentum est orci, at maximus magna scelerisque vel. Quisque interdum eleifend interdum. Donec malesuada nulla arcu, ac dapibus urna lobortis vel. Vestibulum nisl nulla, gravida in turpis ut, hendrerit sollicitudin nibh. Ut metus lectus, mattis quis volutpat quis, fringilla egestas enim. Integer lobortis, ante vestibulum condimentum ullamcorper, dolor justo dignissim nunc, vel lacinia massa augue sit amet metus. Etiam non molestie sem. Nullam sodales aliquet mattis. Donec sit amet dui at leo feugiat ornare. Donec mi odio, volutpat vitae risus porta, semper egestas arcu.</p>
|
||||
<p>Quisque imperdiet scelerisque elit. In non accumsan tortor, sed lacinia felis. Ut et molestie neque, at tristique urna. Fusce malesuada lorem eget sem finibus, auctor eleifend tortor finibus. Quisque sed luctus turpis. Integer ullamcorper ornare dolor, id vulputate augue consectetur ac. Donec dapibus euismod iaculis. Quisque ac mollis turpis.</p>
|
||||
<p>Phasellus imperdiet ultrices turpis, in rhoncus nulla tempor vel. Aliquam posuere pulvinar nulla sit amet consectetur. Morbi nulla erat, cursus a vulputate sit amet, placerat eu dolor. Cras sagittis nibh auctor nunc sagittis pellentesque. Donec et dolor non ligula lacinia sollicitudin. Cras ac augue purus. Sed nisl elit, porta a congue in, finibus eget justo. Duis commodo imperdiet justo. Pellentesque sit amet elit ut orci rutrum faucibus. Vestibulum vitae lacus porta, feugiat metus efficitur, dictum augue.</p>
|
||||
<p>Phasellus nec lacus purus. In feugiat erat congue leo bibendum, vel maximus enim sagittis. Fusce id imperdiet quam. Cras blandit, tortor et rhoncus feugiat, felis mauris dictum ligula, facilisis suscipit dolor justo eu tellus. Sed volutpat massa ac lectus sodales interdum. Integer sit amet sem ut tortor feugiat finibus nec et ligula. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean pretium hendrerit nisi, vitae eleifend elit varius sed. Nam nunc dolor, dapibus a dictum non, posuere eu nunc. Vestibulum fermentum pharetra nibh. Quisque tempus odio ac felis bibendum, sit amet pharetra ex egestas. Pellentesque facilisis risus sit amet lorem feugiat auctor.</p>
|
||||
<p>Donec quis nisi eu erat fringilla sodales in ut risus. Proin diam elit, commodo lacinia odio vel, efficitur mattis justo. Fusce vitae enim tortor. In eget dolor at felis congue imperdiet. Integer eu ex non urna porttitor imperdiet. Pellentesque ante diam, vestibulum ut ex ut, lacinia rutrum velit. Integer ultrices mattis velit quis malesuada. Mauris venenatis nisi erat, id facilisis ligula aliquam et. Suspendisse rhoncus, odio sit amet sagittis fringilla, nibh felis tincidunt justo, nec placerat ipsum urna vel ex. Suspendisse arcu ipsum, finibus ac odio nec, dapibus sollicitudin tortor. Praesent interdum condimentum faucibus. Proin rutrum mi sed ante dictum viverra. Maecenas egestas, quam et venenatis porta, dolor lectus elementum sem, at ornare magna mauris sed mi. Aenean euismod aliquam neque, id sagittis sapien rhoncus vel.</p>
|
||||
<p>Vivamus eget felis eget dolor euismod suscipit. Fusce maximus sagittis ipsum, non sodales enim facilisis in. Praesent pulvinar lectus quis nisl congue, vitae egestas libero elementum. Phasellus consectetur nec risus sed ornare. Curabitur maximus placerat dolor. Pellentesque quis tempor sapien. Integer feugiat pretium nulla at vulputate. Mauris imperdiet nisl eget lectus fermentum accumsan. Integer iaculis iaculis libero, non tincidunt ipsum vehicula quis. Phasellus tellus justo, dignissim et est a, sodales sagittis libero. In scelerisque orci nec facilisis bibendum. Morbi sodales, lorem sed dapibus vulputate, magna justo mollis justo, id gravida turpis nibh non diam. Aenean vulputate justo vel neque molestie fringilla. Donec pellentesque, sapien suscipit feugiat aliquam, augue ligula gravida metus, sed bibendum nunc sem ac lorem. Suspendisse vitae accumsan ipsum. Curabitur aliquam leo ex.</p>
|
||||
<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed tristique felis enim, in auctor leo semper id. Aliquam a porta dui, ut bibendum dolor. Etiam tortor nisi, efficitur sit amet suscipit non, convallis tristique nisi. Ut nec justo pellentesque ex rhoncus laoreet. Nam posuere orci eget ipsum sollicitudin accumsan. Quisque in dolor nec eros cursus consectetur id vel ipsum. Nam convallis, ante vel vulputate bibendum, elit ligula laoreet ipsum, id venenatis est nisl eget purus. Nunc id luctus metus.</p>
|
||||
<p>Sed faucibus urna felis, sit amet posuere urna porta at. Proin dictum tempus sapien, sit amet fermentum magna laoreet sit amet. Phasellus ac rhoncus elit. Suspendisse sed massa vel enim ultrices aliquam. Integer a lectus massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed laoreet orci sed dolor lobortis, vitae laoreet justo laoreet. Nam iaculis lacinia mauris et vestibulum. Quisque imperdiet dolor convallis interdum varius. Donec eget neque dolor. Quisque consequat ultricies nulla, a tincidunt nunc dignissim et. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In augue eros, maximus non neque id, fermentum accumsan orci.</p>
|
||||
<p>Quisque ut nisi id erat sollicitudin tristique volutpat et diam. Pellentesque euismod ex ac lectus pulvinar, nec blandit ipsum elementum. Quisque commodo, turpis et fringilla pretium, ipsum turpis bibendum eros, sit amet tincidunt urna lectus at felis. Nullam ullamcorper arcu enim, eget bibendum augue vestibulum id. Nullam facilisis, diam non venenatis pharetra, urna justo convallis augue, et lobortis nisi odio et magna. Aliquam sodales lectus nec gravida imperdiet. Vestibulum quis diam eget nibh imperdiet luctus eleifend eget mauris.</p>
|
||||
<p>Donec varius dui a sodales commodo. Aliquam fermentum fringilla velit sit amet ullamcorper. Sed non leo pretium, pellentesque diam et, bibendum augue. Praesent quam ipsum, condimentum eget convallis a, ultrices at tellus. Nunc fermentum massa non dui semper interdum. Quisque feugiat consectetur tellus sed fermentum. Duis sem enim, gravida in erat eu, placerat tristique neque. Mauris libero sapien, ullamcorper id elit at, facilisis suscipit nunc. Integer lorem tellus, ultricies id iaculis fermentum, lacinia vitae metus. Pellentesque in lectus pretium arcu suscipit imperdiet. Vivamus id laoreet tortor. Curabitur augue nisi, iaculis ac faucibus eget, tempor quis eros.</p>
|
||||
<p>Suspendisse libero lacus, rutrum ut semper suscipit, facilisis eu ante. Nullam consequat tempor nisi, vitae placerat libero suscipit in. Nullam ut erat finibus elit venenatis porttitor condimentum sed ante. Donec sed euismod sapien. Cras maximus nulla ex, sit amet pretium erat sodales eu. Curabitur lectus felis, porta quis est id, hendrerit consequat eros. Curabitur auctor, est in sodales efficitur, justo diam sollicitudin lacus, nec ornare felis velit vitae magna. Cras ullamcorper pulvinar massa quis semper. Ut rhoncus turpis eu eros gravida dignissim. Quisque facilisis leo vitae dignissim feugiat. Aliquam sagittis, turpis non porta viverra, purus augue euismod ante, ultrices molestie ligula massa sed arcu. In neque tellus, dictum eget facilisis et, gravida in mi.</p>
|
||||
<p>In hac habitasse platea dictumst. Nam quis tellus nec est vulputate auctor at in elit. Phasellus quis efficitur lacus. Nunc eget tempus est. Aliquam eget nisi in quam molestie vestibulum. Nunc sollicitudin massa ut justo imperdiet auctor. Nullam venenatis elementum vulputate.</p>
|
||||
<p>Nulla sed pharetra turpis. Aenean rutrum sem quis nunc viverra ornare. Integer purus enim, ornare sit amet pulvinar vitae, egestas id massa. Praesent condimentum efficitur sagittis. Nullam bibendum nibh enim, eu semper sapien pellentesque quis. Sed lobortis, elit ac vulputate interdum, risus sem hendrerit sapien, ac pharetra dui sem id sem. Pellentesque elementum interdum elementum. Cras dapibus non velit ut vulputate. Nam auctor at tortor vel congue. Nulla vel nisi et augue pretium pretium. Nunc quis metus non mi posuere ultricies. Cras viverra felis id feugiat ornare. Sed sapien nulla, vulputate ac dignissim non, mattis eu lorem.</p>
|
||||
<p>Ut luctus volutpat euismod. Quisque libero felis, euismod ac metus vitae, sollicitudin finibus justo. Sed consequat turpis tempor, placerat diam et, elementum augue. Integer vel venenatis tellus. Donec ut dolor accumsan, aliquet libero sit amet, ornare nisi. Nam id cursus ante. Sed vitae posuere risus. Pellentesque fringilla lectus vel ante vestibulum porttitor. Duis maximus mi purus, vel ullamcorper tellus laoreet eget.</p>
|
||||
<p>Sed et ligula ut lectus euismod tempus quis sed sapien. Phasellus varius pellentesque malesuada. Maecenas sit amet lorem neque. Nam maximus nisi vitae erat feugiat, ac finibus sapien auctor. Quisque accumsan felis at fringilla molestie. Duis leo diam, tincidunt et ultrices at, elementum ut augue. Vestibulum lobortis lorem iaculis ipsum consequat, at condimentum lacus semper. Aliquam velit dui, ultrices vel tellus lacinia, semper aliquet eros. Pellentesque rhoncus dolor nec leo aliquam, vitae lobortis mauris tempor. Praesent lacinia lectus in nisl hendrerit laoreet. Proin efficitur dui diam, sed vulputate eros pharetra eu. Mauris id justo sit amet dui porttitor tincidunt. Donec vitae arcu at lacus ultrices porta. Suspendisse potenti. Nulla blandit hendrerit felis, et dignissim ex fermentum vel.</p>
|
||||
<p>Quisque aliquet, nisl ut ullamcorper hendrerit, magna ex consectetur nisi, sed mattis purus lacus eu neque. Etiam in lacus et dolor dignissim porta eget ac ligula. Nulla eu enim eget ante pretium porttitor. Nullam fringilla rhoncus ipsum id volutpat. Integer at quam quis purus varius finibus id vitae metus. Cras varius gravida eros nec egestas. Praesent pharetra, justo nec pulvinar tempus, erat ante tempus eros, vel fermentum metus ex vitae ante. Sed bibendum, lorem eu rhoncus maximus, lorem neque aliquam velit, at vestibulum diam risus at nibh. Maecenas sit amet egestas erat, eu molestie purus.</p>
|
||||
<p>Vivamus viverra, sapien sit amet vestibulum gravida, eros urna sodales odio, eu tincidunt quam diam ut mi. Morbi posuere nibh ex, in eleifend dolor elementum ut. Vestibulum eu odio eu dolor gravida consequat. Quisque in nisl ac ligula vulputate sodales. Suspendisse rutrum lacus sit amet tincidunt porttitor. Pellentesque ut quam quis nulla ullamcorper tincidunt. Suspendisse eget accumsan neque. Aenean dictum libero vel suscipit tristique. Vivamus dictum vel quam sed congue. Sed pulvinar ut mauris non molestie. Pellentesque fermentum massa in auctor ultricies. Praesent ac feugiat nulla, ut ultricies purus. Nulla mollis posuere sem et euismod.</p>
|
||||
<p>Nam pellentesque sapien fringilla ex efficitur venenatis. Integer vitae nulla tempus, egestas ante at, semper mauris. Ut at nisi eu ante tempus interdum id eget nunc. Cras venenatis facilisis dolor, sit amet mattis felis congue sit amet. Suspendisse potenti. Praesent risus lacus, imperdiet at magna eget, elementum condimentum nulla. Fusce at leo vitae dui porta feugiat. Nunc ut ornare lorem. Suspendisse eu elementum ante. Praesent efficitur ultrices tellus at fringilla.</p>
|
||||
<p>Integer id urna rhoncus, elementum tellus quis, dignissim quam. Nulla nec laoreet turpis. Nulla elit mauris, interdum in purus quis, hendrerit rhoncus nulla. Aliquam sit amet fringilla turpis, sed commodo nisl. Vestibulum pellentesque risus vitae lacus efficitur, a rutrum nulla volutpat. Donec sagittis at lectus vitae pharetra. Suspendisse potenti. Fusce eget fermentum quam. Sed eu pharetra nisi, vitae laoreet massa. Praesent eu pulvinar nisl. Vestibulum vitae faucibus nunc, nec fringilla nulla. Phasellus aliquam felis sed viverra egestas. Pellentesque augue urna, volutpat ut ex eget, feugiat tempus neque. Nulla sed purus lobortis, tincidunt nunc elementum, mattis eros. Curabitur a ipsum nisl.</p>
|
||||
<p>Etiam eget risus neque. Etiam sollicitudin congue est, vitae sodales orci dictum id. Ut in malesuada felis. Praesent id diam quis nunc dapibus lobortis. Sed eu ex mi. Praesent sed ornare nulla. Praesent elementum velit consectetur lectus mattis, nec placerat sapien hendrerit. Curabitur nec arcu iaculis ipsum cursus fermentum. Nam maximus neque ac arcu porttitor, ac rutrum ante blandit. Mauris ullamcorper vehicula quam eget iaculis.</p>
|
||||
<p>Maecenas posuere mi ante, vel finibus nisl aliquam luctus. Aenean in justo eu est maximus tempor eu vel sapien. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc arcu dolor, sagittis vel sapien eu, pharetra lobortis sem. Sed in pretium odio, et maximus augue. Nullam ultricies sapien vitae mauris congue fermentum. Cras et tristique lacus. Duis posuere, metus sit amet faucibus facilisis, felis massa aliquet diam, vitae dignissim ipsum velit ac elit. Suspendisse et auctor ligula, ut dignissim arcu. Sed scelerisque erat at pellentesque dictum. Etiam ac risus at odio venenatis dictum. Aenean commodo, enim ac rutrum blandit, metus dolor lacinia magna, vitae tempus mi diam eu erat. Sed at ornare lorem.</p>
|
||||
<p>Nulla varius efficitur nunc, quis posuere orci hendrerit ac. Nam vitae justo mollis, vestibulum nibh non, fringilla ante. Aenean porttitor nisl nec fermentum vestibulum. Ut pulvinar id quam eu sagittis. Phasellus lobortis, tortor faucibus egestas auctor, justo eros ultricies ligula, nec efficitur lectus ante sit amet felis. Duis cursus, leo ut rutrum dignissim, mauris metus lacinia mi, cursus cursus arcu elit sit amet elit. Integer eleifend scelerisque tincidunt. Morbi vitae leo tortor. Curabitur venenatis tempor diam nec blandit. Etiam blandit eros massa. Vivamus egestas nunc est, at dignissim neque convallis vel. Donec sit amet dictum odio. Nulla non ante nec sem aliquam efficitur. Etiam ullamcorper ultrices nisi, vitae laoreet metus venenatis ac.</p>
|
||||
<p>Aliquam placerat mauris vitae sem cursus, ac mattis nunc rutrum. Aenean semper neque sed enim ornare pharetra. Nulla non tristique enim, ac dictum tortor. Donec laoreet aliquam orci, id vulputate arcu congue ac. Sed tempus tristique eros in imperdiet. In aliquam metus sit amet tortor semper interdum. Suspendisse potenti. Fusce pulvinar velit quam, gravida feugiat ex dapibus sed. Morbi varius hendrerit risus, at mollis augue feugiat ac. Sed et erat fringilla, hendrerit dolor et, pulvinar ante.</p>
|
||||
<p>Ut mattis sapien eu mi dictum, at vestibulum metus lobortis. Ut faucibus enim nec sollicitudin tincidunt. Ut vel risus molestie, feugiat lacus in, hendrerit lorem. Donec risus dui, porttitor sit amet consectetur sit amet, pulvinar in tellus. Suspendisse feugiat ligula lacus, eu laoreet eros ultricies ut. Quisque pulvinar sit amet leo vel consequat. In mollis, enim id ornare pulvinar, nisl lectus varius mauris, quis fermentum ante diam ac justo. Sed pharetra elit sit amet mattis vestibulum. Proin vel maximus dui. Nulla dictum odio non laoreet bibendum. Fusce tincidunt pharetra libero, nec bibendum nisi maximus non. Curabitur vel egestas lectus. Proin nec sem ligula. Curabitur aliquam nisl ac metus rhoncus mollis. Pellentesque tempor orci eget nibh mattis mollis. Vestibulum ac maximus quam.</p>
|
||||
<p>Donec sed tortor non neque maximus tincidunt vel vitae tellus. Praesent fringilla convallis ex, vehicula eleifend lectus efficitur at. Cras non mattis diam. Integer convallis est aliquam urna convallis pharetra. Vestibulum vitae nisl at nisl dapibus molestie. Curabitur interdum tortor non fringilla maximus. Aliquam sagittis augue elit, sit amet dictum dolor condimentum ut.</p>
|
||||
<p>Nullam aliquam ligula eu volutpat convallis. Fusce at laoreet felis. In hac habitasse platea dictumst. Aenean vitae viverra erat, ac pharetra est. Nullam vel magna dui. Proin eu varius tortor. Etiam sed porttitor libero. Cras eget augue nibh. Nulla iaculis pretium enim nec consequat. Nunc id tempus erat, non sodales velit. Phasellus leo risus, gravida sed justo sit amet, vestibulum pretium nulla. Nullam finibus ornare convallis.</p>
|
||||
<p>Etiam in efficitur felis. Vivamus egestas facilisis augue at dictum. Proin tristique venenatis tellus vel convallis. Ut ipsum mauris, hendrerit at tempus vitae, pretium nec felis. Donec vitae euismod est. Nunc ut magna mi. Sed vel tristique purus. Donec eleifend auctor lobortis.</p>
|
||||
<p>Etiam a mollis dui, sit amet luctus purus. Proin dapibus vehicula urna, eget tincidunt mauris ultrices eu. Nulla ac quam eu quam volutpat porttitor. Mauris bibendum, tellus nec egestas blandit, mi neque interdum quam, non volutpat metus arcu eu ante. Maecenas a accumsan mi, ut ullamcorper elit. Vivamus tellus libero, mattis a turpis cursus, suscipit aliquam neque. Morbi facilisis vel mi vel varius. Aliquam magna lectus, lobortis ut purus nec, ullamcorper pharetra magna. Curabitur vulputate, risus et luctus efficitur, massa mauris aliquam enim, consectetur accumsan lorem nisi ut augue. Cras eleifend auctor lorem at fringilla. Pellentesque quis felis quam.</p>
|
||||
<p>Quisque sodales scelerisque mauris at commodo. Pellentesque volutpat nisi mi, vel feugiat metus bibendum eget. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce cursus lectus id est scelerisque scelerisque. Integer convallis sit amet ipsum at iaculis. Nam sed sem dui. Mauris non tempus tortor.</p>
|
||||
<p>Nam at placerat erat. Maecenas egestas pharetra nibh, et facilisis nunc convallis id. Donec convallis sodales ipsum. Vestibulum non nisi ac orci dictum iaculis at at neque. Pellentesque suscipit leo turpis, eget tempor odio pharetra at. Sed leo arcu, pellentesque sed sagittis id, mattis non massa. In eget dui finibus, volutpat quam sed, fringilla leo. Donec fringilla malesuada consequat. Duis non nulla ac massa venenatis congue non nec neque. Donec tincidunt arcu vel leo ullamcorper, non dapibus nisi imperdiet.</p>
|
||||
<p>Sed consequat ex nec fringilla interdum. Fusce sollicitudin, odio ut blandit feugiat, sapien eros aliquam magna, in consequat enim arcu scelerisque mauris. Nunc euismod felis vel ipsum pretium, in interdum tellus posuere. Nullam eget fringilla orci, vel ullamcorper metus. Maecenas vehicula, nulla sit amet tincidunt dictum, ipsum est interdum lacus, vel dictum velit libero id ex. Etiam mollis nec tortor at pretium. Nullam vitae euismod leo. Suspendisse potenti. Vestibulum eu nisi eget augue maximus vulputate vitae vel nisl. Pellentesque faucibus, elit quis vehicula sodales, nibh turpis efficitur sem, sit amet eleifend sem enim porttitor ligula. Nam vitae tellus id leo interdum eleifend.</p>
|
||||
<p>Sed nunc dolor, pharetra ac laoreet a, elementum vitae ipsum. Integer dapibus nisi quis nisi tempor ullamcorper. In condimentum velit libero, cursus facilisis est sodales sed. Proin sed sollicitudin leo. In id metus vel dolor iaculis scelerisque. Quisque eu mi elementum, facilisis mauris bibendum, condimentum risus. Aliquam euismod, sapien at hendrerit fermentum, diam est aliquam turpis, nec pulvinar est ex eu sem. Integer eleifend ligula ac porta suscipit. Sed eros nibh, vulputate vitae placerat a, semper nec diam.</p>
|
||||
<p>Sed gravida tortor massa, a vestibulum velit cursus vitae. Pellentesque sodales ligula est, eget convallis ligula blandit ut. Phasellus ac rhoncus odio. Quisque non efficitur sem. Quisque pharetra viverra maximus. Pellentesque sodales nunc eget massa elementum, ac placerat libero mattis. In porttitor ultricies sem, eu sagittis nisl luctus sit amet. Praesent dapibus mi vitae urna aliquet, nec semper sem efficitur. Sed vel orci eu nisl tempor mollis a sed tellus. Pellentesque vel laoreet lorem, eget facilisis ex. Aliquam blandit nunc at leo faucibus tincidunt. Duis finibus porttitor turpis et tincidunt. Cras sed augue condimentum ligula porttitor facilisis. Vestibulum semper sapien nibh, eget imperdiet sapien aliquet quis. Integer eleifend ipsum sed dolor convallis, et cursus purus convallis.</p>
|
||||
<p>Vestibulum commodo fermentum laoreet. Nam vel justo quis risus eleifend imperdiet id quis risus. Nulla sit amet semper eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Curabitur eget nulla finibus sapien hendrerit auctor. Fusce laoreet porta nisi id convallis. Morbi molestie pellentesque elit, at posuere elit porta non.</p>
|
||||
<p>Vivamus bibendum, nibh eget lobortis maximus, lectus leo bibendum orci, vitae maximus dui orci vitae purus. Praesent mattis nisi cursus nisi imperdiet tempor. Aliquam massa lacus, volutpat id est eu, sagittis blandit ex. Duis eu hendrerit justo. Nam volutpat cursus ex, et tempus diam ullamcorper dapibus. Mauris id finibus felis. Suspendisse efficitur pretium nunc, id pharetra sem consectetur non. Nam tempus odio tortor, at varius tellus efficitur at. Fusce maximus non eros ac venenatis. Fusce imperdiet at turpis nec sollicitudin. Fusce justo est, luctus id orci vel, accumsan tempor ipsum.</p>
|
||||
<p>Phasellus vel semper velit. Phasellus faucibus consectetur diam, quis luctus ipsum pretium at. Mauris sit amet ultricies quam. Morbi ac ornare lorem, eu pellentesque mi. Nam ultrices dolor at enim fermentum pharetra. Ut ac posuere lectus. Morbi ullamcorper urna sed molestie volutpat. Curabitur porta cursus velit, et porta ligula sodales sed. Donec tincidunt est ac gravida luctus. Praesent sed purus elementum, varius arcu laoreet, sagittis dolor. Integer cursus diam dui, vitae condimentum lectus vestibulum a. Maecenas vitae enim urna. Vestibulum vel malesuada leo. In hac habitasse platea dictumst.</p>
|
||||
<p>Integer vitae nisi lorem. Praesent luctus ullamcorper urna, sed tempor erat maximus vitae. Morbi vestibulum nisi dapibus iaculis hendrerit. In quis ligula id mi laoreet molestie sed in elit. Donec at leo dui. Vivamus sit amet placerat urna. Nunc facilisis commodo mauris, ut scelerisque leo luctus eget. Donec sed sem ut tellus malesuada rhoncus. Nulla condimentum fringilla elit, id mollis erat mollis eget. Vestibulum hendrerit massa at egestas bibendum. Donec fermentum nibh non nisl tincidunt, at porta massa vulputate. Sed euismod magna vitae nibh ultricies vehicula. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
|
||||
<p>Proin tincidunt eros sed elit vulputate, id tincidunt sem maximus. In blandit sed metus nec aliquet. Duis bibendum, neque ut malesuada pharetra, felis lectus mollis dui, sed mollis urna ex at ligula. Vivamus vel ex erat. Phasellus tempor imperdiet tortor eget imperdiet. Aliquam semper quam quis felis ultrices, ac congue lorem ultrices. Suspendisse metus justo, ultrices eu erat vitae, ornare consectetur ligula. Mauris eleifend imperdiet ante, ut suscipit eros varius a. Sed pulvinar sollicitudin magna, id tincidunt risus dignissim id.</p>
|
||||
<p>Maecenas feugiat ullamcorper augue, eu porta lacus cursus ac. Fusce pretium at metus sit amet fermentum. Donec auctor lectus non ligula pulvinar, non faucibus erat ultrices. Nunc sit amet nisl elit. Morbi aliquam odio vitae aliquet rhoncus. Suspendisse justo nulla, aliquet quis massa id, laoreet commodo enim. Etiam sed risus lorem. Sed justo massa, cursus eget felis eu, commodo mollis ante.</p>
|
||||
<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc dictum metus ex, quis sodales lorem facilisis vel. Sed condimentum dictum sagittis. Nullam sagittis diam felis, vitae laoreet neque tincidunt quis. Maecenas viverra sed urna ut vestibulum. Suspendisse diam quam, dignissim nec elit sed, congue luctus ligula. Proin aliquam, orci eget viverra ornare, metus sapien gravida libero, sit amet lacinia velit lectus eget ante. Phasellus id nisl in urna euismod euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Integer mauris libero, scelerisque eu posuere et, tempor imperdiet justo. Vestibulum non massa ut nibh accumsan scelerisque interdum sit amet libero. Etiam ut elit ac ante sagittis pellentesque a eu velit.</p>
|
||||
<p>Vivamus in est nec nisl varius elementum. Donec id nisi eu eros convallis egestas. Sed vitae ligula lacinia magna fringilla vulputate. Morbi nibh purus, malesuada nec purus sed, commodo commodo nisl. Praesent pharetra tellus lacus, et sodales elit condimentum non. Vivamus a ornare lectus. Sed sodales aliquam sodales. Fusce ultrices, sapien eget condimentum euismod, nunc tellus convallis ex, vitae tempor enim diam at velit. Pellentesque at egestas velit. Pellentesque a ullamcorper turpis, dictum maximus eros.</p>
|
||||
<p>Etiam vel est tempor, tristique nunc eu, aliquam orci. Proin sem dui, volutpat sed auctor nec, pharetra et purus. Donec aliquam lobortis purus ut pharetra. Mauris at sapien sit amet metus auctor euismod vitae a nunc. Aenean aliquam purus felis, nec bibendum lorem rutrum ut. Donec sed dignissim justo. Sed fringilla sollicitudin felis, malesuada aliquet nulla. Sed at sem gravida, placerat magna a, rhoncus magna. Ut vitae sodales nibh, maximus vulputate dolor.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet sodales metus, ut maximus risus. Aliquam accumsan, massa in varius feugiat, tortor elit aliquet erat, ut consectetur sem ex et orci. Nunc vel pellentesque orci. Vestibulum non augue tristique sapien sagittis iaculis at quis massa. Maecenas nec fermentum augue. Quisque quis vestibulum arcu. Nam hendrerit sem ipsum, sed elementum neque viverra at. Vivamus mattis vehicula eleifend. Aenean viverra nisi quis sagittis ullamcorper. Fusce hendrerit finibus massa nec imperdiet. Praesent suscipit, purus nec dapibus maximus, ante dui dapibus dolor, et vehicula tortor odio id diam. Nullam a odio id est laoreet elementum luctus viverra magna.</p>
|
||||
<p>Nunc ligula mi, eleifend id nulla nec, placerat gravida odio. Fusce ac vulputate est. Integer non purus ac urna congue cursus nec vitae neque. Pellentesque hendrerit velit libero, et cursus quam dictum efficitur. In sagittis et lectus sed consequat. Proin ut gravida eros. Vestibulum in dignissim neque. Etiam placerat dapibus lorem sit amet congue. Integer id velit vitae eros pharetra egestas sed non velit. Aenean eget tristique eros. Ut vitae arcu pharetra, tristique nibh id, ultricies justo. Etiam porttitor neque arcu, eget sollicitudin nibh laoreet eu. Duis nec purus eu erat feugiat blandit nec eget nibh.</p>
|
||||
<p>Aenean scelerisque diam egestas tortor rutrum, eu rhoncus ipsum porta. Etiam vel eros eget lorem cursus efficitur id at lacus. Sed pulvinar ante et lectus sollicitudin dictum. Pellentesque vel interdum lectus. Mauris vulputate et odio sed dapibus. Donec tortor tellus, sodales eu dui quis, consequat mollis nunc. Nunc vel augue neque. Vestibulum urna diam, dignissim in dolor imperdiet, semper iaculis tellus. Nunc ac sem urna. Nullam efficitur, tortor ut sagittis hendrerit, dolor diam posuere lorem, id rutrum ex leo a lacus. Pellentesque molestie, risus quis condimentum posuere, quam nulla blandit erat, quis viverra augue justo faucibus erat. Vestibulum sed rutrum arcu. Nunc dui mi, blandit a tortor vel, laoreet volutpat mi. Aenean dapibus mauris ut elit mollis, sit amet congue est porttitor. Vivamus nec purus justo.</p>
|
||||
<p>Nam nec dui et felis tristique posuere nec quis justo. Suspendisse fringilla nibh a ex tempor finibus. Etiam pretium nulla sem. Etiam ac augue bibendum, blandit ipsum vel, gravida dolor. Curabitur pellentesque orci tincidunt, lacinia felis quis, porttitor mauris. Nullam semper eleifend ante sodales molestie. Duis vitae turpis vel ante lobortis rutrum. Sed ut arcu facilisis, congue est eu, pharetra tellus. Nullam odio metus, scelerisque a quam id, pharetra blandit dolor. Integer ut sollicitudin ligula, eu condimentum ante. In non lectus ex. Praesent efficitur bibendum lacus in faucibus. Donec feugiat fringilla arcu.</p>
|
||||
<p>Phasellus quis tortor eu arcu aliquam viverra. In ut odio interdum, feugiat purus a, convallis diam. Mauris non sapien enim. Nunc gravida nibh elementum, dictum dolor in, tempus neque. Nullam pretium nunc quis maximus malesuada. Fusce in tincidunt quam, eu porttitor arcu. Nunc ornare in magna et facilisis. Mauris accumsan ex ac scelerisque dignissim. Praesent libero massa, congue nec massa et, pulvinar facilisis felis. Aliquam tristique mauris vitae dui consectetur molestie. Donec ac lectus lacus. Aliquam eu augue quis eros volutpat iaculis.</p>
|
||||
<p>Mauris nec accumsan arcu, eu porttitor dolor. Nunc scelerisque pellentesque nisi sed mollis. Integer quis tincidunt sapien. Pellentesque a elit in eros interdum efficitur quis vel arcu. Mauris malesuada nunc lacus, ac auctor mauris lobortis non. Etiam sit amet ullamcorper mi. Vivamus a volutpat lorem. Donec at velit tristique, consectetur nunc vitae, condimentum mauris. Pellentesque molestie lectus ac purus finibus condimentum. Etiam luctus lectus vitae maximus consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi hendrerit risus a odio auctor pellentesque. Suspendisse potenti. Phasellus at ante felis. Nam mattis scelerisque bibendum. Sed vel ornare orci.</p>
|
||||
<p>Duis a euismod quam. Suspendisse non massa mi. Morbi sed tellus egestas, semper felis eu, dignissim justo. Phasellus sollicitudin eu orci eget condimentum. In hac habitasse platea dictumst. Aliquam erat volutpat. Quisque sit amet lacinia ante, ac vehicula mauris. Donec aliquam in augue ut vestibulum.</p>
|
||||
<p>Vivamus efficitur sapien nec mi vulputate, vel eleifend turpis consectetur. Duis quis massa mattis felis faucibus egestas. Donec lobortis leo eu arcu gravida, a vestibulum neque tristique. Vivamus in rhoncus mi. Curabitur ipsum diam, sollicitudin in massa in, viverra lacinia ante. Fusce cursus orci ipsum, non semper neque consequat imperdiet. Donec in lorem vel enim vestibulum sagittis at quis tortor. Suspendisse potenti. Praesent lobortis, eros ut molestie tristique, tortor ante suscipit nibh, a semper felis nibh vel purus. Integer in congue ligula.</p>
|
||||
<p>Aliquam ultrices at purus ac luctus. Proin finibus metus est, non egestas purus dapibus sit amet. Maecenas dapibus, urna ac accumsan sagittis, metus ipsum rutrum dolor, a elementum justo odio ut massa. Nullam sollicitudin ipsum sagittis, venenatis purus non, elementum ligula. Donec quis lacus sem. Fusce pulvinar turpis ut leo facilisis, ac varius lacus lacinia. Quisque viverra augue ut leo gravida porttitor. Maecenas ullamcorper ante sed purus rhoncus tempor. Mauris dictum tellus vitae iaculis facilisis. Cras dignissim at purus id vehicula.</p>
|
||||
<p>Mauris iaculis ultricies nibh, in aliquet odio mattis ut. Proin consectetur blandit risus sit amet aliquam. Vestibulum a urna vestibulum, bibendum dui luctus, facilisis ante. Nullam efficitur tristique enim. Maecenas justo tellus, scelerisque quis blandit at, aliquam vel tortor. Duis nec elit ligula. Pellentesque luctus malesuada tortor, et faucibus velit convallis ut.</p>
|
||||
<p>Morbi ac velit vitae risus aliquam vestibulum a et purus. Sed fringilla euismod erat, quis dapibus arcu efficitur nec. Cras mollis sed turpis maximus maximus. Donec vitae ligula id libero lacinia interdum vel sed massa. Maecenas elementum nunc justo, porttitor suscipit ex imperdiet vel. Nullam accumsan tincidunt ligula, ut venenatis augue sodales eu. Phasellus vestibulum vehicula urna, in lobortis elit vulputate ut. Morbi vulputate convallis mauris, nec eleifend quam dictum ut. Nam dictum turpis eu imperdiet lobortis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam luctus cursus ante, a malesuada ex tincidunt et. Fusce consectetur est vel orci volutpat vulputate. Suspendisse congue dictum nisi quis commodo. Pellentesque molestie, nunc in interdum hendrerit, massa sapien fringilla sapien, eu feugiat enim est eu dolor. Phasellus quam lectus, luctus id justo id, lacinia gravida sapien.</p>
|
||||
<p>Quisque suscipit interdum massa, eget facilisis neque tempor eu. Quisque eu ante at enim ullamcorper aliquam. Nullam at lacus metus. Aenean facilisis, felis eget dictum vulputate, lectus ante commodo est, at fringilla massa metus eu urna. Ut in dignissim lorem. Fusce sit amet magna rhoncus, commodo felis tempor, accumsan nisl. Fusce mollis, eros sit amet egestas feugiat, leo mi auctor justo, eu dignissim turpis elit ut tortor. Mauris nec feugiat urna. Integer dui neque, ultrices vel massa quis, dignissim fringilla sapien. Fusce vulputate tellus in lacus ultricies, sit amet suscipit magna rutrum. Proin at lectus sodales, pharetra neque et, laoreet sapien. Vestibulum diam turpis, gravida eu augue quis, fermentum mollis dui. Integer faucibus, felis ac convallis scelerisque, neque magna laoreet velit, nec dictum lectus elit sed nibh. Integer orci leo, facilisis vel imperdiet at, tristique ac mauris. Pellentesque quis ex sodales, porttitor turpis eu, pharetra dui.</p>
|
||||
<p>Duis euismod diam urna, at ullamcorper dui tincidunt vitae. Curabitur vel erat sit amet arcu vehicula lacinia sed ac arcu. Cras neque nisl, posuere vitae dictum a, dapibus a diam. Cras non euismod ligula. In condimentum, urna vel blandit egestas, sapien diam condimentum turpis, at dapibus elit ante a orci. Mauris maximus dui sapien, luctus imperdiet quam efficitur quis. In sit amet leo nec nisi dictum accumsan id vel mi. Fusce auctor arcu sem, quis cursus urna semper a. Cras quis tempor ante. Integer cursus dapibus erat eu porta. Curabitur eleifend posuere enim nec vehicula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam in dui nec metus vulputate rhoncus a at diam. In turpis libero, luctus quis metus in, posuere blandit metus.</p>
|
||||
<p>Sed auctor dictum finibus. Sed tristique arcu vel diam pulvinar dapibus. Aliquam in arcu nec arcu vehicula euismod non eu nulla. Aliquam fermentum justo non lorem placerat rutrum vel sit amet metus. Suspendisse massa mi, tincidunt et placerat eget, consectetur sed erat. Fusce dictum metus tortor, a pulvinar justo posuere quis. Sed fringilla pharetra ex, eget faucibus lacus dignissim et. Curabitur posuere dui ac turpis scelerisque, eu scelerisque mi viverra. Vestibulum pulvinar nibh eu dui pharetra placerat. Praesent varius augue nisi, quis iaculis leo porttitor aliquet. Nulla imperdiet, nisl in dignissim vestibulum, dolor dui tempor leo, ultrices aliquam libero lacus a purus.</p>
|
||||
<p>Integer ut erat lacinia ligula tincidunt malesuada. Proin sed varius tortor. Ut sodales dolor sit amet porttitor condimentum. Suspendisse sit amet orci euismod, tincidunt augue eget, mattis leo. Suspendisse sit amet arcu posuere tellus porttitor ornare vitae vitae est. Donec quis posuere odio. Nullam ut lacinia nulla, at laoreet risus. Sed dui tortor, facilisis vel tortor sit amet, ullamcorper dictum ante. Pellentesque eget diam augue. Duis dictum felis id ultricies finibus. Vivamus in consectetur dolor, sit amet mollis ex. Sed fermentum neque ac leo faucibus, et eleifend ante interdum. Sed pharetra libero eget mauris tincidunt, ut sagittis sapien facilisis. Vivamus vel malesuada quam. Ut consequat cursus risus sit amet aliquet.</p>
|
||||
<p>Donec in eleifend lacus. Phasellus lacinia diam id ligula rhoncus, a laoreet ipsum mattis. Praesent enim lectus, interdum eu consequat sed, efficitur id diam. Praesent ultricies rhoncus nisl, ac gravida dui sodales non. Aenean molestie pulvinar mi quis consectetur. Curabitur quis hendrerit risus. Mauris justo nisl, pretium at lorem id, euismod volutpat nisl. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse eget magna non tellus pretium imperdiet eget a ligula. Fusce a est non urna rhoncus molestie. Sed at nunc ut nibh eleifend accumsan. Nunc bibendum eu augue nec interdum. Duis consequat, nibh a egestas imperdiet, mauris magna malesuada urna, vitae elementum leo orci eu nulla.</p>
|
||||
<p>Donec sodales ipsum id velit rhoncus efficitur. Aliquam in diam sit amet felis fringilla consectetur nec nec nibh. Proin tincidunt dignissim neque sed volutpat. Curabitur in vulputate tortor. Duis at lacinia nisi, non laoreet lectus. Nullam neque lacus, scelerisque in augue sed, finibus pharetra tellus. Cras quis mi in elit accumsan volutpat. Nullam feugiat, est vel luctus iaculis, libero arcu ultricies lorem, non hendrerit ex nisl nec est. Sed ullamcorper, eros ut vulputate pretium, orci augue porta urna, ullamcorper interdum dui nunc et mi.</p>
|
||||
<p>Nunc maximus sem tortor, ut iaculis justo fringilla a. Nulla facilisis sit amet urna ut suscipit. Curabitur sodales volutpat velit eget accumsan. Nulla in dolor vel nulla gravida porta. Suspendisse vehicula augue eget risus luctus dapibus. Etiam bibendum quam ante, scelerisque vulputate elit faucibus at. Vestibulum eu bibendum neque, at convallis dui. Aenean facilisis, orci non eleifend bibendum, sapien neque rhoncus est, sit amet blandit lorem quam vel sapien.</p>
|
||||
<p>Aliquam eros elit, auctor non pharetra id, egestas ac nulla. Morbi maximus, orci nec viverra interdum, nulla lacus ultrices justo, sit amet sodales massa magna id dolor. Praesent tempus augue non erat vehicula, ut laoreet erat accumsan. Phasellus commodo, neque ac efficitur finibus, urna nibh maximus eros, eget scelerisque enim urna a dui. Nullam malesuada felis at ante vulputate, eget blandit elit ornare. Proin ac elit sollicitudin, malesuada quam vel, eleifend orci. Fusce mollis vel quam in elementum.</p>
|
||||
<p>Nullam ut erat id metus elementum tincidunt at ac dolor. Morbi vel commodo neque, ac commodo eros. Nulla fermentum massa urna, a fermentum elit bibendum vitae. Duis pharetra mauris nec ultrices feugiat. Vestibulum justo nisl, tempor at neque vel, accumsan consequat lacus. Sed quis enim eu augue luctus consequat ac sit amet felis. Ut bibendum, augue et volutpat blandit, nisi odio egestas mauris, eu euismod tellus tellus vitae tortor. Morbi sit amet magna condimentum, blandit lacus a, condimentum ipsum. Nullam ut tristique justo. Integer laoreet laoreet facilisis. Nullam molestie nec sapien nec pulvinar. Aliquam venenatis hendrerit nunc eget hendrerit. Mauris finibus, risus id lacinia dignissim, leo nulla consequat diam, vitae porttitor dolor diam eget magna.</p>
|
||||
<p>Morbi venenatis mauris velit. Cras sit amet urna tortor. Quisque quis tempor magna, ut sollicitudin augue. Phasellus consectetur nisi metus, et vestibulum urna dapibus semper. Praesent vulputate, turpis non consequat fringilla, lectus neque scelerisque sem, imperdiet rhoncus est metus finibus nibh. Curabitur fermentum sodales ultrices. Vestibulum tristique turpis sed libero finibus finibus. Donec ac tincidunt risus, non consectetur nisi. Sed at finibus odio, ut scelerisque nisi. Vivamus et scelerisque orci. Aliquam vitae diam dolor. Integer hendrerit aliquam arcu, a fringilla nisl rutrum ut. Curabitur euismod dignissim magna id bibendum. Suspendisse venenatis augue odio, at consequat sem vehicula ultricies. Maecenas sollicitudin aliquam urna vel varius. Donec placerat imperdiet tristique.</p>
|
||||
<p>Aliquam consectetur tincidunt diam fermentum volutpat. Etiam quis elementum risus. Donec dictum faucibus urna. Cras a consequat ante. Nam finibus est a dolor aliquet ullamcorper. Ut ac sodales justo. Nunc finibus eleifend leo nec aliquam.</p>
|
||||
<p>Duis enim erat, finibus nec faucibus et, vulputate sit amet orci. Integer pretium facilisis arcu, at molestie sem dictum eu. Sed tempus ut diam vitae ultricies. Suspendisse consequat tincidunt venenatis. Etiam consequat eu eros non convallis. Curabitur eleifend mattis justo, eu sagittis leo ultricies condimentum. Integer in tellus ac ipsum feugiat molestie non in mauris. Etiam laoreet sapien purus, nec molestie purus accumsan quis. Nulla sit amet mattis mauris, tristique tristique magna. Mauris faucibus pulvinar orci ut lacinia. Nam ac nisi nec dolor auctor accumsan eu eget justo. Nullam ut sem ultrices neque tempor dictum. Integer facilisis venenatis arcu sed sollicitudin. Nullam mattis fermentum enim, eget ultrices nunc. Aenean blandit, nunc in lacinia suscipit, lorem libero volutpat lorem, eget pellentesque dui nisi et dui.</p>
|
||||
<p>Duis dictum, nunc ut imperdiet bibendum, est metus congue diam, at tempus augue nisi ac dolor. Nulla scelerisque nibh nibh, sit amet elementum nisl feugiat non. Proin maximus quam molestie feugiat facilisis. In quis vestibulum dui, vitae malesuada dui. Morbi commodo ornare justo, vel euismod mi pellentesque ac. Maecenas quis turpis at diam euismod condimentum. Sed molestie aliquam dictum. Vestibulum rutrum ullamcorper urna vitae ultrices. Nullam maximus nibh auctor, semper enim vel, suscipit ligula. Fusce et ligula ante. Phasellus tempor turpis in facilisis tincidunt.</p>
|
||||
<p>Vestibulum orci leo, pulvinar non dolor nec, cursus tincidunt nulla. Sed tempor nunc neque, quis vehicula enim pulvinar sit amet. Morbi aliquet finibus justo ac luctus. Proin urna justo, egestas eu ullamcorper et, volutpat vel odio. Duis venenatis est ut dui mattis imperdiet. Pellentesque ut lectus velit. Maecenas mi est, bibendum nec tristique nec, cursus quis tortor. Aliquam erat volutpat. Donec blandit sapien sed urna tempor, non viverra ex sodales. Vivamus ac enim nec diam condimentum rhoncus. Phasellus ullamcorper augue ut vulputate egestas. Aenean eu urna ultrices, dignissim augue eu, lobortis diam. Sed auctor eget ante in tempor. Integer eget elementum nisl.</p>
|
||||
<p>Donec dignissim faucibus risus. Suspendisse dictum dictum ex, eu rhoncus augue cursus at. Integer lectus est, pulvinar vitae tempus at, luctus eget risus. Sed vulputate enim diam, sed tempus lorem posuere non. Quisque eget libero velit. Morbi vitae mollis elit. Ut laoreet, nibh non molestie sollicitudin, felis massa convallis est, sit amet congue nisl metus in justo. Donec et tincidunt lacus. Morbi fringilla lorem efficitur ligula gravida convallis. Integer sagittis eu nunc vel efficitur. Maecenas sodales neque massa.</p>
|
||||
<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur non volutpat est. Etiam tristique nulla arcu, vel dictum risus euismod a. Pellentesque sit amet risus in purus ultricies pulvinar. Nam cursus erat ac gravida porttitor. Nam sit amet ex sed neque semper volutpat. Morbi in volutpat diam, sed scelerisque metus. Mauris non cursus erat, a tincidunt arcu. Vivamus porttitor ut lectus et consequat. Aenean diam enim, dignissim nec arcu non, condimentum venenatis metus. Nunc finibus justo vel risus varius elementum. Praesent scelerisque, risus in ultrices lacinia, nunc lectus volutpat odio, in eleifend diam est eget magna. Phasellus varius lacinia malesuada. Curabitur ligula nunc, mattis eget purus ac, euismod condimentum est.</p>
|
||||
<p>Suspendisse volutpat erat et convallis suscipit. Nulla eget urna pellentesque, maximus mauris sit amet, fringilla urna. Sed vitae nulla sed ante malesuada consequat. Donec libero purus, laoreet et dolor vel, blandit accumsan velit. Etiam tincidunt libero tempus, blandit nisi at, tincidunt nisi. Maecenas justo justo, pretium a nunc rhoncus, pellentesque pharetra dui. Ut quis odio imperdiet, semper lorem id, placerat odio. Curabitur vitae volutpat nisl. Vivamus et ipsum cursus, aliquam sem vitae, feugiat ligula. Suspendisse malesuada semper tortor, eu ultrices magna molestie eget. Curabitur placerat, diam vel volutpat sagittis, lacus arcu posuere risus, at rutrum nibh augue ac lacus. Fusce non metus at quam gravida hendrerit. Nam id justo nisi. Maecenas convallis, dolor lacinia tristique cursus, elit enim fringilla turpis, ut tincidunt leo lacus ut massa. Ut hendrerit eget ante vel hendrerit.</p>
|
||||
<p>Mauris hendrerit augue interdum, commodo felis a, pretium felis. Vivamus ultricies augue sed egestas consectetur. Pellentesque nec egestas sem. Fusce at volutpat arcu, et tincidunt libero. Sed condimentum malesuada interdum. Nullam id felis et nunc tempor sollicitudin vel non ipsum. Etiam porttitor convallis mattis.</p>
|
||||
<p>Vestibulum id nunc sagittis, blandit odio ut, fringilla leo. Nam lacus eros, lobortis in ipsum cursus, sodales efficitur est. Cras ac tempor ligula, eu tincidunt ante. Donec eu sapien quis massa tempor placerat. Duis volutpat justo massa, nec condimentum lorem commodo consequat. Aenean interdum mi ut lacinia luctus. Cras vel diam est. Sed sapien diam, semper non odio sit amet, sodales suscipit lorem. In molestie, justo non feugiat maximus, justo nisi lacinia velit, quis blandit arcu lorem sed nisi. Etiam vitae tortor elementum, sagittis mi id, feugiat enim. Vestibulum sodales lorem urna, eget cursus tortor rhoncus sed. Suspendisse potenti. Fusce vitae eros eu ex gravida auctor gravida a nunc. Quisque viverra, lorem non iaculis efficitur, nisi est pretium mi, in accumsan magna eros ac felis. Quisque bibendum, magna sed vulputate ornare, purus massa sodales nisi, eget porttitor urna elit a libero. Nullam in luctus diam.</p>
|
||||
<p>Nam sodales dapibus lectus ut laoreet. Donec feugiat magna et quam bibendum tincidunt. In vitae laoreet odio. Praesent nec elit mollis, imperdiet erat sed, porttitor libero. Phasellus sollicitudin orci et dui aliquam blandit. Integer sem arcu, feugiat quis luctus sit amet, ornare vel enim. Aenean faucibus placerat orci nec tristique.</p>
|
||||
<p>Aenean maximus metus a quam euismod aliquet. Ut vitae tellus purus. Sed convallis nisi a magna malesuada rutrum. Curabitur finibus, diam in rutrum porttitor, dolor arcu ultricies tellus, quis rutrum metus eros eget sapien. Aenean ut eros sed eros viverra feugiat sed vel nisl. Donec facilisis, sapien ac tempus tristique, sapien enim posuere lorem, at cursus nisl tellus vel urna. Maecenas facilisis, dolor viverra aliquam mollis, purus mi tincidunt nibh, scelerisque finibus mauris sem id dui. Pellentesque placerat sapien posuere, venenatis purus at, bibendum elit. Quisque odio orci, sagittis vel tempus sed, tristique eu nisl. Vivamus ut ligula interdum, consequat dui ultricies, congue neque.</p>
|
||||
<p>Ut purus leo, viverra interdum enim et, posuere congue libero. Sed bibendum rhoncus neque, in interdum dui congue sed. Aenean a diam sollicitudin, faucibus tortor a, auctor nisl. Integer mi mi, consectetur quis interdum eu, placerat ac enim. Quisque consequat faucibus arcu, sed hendrerit est. Maecenas interdum velit ut libero vehicula interdum. Vestibulum suscipit fermentum sodales. Suspendisse sem sapien, consectetur non convallis et, dapibus ut urna. Fusce efficitur eros nec iaculis sollicitudin. Praesent vitae blandit eros. Nullam porta finibus bibendum. Praesent eget molestie nisi. Aliquam rutrum ipsum et ligula tristique, ac mattis diam gravida. Proin ut interdum enim. Maecenas aliquam sapien est, quis laoreet nisl cursus in. Duis justo neque, efficitur id lacus vel, condimentum dapibus nibh.</p>
|
||||
<p>Vivamus interdum dui in sem porta, quis luctus ante efficitur. Suspendisse quis tortor et eros aliquam ultrices eu ac felis. Phasellus at consectetur nisl. Ut nibh tellus, dapibus eget diam non, ullamcorper suscipit elit. Quisque accumsan egestas finibus. Cras laoreet libero vel elit pretium, non euismod eros sollicitudin. Pellentesque metus felis, laoreet quis tortor non, tincidunt aliquam ligula. Vivamus at sagittis dui. Praesent non fringilla erat. Duis eu pharetra massa. Fusce bibendum, velit dignissim molestie dapibus, ipsum turpis pulvinar arcu, ac porttitor sapien nisi quis lorem. Cras fringilla maximus facilisis.</p>
|
||||
<p>Maecenas facilisis sodales venenatis. Quisque imperdiet, enim ac vulputate vulputate, justo nibh pretium odio, vel mollis quam ipsum in eros. Praesent nibh mauris, faucibus et posuere id, consequat a tortor. Vestibulum dapibus lacus id erat rutrum, vel lobortis nisi consectetur. Nulla aliquet, justo sed consequat sollicitudin, dui mi venenatis erat, nec pretium mauris dui eget sem. Nullam ac pharetra lacus. Integer vulputate, diam et tincidunt mattis, est turpis mattis est, vitae vehicula quam lectus nec mauris. Mauris pellentesque mattis faucibus.</p>
|
||||
<p>Maecenas vel lectus condimentum, eleifend metus a, rhoncus dui. Etiam consectetur enim sed dui interdum sollicitudin. Duis massa lorem, tincidunt eu nibh a, semper fermentum felis. Integer eu sapien sit amet dui vulputate hendrerit ac sit amet erat. Phasellus suscipit massa vehicula nunc varius fermentum. Praesent bibendum iaculis elit nec tristique. Pellentesque facilisis egestas urna, et commodo dui pretium at. Etiam semper, massa volutpat sollicitudin venenatis, neque turpis dictum ex, eu condimentum sem quam non ex. Sed lacinia lectus vitae rhoncus vehicula. Vestibulum vel accumsan libero. Donec varius lectus mollis, dignissim orci et, rhoncus enim.</p>
|
||||
<p>Suspendisse posuere lorem non enim consectetur, et convallis augue tempor. Vestibulum lobortis, tortor et consequat convallis, nisl neque convallis quam, vel varius est tortor eget felis. Cras a lobortis est, id condimentum nunc. Pellentesque auctor est rhoncus suscipit interdum. Nullam eu tristique felis. Aenean imperdiet maximus quam, tempus pulvinar sem condimentum fringilla. Nulla non scelerisque lacus. Quisque gravida eu ligula et sagittis. Fusce laoreet nisl id sagittis ultricies. Aliquam ut augue ut tortor sollicitudin semper id et sem. Nunc quis dui congue, dapibus enim non, egestas nunc.</p>
|
||||
<p>Pellentesque tellus nibh, feugiat ut fringilla et, tincidunt in nisl. Quisque vestibulum eros eros, a ullamcorper velit posuere vitae. Fusce maximus risus at est tristique lobortis. In ex quam, porttitor ut tortor vitae, scelerisque aliquam neque. Nullam et ligula sit amet ex malesuada commodo quis tincidunt justo. Nulla tempor bibendum tellus, vitae mattis ipsum facilisis vel. Morbi scelerisque ligula sed dapibus lobortis. Vestibulum volutpat magna felis, ac tincidunt libero venenatis et. Sed pulvinar libero est. Vivamus sagittis risus quis magna hendrerit consectetur. Ut nec euismod sem, et cursus nulla. Etiam dapibus, purus ac aliquet venenatis, nulla libero pretium erat, in rutrum diam purus a lectus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut odio elit, ultrices vitae lectus at, iaculis tempus massa. Phasellus hendrerit posuere tincidunt.</p>
|
||||
<p>Aenean nibh elit, dapibus vitae consectetur ac, dignissim at libero. Quisque blandit est ac risus tincidunt finibus. Donec sollicitudin fringilla facilisis. Nullam euismod, sem at egestas pharetra, lacus tellus ullamcorper lectus, ut commodo nulla sem a orci. Proin sed felis sem. Ut turpis mauris, feugiat sed dolor non, mollis lacinia sem. Mauris est dolor, hendrerit non pulvinar vitae, posuere id nulla. Aliquam rutrum, diam id interdum placerat, velit quam lobortis tortor, ut pretium lacus mauris tristique ex.</p>
|
||||
<p>Curabitur vitae dolor vitae mauris pretium luctus. Maecenas malesuada mollis nisi. Cras non aliquam arcu. Sed facilisis pretium velit, a finibus dolor finibus at. Sed interdum elit at est scelerisque placerat. Donec accumsan sodales lorem ac condimentum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ut facilisis urna. Aliquam ut nulla turpis. Donec pretium mauris urna, non ultricies lectus mattis non. Nulla facilisi. Nullam hendrerit a sapien ac iaculis. In efficitur consequat nulla sit amet semper. Sed non viverra dolor.</p>
|
||||
<p>Donec venenatis vitae libero vitae mollis. Sed et semper lectus. Aenean id vehicula arcu, sed tempus leo. Phasellus non mi aliquet dui tincidunt eleifend. Pellentesque congue ultricies est. Morbi varius nec ligula eu hendrerit. Nulla fringilla vel turpis in cursus. Curabitur nec ex ac augue luctus vestibulum at nec leo. Duis quis consequat turpis, nec sodales odio. Etiam eu arcu blandit, euismod nibh in, pellentesque purus. Nullam elit turpis, tempus eget elit non, vestibulum interdum ligula.</p>
|
||||
<p>Maecenas est justo, facilisis sagittis arcu a, venenatis dictum eros. Mauris ullamcorper ligula mauris, ac sollicitudin urna dignissim et. Integer fermentum lectus in pharetra mattis. Nunc porttitor in est eu sagittis. Mauris iaculis felis at tortor venenatis viverra. Donec iaculis elementum efficitur. Nunc eu urna ut ante facilisis ultricies. Sed volutpat orci at tristique semper. Nullam eget blandit nulla. Curabitur porttitor, felis ut rhoncus elementum, urna magna euismod ipsum, non fermentum mi nulla rhoncus sapien. In hac habitasse platea dictumst. Nulla euismod ligula ac nulla vestibulum, non pharetra mi placerat. Nam sed libero non erat blandit luctus a non urna. Quisque ut enim lacus. Donec a diam id dui vehicula viverra. Maecenas vehicula semper metus.</p>
|
||||
<p>Curabitur sem turpis, ultricies non venenatis vel, bibendum ut nibh. Cras ac viverra massa, at sagittis metus. Aenean lobortis imperdiet quam at pharetra. Suspendisse ac commodo velit. Nunc eu tincidunt neque. Duis pretium ante id enim vulputate dignissim. In vestibulum fermentum dolor, id scelerisque neque pulvinar vel. Maecenas non euismod felis, ac rhoncus augue. Nulla imperdiet tempus justo non imperdiet. Duis ac aliquam tellus. Morbi gravida, nulla sit amet efficitur varius, ante elit mattis tellus, a egestas augue lacus non nibh. Pellentesque purus lectus, efficitur non orci ut, dapibus dignissim lacus. Cras ut tellus volutpat, finibus erat nec, suscipit velit.</p>
|
||||
<p>Integer auctor mi in rhoncus facilisis. Donec eleifend mollis sem, eget condimentum neque eleifend id. Fusce at nibh tempor, laoreet lectus et, aliquam felis. Morbi fringilla placerat neque, a gravida mi bibendum fermentum. Nam id mi eu augue aliquet feugiat. Donec ut quam et tortor auctor aliquet. Ut pharetra pharetra mollis. Phasellus ultricies vitae justo at lobortis. Nulla et arcu consequat, vehicula turpis et, hendrerit eros. In efficitur faucibus est ac elementum. Phasellus imperdiet scelerisque ipsum nec sagittis. Integer aliquam commodo eros, eget ultricies ipsum consequat id. Aenean venenatis nisi purus, at facilisis magna fermentum vel. Quisque faucibus eros eu nulla lobortis, sed aliquam arcu consectetur.</p>
|
||||
<p>Suspendisse vitae tristique ligula. Sed ullamcorper ut libero ut finibus. Mauris volutpat nisi eu nulla viverra tincidunt. Sed fringilla velit sit amet diam posuere, vitae porttitor odio pulvinar. Integer vehicula mauris neque, at commodo est aliquet venenatis. Sed blandit tortor quis orci aliquet tempus. Fusce dapibus ac eros quis interdum. Vivamus nec viverra est, vel viverra nibh.</p>
|
||||
<p>Morbi varius mauris et sagittis gravida. Nunc ac dolor tellus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc laoreet accumsan ligula, et vulputate massa dictum maximus. Aliquam erat volutpat. Nunc malesuada justo eget velit maximus, at imperdiet nisi pretium. Maecenas a justo vitae sapien tincidunt placerat vel nec lorem. Donec quis mi nulla. Etiam sagittis ultrices nibh, eu egestas tellus aliquam quis. Vivamus vel iaculis felis. Aenean egestas, turpis eget fringilla luctus, tortor tortor sodales purus, at vehicula elit turpis non turpis. Morbi pellentesque ligula non quam vehicula rhoncus.</p>
|
||||
<p>In hac habitasse platea dictumst. Cras felis lacus, porttitor aliquet elit vel, semper luctus risus. Nulla ex dolor, blandit id lacus at, dictum iaculis nulla. Nam eleifend accumsan ante. Praesent feugiat a quam non fermentum. Donec ut maximus arcu. Phasellus eu dolor in mi iaculis consectetur nec vel urna. Nam mi erat, auctor at porta non, hendrerit id tortor. Maecenas convallis ultrices mauris, pellentesque bibendum lectus dictum non. Ut nec justo faucibus, auctor leo non, placerat magna. Curabitur ultricies massa eget nibh venenatis aliquet. Aenean turpis tellus, maximus vel ultrices ut, scelerisque a turpis. Duis risus sem, dapibus consequat pellentesque sit amet, sagittis nec libero. Curabitur tempus posuere lectus, id vulputate augue consequat iaculis. Suspendisse vulputate, odio vel aliquet gravida, justo sem pellentesque neque, in tempor ligula mauris eu dui. Nulla consectetur, nibh vel pharetra dictum, dolor justo commodo nunc, vel sodales est odio id neque.</p>
|
||||
<p>Nam eu orci tempor ex vestibulum luctus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis non odio efficitur leo aliquet condimentum. Vestibulum gravida eu urna id fermentum. Donec finibus lobortis erat ut elementum. Duis aliquam dolor in ipsum efficitur, non sollicitudin eros rhoncus. Nulla tempus suscipit tellus, non posuere odio porta at.</p>
|
||||
<p>In hac habitasse platea dictumst. Cras vestibulum massa posuere, ultrices risus vitae, semper elit. Aliquam aliquam urna maximus, venenatis nibh nec, feugiat mauris. Nulla consequat est eu massa viverra iaculis. Integer ante eros, vehicula eget quam vel, tempor ultrices lacus. Duis ultricies sed velit tincidunt mollis. Nam mattis neque eu ultrices blandit. Curabitur vel nisl ut turpis aliquam condimentum.</p>
|
||||
<p>Duis eget purus elit. Curabitur congue, quam et accumsan sollicitudin, libero metus condimentum orci, nec euismod lectus lorem euismod erat. Duis molestie orci ut nisi finibus aliquam. Quisque in turpis sit amet purus gravida pulvinar. Mauris mi ipsum, laoreet vitae est quis, dignissim lobortis leo. Proin tempor lobortis aliquam. Suspendisse nec finibus est. Mauris urna est, interdum eleifend facilisis nec, imperdiet quis felis. Sed porta, lectus quis sollicitudin aliquam, nisl dui sodales massa, ac aliquam mauris neque a quam. Vivamus egestas odio ac purus auctor, vitae finibus risus volutpat. Aliquam et augue aliquet, pretium diam eget, imperdiet ipsum. Nulla tempus enim quis ullamcorper efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean porttitor tortor at quam lobortis, id dignissim quam dapibus.</p>
|
||||
<p>Quisque sit amet purus risus. Nulla ut elementum sem. Phasellus ac dictum dolor. Suspendisse porta sed est a consequat. Morbi turpis nulla, egestas a enim et, vulputate feugiat turpis. Morbi enim nibh, efficitur id leo vel, cursus tincidunt turpis. Ut volutpat, lectus sed blandit eleifend, erat ex luctus turpis, ac interdum nulla nibh sed est. Fusce ornare ex id magna efficitur, finibus fermentum velit facilisis. Praesent quis velit vehicula, lobortis quam non, ullamcorper urna. Donec at tempor ante. Phasellus egestas tincidunt iaculis. Donec vitae urna eget ipsum gravida maximus. Nulla congue dapibus sodales. Praesent quis erat lacus. Sed non risus non tortor ullamcorper ullamcorper.</p>
|
||||
<p>Aenean id neque id lectus suscipit varius. Ut porta ipsum mattis ligula varius semper. Proin tincidunt dolor velit, a pharetra magna faucibus sit amet. Praesent ut auctor sapien, ac accumsan nisl. Etiam erat ipsum, luctus et velit a, facilisis eleifend tortor. Cras cursus pulvinar iaculis. Pellentesque nec neque nec leo interdum sodales in nec odio. Pellentesque condimentum, ante at dictum ullamcorper, tortor risus ultricies tortor, a hendrerit lorem odio ac diam. Nullam posuere luctus orci. Sed odio erat, aliquam non tincidunt vel, aliquam at nisi. Sed vel sem vulputate, vestibulum est non, volutpat ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vel maximus sem. Mauris eu odio lorem.</p>
|
||||
<p>Nunc iaculis nulla neque, a semper arcu mattis eget. Donec commodo quam diam, sed mollis justo semper vitae. Ut tincidunt facilisis tellus, ut faucibus sapien facilisis ut. Pellentesque in turpis finibus velit aliquam blandit sed eu elit. Nam ut molestie nisi. Quisque in sem quis erat ultrices ultricies mattis eu tellus. Aenean eu turpis egestas, tempus arcu quis, tincidunt quam. Proin ullamcorper risus at porttitor tincidunt. Sed quis elementum orci, non varius risus. Maecenas pretium facilisis ullamcorper. Nunc arcu nisl, pharetra non urna vitae, tristique laoreet elit.</p>
|
||||
<p>Aliquam ac tempor lectus. Curabitur vitae eleifend nunc. Nulla facilisi. In turpis sem, dictum sed mauris ut, egestas consectetur dui. Proin ut orci auctor ex condimentum aliquet sit amet imperdiet odio. Vestibulum ac auctor sapien, at convallis enim. Donec at aliquet ex. Suspendisse nunc nulla, placerat ut dapibus eget, tempor non lectus.</p>
|
||||
<p>Aliquam ac quam rutrum, bibendum lectus vitae, malesuada sem. Ut dapibus vehicula massa vel facilisis. Proin pharetra eros nibh, sit amet ornare mauris lacinia id. Quisque eget ligula porttitor, porttitor ex eget, dignissim eros. Curabitur vestibulum, risus eget mattis laoreet, dui velit dignissim odio, id luctus neque lectus quis ligula. Nam et venenatis nunc. In porta sagittis libero vel laoreet. Sed id nisi nisi. Nullam eget congue turpis. Integer nec odio ut ligula malesuada iaculis ac vitae lorem. Ut fermentum viverra maximus. Integer eget venenatis erat, non porttitor elit.</p>
|
||||
<p>Morbi quis lectus sapien. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur sed dolor eu erat accumsan elementum. Suspendisse finibus commodo est eu facilisis. Fusce varius dui ac pulvinar consequat. Praesent eleifend tempus cursus. Cras convallis efficitur diam quis tempus. Nulla luctus, libero et pellentesque luctus, ex turpis accumsan magna, sed consequat nulla nulla a purus.</p>
|
||||
<p>Etiam vel mattis nulla. In lobortis eleifend facilisis. Morbi cursus, mi eget pretium eleifend, ante dui commodo erat, rhoncus iaculis metus enim at tortor. Fusce viverra diam sit amet maximus venenatis. Donec blandit felis non iaculis hendrerit. Proin et dui laoreet est blandit ultrices. Sed non ullamcorper risus. Nunc fringilla dolor ac nibh blandit gravida. Duis semper sagittis est auctor semper. Phasellus ut justo ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Curabitur vestibulum bibendum velit nec consectetur. Nullam quis nulla sagittis leo maximus imperdiet. Fusce eleifend vitae justo gravida faucibus. Vivamus luctus quis lorem vel rhoncus. Integer sit amet orci sit amet lectus vestibulum tincidunt non vitae quam.</p>
|
||||
<p>Pellentesque lobortis elit ut ante tempus, ultricies fermentum neque sodales. Sed eget porta ipsum. Nullam nibh dolor, auctor eu imperdiet nec, pulvinar sed justo. In vel pulvinar ex. Quisque quis feugiat nulla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Maecenas mattis lorem eget pulvinar aliquet. Nunc pretium sapien id risus mattis tristique.</p>
|
||||
<p>Praesent sodales mollis rutrum. Pellentesque malesuada ornare cursus. In hac habitasse platea dictumst. Nullam elementum imperdiet feugiat. Sed at erat pellentesque, elementum nisi nec, dignissim tortor. Aliquam eleifend ligula nec orci commodo sagittis. Sed suscipit elit pulvinar ex hendrerit scelerisque. Nam non diam non tellus gravida tristique. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vestibulum aliquet ligula sed velit ultrices, quis posuere augue dignissim. Praesent nec mi sapien.</p>
|
||||
<p>Cras sit amet feugiat sapien. Sed ac laoreet arcu, non venenatis mauris. Integer nec tempus justo, non vehicula ante. Cras sed mi ipsum. Nullam aliquam imperdiet ante, sed fringilla ex placerat a. Vivamus leo massa, venenatis non nulla a, gravida pretium quam. Curabitur eget massa rhoncus, viverra elit commodo, elementum arcu. Aenean sit amet purus at libero pretium auctor vel tempus lorem. Suspendisse at felis mauris. Duis eros nulla, volutpat at urna non, blandit imperdiet velit. Integer eros neque, viverra sed sagittis eu, sagittis at urna.</p>
|
||||
<p>Aliquam nibh ligula, posuere ut malesuada in, hendrerit at dolor. Sed pharetra neque id ex ultrices, at rutrum mi facilisis. Sed ultricies varius lectus in feugiat. Sed venenatis sed leo in sollicitudin. Nam aliquet nisl ut dui egestas rhoncus. Mauris ac nisl a purus eleifend ornare ac sed turpis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras nec vulputate urna, non interdum leo. Nam viverra ac lacus at tempus. In hac habitasse platea dictumst. Vestibulum lectus nunc, imperdiet ac est venenatis, tincidunt pellentesque eros. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed leo velit, interdum ac mi condimentum, dictum imperdiet massa. Proin pharetra dui vel facilisis tincidunt. Proin ipsum ipsum, bibendum eu mattis at, volutpat id massa. Maecenas at fermentum elit, eu tincidunt ex.</p>
|
||||
<p>Cras quam tellus, molestie lobortis nisl et, pellentesque ultricies odio. Nunc vel dictum risus, non posuere libero. Nulla venenatis nulla massa, nec euismod erat scelerisque at. Praesent sit amet semper ligula, eget tristique ante. Curabitur sed placerat nisi, non ultrices augue. Donec vehicula tempor diam, ac pharetra nibh ultricies nec. Vestibulum ligula turpis, volutpat a ultricies quis, dapibus et enim. Pellentesque scelerisque commodo ipsum, vel sollicitudin nibh laoreet non.</p>
|
||||
<p>Vivamus tortor nulla, feugiat sed diam pharetra, semper maximus velit. Nullam nibh nisl, tincidunt tempus tellus sit amet, iaculis sollicitudin erat. Vestibulum ut sodales nunc. Aliquam nec sem et diam maximus congue. Mauris vitae convallis felis. Cras volutpat interdum lorem, a pellentesque ligula condimentum ac. Aliquam iaculis pellentesque leo a congue. Nulla pretium nisl id eros consectetur, quis ornare mi accumsan.</p>
|
||||
<p>Vivamus vel quam tempus, vehicula ex sed, semper diam. Mauris in eleifend nibh. Fusce arcu augue, condimentum non nulla at, auctor accumsan mi. Ut ut justo laoreet, varius mauris sed, luctus mi. Mauris aliquet quis turpis id tristique. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In dictum justo ut diam lacinia, sed commodo massa rutrum. Nulla consequat sem nunc, ut iaculis dui accumsan eu. Praesent eget est nulla. Phasellus posuere, massa id sagittis rhoncus, justo erat blandit enim, ac lacinia magna turpis vehicula quam.</p>
|
||||
<p>Nunc fringilla est et sem efficitur maximus. Nam laoreet sit amet nunc sed venenatis. Nullam nisi odio, ullamcorper ut orci quis, bibendum venenatis nunc. Maecenas non scelerisque odio. Quisque quis vulputate velit, vel euismod nibh. In sem arcu, commodo sit amet massa ac, tempus fringilla eros. Curabitur dignissim orci posuere, rhoncus nunc ac, dictum nisl. Suspendisse quis lectus nec felis vehicula interdum. Donec elementum ac ante nec porta. Vivamus in ante vel odio sagittis viverra. Nam non ligula feugiat, eleifend nibh et, dignissim tortor. Ut id felis facilisis, accumsan arcu eget, lacinia elit. Quisque semper volutpat est, eget ultricies turpis hendrerit sed. Praesent eget accumsan ligula.</p>
|
||||
<p>Phasellus non justo eleifend, iaculis magna vel, tincidunt enim. Phasellus rhoncus ipsum non nisi dictum, sed tincidunt ex iaculis. Aenean ut ornare ante. Maecenas consectetur condimentum justo sit amet mattis. Nullam finibus erat et interdum dignissim. Phasellus finibus euismod felis, at consectetur odio faucibus sit amet. Vivamus rutrum bibendum nisl at rutrum. Sed consectetur cursus suscipit. Ut faucibus tristique faucibus. Vestibulum vitae nulla eget sem venenatis pretium at quis ex.</p>
|
||||
<p>Aenean eget viverra lorem, vel ornare leo. Aliquam condimentum tortor id ante ullamcorper rhoncus id sed eros. Phasellus odio dui, tincidunt ac dictum id, venenatis eu arcu. Nunc vehicula pellentesque magna nec iaculis. Aenean ullamcorper velit nec lorem laoreet lacinia. Cras maximus dolor nec nulla consequat, sed accumsan lectus egestas. Proin nulla risus, porttitor at eleifend eget, consequat non odio. Integer dictum neque sit amet congue auctor.</p>
|
||||
<p>Sed ut tortor molestie, volutpat eros eget, blandit felis. Etiam at accumsan felis. Proin condimentum iaculis justo, et aliquet nulla tincidunt eget. Vestibulum sollicitudin tellus tristique urna dignissim auctor. Vivamus malesuada augue id lobortis posuere. Fusce semper vitae sapien id dictum. Pellentesque vitae ligula et purus pellentesque lacinia.</p>
|
||||
<p>Quisque tempor, lectus non faucibus feugiat, arcu libero porttitor urna, a volutpat sem nibh at diam. Donec sit amet sollicitudin purus. Vestibulum in blandit dolor. Nunc ultrices quis elit eget eleifend. Nulla finibus quis sem vitae tempor. Aenean eu mi blandit velit rhoncus ullamcorper. Nullam non risus iaculis, venenatis erat facilisis, laoreet leo. Nulla in dui et dolor gravida condimentum id id mauris. Nulla eu suscipit mi, quis ultricies nisi. Praesent luctus, dolor sit amet ornare pellentesque, turpis est feugiat massa, id pharetra libero enim nec urna. Morbi odio mi, elementum quis molestie eget, suscipit vel leo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Morbi feugiat pellentesque justo quis gravida.</p>
|
||||
<p>Vestibulum auctor nibh vitae laoreet sagittis. In tristique efficitur magna congue pulvinar. Nullam sed urna eget odio mollis scelerisque. Cras et tortor a lectus feugiat semper a eu est. Ut condimentum libero a erat ultrices commodo. Maecenas in semper nulla, a malesuada tellus. Suspendisse viverra pulvinar arcu porta malesuada. Nullam auctor nunc velit, sed tempus est efficitur quis. Sed viverra mi dolor, in efficitur ipsum egestas viverra. Fusce congue libero tortor, id cursus diam dapibus in. Ut eleifend blandit neque sed sodales.</p>
|
||||
<p>Mauris quis faucibus dolor. Duis tristique dolor vitae libero bibendum, nec sagittis lorem sagittis. In eleifend accumsan nisl et convallis. Nulla ut tincidunt nisl. Morbi fermentum, odio vitae lacinia semper, diam leo interdum mauris, sit amet suscipit tortor orci a nibh. Etiam pretium venenatis metus, vitae tristique quam commodo in. Praesent elementum mauris nec lectus venenatis condimentum. Nullam porttitor faucibus purus. Mauris laoreet sit amet neque nec luctus.</p>
|
||||
<p>Maecenas viverra auctor arcu ac feugiat. Nunc a tincidunt ipsum, at scelerisque ipsum. Vestibulum nulla tellus, rutrum ut quam sit amet, pulvinar bibendum urna. Vivamus et mollis est. Maecenas semper leo vel nisi lobortis aliquam. Integer non ultrices ex, sed tincidunt est. Duis velit sem, finibus iaculis consectetur et, laoreet sagittis ligula. Suspendisse potenti. Nulla cursus cursus est quis vulputate. Pellentesque ante risus, fringilla et nibh sagittis, iaculis condimentum dolor.</p>
|
||||
<p>Donec fermentum tellus tempor nibh vehicula, sit amet venenatis ipsum tempus. Donec sed luctus metus, facilisis maximus lectus. Mauris tristique rutrum gravida. Etiam erat diam, bibendum eu tempus eget, tristique ut enim. Cras sit amet bibendum sem, ac hendrerit odio. Ut vel diam malesuada libero volutpat malesuada. Proin sit amet ligula ut massa venenatis dapibus. Praesent ac risus et sapien sollicitudin semper feugiat quis tortor. Etiam eget lacus eu ligula suscipit pellentesque. Suspendisse fermentum metus at pellentesque hendrerit. Aenean eget eros non metus rutrum sollicitudin et at tortor. Cras sollicitudin dolor risus, et sollicitudin tortor dictum vel. Nam a hendrerit ex.</p>
|
||||
<p>Curabitur at vestibulum diam, nec vehicula enim. Sed aliquet gravida suscipit. Duis non odio at odio elementum tempus in sit amet quam. Interdum et malesuada fames ac ante ipsum primis in faucibus. Suspendisse dignissim scelerisque risus, condimentum fringilla justo laoreet ac. Nullam scelerisque magna ac mi ornare tempus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean in ante nec elit condimentum interdum. Praesent rhoncus facilisis accumsan. Vivamus vestibulum enim a laoreet vestibulum. Curabitur nibh sapien, elementum efficitur tempor quis, egestas rutrum est. Suspendisse potenti. Mauris lectus sem, rhoncus nec malesuada in, sollicitudin sollicitudin ligula. Nulla at nibh nec arcu luctus scelerisque. Praesent consectetur magna sapien, nec ullamcorper mi malesuada in. Donec sapien dolor, lobortis et risus a, convallis blandit urna.</p>
|
||||
<p>Proin eget faucibus massa, quis ullamcorper dui. Cras varius faucibus imperdiet. Vestibulum ultricies urna vitae sapien hendrerit semper. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nullam non sollicitudin enim, in consectetur sem. Vestibulum suscipit eleifend auctor. Aliquam sed purus in dolor sollicitudin fringilla at non dolor.</p>
|
||||
<p>In ultrices tempus nulla nec venenatis. Phasellus ultrices id turpis a porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean vehicula lacus in pretium imperdiet. In sollicitudin, elit sit amet pulvinar tempor, augue tortor cursus tellus, vitae gravida metus magna at tortor. Aliquam erat volutpat. Etiam vitae sapien malesuada, facilisis erat et, placerat turpis. Suspendisse posuere mi vel magna iaculis, sit amet tristique leo euismod. Ut sem est, mollis eget massa sit amet, dapibus commodo tortor. Mauris pharetra feugiat turpis, non sodales mauris. Nam feugiat fermentum magna.</p>
|
||||
<p>Vivamus aliquet interdum velit. Suspendisse tempor, sem non tincidunt bibendum, quam massa lobortis odio, eu aliquam quam nunc id odio. Pellentesque vitae mi ut nulla semper lacinia. Aenean non nunc molestie, euismod lacus eget, vehicula lorem. Aliquam quis libero laoreet, mattis enim sed, dictum justo. Cras placerat ultricies accumsan. Fusce eu semper enim, id dignissim leo. Sed porttitor pharetra lectus sit amet tristique. Donec convallis rhoncus nisl quis porta. Integer sodales turpis eleifend, aliquet elit non, consectetur nisi. Cras eu commodo mi. Ut porta diam neque, id tincidunt justo consequat ut. Suspendisse potenti. Proin in pretium tortor, non varius nulla. Donec non nulla turpis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p>
|
||||
<p>Fusce non bibendum turpis. In elit arcu, malesuada ornare facilisis sit amet, laoreet quis sem. Nunc maximus elit vel consequat feugiat. Nulla tempus in sapien ut lacinia. Quisque elit dolor, vulputate vel convallis sit amet, accumsan id orci. Suspendisse ex nisi, faucibus eu vulputate non, consectetur et odio. Maecenas condimentum libero ac felis aliquet tempor cursus sit amet metus. Suspendisse mollis, felis eu finibus ultricies, metus libero facilisis odio, a tempus odio mauris vel nisl. Phasellus aliquam finibus enim non porttitor. Phasellus auctor justo in fringilla volutpat.</p>
|
||||
<p>Mauris vehicula posuere sagittis. In auctor urna viverra massa fringilla, vel maximus elit rutrum. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla suscipit neque ligula, auctor tincidunt magna convallis vel. Donec erat est, dictum non justo nec, volutpat condimentum mi. Curabitur condimentum dapibus erat eget vestibulum. Nullam velit massa, mollis ac hendrerit sit amet, aliquet id lacus. Curabitur ligula odio, condimentum malesuada mattis et, bibendum non leo. Donec rhoncus diam id quam aliquet ultrices. Integer posuere rutrum eros, sit amet semper eros tincidunt ac. Mauris hendrerit sapien magna, ut pulvinar massa fermentum et. Nam a auctor erat, in congue tortor. Proin in orci fermentum quam posuere cursus ac vel leo. Curabitur non egestas velit, at porta lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>
|
||||
<p>Sed lacinia vitae dui luctus condimentum. Duis ut semper nisi. Sed consequat elementum lacus vitae tincidunt. Etiam ac tortor nec sapien aliquam ultricies. Curabitur viverra diam quis porta fermentum. Sed dignissim, est ut posuere accumsan, mauris elit malesuada lorem, eu molestie felis metus et nulla. Cras molestie erat mollis enim ullamcorper, non volutpat justo finibus. Suspendisse rhoncus eu turpis vitae aliquam. Sed mi nisl, ullamcorper eu diam eget, semper iaculis leo. Cras lobortis vestibulum ornare. Vivamus consequat magna ut luctus blandit. Phasellus placerat tincidunt eleifend. Etiam diam leo, rhoncus ac sem eu, efficitur porta magna. Mauris sit amet mi sit amet nunc consectetur congue quis eu arcu. Donec dignissim erat vitae ipsum dignissim, sit amet pretium lacus dictum. Mauris cursus a eros in convallis.</p>
|
||||
<p>Vestibulum sit amet ligula erat. Praesent nec sapien ac leo facilisis suscipit. Aliquam erat volutpat. Integer mi est, pulvinar vel nibh at, ullamcorper tristique erat. Donec accumsan semper ex eu consequat. Phasellus vulputate posuere efficitur. Praesent fermentum rutrum magna at interdum. Morbi scelerisque quam sed mauris dapibus consequat. Nullam vehicula dictum rutrum. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce a rutrum ligula. Ut quis nunc id metus ullamcorper porta eget tempor velit. Duis hendrerit tellus mi, nec malesuada nunc consequat et.</p>
|
||||
<p>Nam erat ligula, pharetra ut euismod in, efficitur quis quam. Sed lorem ex, accumsan nec elementum non, porta in velit. Nunc lobortis mattis urna at malesuada. Nulla aliquam, sem ac commodo convallis, lacus nulla condimentum odio, at volutpat erat sapien at erat. Fusce suscipit eleifend leo elementum vestibulum. Duis vel sapien commodo, sollicitudin sem eu, vestibulum turpis. Sed a tellus eget justo ornare ornare sed ut ipsum.</p>
|
||||
<p>Duis a blandit ligula, vel vestibulum dui. Cras sed mauris at nisi ornare posuere id vel ligula. Sed sem mi, tempus a volutpat sed, suscipit ut arcu. Nulla in pretium enim. Mauris sed massa semper, porttitor nisi porta, convallis quam. Vestibulum sollicitudin massa ac nisl scelerisque ultricies. Ut sit amet mi eget nulla pharetra convallis eget ut dui. Nunc ultricies ultrices enim, in faucibus lorem. Donec pharetra tellus id bibendum auctor. Etiam vitae quam interdum dolor venenatis feugiat. Integer turpis mauris, ultricies nec vehicula id, tempor consequat tellus.</p>
|
||||
<p>In in consequat sapien. Nullam congue orci velit, eget vestibulum enim ullamcorper ac. Pellentesque maximus, diam id posuere fringilla, ipsum purus venenatis turpis, nec egestas erat neque ut felis. Sed porttitor feugiat sem, vel volutpat sem laoreet maximus. Aliquam quis mauris ut tortor aliquam condimentum. Cras a urna aliquam, hendrerit nisi tincidunt, condimentum nisi. Nulla facilisi. Proin suscipit augue at tempor finibus. Duis ultricies lacus ac metus vulputate, vel convallis nulla efficitur. Ut metus sapien, finibus a ante vitae, pulvinar porta ante. Quisque iaculis est in sem blandit, eu lacinia mauris pharetra. Integer sagittis nisi sed dui tempor, sit amet luctus justo faucibus. Phasellus eu eros vel erat porttitor aliquet in quis mauris. Quisque viverra nulla nibh. Aliquam eget mauris tempor, faucibus turpis non, ullamcorper nunc. Aliquam laoreet risus eros, ut consectetur sapien pulvinar at.</p>
|
||||
<p>Morbi rhoncus rhoncus imperdiet. Aenean sed neque ullamcorper, dictum ex id, ultricies purus. Sed et augue nec velit varius consectetur. Fusce et malesuada velit. Sed luctus lacus sed turpis convallis blandit. Duis sed eleifend nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque ex neque, vulputate eget efficitur et, sagittis sed leo. In rutrum velit efficitur velit finibus varius. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In vel scelerisque lorem, sed ornare velit. Ut vel pharetra leo. Vivamus risus felis, imperdiet sed porta et, euismod sed quam. Quisque et massa est. Aenean dapibus suscipit ultricies.</p>
|
||||
<p>Vivamus fermentum vehicula orci ac vulputate. Curabitur vitae leo tincidunt, vestibulum leo vitae, laoreet libero. Donec euismod tincidunt molestie. Donec vulputate velit ut augue bibendum sodales. Duis ut vestibulum lorem. Cras quis faucibus arcu. Proin egestas tempor lorem vitae vestibulum. Integer eget fringilla arcu. Pellentesque libero ipsum, sollicitudin mollis metus nec, hendrerit luctus arcu. Proin tempus purus in leo aliquet, a porta arcu hendrerit. Praesent ultricies luctus nisl. Vivamus condimentum nisl vel porta commodo. Donec posuere enim ut tincidunt gravida. Suspendisse potenti.</p>
|
||||
<p>Proin in imperdiet neque. In rhoncus augue id dui gravida, a maximus ante tincidunt. Praesent scelerisque augue sed nisi pellentesque ornare. Maecenas eget tempus nisl. In pulvinar velit vulputate pulvinar viverra. Donec dignissim, libero in blandit vestibulum, libero augue accumsan ipsum, quis porta massa turpis eget purus. Vivamus tincidunt non magna non auctor. Suspendisse efficitur sapien ac neque dictum dapibus.</p>
|
||||
<p>Sed sagittis libero vel iaculis sagittis. Suspendisse viverra vulputate ligula vel imperdiet. Phasellus tincidunt semper augue at viverra. Donec nisi mi, placerat quis diam ut, porta tempus arcu. Nullam placerat ligula et luctus eleifend. Etiam erat mauris, finibus placerat turpis at, eleifend pellentesque dolor. Quisque a consectetur eros, vel tincidunt erat.</p>
|
||||
<p>Aliquam sit amet congue mauris, et interdum eros. Sed in nisl eu nisl fringilla vestibulum. Nullam rhoncus malesuada nisi nec dapibus. Nam tristique enim eros, a tincidunt justo semper egestas. Mauris tempus elementum purus id pellentesque. Quisque dictum eget justo ut feugiat. Morbi in nulla at leo vehicula venenatis at non dui. Phasellus volutpat accumsan magna, at vulputate elit imperdiet vel.</p>
|
||||
<p>Cras tempus est at nunc tincidunt, sagittis sagittis sapien sollicitudin. Curabitur ipsum nisl, lacinia quis purus vitae, vestibulum dictum metus. Sed posuere neque elit. Sed at auctor dui. Phasellus eget faucibus nisl. Phasellus id erat ut ex semper fermentum. Maecenas rhoncus ligula lectus, quis ultricies diam ultricies nec. Vivamus id odio non nisl luctus ultricies non vel nibh. Ut quis dolor fringilla, finibus dolor id, interdum nisl. Vivamus vitae neque eu ligula viverra mattis non in augue. Donec scelerisque condimentum nulla a finibus. Nulla sollicitudin purus enim, a consectetur nisi tempor ut.</p>
|
||||
<p>Morbi lacus purus, lobortis sed dapibus quis, finibus at elit. Nunc ultricies ante et convallis facilisis. Nulla sit amet ullamcorper eros, ac condimentum nisi. Quisque eget ullamcorper tortor, et placerat mi. Duis lacinia sapien sed fermentum tempus. Donec quis diam tincidunt, hendrerit sapien vitae, luctus nulla. Nam sit amet quam dolor. Donec euismod venenatis ipsum et vehicula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Etiam suscipit, odio ac interdum maximus, velit urna accumsan justo, eu rutrum tortor lacus tincidunt nisl.</p>
|
||||
<p>Suspendisse aliquet molestie augue, quis scelerisque risus mattis ac. Praesent quis varius tortor. Sed feugiat erat et odio iaculis congue. Curabitur cursus, neque vel molestie tempor, diam enim mattis ipsum, egestas efficitur purus lacus ut neque. Aenean lobortis nulla est, sed dictum urna facilisis quis. Vivamus venenatis mollis accumsan. Integer vulputate tristique venenatis. Etiam lacinia augue turpis, at mattis mauris finibus eu. Phasellus convallis elit ante, in pharetra lectus dapibus in. Etiam scelerisque id dolor ac feugiat. Aliquam volutpat dolor et nisl feugiat pellentesque.</p>
|
||||
<p>Phasellus nec turpis vehicula, tincidunt tortor in, vehicula ligula. Mauris pharetra tempor neque, quis semper nunc gravida a. Donec est massa, pellentesque eget semper a, congue vitae erat. Donec scelerisque enim justo, quis iaculis enim rhoncus in. Suspendisse at sem nisi. Nam rutrum, sapien nec ullamcorper imperdiet, magna lectus aliquam neque, tristique sollicitudin elit sapien vitae mauris. Aenean ac nulla eget sem rhoncus blandit. Quisque fringilla ante vel tellus scelerisque porttitor. Vestibulum blandit ligula ut vulputate dignissim. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce vel leo libero. Proin ut pretium neque, in eleifend felis. Quisque dignissim, ante ut ultrices maximus, augue arcu congue metus, non sagittis risus elit vitae eros.</p>
|
||||
<p>Nullam consequat velit enim, nec commodo nibh varius vel. Aliquam diam sapien, egestas in dictum in, imperdiet vel erat. Maecenas ut purus a libero facilisis malesuada. Nunc lacinia non urna vitae cursus. Donec tristique, ligula nec pharetra sodales, erat velit pulvinar tellus, ut pulvinar massa metus quis magna. Vivamus aliquam sed nisl vel faucibus. Proin finibus ante in vulputate imperdiet. Phasellus eget ipsum a velit lobortis cursus non non augue. Integer tincidunt dignissim urna, et mattis sapien faucibus eget. Curabitur dapibus dapibus lorem vel scelerisque. Proin sed dictum sapien. Donec quis est vel tortor pulvinar tincidunt sit amet et enim. Vivamus urna erat, venenatis sed blandit scelerisque, ultricies non tellus. Vivamus vitae ligula euismod lacus accumsan viverra. In mollis consectetur dapibus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>
|
||||
<p>Aenean maximus dictum tortor, ac semper felis efficitur et. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec semper erat neque, sed cursus tellus semper non. Integer pellentesque massa leo. Praesent consequat malesuada nulla, sit amet faucibus velit molestie vel. Suspendisse vel libero viverra, sagittis orci vulputate, volutpat turpis. Sed nec ipsum tincidunt, mattis nulla ac, lacinia dui. Sed non elit quis ex rhoncus pharetra id in odio. Aliquam porttitor, turpis nec ultricies egestas, nulla libero iaculis massa, facilisis elementum magna velit ac erat. Nulla fringilla nulla et leo sodales vulputate. Integer consequat metus vel nunc laoreet, ac suscipit ex lobortis. Suspendisse lacus turpis, ultricies non tempor id, faucibus ac erat. Quisque ullamcorper venenatis metus, eu lacinia metus accumsan non. Aenean facilisis quis neque vitae aliquam. Nulla nec tortor facilisis, sodales turpis at, ultrices dolor.</p>
|
||||
<p>Maecenas in cursus ex. Curabitur sed diam vitae dolor varius vestibulum eu id diam. Suspendisse sed egestas elit. Cras feugiat tortor vel tempus tincidunt. Pellentesque rutrum, nisi venenatis viverra pulvinar, lectus nisl tristique quam, scelerisque ultricies sapien nibh vitae elit. Mauris a interdum tellus, eget fermentum mi. Aenean eget elit tempus, mollis dui vitae, elementum justo.</p>
|
||||
<p>Fusce a arcu velit. Aliquam at arcu ut turpis tristique ultricies eget non lectus. Aenean nisl tortor, consectetur vitae lectus sit amet, eleifend dignissim felis. Curabitur in arcu magna. Aenean in vulputate nulla. Suspendisse id interdum erat, nec tristique augue. Fusce augue ligula, fermentum eu felis non, vestibulum consectetur mi. Morbi faucibus augue leo, vel viverra velit iaculis vel. Curabitur ornare eros sed ex varius semper. Pellentesque vel orci eu nisi vulputate imperdiet. Donec et nulla eget orci vulputate hendrerit in et ex.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras turpis leo, placerat vitae ultricies interdum, semper quis urna. In dolor est, placerat vestibulum velit eleifend, finibus viverra risus. Vestibulum eleifend massa vitae ultricies laoreet. Integer maximus dolor sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus metus magna, tincidunt in libero et, interdum tempus ligula. Curabitur nunc urna, sodales vel vestibulum quis, sodales non nunc. Quisque pharetra urna ultrices, accumsan augue eu, aliquet elit. Cras ut venenatis justo, eget sagittis risus. Duis vehicula enim non varius sollicitudin.</p>
|
||||
<p>Ut nulla sapien, rhoncus ac erat ac, dignissim fermentum nibh. Nunc varius turpis at augue interdum efficitur pellentesque a nisi. Integer in aliquam tellus, in condimentum libero. Aenean maximus dolor sit amet urna sodales, at facilisis augue tempor. Ut pretium nisi in dui pellentesque vulputate. Nulla nec elementum odio. Etiam finibus, massa in porta convallis, diam tellus efficitur mauris, ac luctus justo odio a ex. In varius lacus elit, ac dapibus nisl dictum non. Aenean a erat vitae erat porta congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla nec arcu sem. Sed pulvinar mi eu venenatis auctor. In aliquet, neque in mattis auctor, nunc nunc pellentesque leo, eget dapibus velit libero sit amet magna. Morbi vestibulum, odio vel elementum cursus, eros mi tempus nulla, id faucibus lorem eros vel eros. Aliquam erat volutpat. Pellentesque laoreet quis orci in porttitor.</p>
|
||||
<p>Nam vitae ultrices ex. Sed rutrum purus et enim pharetra, ut aliquam nunc accumsan. Ut eu aliquam nunc, ac elementum tellus. Vivamus porttitor mi augue, sed rutrum elit dictum in. Vestibulum porta sodales orci, non dictum lacus. Maecenas a est eu quam imperdiet tincidunt. Ut posuere, lorem sit amet hendrerit tempor, turpis ante sagittis ante, non bibendum ipsum diam sed tellus.</p>
|
||||
<p>Vivamus fringilla arcu sit amet sapien dapibus pulvinar. Sed tempus nec lacus in scelerisque. Suspendisse vel vestibulum mauris, condimentum porttitor lacus. Nunc finibus vel sapien eget lobortis. Praesent auctor, neque vel eleifend vestibulum, mi risus facilisis leo, vitae dictum ligula tellus quis erat. Quisque volutpat tempor sapien a commodo. Mauris vehicula, diam ac vulputate semper, justo tortor vestibulum nibh, nec interdum lacus lorem et massa. Donec eu ex urna. Praesent scelerisque feugiat nisl non consequat. Vivamus in pretium nibh. Vivamus fringilla purus at nisi varius, sit amet congue nisl pharetra. Aliquam erat volutpat.</p>
|
||||
<p>Nullam ultrices eros dolor, ut consequat ex eleifend at. Cras eleifend venenatis ligula in volutpat. Sed mollis felis a eleifend porttitor. Proin a diam sem. Etiam eu velit mi. Proin quis elit eget urna maximus gravida sed nec nunc. In blandit efficitur neque eget feugiat.</p>
|
||||
<p>Integer sit amet consequat sem. Curabitur suscipit arcu a ex tristique rhoncus. Ut id tincidunt augue. Suspendisse volutpat iaculis elit, ut fringilla mauris laoreet hendrerit. Aenean malesuada purus commodo leo maximus rutrum. Donec id sapien augue. Donec in aliquam nisl. Phasellus eget cursus augue. Pellentesque tincidunt lacus urna, varius dignissim dolor dignissim nec. Sed at laoreet dui. Ut varius lectus sit amet ligula euismod, ac eleifend felis efficitur. Proin a nunc sed eros dignissim mattis ut vitae libero.</p>
|
||||
<p>Maecenas et hendrerit mi. Nam id sodales leo. In et semper dolor. Morbi pellentesque, justo a condimentum aliquam, nunc purus mollis urna, ac viverra augue tellus sed urna. Mauris vestibulum rutrum erat, pharetra ultricies quam finibus sed. Mauris a laoreet ante, ut dictum augue. Duis dolor neque, consectetur eleifend elementum in, lacinia id sapien.</p>
|
||||
<p>Maecenas ullamcorper vehicula sapien at tristique. Mauris eget nisi a sapien laoreet blandit a eu velit. Curabitur pellentesque sollicitudin purus. Proin eu commodo ligula, a sagittis lorem. Suspendisse aliquet sollicitudin diam, ac placerat lectus. In sit amet urna eget turpis iaculis scelerisque nec ac lorem. Etiam at magna rhoncus, molestie tellus a, ornare sapien. Phasellus quis velit in turpis vulputate pretium in a mi.</p>
|
||||
<p>Morbi eget nunc at elit ultrices rutrum quis a dolor. Curabitur id quam vel elit rhoncus molestie vitae in purus. Proin vehicula magna a tellus imperdiet, non ultrices dolor tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed commodo ex ac tortor suscipit ultrices non vel mauris. Fusce a molestie felis, ut eleifend felis. Nunc odio urna, vulputate vitae dignissim ut, mattis accumsan lectus. Curabitur vel velit purus. Nunc volutpat, augue sed pretium euismod, magna sapien elementum purus, eget tincidunt leo purus eget leo. Integer iaculis arcu sit amet venenatis sagittis. Aenean vitae massa a mauris sodales dignissim.</p>
|
||||
<p>Curabitur tempor ornare sagittis. Quisque maximus, elit ac lacinia vehicula, mi elit pretium elit, in eleifend massa turpis non arcu. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras molestie finibus metus at porttitor. Pellentesque a facilisis justo. Vivamus molestie ante elit, id suscipit sapien rutrum eget. Maecenas tellus ligula, pretium quis nisi sed, finibus egestas nisl. Sed non metus maximus, blandit ex eget, aliquet tellus. Sed a blandit purus, ut aliquet erat. Fusce tincidunt tortor sapien, quis porta ante venenatis quis. In varius, purus id malesuada finibus, risus lorem lobortis orci, vitae congue lectus ante ut nunc. Mauris hendrerit mollis condimentum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus non urna id sapien finibus mattis. Integer vitae eros dolor.</p>
|
||||
<p>Phasellus scelerisque mi non ultricies sagittis. Proin in fringilla mauris. Duis quis porta tortor, at fringilla nisl. Sed at varius urna. Donec vitae luctus lectus. Suspendisse blandit massa ut porta convallis. Mauris lobortis lobortis sem, et congue elit consectetur et. Ut posuere eu urna id laoreet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec at sapien vel ante semper rutrum vitae nec justo. Vivamus pulvinar magna a leo hendrerit, vitae aliquam magna vulputate. Suspendisse potenti.</p>
|
||||
<p>Suspendisse quis sapien quam. Nam fermentum est orci, at maximus magna scelerisque vel. Quisque interdum eleifend interdum. Donec malesuada nulla arcu, ac dapibus urna lobortis vel. Vestibulum nisl nulla, gravida in turpis ut, hendrerit sollicitudin nibh. Ut metus lectus, mattis quis volutpat quis, fringilla egestas enim. Integer lobortis, ante vestibulum condimentum ullamcorper, dolor justo dignissim nunc, vel lacinia massa augue sit amet metus. Etiam non molestie sem. Nullam sodales aliquet mattis. Donec sit amet dui at leo feugiat ornare. Donec mi odio, volutpat vitae risus porta, semper egestas arcu.</p>
|
||||
<p>Quisque imperdiet scelerisque elit. In non accumsan tortor, sed lacinia felis. Ut et molestie neque, at tristique urna. Fusce malesuada lorem eget sem finibus, auctor eleifend tortor finibus. Quisque sed luctus turpis. Integer ullamcorper ornare dolor, id vulputate augue consectetur ac. Donec dapibus euismod iaculis. Quisque ac mollis turpis.</p>
|
||||
<p>Phasellus imperdiet ultrices turpis, in rhoncus nulla tempor vel. Aliquam posuere pulvinar nulla sit amet consectetur. Morbi nulla erat, cursus a vulputate sit amet, placerat eu dolor. Cras sagittis nibh auctor nunc sagittis pellentesque. Donec et dolor non ligula lacinia sollicitudin. Cras ac augue purus. Sed nisl elit, porta a congue in, finibus eget justo. Duis commodo imperdiet justo. Pellentesque sit amet elit ut orci rutrum faucibus. Vestibulum vitae lacus porta, feugiat metus efficitur, dictum augue.</p>
|
||||
<p>Phasellus nec lacus purus. In feugiat erat congue leo bibendum, vel maximus enim sagittis. Fusce id imperdiet quam. Cras blandit, tortor et rhoncus feugiat, felis mauris dictum ligula, facilisis suscipit dolor justo eu tellus. Sed volutpat massa ac lectus sodales interdum. Integer sit amet sem ut tortor feugiat finibus nec et ligula. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean pretium hendrerit nisi, vitae eleifend elit varius sed. Nam nunc dolor, dapibus a dictum non, posuere eu nunc. Vestibulum fermentum pharetra nibh. Quisque tempus odio ac felis bibendum, sit amet pharetra ex egestas. Pellentesque facilisis risus sit amet lorem feugiat auctor.</p>
|
||||
<p>Donec quis nisi eu erat fringilla sodales in ut risus. Proin diam elit, commodo lacinia odio vel, efficitur mattis justo. Fusce vitae enim tortor. In eget dolor at felis congue imperdiet. Integer eu ex non urna porttitor imperdiet. Pellentesque ante diam, vestibulum ut ex ut, lacinia rutrum velit. Integer ultrices mattis velit quis malesuada. Mauris venenatis nisi erat, id facilisis ligula aliquam et. Suspendisse rhoncus, odio sit amet sagittis fringilla, nibh felis tincidunt justo, nec placerat ipsum urna vel ex. Suspendisse arcu ipsum, finibus ac odio nec, dapibus sollicitudin tortor. Praesent interdum condimentum faucibus. Proin rutrum mi sed ante dictum viverra. Maecenas egestas, quam et venenatis porta, dolor lectus elementum sem, at ornare magna mauris sed mi. Aenean euismod aliquam neque, id sagittis sapien rhoncus vel.</p>
|
||||
<p>Vivamus eget felis eget dolor euismod suscipit. Fusce maximus sagittis ipsum, non sodales enim facilisis in. Praesent pulvinar lectus quis nisl congue, vitae egestas libero elementum. Phasellus consectetur nec risus sed ornare. Curabitur maximus placerat dolor. Pellentesque quis tempor sapien. Integer feugiat pretium nulla at vulputate. Mauris imperdiet nisl eget lectus fermentum accumsan. Integer iaculis iaculis libero, non tincidunt ipsum vehicula quis. Phasellus tellus justo, dignissim et est a, sodales sagittis libero. In scelerisque orci nec facilisis bibendum. Morbi sodales, lorem sed dapibus vulputate, magna justo mollis justo, id gravida turpis nibh non diam. Aenean vulputate justo vel neque molestie fringilla. Donec pellentesque, sapien suscipit feugiat aliquam, augue ligula gravida metus, sed bibendum nunc sem ac lorem. Suspendisse vitae accumsan ipsum. Curabitur aliquam leo ex.</p>
|
||||
<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed tristique felis enim, in auctor leo semper id. Aliquam a porta dui, ut bibendum dolor. Etiam tortor nisi, efficitur sit amet suscipit non, convallis tristique nisi. Ut nec justo pellentesque ex rhoncus laoreet. Nam posuere orci eget ipsum sollicitudin accumsan. Quisque in dolor nec eros cursus consectetur id vel ipsum. Nam convallis, ante vel vulputate bibendum, elit ligula laoreet ipsum, id venenatis est nisl eget purus. Nunc id luctus metus.</p>
|
||||
<p>Sed faucibus urna felis, sit amet posuere urna porta at. Proin dictum tempus sapien, sit amet fermentum magna laoreet sit amet. Phasellus ac rhoncus elit. Suspendisse sed massa vel enim ultrices aliquam. Integer a lectus massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed laoreet orci sed dolor lobortis, vitae laoreet justo laoreet. Nam iaculis lacinia mauris et vestibulum. Quisque imperdiet dolor convallis interdum varius. Donec eget neque dolor. Quisque consequat ultricies nulla, a tincidunt nunc dignissim et. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In augue eros, maximus non neque id, fermentum accumsan orci.</p>
|
||||
<p>Quisque ut nisi id erat sollicitudin tristique volutpat et diam. Pellentesque euismod ex ac lectus pulvinar, nec blandit ipsum elementum. Quisque commodo, turpis et fringilla pretium, ipsum turpis bibendum eros, sit amet tincidunt urna lectus at felis. Nullam ullamcorper arcu enim, eget bibendum augue vestibulum id. Nullam facilisis, diam non venenatis pharetra, urna justo convallis augue, et lobortis nisi odio et magna. Aliquam sodales lectus nec gravida imperdiet. Vestibulum quis diam eget nibh imperdiet luctus eleifend eget mauris.</p>
|
||||
<p>Donec varius dui a sodales commodo. Aliquam fermentum fringilla velit sit amet ullamcorper. Sed non leo pretium, pellentesque diam et, bibendum augue. Praesent quam ipsum, condimentum eget convallis a, ultrices at tellus. Nunc fermentum massa non dui semper interdum. Quisque feugiat consectetur tellus sed fermentum. Duis sem enim, gravida in erat eu, placerat tristique neque. Mauris libero sapien, ullamcorper id elit at, facilisis suscipit nunc. Integer lorem tellus, ultricies id iaculis fermentum, lacinia vitae metus. Pellentesque in lectus pretium arcu suscipit imperdiet. Vivamus id laoreet tortor. Curabitur augue nisi, iaculis ac faucibus eget, tempor quis eros.</p>
|
||||
<p>Suspendisse libero lacus, rutrum ut semper suscipit, facilisis eu ante. Nullam consequat tempor nisi, vitae placerat libero suscipit in. Nullam ut erat finibus elit venenatis porttitor condimentum sed ante. Donec sed euismod sapien. Cras maximus nulla ex, sit amet pretium erat sodales eu. Curabitur lectus felis, porta quis est id, hendrerit consequat eros. Curabitur auctor, est in sodales efficitur, justo diam sollicitudin lacus, nec ornare felis velit vitae magna. Cras ullamcorper pulvinar massa quis semper. Ut rhoncus turpis eu eros gravida dignissim. Quisque facilisis leo vitae dignissim feugiat. Aliquam sagittis, turpis non porta viverra, purus augue euismod ante, ultrices molestie ligula massa sed arcu. In neque tellus, dictum eget facilisis et, gravida in mi.</p>
|
||||
<p>In hac habitasse platea dictumst. Nam quis tellus nec est vulputate auctor at in elit. Phasellus quis efficitur lacus. Nunc eget tempus est. Aliquam eget nisi in quam molestie vestibulum. Nunc sollicitudin massa ut justo imperdiet auctor. Nullam venenatis elementum vulputate.</p>
|
||||
<p>Nulla sed pharetra turpis. Aenean rutrum sem quis nunc viverra ornare. Integer purus enim, ornare sit amet pulvinar vitae, egestas id massa. Praesent condimentum efficitur sagittis. Nullam bibendum nibh enim, eu semper sapien pellentesque quis. Sed lobortis, elit ac vulputate interdum, risus sem hendrerit sapien, ac pharetra dui sem id sem. Pellentesque elementum interdum elementum. Cras dapibus non velit ut vulputate. Nam auctor at tortor vel congue. Nulla vel nisi et augue pretium pretium. Nunc quis metus non mi posuere ultricies. Cras viverra felis id feugiat ornare. Sed sapien nulla, vulputate ac dignissim non, mattis eu lorem.</p>
|
||||
<p>Ut luctus volutpat euismod. Quisque libero felis, euismod ac metus vitae, sollicitudin finibus justo. Sed consequat turpis tempor, placerat diam et, elementum augue. Integer vel venenatis tellus. Donec ut dolor accumsan, aliquet libero sit amet, ornare nisi. Nam id cursus ante. Sed vitae posuere risus. Pellentesque fringilla lectus vel ante vestibulum porttitor. Duis maximus mi purus, vel ullamcorper tellus laoreet eget.</p>
|
||||
<p>Sed et ligula ut lectus euismod tempus quis sed sapien. Phasellus varius pellentesque malesuada. Maecenas sit amet lorem neque. Nam maximus nisi vitae erat feugiat, ac finibus sapien auctor. Quisque accumsan felis at fringilla molestie. Duis leo diam, tincidunt et ultrices at, elementum ut augue. Vestibulum lobortis lorem iaculis ipsum consequat, at condimentum lacus semper. Aliquam velit dui, ultrices vel tellus lacinia, semper aliquet eros. Pellentesque rhoncus dolor nec leo aliquam, vitae lobortis mauris tempor. Praesent lacinia lectus in nisl hendrerit laoreet. Proin efficitur dui diam, sed vulputate eros pharetra eu. Mauris id justo sit amet dui porttitor tincidunt. Donec vitae arcu at lacus ultrices porta. Suspendisse potenti. Nulla blandit hendrerit felis, et dignissim ex fermentum vel.</p>
|
||||
<p>Quisque aliquet, nisl ut ullamcorper hendrerit, magna ex consectetur nisi, sed mattis purus lacus eu neque. Etiam in lacus et dolor dignissim porta eget ac ligula. Nulla eu enim eget ante pretium porttitor. Nullam fringilla rhoncus ipsum id volutpat. Integer at quam quis purus varius finibus id vitae metus. Cras varius gravida eros nec egestas. Praesent pharetra, justo nec pulvinar tempus, erat ante tempus eros, vel fermentum metus ex vitae ante. Sed bibendum, lorem eu rhoncus maximus, lorem neque aliquam velit, at vestibulum diam risus at nibh. Maecenas sit amet egestas erat, eu molestie purus.</p>
|
||||
<p>Vivamus viverra, sapien sit amet vestibulum gravida, eros urna sodales odio, eu tincidunt quam diam ut mi. Morbi posuere nibh ex, in eleifend dolor elementum ut. Vestibulum eu odio eu dolor gravida consequat. Quisque in nisl ac ligula vulputate sodales. Suspendisse rutrum lacus sit amet tincidunt porttitor. Pellentesque ut quam quis nulla ullamcorper tincidunt. Suspendisse eget accumsan neque. Aenean dictum libero vel suscipit tristique. Vivamus dictum vel quam sed congue. Sed pulvinar ut mauris non molestie. Pellentesque fermentum massa in auctor ultricies. Praesent ac feugiat nulla, ut ultricies purus. Nulla mollis posuere sem et euismod.</p>
|
||||
<p>Nam pellentesque sapien fringilla ex efficitur venenatis. Integer vitae nulla tempus, egestas ante at, semper mauris. Ut at nisi eu ante tempus interdum id eget nunc. Cras venenatis facilisis dolor, sit amet mattis felis congue sit amet. Suspendisse potenti. Praesent risus lacus, imperdiet at magna eget, elementum condimentum nulla. Fusce at leo vitae dui porta feugiat. Nunc ut ornare lorem. Suspendisse eu elementum ante. Praesent efficitur ultrices tellus at fringilla.</p>
|
||||
<p>Integer id urna rhoncus, elementum tellus quis, dignissim quam. Nulla nec laoreet turpis. Nulla elit mauris, interdum in purus quis, hendrerit rhoncus nulla. Aliquam sit amet fringilla turpis, sed commodo nisl. Vestibulum pellentesque risus vitae lacus efficitur, a rutrum nulla volutpat. Donec sagittis at lectus vitae pharetra. Suspendisse potenti. Fusce eget fermentum quam. Sed eu pharetra nisi, vitae laoreet massa. Praesent eu pulvinar nisl. Vestibulum vitae faucibus nunc, nec fringilla nulla. Phasellus aliquam felis sed viverra egestas. Pellentesque augue urna, volutpat ut ex eget, feugiat tempus neque. Nulla sed purus lobortis, tincidunt nunc elementum, mattis eros. Curabitur a ipsum nisl.</p>
|
||||
<p>Etiam eget risus neque. Etiam sollicitudin congue est, vitae sodales orci dictum id. Ut in malesuada felis. Praesent id diam quis nunc dapibus lobortis. Sed eu ex mi. Praesent sed ornare nulla. Praesent elementum velit consectetur lectus mattis, nec placerat sapien hendrerit. Curabitur nec arcu iaculis ipsum cursus fermentum. Nam maximus neque ac arcu porttitor, ac rutrum ante blandit. Mauris ullamcorper vehicula quam eget iaculis.</p>
|
||||
<p>Maecenas posuere mi ante, vel finibus nisl aliquam luctus. Aenean in justo eu est maximus tempor eu vel sapien. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc arcu dolor, sagittis vel sapien eu, pharetra lobortis sem. Sed in pretium odio, et maximus augue. Nullam ultricies sapien vitae mauris congue fermentum. Cras et tristique lacus. Duis posuere, metus sit amet faucibus facilisis, felis massa aliquet diam, vitae dignissim ipsum velit ac elit. Suspendisse et auctor ligula, ut dignissim arcu. Sed scelerisque erat at pellentesque dictum. Etiam ac risus at odio venenatis dictum. Aenean commodo, enim ac rutrum blandit, metus dolor lacinia magna, vitae tempus mi diam eu erat. Sed at ornare lorem.</p>
|
||||
<p>Nulla varius efficitur nunc, quis posuere orci hendrerit ac. Nam vitae justo mollis, vestibulum nibh non, fringilla ante. Aenean porttitor nisl nec fermentum vestibulum. Ut pulvinar id quam eu sagittis. Phasellus lobortis, tortor faucibus egestas auctor, justo eros ultricies ligula, nec efficitur lectus ante sit amet felis. Duis cursus, leo ut rutrum dignissim, mauris metus lacinia mi, cursus cursus arcu elit sit amet elit. Integer eleifend scelerisque tincidunt. Morbi vitae leo tortor. Curabitur venenatis tempor diam nec blandit. Etiam blandit eros massa. Vivamus egestas nunc est, at dignissim neque convallis vel. Donec sit amet dictum odio. Nulla non ante nec sem aliquam efficitur. Etiam ullamcorper ultrices nisi, vitae laoreet metus venenatis ac.</p>
|
||||
<p>Aliquam placerat mauris vitae sem cursus, ac mattis nunc rutrum. Aenean semper neque sed enim ornare pharetra. Nulla non tristique enim, ac dictum tortor. Donec laoreet aliquam orci, id vulputate arcu congue ac. Sed tempus tristique eros in imperdiet. In aliquam metus sit amet tortor semper interdum. Suspendisse potenti. Fusce pulvinar velit quam, gravida feugiat ex dapibus sed. Morbi varius hendrerit risus, at mollis augue feugiat ac. Sed et erat fringilla, hendrerit dolor et, pulvinar ante.</p>
|
||||
<p>Ut mattis sapien eu mi dictum, at vestibulum metus lobortis. Ut faucibus enim nec sollicitudin tincidunt. Ut vel risus molestie, feugiat lacus in, hendrerit lorem. Donec risus dui, porttitor sit amet consectetur sit amet, pulvinar in tellus. Suspendisse feugiat ligula lacus, eu laoreet eros ultricies ut. Quisque pulvinar sit amet leo vel consequat. In mollis, enim id ornare pulvinar, nisl lectus varius mauris, quis fermentum ante diam ac justo. Sed pharetra elit sit amet mattis vestibulum. Proin vel maximus dui. Nulla dictum odio non laoreet bibendum. Fusce tincidunt pharetra libero, nec bibendum nisi maximus non. Curabitur vel egestas lectus. Proin nec sem ligula. Curabitur aliquam nisl ac metus rhoncus mollis. Pellentesque tempor orci eget nibh mattis mollis. Vestibulum ac maximus quam.</p>
|
||||
<p>Donec sed tortor non neque maximus tincidunt vel vitae tellus. Praesent fringilla convallis ex, vehicula eleifend lectus efficitur at. Cras non mattis diam. Integer convallis est aliquam urna convallis pharetra. Vestibulum vitae nisl at nisl dapibus molestie. Curabitur interdum tortor non fringilla maximus. Aliquam sagittis augue elit, sit amet dictum dolor condimentum ut.</p>
|
||||
<p>Nullam aliquam ligula eu volutpat convallis. Fusce at laoreet felis. In hac habitasse platea dictumst. Aenean vitae viverra erat, ac pharetra est. Nullam vel magna dui. Proin eu varius tortor. Etiam sed porttitor libero. Cras eget augue nibh. Nulla iaculis pretium enim nec consequat. Nunc id tempus erat, non sodales velit. Phasellus leo risus, gravida sed justo sit amet, vestibulum pretium nulla. Nullam finibus ornare convallis.</p>
|
||||
<p>Etiam in efficitur felis. Vivamus egestas facilisis augue at dictum. Proin tristique venenatis tellus vel convallis. Ut ipsum mauris, hendrerit at tempus vitae, pretium nec felis. Donec vitae euismod est. Nunc ut magna mi. Sed vel tristique purus. Donec eleifend auctor lobortis.</p>
|
||||
<p>Etiam a mollis dui, sit amet luctus purus. Proin dapibus vehicula urna, eget tincidunt mauris ultrices eu. Nulla ac quam eu quam volutpat porttitor. Mauris bibendum, tellus nec egestas blandit, mi neque interdum quam, non volutpat metus arcu eu ante. Maecenas a accumsan mi, ut ullamcorper elit. Vivamus tellus libero, mattis a turpis cursus, suscipit aliquam neque. Morbi facilisis vel mi vel varius. Aliquam magna lectus, lobortis ut purus nec, ullamcorper pharetra magna. Curabitur vulputate, risus et luctus efficitur, massa mauris aliquam enim, consectetur accumsan lorem nisi ut augue. Cras eleifend auctor lorem at fringilla. Pellentesque quis felis quam.</p>
|
||||
<p>Quisque sodales scelerisque mauris at commodo. Pellentesque volutpat nisi mi, vel feugiat metus bibendum eget. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce cursus lectus id est scelerisque scelerisque. Integer convallis sit amet ipsum at iaculis. Nam sed sem dui. Mauris non tempus tortor.</p>
|
||||
<p>Nam at placerat erat. Maecenas egestas pharetra nibh, et facilisis nunc convallis id. Donec convallis sodales ipsum. Vestibulum non nisi ac orci dictum iaculis at at neque. Pellentesque suscipit leo turpis, eget tempor odio pharetra at. Sed leo arcu, pellentesque sed sagittis id, mattis non massa. In eget dui finibus, volutpat quam sed, fringilla leo. Donec fringilla malesuada consequat. Duis non nulla ac massa venenatis congue non nec neque. Donec tincidunt arcu vel leo ullamcorper, non dapibus nisi imperdiet.</p>
|
||||
<p>Sed consequat ex nec fringilla interdum. Fusce sollicitudin, odio ut blandit feugiat, sapien eros aliquam magna, in consequat enim arcu scelerisque mauris. Nunc euismod felis vel ipsum pretium, in interdum tellus posuere. Nullam eget fringilla orci, vel ullamcorper metus. Maecenas vehicula, nulla sit amet tincidunt dictum, ipsum est interdum lacus, vel dictum velit libero id ex. Etiam mollis nec tortor at pretium. Nullam vitae euismod leo. Suspendisse potenti. Vestibulum eu nisi eget augue maximus vulputate vitae vel nisl. Pellentesque faucibus, elit quis vehicula sodales, nibh turpis efficitur sem, sit amet eleifend sem enim porttitor ligula. Nam vitae tellus id leo interdum eleifend.</p>
|
||||
<p>Sed nunc dolor, pharetra ac laoreet a, elementum vitae ipsum. Integer dapibus nisi quis nisi tempor ullamcorper. In condimentum velit libero, cursus facilisis est sodales sed. Proin sed sollicitudin leo. In id metus vel dolor iaculis scelerisque. Quisque eu mi elementum, facilisis mauris bibendum, condimentum risus. Aliquam euismod, sapien at hendrerit fermentum, diam est aliquam turpis, nec pulvinar est ex eu sem. Integer eleifend ligula ac porta suscipit. Sed eros nibh, vulputate vitae placerat a, semper nec diam.</p>
|
||||
<p>Sed gravida tortor massa, a vestibulum velit cursus vitae. Pellentesque sodales ligula est, eget convallis ligula blandit ut. Phasellus ac rhoncus odio. Quisque non efficitur sem. Quisque pharetra viverra maximus. Pellentesque sodales nunc eget massa elementum, ac placerat libero mattis. In porttitor ultricies sem, eu sagittis nisl luctus sit amet. Praesent dapibus mi vitae urna aliquet, nec semper sem efficitur. Sed vel orci eu nisl tempor mollis a sed tellus. Pellentesque vel laoreet lorem, eget facilisis ex. Aliquam blandit nunc at leo faucibus tincidunt. Duis finibus porttitor turpis et tincidunt. Cras sed augue condimentum ligula porttitor facilisis. Vestibulum semper sapien nibh, eget imperdiet sapien aliquet quis. Integer eleifend ipsum sed dolor convallis, et cursus purus convallis.</p>
|
||||
<p>Vestibulum commodo fermentum laoreet. Nam vel justo quis risus eleifend imperdiet id quis risus. Nulla sit amet semper eros. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Curabitur eget nulla finibus sapien hendrerit auctor. Fusce laoreet porta nisi id convallis. Morbi molestie pellentesque elit, at posuere elit porta non.</p>
|
||||
<p>Vivamus bibendum, nibh eget lobortis maximus, lectus leo bibendum orci, vitae maximus dui orci vitae purus. Praesent mattis nisi cursus nisi imperdiet tempor. Aliquam massa lacus, volutpat id est eu, sagittis blandit ex. Duis eu hendrerit justo. Nam volutpat cursus ex, et tempus diam ullamcorper dapibus. Mauris id finibus felis. Suspendisse efficitur pretium nunc, id pharetra sem consectetur non. Nam tempus odio tortor, at varius tellus efficitur at. Fusce maximus non eros ac venenatis. Fusce imperdiet at turpis nec sollicitudin. Fusce justo est, luctus id orci vel, accumsan tempor ipsum.</p>
|
||||
<p>Phasellus vel semper velit. Phasellus faucibus consectetur diam, quis luctus ipsum pretium at. Mauris sit amet ultricies quam. Morbi ac ornare lorem, eu pellentesque mi. Nam ultrices dolor at enim fermentum pharetra. Ut ac posuere lectus. Morbi ullamcorper urna sed molestie volutpat. Curabitur porta cursus velit, et porta ligula sodales sed. Donec tincidunt est ac gravida luctus. Praesent sed purus elementum, varius arcu laoreet, sagittis dolor. Integer cursus diam dui, vitae condimentum lectus vestibulum a. Maecenas vitae enim urna. Vestibulum vel malesuada leo. In hac habitasse platea dictumst.</p>
|
||||
<p>Integer vitae nisi lorem. Praesent luctus ullamcorper urna, sed tempor erat maximus vitae. Morbi vestibulum nisi dapibus iaculis hendrerit. In quis ligula id mi laoreet molestie sed in elit. Donec at leo dui. Vivamus sit amet placerat urna. Nunc facilisis commodo mauris, ut scelerisque leo luctus eget. Donec sed sem ut tellus malesuada rhoncus. Nulla condimentum fringilla elit, id mollis erat mollis eget. Vestibulum hendrerit massa at egestas bibendum. Donec fermentum nibh non nisl tincidunt, at porta massa vulputate. Sed euismod magna vitae nibh ultricies vehicula. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
|
||||
<p>Proin tincidunt eros sed elit vulputate, id tincidunt sem maximus. In blandit sed metus nec aliquet. Duis bibendum, neque ut malesuada pharetra, felis lectus mollis dui, sed mollis urna ex at ligula. Vivamus vel ex erat. Phasellus tempor imperdiet tortor eget imperdiet. Aliquam semper quam quis felis ultrices, ac congue lorem ultrices. Suspendisse metus justo, ultrices eu erat vitae, ornare consectetur ligula. Mauris eleifend imperdiet ante, ut suscipit eros varius a. Sed pulvinar sollicitudin magna, id tincidunt risus dignissim id.</p>
|
||||
<p>Maecenas feugiat ullamcorper augue, eu porta lacus cursus ac. Fusce pretium at metus sit amet fermentum. Donec auctor lectus non ligula pulvinar, non faucibus erat ultrices. Nunc sit amet nisl elit. Morbi aliquam odio vitae aliquet rhoncus. Suspendisse justo nulla, aliquet quis massa id, laoreet commodo enim. Etiam sed risus lorem. Sed justo massa, cursus eget felis eu, commodo mollis ante.</p>
|
||||
<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc dictum metus ex, quis sodales lorem facilisis vel. Sed condimentum dictum sagittis. Nullam sagittis diam felis, vitae laoreet neque tincidunt quis. Maecenas viverra sed urna ut vestibulum. Suspendisse diam quam, dignissim nec elit sed, congue luctus ligula. Proin aliquam, orci eget viverra ornare, metus sapien gravida libero, sit amet lacinia velit lectus eget ante. Phasellus id nisl in urna euismod euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Integer mauris libero, scelerisque eu posuere et, tempor imperdiet justo. Vestibulum non massa ut nibh accumsan scelerisque interdum sit amet libero. Etiam ut elit ac ante sagittis pellentesque a eu velit.</p>
|
||||
<p>Vivamus in est nec nisl varius elementum. Donec id nisi eu eros convallis egestas. Sed vitae ligula lacinia magna fringilla vulputate. Morbi nibh purus, malesuada nec purus sed, commodo commodo nisl. Praesent pharetra tellus lacus, et sodales elit condimentum non. Vivamus a ornare lectus. Sed sodales aliquam sodales. Fusce ultrices, sapien eget condimentum euismod, nunc tellus convallis ex, vitae tempor enim diam at velit. Pellentesque at egestas velit. Pellentesque a ullamcorper turpis, dictum maximus eros.</p>
|
||||
<p>Etiam vel est tempor, tristique nunc eu, aliquam orci. Proin sem dui, volutpat sed auctor nec, pharetra et purus. Donec aliquam lobortis purus ut pharetra. Mauris at sapien sit amet metus auctor euismod vitae a nunc. Aenean aliquam purus felis, nec bibendum lorem rutrum ut. Donec sed dignissim justo. Sed fringilla sollicitudin felis, malesuada aliquet nulla. Sed at sem gravida, placerat magna a, rhoncus magna. Ut vitae sodales nibh, maximus vulputate dolor.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet sodales metus, ut maximus risus. Aliquam accumsan, massa in varius feugiat, tortor elit aliquet erat, ut consectetur sem ex et orci. Nunc vel pellentesque orci. Vestibulum non augue tristique sapien sagittis iaculis at quis massa. Maecenas nec fermentum augue. Quisque quis vestibulum arcu. Nam hendrerit sem ipsum, sed elementum neque viverra at. Vivamus mattis vehicula eleifend. Aenean viverra nisi quis sagittis ullamcorper. Fusce hendrerit finibus massa nec imperdiet. Praesent suscipit, purus nec dapibus maximus, ante dui dapibus dolor, et vehicula tortor odio id diam. Nullam a odio id est laoreet elementum luctus viverra magna.</p>
|
||||
<p>Nunc ligula mi, eleifend id nulla nec, placerat gravida odio. Fusce ac vulputate est. Integer non purus ac urna congue cursus nec vitae neque. Pellentesque hendrerit velit libero, et cursus quam dictum efficitur. In sagittis et lectus sed consequat. Proin ut gravida eros. Vestibulum in dignissim neque. Etiam placerat dapibus lorem sit amet congue. Integer id velit vitae eros pharetra egestas sed non velit. Aenean eget tristique eros. Ut vitae arcu pharetra, tristique nibh id, ultricies justo. Etiam porttitor neque arcu, eget sollicitudin nibh laoreet eu. Duis nec purus eu erat feugiat blandit nec eget nibh.</p>
|
||||
<p>Aenean scelerisque diam egestas tortor rutrum, eu rhoncus ipsum porta. Etiam vel eros eget lorem cursus efficitur id at lacus. Sed pulvinar ante et lectus sollicitudin dictum. Pellentesque vel interdum lectus. Mauris vulputate et odio sed dapibus. Donec tortor tellus, sodales eu dui quis, consequat mollis nunc. Nunc vel augue neque. Vestibulum urna diam, dignissim in dolor imperdiet, semper iaculis tellus. Nunc ac sem urna. Nullam efficitur, tortor ut sagittis hendrerit, dolor diam posuere lorem, id rutrum ex leo a lacus. Pellentesque molestie, risus quis condimentum posuere, quam nulla blandit erat, quis viverra augue justo faucibus erat. Vestibulum sed rutrum arcu. Nunc dui mi, blandit a tortor vel, laoreet volutpat mi. Aenean dapibus mauris ut elit mollis, sit amet congue est porttitor. Vivamus nec purus justo.</p>
|
||||
<p>Nam nec dui et felis tristique posuere nec quis justo. Suspendisse fringilla nibh a ex tempor finibus. Etiam pretium nulla sem. Etiam ac augue bibendum, blandit ipsum vel, gravida dolor. Curabitur pellentesque orci tincidunt, lacinia felis quis, porttitor mauris. Nullam semper eleifend ante sodales molestie. Duis vitae turpis vel ante lobortis rutrum. Sed ut arcu facilisis, congue est eu, pharetra tellus. Nullam odio metus, scelerisque a quam id, pharetra blandit dolor. Integer ut sollicitudin ligula, eu condimentum ante. In non lectus ex. Praesent efficitur bibendum lacus in faucibus. Donec feugiat fringilla arcu.</p>
|
||||
<p>Phasellus quis tortor eu arcu aliquam viverra. In ut odio interdum, feugiat purus a, convallis diam. Mauris non sapien enim. Nunc gravida nibh elementum, dictum dolor in, tempus neque. Nullam pretium nunc quis maximus malesuada. Fusce in tincidunt quam, eu porttitor arcu. Nunc ornare in magna et facilisis. Mauris accumsan ex ac scelerisque dignissim. Praesent libero massa, congue nec massa et, pulvinar facilisis felis. Aliquam tristique mauris vitae dui consectetur molestie. Donec ac lectus lacus. Aliquam eu augue quis eros volutpat iaculis.</p>
|
||||
<p>Mauris nec accumsan arcu, eu porttitor dolor. Nunc scelerisque pellentesque nisi sed mollis. Integer quis tincidunt sapien. Pellentesque a elit in eros interdum efficitur quis vel arcu. Mauris malesuada nunc lacus, ac auctor mauris lobortis non. Etiam sit amet ullamcorper mi. Vivamus a volutpat lorem. Donec at velit tristique, consectetur nunc vitae, condimentum mauris. Pellentesque molestie lectus ac purus finibus condimentum. Etiam luctus lectus vitae maximus consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi hendrerit risus a odio auctor pellentesque. Suspendisse potenti. Phasellus at ante felis. Nam mattis scelerisque bibendum. Sed vel ornare orci.</p>
|
||||
<p>Duis a euismod quam. Suspendisse non massa mi. Morbi sed tellus egestas, semper felis eu, dignissim justo. Phasellus sollicitudin eu orci eget condimentum. In hac habitasse platea dictumst. Aliquam erat volutpat. Quisque sit amet lacinia ante, ac vehicula mauris. Donec aliquam in augue ut vestibulum.</p>
|
||||
<p>Vivamus efficitur sapien nec mi vulputate, vel eleifend turpis consectetur. Duis quis massa mattis felis faucibus egestas. Donec lobortis leo eu arcu gravida, a vestibulum neque tristique. Vivamus in rhoncus mi. Curabitur ipsum diam, sollicitudin in massa in, viverra lacinia ante. Fusce cursus orci ipsum, non semper neque consequat imperdiet. Donec in lorem vel enim vestibulum sagittis at quis tortor. Suspendisse potenti. Praesent lobortis, eros ut molestie tristique, tortor ante suscipit nibh, a semper felis nibh vel purus. Integer in congue ligula.</p>
|
||||
<p>Aliquam ultrices at purus ac luctus. Proin finibus metus est, non egestas purus dapibus sit amet. Maecenas dapibus, urna ac accumsan sagittis, metus ipsum rutrum dolor, a elementum justo odio ut massa. Nullam sollicitudin ipsum sagittis, venenatis purus non, elementum ligula. Donec quis lacus sem. Fusce pulvinar turpis ut leo facilisis, ac varius lacus lacinia. Quisque viverra augue ut leo gravida porttitor. Maecenas ullamcorper ante sed purus rhoncus tempor. Mauris dictum tellus vitae iaculis facilisis. Cras dignissim at purus id vehicula.</p>
|
||||
<p>Mauris iaculis ultricies nibh, in aliquet odio mattis ut. Proin consectetur blandit risus sit amet aliquam. Vestibulum a urna vestibulum, bibendum dui luctus, facilisis ante. Nullam efficitur tristique enim. Maecenas justo tellus, scelerisque quis blandit at, aliquam vel tortor. Duis nec elit ligula. Pellentesque luctus malesuada tortor, et faucibus velit convallis ut.</p>
|
||||
<p>Morbi ac velit vitae risus aliquam vestibulum a et purus. Sed fringilla euismod erat, quis dapibus arcu efficitur nec. Cras mollis sed turpis maximus maximus. Donec vitae ligula id libero lacinia interdum vel sed massa. Maecenas elementum nunc justo, porttitor suscipit ex imperdiet vel. Nullam accumsan tincidunt ligula, ut venenatis augue sodales eu. Phasellus vestibulum vehicula urna, in lobortis elit vulputate ut. Morbi vulputate convallis mauris, nec eleifend quam dictum ut. Nam dictum turpis eu imperdiet lobortis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam luctus cursus ante, a malesuada ex tincidunt et. Fusce consectetur est vel orci volutpat vulputate. Suspendisse congue dictum nisi quis commodo. Pellentesque molestie, nunc in interdum hendrerit, massa sapien fringilla sapien, eu feugiat enim est eu dolor. Phasellus quam lectus, luctus id justo id, lacinia gravida sapien.</p>
|
||||
<p>Quisque suscipit interdum massa, eget facilisis neque tempor eu. Quisque eu ante at enim ullamcorper aliquam. Nullam at lacus metus. Aenean facilisis, felis eget dictum vulputate, lectus ante commodo est, at fringilla massa metus eu urna. Ut in dignissim lorem. Fusce sit amet magna rhoncus, commodo felis tempor, accumsan nisl. Fusce mollis, eros sit amet egestas feugiat, leo mi auctor justo, eu dignissim turpis elit ut tortor. Mauris nec feugiat urna. Integer dui neque, ultrices vel massa quis, dignissim fringilla sapien. Fusce vulputate tellus in lacus ultricies, sit amet suscipit magna rutrum. Proin at lectus sodales, pharetra neque et, laoreet sapien. Vestibulum diam turpis, gravida eu augue quis, fermentum mollis dui. Integer faucibus, felis ac convallis scelerisque, neque magna laoreet velit, nec dictum lectus elit sed nibh. Integer orci leo, facilisis vel imperdiet at, tristique ac mauris. Pellentesque quis ex sodales, porttitor turpis eu, pharetra dui.</p>
|
||||
<p>Duis euismod diam urna, at ullamcorper dui tincidunt vitae. Curabitur vel erat sit amet arcu vehicula lacinia sed ac arcu. Cras neque nisl, posuere vitae dictum a, dapibus a diam. Cras non euismod ligula. In condimentum, urna vel blandit egestas, sapien diam condimentum turpis, at dapibus elit ante a orci. Mauris maximus dui sapien, luctus imperdiet quam efficitur quis. In sit amet leo nec nisi dictum accumsan id vel mi. Fusce auctor arcu sem, quis cursus urna semper a. Cras quis tempor ante. Integer cursus dapibus erat eu porta. Curabitur eleifend posuere enim nec vehicula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam in dui nec metus vulputate rhoncus a at diam. In turpis libero, luctus quis metus in, posuere blandit metus.</p>
|
||||
<p>Sed auctor dictum finibus. Sed tristique arcu vel diam pulvinar dapibus. Aliquam in arcu nec arcu vehicula euismod non eu nulla. Aliquam fermentum justo non lorem placerat rutrum vel sit amet metus. Suspendisse massa mi, tincidunt et placerat eget, consectetur sed erat. Fusce dictum metus tortor, a pulvinar justo posuere quis. Sed fringilla pharetra ex, eget faucibus lacus dignissim et. Curabitur posuere dui ac turpis scelerisque, eu scelerisque mi viverra. Vestibulum pulvinar nibh eu dui pharetra placerat. Praesent varius augue nisi, quis iaculis leo porttitor aliquet. Nulla imperdiet, nisl in dignissim vestibulum, dolor dui tempor leo, ultrices aliquam libero lacus a purus.</p>
|
||||
<p>Integer ut erat lacinia ligula tincidunt malesuada. Proin sed varius tortor. Ut sodales dolor sit amet porttitor condimentum. Suspendisse sit amet orci euismod, tincidunt augue eget, mattis leo. Suspendisse sit amet arcu posuere tellus porttitor ornare vitae vitae est. Donec quis posuere odio. Nullam ut lacinia nulla, at laoreet risus. Sed dui tortor, facilisis vel tortor sit amet, ullamcorper dictum ante. Pellentesque eget diam augue. Duis dictum felis id ultricies finibus. Vivamus in consectetur dolor, sit amet mollis ex. Sed fermentum neque ac leo faucibus, et eleifend ante interdum. Sed pharetra libero eget mauris tincidunt, ut sagittis sapien facilisis. Vivamus vel malesuada quam. Ut consequat cursus risus sit amet aliquet.</p>
|
||||
<p>Donec in eleifend lacus. Phasellus lacinia diam id ligula rhoncus, a laoreet ipsum mattis. Praesent enim lectus, interdum eu consequat sed, efficitur id diam. Praesent ultricies rhoncus nisl, ac gravida dui sodales non. Aenean molestie pulvinar mi quis consectetur. Curabitur quis hendrerit risus. Mauris justo nisl, pretium at lorem id, euismod volutpat nisl. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse eget magna non tellus pretium imperdiet eget a ligula. Fusce a est non urna rhoncus molestie. Sed at nunc ut nibh eleifend accumsan. Nunc bibendum eu augue nec interdum. Duis consequat, nibh a egestas imperdiet, mauris magna malesuada urna, vitae elementum leo orci eu nulla.</p>
|
||||
<p>Donec sodales ipsum id velit rhoncus efficitur. Aliquam in diam sit amet felis fringilla consectetur nec nec nibh. Proin tincidunt dignissim neque sed volutpat. Curabitur in vulputate tortor. Duis at lacinia nisi, non laoreet lectus. Nullam neque lacus, scelerisque in augue sed, finibus pharetra tellus. Cras quis mi in elit accumsan volutpat. Nullam feugiat, est vel luctus iaculis, libero arcu ultricies lorem, non hendrerit ex nisl nec est. Sed ullamcorper, eros ut vulputate pretium, orci augue porta urna, ullamcorper interdum dui nunc et mi.</p>
|
||||
<p>Nunc maximus sem tortor, ut iaculis justo fringilla a. Nulla facilisis sit amet urna ut suscipit. Curabitur sodales volutpat velit eget accumsan. Nulla in dolor vel nulla gravida porta. Suspendisse vehicula augue eget risus luctus dapibus. Etiam bibendum quam ante, scelerisque vulputate elit faucibus at. Vestibulum eu bibendum neque, at convallis dui. Aenean facilisis, orci non eleifend bibendum, sapien neque rhoncus est, sit amet blandit lorem quam vel sapien.</p>
|
||||
<p>Aliquam eros elit, auctor non pharetra id, egestas ac nulla. Morbi maximus, orci nec viverra interdum, nulla lacus ultrices justo, sit amet sodales massa magna id dolor. Praesent tempus augue non erat vehicula, ut laoreet erat accumsan. Phasellus commodo, neque ac efficitur finibus, urna nibh maximus eros, eget scelerisque enim urna a dui. Nullam malesuada felis at ante vulputate, eget blandit elit ornare. Proin ac elit sollicitudin, malesuada quam vel, eleifend orci. Fusce mollis vel quam in elementum.</p>
|
||||
<p>Nullam ut erat id metus elementum tincidunt at ac dolor. Morbi vel commodo neque, ac commodo eros. Nulla fermentum massa urna, a fermentum elit bibendum vitae. Duis pharetra mauris nec ultrices feugiat. Vestibulum justo nisl, tempor at neque vel, accumsan consequat lacus. Sed quis enim eu augue luctus consequat ac sit amet felis. Ut bibendum, augue et volutpat blandit, nisi odio egestas mauris, eu euismod tellus tellus vitae tortor. Morbi sit amet magna condimentum, blandit lacus a, condimentum ipsum. Nullam ut tristique justo. Integer laoreet laoreet facilisis. Nullam molestie nec sapien nec pulvinar. Aliquam venenatis hendrerit nunc eget hendrerit. Mauris finibus, risus id lacinia dignissim, leo nulla consequat diam, vitae porttitor dolor diam eget magna.</p>
|
||||
<p>Morbi venenatis mauris velit. Cras sit amet urna tortor. Quisque quis tempor magna, ut sollicitudin augue. Phasellus consectetur nisi metus, et vestibulum urna dapibus semper. Praesent vulputate, turpis non consequat fringilla, lectus neque scelerisque sem, imperdiet rhoncus est metus finibus nibh. Curabitur fermentum sodales ultrices. Vestibulum tristique turpis sed libero finibus finibus. Donec ac tincidunt risus, non consectetur nisi. Sed at finibus odio, ut scelerisque nisi. Vivamus et scelerisque orci. Aliquam vitae diam dolor. Integer hendrerit aliquam arcu, a fringilla nisl rutrum ut. Curabitur euismod dignissim magna id bibendum. Suspendisse venenatis augue odio, at consequat sem vehicula ultricies. Maecenas sollicitudin aliquam urna vel varius. Donec placerat imperdiet tristique.</p>
|
||||
<p>Aliquam consectetur tincidunt diam fermentum volutpat. Etiam quis elementum risus. Donec dictum faucibus urna. Cras a consequat ante. Nam finibus est a dolor aliquet ullamcorper. Ut ac sodales justo. Nunc finibus eleifend leo nec aliquam.</p>
|
||||
<p>Duis enim erat, finibus nec faucibus et, vulputate sit amet orci. Integer pretium facilisis arcu, at molestie sem dictum eu. Sed tempus ut diam vitae ultricies. Suspendisse consequat tincidunt venenatis. Etiam consequat eu eros non convallis. Curabitur eleifend mattis justo, eu sagittis leo ultricies condimentum. Integer in tellus ac ipsum feugiat molestie non in mauris. Etiam laoreet sapien purus, nec molestie purus accumsan quis. Nulla sit amet mattis mauris, tristique tristique magna. Mauris faucibus pulvinar orci ut lacinia. Nam ac nisi nec dolor auctor accumsan eu eget justo. Nullam ut sem ultrices neque tempor dictum. Integer facilisis venenatis arcu sed sollicitudin. Nullam mattis fermentum enim, eget ultrices nunc. Aenean blandit, nunc in lacinia suscipit, lorem libero volutpat lorem, eget pellentesque dui nisi et dui.</p>
|
||||
<p>Duis dictum, nunc ut imperdiet bibendum, est metus congue diam, at tempus augue nisi ac dolor. Nulla scelerisque nibh nibh, sit amet elementum nisl feugiat non. Proin maximus quam molestie feugiat facilisis. In quis vestibulum dui, vitae malesuada dui. Morbi commodo ornare justo, vel euismod mi pellentesque ac. Maecenas quis turpis at diam euismod condimentum. Sed molestie aliquam dictum. Vestibulum rutrum ullamcorper urna vitae ultrices. Nullam maximus nibh auctor, semper enim vel, suscipit ligula. Fusce et ligula ante. Phasellus tempor turpis in facilisis tincidunt.</p>
|
||||
<p>Vestibulum orci leo, pulvinar non dolor nec, cursus tincidunt nulla. Sed tempor nunc neque, quis vehicula enim pulvinar sit amet. Morbi aliquet finibus justo ac luctus. Proin urna justo, egestas eu ullamcorper et, volutpat vel odio. Duis venenatis est ut dui mattis imperdiet. Pellentesque ut lectus velit. Maecenas mi est, bibendum nec tristique nec, cursus quis tortor. Aliquam erat volutpat. Donec blandit sapien sed urna tempor, non viverra ex sodales. Vivamus ac enim nec diam condimentum rhoncus. Phasellus ullamcorper augue ut vulputate egestas. Aenean eu urna ultrices, dignissim augue eu, lobortis diam. Sed auctor eget ante in tempor. Integer eget elementum nisl.</p>
|
||||
<p>Donec dignissim faucibus risus. Suspendisse dictum dictum ex, eu rhoncus augue cursus at. Integer lectus est, pulvinar vitae tempus at, luctus eget risus. Sed vulputate enim diam, sed tempus lorem posuere non. Quisque eget libero velit. Morbi vitae mollis elit. Ut laoreet, nibh non molestie sollicitudin, felis massa convallis est, sit amet congue nisl metus in justo. Donec et tincidunt lacus. Morbi fringilla lorem efficitur ligula gravida convallis. Integer sagittis eu nunc vel efficitur. Maecenas sodales neque massa.</p>
|
||||
<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur non volutpat est. Etiam tristique nulla arcu, vel dictum risus euismod a. Pellentesque sit amet risus in purus ultricies pulvinar. Nam cursus erat ac gravida porttitor. Nam sit amet ex sed neque semper volutpat. Morbi in volutpat diam, sed scelerisque metus. Mauris non cursus erat, a tincidunt arcu. Vivamus porttitor ut lectus et consequat. Aenean diam enim, dignissim nec arcu non, condimentum venenatis metus. Nunc finibus justo vel risus varius elementum. Praesent scelerisque, risus in ultrices lacinia, nunc lectus volutpat odio, in eleifend diam est eget magna. Phasellus varius lacinia malesuada. Curabitur ligula nunc, mattis eget purus ac, euismod condimentum est.</p>
|
||||
<p>Suspendisse volutpat erat et convallis suscipit. Nulla eget urna pellentesque, maximus mauris sit amet, fringilla urna. Sed vitae nulla sed ante malesuada consequat. Donec libero purus, laoreet et dolor vel, blandit accumsan velit. Etiam tincidunt libero tempus, blandit nisi at, tincidunt nisi. Maecenas justo justo, pretium a nunc rhoncus, pellentesque pharetra dui. Ut quis odio imperdiet, semper lorem id, placerat odio. Curabitur vitae volutpat nisl. Vivamus et ipsum cursus, aliquam sem vitae, feugiat ligula. Suspendisse malesuada semper tortor, eu ultrices magna molestie eget. Curabitur placerat, diam vel volutpat sagittis, lacus arcu posuere risus, at rutrum nibh augue ac lacus. Fusce non metus at quam gravida hendrerit. Nam id justo nisi. Maecenas convallis, dolor lacinia tristique cursus, elit enim fringilla turpis, ut tincidunt leo lacus ut massa. Ut hendrerit eget ante vel hendrerit.</p>
|
||||
<p>Mauris hendrerit augue interdum, commodo felis a, pretium felis. Vivamus ultricies augue sed egestas consectetur. Pellentesque nec egestas sem. Fusce at volutpat arcu, et tincidunt libero. Sed condimentum malesuada interdum. Nullam id felis et nunc tempor sollicitudin vel non ipsum. Etiam porttitor convallis mattis.</p>
|
||||
<p>Vestibulum id nunc sagittis, blandit odio ut, fringilla leo. Nam lacus eros, lobortis in ipsum cursus, sodales efficitur est. Cras ac tempor ligula, eu tincidunt ante. Donec eu sapien quis massa tempor placerat. Duis volutpat justo massa, nec condimentum lorem commodo consequat. Aenean interdum mi ut lacinia luctus. Cras vel diam est. Sed sapien diam, semper non odio sit amet, sodales suscipit lorem. In molestie, justo non feugiat maximus, justo nisi lacinia velit, quis blandit arcu lorem sed nisi. Etiam vitae tortor elementum, sagittis mi id, feugiat enim. Vestibulum sodales lorem urna, eget cursus tortor rhoncus sed. Suspendisse potenti. Fusce vitae eros eu ex gravida auctor gravida a nunc. Quisque viverra, lorem non iaculis efficitur, nisi est pretium mi, in accumsan magna eros ac felis. Quisque bibendum, magna sed vulputate ornare, purus massa sodales nisi, eget porttitor urna elit a libero. Nullam in luctus diam.</p>
|
||||
<p>Nam sodales dapibus lectus ut laoreet. Donec feugiat magna et quam bibendum tincidunt. In vitae laoreet odio. Praesent nec elit mollis, imperdiet erat sed, porttitor libero. Phasellus sollicitudin orci et dui aliquam blandit. Integer sem arcu, feugiat quis luctus sit amet, ornare vel enim. Aenean faucibus placerat orci nec tristique.</p>
|
||||
<p>Aenean maximus metus a quam euismod aliquet. Ut vitae tellus purus. Sed convallis nisi a magna malesuada rutrum. Curabitur finibus, diam in rutrum porttitor, dolor arcu ultricies tellus, quis rutrum metus eros eget sapien. Aenean ut eros sed eros viverra feugiat sed vel nisl. Donec facilisis, sapien ac tempus tristique, sapien enim posuere lorem, at cursus nisl tellus vel urna. Maecenas facilisis, dolor viverra aliquam mollis, purus mi tincidunt nibh, scelerisque finibus mauris sem id dui. Pellentesque placerat sapien posuere, venenatis purus at, bibendum elit. Quisque odio orci, sagittis vel tempus sed, tristique eu nisl. Vivamus ut ligula interdum, consequat dui ultricies, congue neque.</p>
|
||||
<p>Ut purus leo, viverra interdum enim et, posuere congue libero. Sed bibendum rhoncus neque, in interdum dui congue sed. Aenean a diam sollicitudin, faucibus tortor a, auctor nisl. Integer mi mi, consectetur quis interdum eu, placerat ac enim. Quisque consequat faucibus arcu, sed hendrerit est. Maecenas interdum velit ut libero vehicula interdum. Vestibulum suscipit fermentum sodales. Suspendisse sem sapien, consectetur non convallis et, dapibus ut urna. Fusce efficitur eros nec iaculis sollicitudin. Praesent vitae blandit eros. Nullam porta finibus bibendum. Praesent eget molestie nisi. Aliquam rutrum ipsum et ligula tristique, ac mattis diam gravida. Proin ut interdum enim. Maecenas aliquam sapien est, quis laoreet nisl cursus in. Duis justo neque, efficitur id lacus vel, condimentum dapibus nibh.</p>
|
||||
<p>Vivamus interdum dui in sem porta, quis luctus ante efficitur. Suspendisse quis tortor et eros aliquam ultrices eu ac felis. Phasellus at consectetur nisl. Ut nibh tellus, dapibus eget diam non, ullamcorper suscipit elit. Quisque accumsan egestas finibus. Cras laoreet libero vel elit pretium, non euismod eros sollicitudin. Pellentesque metus felis, laoreet quis tortor non, tincidunt aliquam ligula. Vivamus at sagittis dui. Praesent non fringilla erat. Duis eu pharetra massa. Fusce bibendum, velit dignissim molestie dapibus, ipsum turpis pulvinar arcu, ac porttitor sapien nisi quis lorem. Cras fringilla maximus facilisis.</p>
|
||||
<p>Maecenas facilisis sodales venenatis. Quisque imperdiet, enim ac vulputate vulputate, justo nibh pretium odio, vel mollis quam ipsum in eros. Praesent nibh mauris, faucibus et posuere id, consequat a tortor. Vestibulum dapibus lacus id erat rutrum, vel lobortis nisi consectetur. Nulla aliquet, justo sed consequat sollicitudin, dui mi venenatis erat, nec pretium mauris dui eget sem. Nullam ac pharetra lacus. Integer vulputate, diam et tincidunt mattis, est turpis mattis est, vitae vehicula quam lectus nec mauris. Mauris pellentesque mattis faucibus.</p>
|
||||
<p>Maecenas vel lectus condimentum, eleifend metus a, rhoncus dui. Etiam consectetur enim sed dui interdum sollicitudin. Duis massa lorem, tincidunt eu nibh a, semper fermentum felis. Integer eu sapien sit amet dui vulputate hendrerit ac sit amet erat. Phasellus suscipit massa vehicula nunc varius fermentum. Praesent bibendum iaculis elit nec tristique. Pellentesque facilisis egestas urna, et commodo dui pretium at. Etiam semper, massa volutpat sollicitudin venenatis, neque turpis dictum ex, eu condimentum sem quam non ex. Sed lacinia lectus vitae rhoncus vehicula. Vestibulum vel accumsan libero. Donec varius lectus mollis, dignissim orci et, rhoncus enim.</p>
|
||||
<p>Suspendisse posuere lorem non enim consectetur, et convallis augue tempor. Vestibulum lobortis, tortor et consequat convallis, nisl neque convallis quam, vel varius est tortor eget felis. Cras a lobortis est, id condimentum nunc. Pellentesque auctor est rhoncus suscipit interdum. Nullam eu tristique felis. Aenean imperdiet maximus quam, tempus pulvinar sem condimentum fringilla. Nulla non scelerisque lacus. Quisque gravida eu ligula et sagittis. Fusce laoreet nisl id sagittis ultricies. Aliquam ut augue ut tortor sollicitudin semper id et sem. Nunc quis dui congue, dapibus enim non, egestas nunc.</p>
|
||||
<p>Pellentesque tellus nibh, feugiat ut fringilla et, tincidunt in nisl. Quisque vestibulum eros eros, a ullamcorper velit posuere vitae. Fusce maximus risus at est tristique lobortis. In ex quam, porttitor ut tortor vitae, scelerisque aliquam neque. Nullam et ligula sit amet ex malesuada commodo quis tincidunt justo. Nulla tempor bibendum tellus, vitae mattis ipsum facilisis vel. Morbi scelerisque ligula sed dapibus lobortis. Vestibulum volutpat magna felis, ac tincidunt libero venenatis et. Sed pulvinar libero est. Vivamus sagittis risus quis magna hendrerit consectetur. Ut nec euismod sem, et cursus nulla. Etiam dapibus, purus ac aliquet venenatis, nulla libero pretium erat, in rutrum diam purus a lectus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut odio elit, ultrices vitae lectus at, iaculis tempus massa. Phasellus hendrerit posuere tincidunt.</p>
|
||||
<p>Aenean nibh elit, dapibus vitae consectetur ac, dignissim at libero. Quisque blandit est ac risus tincidunt finibus. Donec sollicitudin fringilla facilisis. Nullam euismod, sem at egestas pharetra, lacus tellus ullamcorper lectus, ut commodo nulla sem a orci. Proin sed felis sem. Ut turpis mauris, feugiat sed dolor non, mollis lacinia sem. Mauris est dolor, hendrerit non pulvinar vitae, posuere id nulla. Aliquam rutrum, diam id interdum placerat, velit quam lobortis tortor, ut pretium lacus mauris tristique ex.</p>
|
||||
<p>Curabitur vitae dolor vitae mauris pretium luctus. Maecenas malesuada mollis nisi. Cras non aliquam arcu. Sed facilisis pretium velit, a finibus dolor finibus at. Sed interdum elit at est scelerisque placerat. Donec accumsan sodales lorem ac condimentum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ut facilisis urna. Aliquam ut nulla turpis. Donec pretium mauris urna, non ultricies lectus mattis non. Nulla facilisi. Nullam hendrerit a sapien ac iaculis. In efficitur consequat nulla sit amet semper. Sed non viverra dolor.</p>
|
||||
<p>Donec venenatis vitae libero vitae mollis. Sed et semper lectus. Aenean id vehicula arcu, sed tempus leo. Phasellus non mi aliquet dui tincidunt eleifend. Pellentesque congue ultricies est. Morbi varius nec ligula eu hendrerit. Nulla fringilla vel turpis in cursus. Curabitur nec ex ac augue luctus vestibulum at nec leo. Duis quis consequat turpis, nec sodales odio. Etiam eu arcu blandit, euismod nibh in, pellentesque purus. Nullam elit turpis, tempus eget elit non, vestibulum interdum ligula.</p>
|
||||
<p>Maecenas est justo, facilisis sagittis arcu a, venenatis dictum eros. Mauris ullamcorper ligula mauris, ac sollicitudin urna dignissim et. Integer fermentum lectus in pharetra mattis. Nunc porttitor in est eu sagittis. Mauris iaculis felis at tortor venenatis viverra. Donec iaculis elementum efficitur. Nunc eu urna ut ante facilisis ultricies. Sed volutpat orci at tristique semper. Nullam eget blandit nulla. Curabitur porttitor, felis ut rhoncus elementum, urna magna euismod ipsum, non fermentum mi nulla rhoncus sapien. In hac habitasse platea dictumst. Nulla euismod ligula ac nulla vestibulum, non pharetra mi placerat. Nam sed libero non erat blandit luctus a non urna. Quisque ut enim lacus. Donec a diam id dui vehicula viverra. Maecenas vehicula semper metus.</p>
|
||||
<p>Curabitur sem turpis, ultricies non venenatis vel, bibendum ut nibh. Cras ac viverra massa, at sagittis metus. Aenean lobortis imperdiet quam at pharetra. Suspendisse ac commodo velit. Nunc eu tincidunt neque. Duis pretium ante id enim vulputate dignissim. In vestibulum fermentum dolor, id scelerisque neque pulvinar vel. Maecenas non euismod felis, ac rhoncus augue. Nulla imperdiet tempus justo non imperdiet. Duis ac aliquam tellus. Morbi gravida, nulla sit amet efficitur varius, ante elit mattis tellus, a egestas augue lacus non nibh. Pellentesque purus lectus, efficitur non orci ut, dapibus dignissim lacus. Cras ut tellus volutpat, finibus erat nec, suscipit velit.</p>
|
||||
<p>Integer auctor mi in rhoncus facilisis. Donec eleifend mollis sem, eget condimentum neque eleifend id. Fusce at nibh tempor, laoreet lectus et, aliquam felis. Morbi fringilla placerat neque, a gravida mi bibendum fermentum. Nam id mi eu augue aliquet feugiat. Donec ut quam et tortor auctor aliquet. Ut pharetra pharetra mollis. Phasellus ultricies vitae justo at lobortis. Nulla et arcu consequat, vehicula turpis et, hendrerit eros. In efficitur faucibus est ac elementum. Phasellus imperdiet scelerisque ipsum nec sagittis. Integer aliquam commodo eros, eget ultricies ipsum consequat id. Aenean venenatis nisi purus, at facilisis magna fermentum vel. Quisque faucibus eros eu nulla lobortis, sed aliquam arcu consectetur.</p>
|
||||
<p>Suspendisse vitae tristique ligula. Sed ullamcorper ut libero ut finibus. Mauris volutpat nisi eu nulla viverra tincidunt. Sed fringilla velit sit amet diam posuere, vitae porttitor odio pulvinar. Integer vehicula mauris neque, at commodo est aliquet venenatis. Sed blandit tortor quis orci aliquet tempus. Fusce dapibus ac eros quis interdum. Vivamus nec viverra est, vel viverra nibh.</p>
|
||||
<p>Morbi varius mauris et sagittis gravida. Nunc ac dolor tellus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc laoreet accumsan ligula, et vulputate massa dictum maximus. Aliquam erat volutpat. Nunc malesuada justo eget velit maximus, at imperdiet nisi pretium. Maecenas a justo vitae sapien tincidunt placerat vel nec lorem. Donec quis mi nulla. Etiam sagittis ultrices nibh, eu egestas tellus aliquam quis. Vivamus vel iaculis felis. Aenean egestas, turpis eget fringilla luctus, tortor tortor sodales purus, at vehicula elit turpis non turpis. Morbi pellentesque ligula non quam vehicula rhoncus.</p>
|
||||
<p>In hac habitasse platea dictumst. Cras felis lacus, porttitor aliquet elit vel, semper luctus risus. Nulla ex dolor, blandit id lacus at, dictum iaculis nulla. Nam eleifend accumsan ante. Praesent feugiat a quam non fermentum. Donec ut maximus arcu. Phasellus eu dolor in mi iaculis consectetur nec vel urna. Nam mi erat, auctor at porta non, hendrerit id tortor. Maecenas convallis ultrices mauris, pellentesque bibendum lectus dictum non. Ut nec justo faucibus, auctor leo non, placerat magna. Curabitur ultricies massa eget nibh venenatis aliquet. Aenean turpis tellus, maximus vel ultrices ut, scelerisque a turpis. Duis risus sem, dapibus consequat pellentesque sit amet, sagittis nec libero. Curabitur tempus posuere lectus, id vulputate augue consequat iaculis. Suspendisse vulputate, odio vel aliquet gravida, justo sem pellentesque neque, in tempor ligula mauris eu dui. Nulla consectetur, nibh vel pharetra dictum, dolor justo commodo nunc, vel sodales est odio id neque.</p>
|
||||
`
|
@ -1,24 +0,0 @@
|
||||
import React from 'react'
|
||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'
|
||||
import './CodeBlockComponent.scss'
|
||||
|
||||
export default ({ node: { attrs: { language: defaultLanguage } }, updateAttributes, extension }) => (
|
||||
<NodeViewWrapper className="code-block">
|
||||
<select contentEditable={false} defaultValue={defaultLanguage} onChange={event => updateAttributes({ language: event.target.value })}>
|
||||
<option value="null">
|
||||
auto
|
||||
</option>
|
||||
<option disabled>
|
||||
—
|
||||
</option>
|
||||
{extension.options.lowlight.listLanguages().map((lang, index) => (
|
||||
<option key={index} value={lang}>
|
||||
{lang}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<pre>
|
||||
<NodeViewContent as="code" />
|
||||
</pre>
|
||||
</NodeViewWrapper>
|
||||
)
|
@ -1,9 +0,0 @@
|
||||
.code-block {
|
||||
position: relative;
|
||||
|
||||
select {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent, ReactNodeViewRenderer } from '@tiptap/react'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||
import CodeBlockComponent from './CodeBlockComponent'
|
||||
|
||||
// load all highlight.js languages
|
||||
import lowlight from 'lowlight'
|
||||
|
||||
// load specific languages only
|
||||
// import lowlight from 'lowlight/lib/core'
|
||||
// import javascript from 'highlight.js/lib/languages/javascript'
|
||||
// lowlight.registerLanguage('javascript', javascript)
|
||||
import './styles.scss'
|
||||
|
||||
const MenuBar = ({ editor }) => {
|
||||
if (!editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={() => editor.chain().focus().toggleCodeBlock().run()} className={editor.isActive('codeBlock') ? 'is-active' : ''}>
|
||||
code block
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
CodeBlockLowlight
|
||||
.extend({
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(CodeBlockComponent)
|
||||
},
|
||||
})
|
||||
.configure({ lowlight }),
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
That’s a boring paragraph followed by a fenced code block:
|
||||
</p>
|
||||
<pre><code class="language-javascript">for (var i=1; i <= 20; i++)
|
||||
{
|
||||
if (i % 15 == 0)
|
||||
console.log("FizzBuzz");
|
||||
else if (i % 3 == 0)
|
||||
console.log("Fizz");
|
||||
else if (i % 5 == 0)
|
||||
console.log("Buzz");
|
||||
else
|
||||
console.log(i);
|
||||
}</code></pre>
|
||||
<p>
|
||||
Press Command/Ctrl + Enter to leave the fenced code block and continue typing in boring paragraphs.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MenuBar editor={editor} />
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #0d0d0d;
|
||||
color: #fff;
|
||||
font-family: "JetBrainsMono", monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
code {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #f98181;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-meta,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #fbbc88;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet {
|
||||
color: #b9f18d;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #faf594;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #70cff8;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<node-view-wrapper class="code-block">
|
||||
<select contenteditable="false" v-model="selectedLanguage">
|
||||
<option :value="null">
|
||||
auto
|
||||
</option>
|
||||
<option disabled>
|
||||
—
|
||||
</option>
|
||||
<option v-for="(language, index) in languages" :value="language" :key="index">
|
||||
{{ language }}
|
||||
</option>
|
||||
</select>
|
||||
<pre><node-view-content as="code" /></pre>
|
||||
</node-view-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper, NodeViewContent, nodeViewProps } from '@tiptap/vue-2'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
},
|
||||
|
||||
props: nodeViewProps,
|
||||
|
||||
data() {
|
||||
return {
|
||||
languages: this.extension.options.lowlight.listLanguages(),
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
selectedLanguage: {
|
||||
get() {
|
||||
return this.node.attrs.language
|
||||
},
|
||||
set(language) {
|
||||
this.updateAttributes({ language })
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.code-block {
|
||||
position: relative;
|
||||
|
||||
select {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,154 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
|
||||
code block
|
||||
</button>
|
||||
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent, VueNodeViewRenderer } from '@tiptap/vue-2'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||
import CodeBlockComponent from './CodeBlockComponent'
|
||||
|
||||
// load all highlight.js languages
|
||||
import lowlight from 'lowlight'
|
||||
|
||||
// load specific languages only
|
||||
// import lowlight from 'lowlight/lib/core'
|
||||
// import javascript from 'highlight.js/lib/languages/javascript'
|
||||
// lowlight.registerLanguage('javascript', javascript)
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
CodeBlockLowlight
|
||||
.extend({
|
||||
addNodeView() {
|
||||
return VueNodeViewRenderer(CodeBlockComponent)
|
||||
},
|
||||
})
|
||||
.configure({ lowlight }),
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
That’s a boring paragraph followed by a fenced code block:
|
||||
</p>
|
||||
<pre><code class="language-javascript">for (var i=1; i <= 20; i++)
|
||||
{
|
||||
if (i % 15 == 0)
|
||||
console.log("FizzBuzz");
|
||||
else if (i % 3 == 0)
|
||||
console.log("Fizz");
|
||||
else if (i % 5 == 0)
|
||||
console.log("Buzz");
|
||||
else
|
||||
console.log(i);
|
||||
}</code></pre>
|
||||
<p>
|
||||
Press Command/Ctrl + Enter to leave the fenced code block and continue typing in boring paragraphs.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #0D0D0D;
|
||||
color: #FFF;
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
code {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #F98181;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-meta,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #FBBC88;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet {
|
||||
color: #B9F18D;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #FAF594;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #70CFF8;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,161 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-for="(item, index) in items">
|
||||
<div class="divider" v-if="item.type === 'divider'" :key="index" />
|
||||
<menu-item v-else :key="index" v-bind="item" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MenuItem from './MenuItem.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuItem,
|
||||
},
|
||||
|
||||
props: {
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
items: [
|
||||
{
|
||||
icon: 'bold',
|
||||
title: 'Bold',
|
||||
action: () => this.editor.chain().focus().toggleBold().run(),
|
||||
isActive: () => this.editor.isActive('bold'),
|
||||
},
|
||||
{
|
||||
icon: 'italic',
|
||||
title: 'Italic',
|
||||
action: () => this.editor.chain().focus().toggleItalic().run(),
|
||||
isActive: () => this.editor.isActive('italic'),
|
||||
},
|
||||
{
|
||||
icon: 'strikethrough',
|
||||
title: 'Strike',
|
||||
action: () => this.editor.chain().focus().toggleStrike().run(),
|
||||
isActive: () => this.editor.isActive('strike'),
|
||||
},
|
||||
{
|
||||
icon: 'code-view',
|
||||
title: 'Code',
|
||||
action: () => this.editor.chain().focus().toggleCode().run(),
|
||||
isActive: () => this.editor.isActive('code'),
|
||||
},
|
||||
{
|
||||
icon: 'mark-pen-line',
|
||||
title: 'Highlight',
|
||||
action: () => this.editor.chain().focus().toggleHighlight().run(),
|
||||
isActive: () => this.editor.isActive('highlight'),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
icon: 'h-1',
|
||||
title: 'Heading 1',
|
||||
action: () => this.editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
||||
isActive: () => this.editor.isActive('heading', { level: 1 }),
|
||||
},
|
||||
{
|
||||
icon: 'h-2',
|
||||
title: 'Heading 2',
|
||||
action: () => this.editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
||||
isActive: () => this.editor.isActive('heading', { level: 2 }),
|
||||
},
|
||||
{
|
||||
icon: 'paragraph',
|
||||
title: 'Paragraph',
|
||||
action: () => this.editor.chain().focus().setParagraph().run(),
|
||||
isActive: () => this.editor.isActive('paragraph'),
|
||||
},
|
||||
{
|
||||
icon: 'list-unordered',
|
||||
title: 'Bullet List',
|
||||
action: () => this.editor.chain().focus().toggleBulletList().run(),
|
||||
isActive: () => this.editor.isActive('bulletList'),
|
||||
},
|
||||
{
|
||||
icon: 'list-ordered',
|
||||
title: 'Ordered List',
|
||||
action: () => this.editor.chain().focus().toggleOrderedList().run(),
|
||||
isActive: () => this.editor.isActive('orderedList'),
|
||||
},
|
||||
{
|
||||
icon: 'list-check-2',
|
||||
title: 'Task List',
|
||||
action: () => this.editor.chain().focus().toggleTaskList().run(),
|
||||
isActive: () => this.editor.isActive('taskList'),
|
||||
},
|
||||
{
|
||||
icon: 'code-box-line',
|
||||
title: 'Code Block',
|
||||
action: () => this.editor.chain().focus().toggleCodeBlock().run(),
|
||||
isActive: () => this.editor.isActive('codeBlock'),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
icon: 'double-quotes-l',
|
||||
title: 'Blockquote',
|
||||
action: () => this.editor.chain().focus().toggleBlockquote().run(),
|
||||
isActive: () => this.editor.isActive('blockquote'),
|
||||
},
|
||||
{
|
||||
icon: 'separator',
|
||||
title: 'Horizontal Rule',
|
||||
action: () => this.editor.chain().focus().setHorizontalRule().run(),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
icon: 'text-wrap',
|
||||
title: 'Hard Break',
|
||||
action: () => this.editor.chain().focus().setHardBreak().run(),
|
||||
},
|
||||
{
|
||||
icon: 'format-clear',
|
||||
title: 'Clear Format',
|
||||
action: () => this.editor.chain()
|
||||
.focus()
|
||||
.clearNodes()
|
||||
.unsetAllMarks()
|
||||
.run(),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
icon: 'arrow-go-back-line',
|
||||
title: 'Undo',
|
||||
action: () => this.editor.chain().focus().undo().run(),
|
||||
},
|
||||
{
|
||||
icon: 'arrow-go-forward-line',
|
||||
title: 'Redo',
|
||||
action: () => this.editor.chain().focus().redo().run(),
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.divider {
|
||||
width: 2px;
|
||||
height: 1.25rem;
|
||||
background-color: rgba($colorBlack, 0.1);
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
</style>
|
@ -1,63 +0,0 @@
|
||||
<template>
|
||||
<button
|
||||
class="menu-item"
|
||||
:class="{ 'is-active': isActive ? isActive(): null }"
|
||||
@click="action"
|
||||
:title="title"
|
||||
>
|
||||
<svg class="remix">
|
||||
<use :xlink:href="require('remixicon/fonts/remixicon.symbol.svg') + `#ri-${icon}`" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
action: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
|
||||
isActive: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-item {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
color: #0D0D0D;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
border-radius: 0.4rem;
|
||||
padding: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
&.is-active,
|
||||
&:hover {
|
||||
color: #FFF;
|
||||
background-color: #0D0D0D;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/CollaborativeEditing', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/CollaborativeEditing')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,349 +0,0 @@
|
||||
<template>
|
||||
<div class="editor" v-if="editor">
|
||||
<menu-bar class="editor__header" :editor="editor" />
|
||||
<editor-content class="editor__content" :editor="editor" />
|
||||
<div class="editor__footer">
|
||||
<div :class="`editor__status editor__status--${status}`">
|
||||
<template v-if="status === 'connected'">
|
||||
{{ users.length }} user{{ users.length === 1 ? '' : 's' }} online in {{ room }}
|
||||
</template>
|
||||
<template v-else>
|
||||
offline
|
||||
</template>
|
||||
</div>
|
||||
<div class="editor__name">
|
||||
<button @click="setName">
|
||||
{{ currentUser.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Collaboration from '@tiptap/extension-collaboration'
|
||||
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
||||
import TaskList from '@tiptap/extension-task-list'
|
||||
import TaskItem from '@tiptap/extension-task-item'
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
import CharacterCount from '@tiptap/extension-character-count'
|
||||
import * as Y from 'yjs'
|
||||
import { WebsocketProvider } from 'y-websocket'
|
||||
import { IndexeddbPersistence } from 'y-indexeddb'
|
||||
import MenuBar from './MenuBar.vue'
|
||||
|
||||
const getRandomElement = list => {
|
||||
return list[Math.floor(Math.random() * list.length)]
|
||||
}
|
||||
|
||||
const getRandomRoom = () => {
|
||||
return getRandomElement([
|
||||
'rooms.7',
|
||||
'rooms.8',
|
||||
'rooms.9',
|
||||
])
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
MenuBar,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
currentUser: JSON.parse(localStorage.getItem('currentUser')) || {
|
||||
name: this.getRandomName(),
|
||||
color: this.getRandomColor(),
|
||||
},
|
||||
provider: null,
|
||||
indexdb: null,
|
||||
editor: null,
|
||||
users: [],
|
||||
status: 'connecting',
|
||||
room: getRandomRoom(),
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const ydoc = new Y.Doc()
|
||||
this.provider = new WebsocketProvider('wss://websocket.tiptap.dev', this.room, ydoc)
|
||||
this.provider.on('status', event => {
|
||||
this.status = event.status
|
||||
})
|
||||
|
||||
window.ydoc = ydoc
|
||||
|
||||
this.indexdb = new IndexeddbPersistence(this.room, ydoc)
|
||||
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
history: false,
|
||||
}),
|
||||
Highlight,
|
||||
TaskList,
|
||||
TaskItem,
|
||||
Collaboration.configure({
|
||||
document: ydoc,
|
||||
}),
|
||||
CollaborationCursor.configure({
|
||||
provider: this.provider,
|
||||
user: this.currentUser,
|
||||
onUpdate: users => {
|
||||
this.users = users
|
||||
},
|
||||
}),
|
||||
CharacterCount.configure({
|
||||
limit: 10000,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
localStorage.setItem('currentUser', JSON.stringify(this.currentUser))
|
||||
},
|
||||
|
||||
methods: {
|
||||
setName() {
|
||||
const name = (window.prompt('Name') || '')
|
||||
.trim()
|
||||
.substring(0, 32)
|
||||
|
||||
if (name) {
|
||||
return this.updateCurrentUser({
|
||||
name,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
updateCurrentUser(attributes) {
|
||||
this.currentUser = { ...this.currentUser, ...attributes }
|
||||
this.editor.chain().focus().user(this.currentUser).run()
|
||||
|
||||
localStorage.setItem('currentUser', JSON.stringify(this.currentUser))
|
||||
},
|
||||
|
||||
getRandomColor() {
|
||||
return getRandomElement([
|
||||
'#958DF1',
|
||||
'#F98181',
|
||||
'#FBBC88',
|
||||
'#FAF594',
|
||||
'#70CFF8',
|
||||
'#94FADB',
|
||||
'#B9F18D',
|
||||
])
|
||||
},
|
||||
|
||||
getRandomName() {
|
||||
return getRandomElement([
|
||||
'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet',
|
||||
])
|
||||
},
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
this.provider.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 400px;
|
||||
color: #0D0D0D;
|
||||
background-color: $colorWhite;
|
||||
border: 3px solid #0D0D0D;
|
||||
border-radius: 0.75rem;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.25rem;
|
||||
border-bottom: 3px solid #0D0D0D;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 1.25rem 1rem;
|
||||
flex: 1 1 auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
white-space: nowrap;
|
||||
border-top: 3px solid #0D0D0D;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #0D0D0D;
|
||||
white-space: nowrap;
|
||||
padding: 0.25rem 0.75rem;
|
||||
}
|
||||
|
||||
/* Some information about the status */
|
||||
&__status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
flex: 0 0 auto;
|
||||
display: inline-block;
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
background: rgba(#0D0D0D, 0.5);
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&--connecting::before {
|
||||
background: #616161;
|
||||
}
|
||||
|
||||
&--connected::before {
|
||||
background: #B9F18D;
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #0D0D0D;
|
||||
border-radius: 0.4rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
|
||||
&:hover {
|
||||
color: #FFF;
|
||||
background-color: #0D0D0D;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<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;
|
||||
}
|
||||
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #FAF594;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
ul[data-type="taskList"] {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> label {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,79 +0,0 @@
|
||||
import React from 'react'
|
||||
import './MentionList.scss'
|
||||
|
||||
export class MentionList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
selectedIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(oldProps) {
|
||||
if (this.props.items !== oldProps.items) {
|
||||
this.setState({
|
||||
selectedIndex: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown({ event }) {
|
||||
if (event.key === 'ArrowUp') {
|
||||
this.upHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
this.downHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
this.enterHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
upHandler() {
|
||||
this.setState({
|
||||
selectedIndex: ((this.state.selectedIndex + this.props.items.length) - 1) % this.props.items.length,
|
||||
})
|
||||
}
|
||||
|
||||
downHandler() {
|
||||
this.setState({
|
||||
selectedIndex: (this.state.selectedIndex + 1) % this.props.items.length,
|
||||
})
|
||||
}
|
||||
|
||||
enterHandler() {
|
||||
this.selectItem(this.state.selectedIndex)
|
||||
}
|
||||
|
||||
selectItem(index) {
|
||||
const item = this.props.items[index]
|
||||
|
||||
if (item) {
|
||||
this.props.command({ id: item })
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="items">
|
||||
{this.props.items.map((item, index) => (
|
||||
<button
|
||||
className={`item ${index === this.state.selectedIndex ? 'is-selected' : ''}`}
|
||||
key={index}
|
||||
onClick={() => this.selectItem(index)}
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
.items {
|
||||
position: relative;
|
||||
border-radius: 0.25rem;
|
||||
background: white;
|
||||
color: rgba(black, 0.8);
|
||||
overflow: hidden;
|
||||
font-size: 0.9rem;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0, 0, 0, 0.1),
|
||||
0px 10px 20px rgba(0, 0, 0, 0.1),
|
||||
;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0.2rem 0.5rem;
|
||||
|
||||
&.is-selected,
|
||||
&:hover {
|
||||
color: #A975FF;
|
||||
background: rgba(#A975FF, 0.1);
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
import React from 'react'
|
||||
import tippy from 'tippy.js'
|
||||
import { useEditor, EditorContent, ReactRenderer } from '@tiptap/react'
|
||||
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'
|
||||
import Mention from '@tiptap/extension-mention'
|
||||
import { MentionList } from './MentionList'
|
||||
import './styles.scss'
|
||||
|
||||
export default () => {
|
||||
const limit = 280
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
CharacterCount.configure({
|
||||
limit,
|
||||
}),
|
||||
Mention.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'mention',
|
||||
},
|
||||
suggestion: {
|
||||
items: query => {
|
||||
return [
|
||||
'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet',
|
||||
].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5)
|
||||
},
|
||||
render: () => {
|
||||
let reactRenderer
|
||||
let popup
|
||||
|
||||
return {
|
||||
onStart: props => {
|
||||
reactRenderer = new ReactRenderer(MentionList, {
|
||||
props,
|
||||
editor: props.editor,
|
||||
})
|
||||
|
||||
popup = tippy('body', {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.body,
|
||||
content: reactRenderer.element,
|
||||
showOnCreate: true,
|
||||
interactive: true,
|
||||
trigger: 'manual',
|
||||
placement: 'bottom-start',
|
||||
})
|
||||
},
|
||||
onUpdate(props) {
|
||||
reactRenderer.updateProps(props)
|
||||
|
||||
popup[0].setProps({
|
||||
getReferenceClientRect: props.clientRect,
|
||||
})
|
||||
},
|
||||
onKeyDown(props) {
|
||||
if (props.event.key === 'Escape') {
|
||||
popup[0].hide()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return reactRenderer.ref?.onKeyDown(props)
|
||||
},
|
||||
onExit() {
|
||||
popup[0].destroy()
|
||||
reactRenderer.destroy()
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
What do you all think about the new <span data-mention data-id="Winona Ryder"></span> movie?
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
|
||||
const percentage = editor
|
||||
? Math.round((100 / limit) * editor.getCharacterCount())
|
||||
: 0
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EditorContent editor={editor} />
|
||||
{editor
|
||||
&& <div className={`character-count ${editor.getCharacterCount() === limit ? 'character-count--warning' : ''}`}>
|
||||
<svg
|
||||
height="20"
|
||||
width="20"
|
||||
viewBox="0 0 20 20"
|
||||
className="character-count__graph"
|
||||
>
|
||||
<circle
|
||||
r="10"
|
||||
cx="10"
|
||||
cy="10"
|
||||
fill="#e9ecef"
|
||||
/>
|
||||
<circle
|
||||
r="5"
|
||||
cx="10"
|
||||
cy="10"
|
||||
fill="transparent"
|
||||
stroke="currentColor"
|
||||
strokeWidth="10"
|
||||
strokeDasharray={`calc(${percentage} * 31.4 / 100) 31.4`}
|
||||
transform="rotate(-90) translate(-20)"
|
||||
/>
|
||||
<circle
|
||||
r="6"
|
||||
cx="10"
|
||||
cy="10"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div className="character-count__text">
|
||||
{editor.getCharacterCount()}/{limit} characters
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
color: #A975FF;
|
||||
background-color: rgba(#A975FF, 0.1);
|
||||
border-radius: 0.3rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
}
|
||||
|
||||
.character-count {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #68CEF8;
|
||||
|
||||
&--warning {
|
||||
color: #FB5151;
|
||||
}
|
||||
|
||||
&__graph {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: #868e96;
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<div class="items">
|
||||
<button
|
||||
class="item"
|
||||
:class="{ 'is-selected': index === selectedIndex }"
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
@click="selectItem(index)"
|
||||
>
|
||||
{{ item }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
command: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: 0,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
items() {
|
||||
this.selectedIndex = 0
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onKeyDown({ event }) {
|
||||
if (event.key === 'ArrowUp') {
|
||||
this.upHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
this.downHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
this.enterHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
upHandler() {
|
||||
this.selectedIndex = ((this.selectedIndex + this.items.length) - 1) % this.items.length
|
||||
},
|
||||
|
||||
downHandler() {
|
||||
this.selectedIndex = (this.selectedIndex + 1) % this.items.length
|
||||
},
|
||||
|
||||
enterHandler() {
|
||||
this.selectItem(this.selectedIndex)
|
||||
},
|
||||
|
||||
selectItem(index) {
|
||||
const item = this.items[index]
|
||||
|
||||
if (item) {
|
||||
this.command({ id: item })
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.items {
|
||||
position: relative;
|
||||
border-radius: 0.25rem;
|
||||
background: white;
|
||||
color: rgba(black, 0.8);
|
||||
overflow: hidden;
|
||||
font-size: 0.9rem;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0, 0, 0, 0.1),
|
||||
0px 10px 20px rgba(0, 0, 0, 0.1),
|
||||
;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0.2rem 0.5rem;
|
||||
|
||||
&.is-selected,
|
||||
&:hover {
|
||||
color: #A975FF;
|
||||
background: rgba(#A975FF, 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/Community/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Community/Vue')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,192 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<editor-content :editor="editor" />
|
||||
|
||||
<div v-if="editor" :class="{'character-count': true, 'character-count--warning': editor.getCharacterCount() === limit}">
|
||||
<svg
|
||||
height="20"
|
||||
width="20"
|
||||
viewBox="0 0 20 20"
|
||||
class="character-count__graph"
|
||||
>
|
||||
<circle
|
||||
r="10"
|
||||
cx="10"
|
||||
cy="10"
|
||||
fill="#e9ecef"
|
||||
/>
|
||||
<circle
|
||||
r="5"
|
||||
cx="10"
|
||||
cy="10"
|
||||
fill="transparent"
|
||||
stroke="currentColor"
|
||||
stroke-width="10"
|
||||
:stroke-dasharray="`calc(${percentage} * 31.4 / 100) 31.4`"
|
||||
transform="rotate(-90) translate(-20)"
|
||||
/>
|
||||
<circle
|
||||
r="6"
|
||||
cx="10"
|
||||
cy="10"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div class="character-count__text">
|
||||
{{ editor.getCharacterCount() }}/{{ limit }} characters
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tippy from 'tippy.js'
|
||||
import { Editor, EditorContent, VueRenderer } from '@tiptap/vue-2'
|
||||
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'
|
||||
import Mention from '@tiptap/extension-mention'
|
||||
import MentionList from './MentionList'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
limit: 280,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
CharacterCount.configure({
|
||||
limit: this.limit,
|
||||
}),
|
||||
Mention.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'mention',
|
||||
},
|
||||
suggestion: {
|
||||
items: query => {
|
||||
return [
|
||||
'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet',
|
||||
].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5)
|
||||
},
|
||||
render: () => {
|
||||
let component
|
||||
let popup
|
||||
|
||||
return {
|
||||
onStart: props => {
|
||||
component = new VueRenderer(MentionList, {
|
||||
parent: this,
|
||||
propsData: props,
|
||||
})
|
||||
|
||||
popup = tippy('body', {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.body,
|
||||
content: component.element,
|
||||
showOnCreate: true,
|
||||
interactive: true,
|
||||
trigger: 'manual',
|
||||
placement: 'bottom-start',
|
||||
})
|
||||
},
|
||||
onUpdate(props) {
|
||||
component.updateProps(props)
|
||||
|
||||
popup[0].setProps({
|
||||
getReferenceClientRect: props.clientRect,
|
||||
})
|
||||
},
|
||||
onKeyDown(props) {
|
||||
if (props.event.key === 'Escape') {
|
||||
popup[0].hide()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return component.ref?.onKeyDown(props)
|
||||
},
|
||||
onExit() {
|
||||
popup[0].destroy()
|
||||
component.destroy()
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
What do you all think about the new <span data-mention data-id="Winona Ryder"></span> movie?
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
computed: {
|
||||
percentage() {
|
||||
return Math.round((100 / this.limit) * this.editor.getCharacterCount())
|
||||
},
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
color: #A975FF;
|
||||
background-color: rgba(#A975FF, 0.1);
|
||||
border-radius: 0.3rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
}
|
||||
|
||||
.character-count {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #68CEF8;
|
||||
|
||||
&--warning {
|
||||
color: #FB5151;
|
||||
}
|
||||
|
||||
&__graph {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: #868e96;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,168 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import './styles.scss'
|
||||
|
||||
const MenuBar = ({ editor }) => {
|
||||
if (!editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||
className={editor.isActive('code') ? 'is-active' : ''}
|
||||
>
|
||||
code
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
|
||||
clear marks
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().clearNodes().run()}>
|
||||
clear nodes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().setParagraph().run()}
|
||||
className={editor.isActive('paragraph') ? 'is-active' : ''}
|
||||
>
|
||||
paragraph
|
||||
</button>
|
||||
<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().toggleHeading({ level: 3 }).run()}
|
||||
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
|
||||
>
|
||||
h3
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
|
||||
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
|
||||
>
|
||||
h4
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
|
||||
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
|
||||
>
|
||||
h5
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
|
||||
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
|
||||
>
|
||||
h6
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
className={editor.isActive('bulletList') ? 'is-active' : ''}
|
||||
>
|
||||
bullet list
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
className={editor.isActive('orderedList') ? 'is-active' : ''}
|
||||
>
|
||||
ordered list
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||
className={editor.isActive('codeBlock') ? 'is-active' : ''}
|
||||
>
|
||||
code block
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||
className={editor.isActive('blockquote') ? 'is-active' : ''}
|
||||
>
|
||||
blockquote
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
|
||||
horizontal rule
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setHardBreak().run()}>
|
||||
hard break
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().undo().run()}>
|
||||
undo
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().redo().run()}>
|
||||
redo
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
],
|
||||
content: `
|
||||
<h2>
|
||||
Hi there,
|
||||
</h2>
|
||||
<p>
|
||||
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
That’s a bullet list with one …
|
||||
</li>
|
||||
<li>
|
||||
… or two list items.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
|
||||
</p>
|
||||
<pre><code class="language-css">body {
|
||||
display: none;
|
||||
}</code></pre>
|
||||
<p>
|
||||
I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.
|
||||
</p>
|
||||
<blockquote>
|
||||
Wow, that’s amazing. Good work, boy! 👏
|
||||
<br />
|
||||
— Mom
|
||||
</blockquote>
|
||||
`,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MenuBar editor={editor} />
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
context('/demos/Examples/Default/React', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Default/React')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<h1>Example Text</h1>')
|
||||
cy.get('.ProseMirror').type('{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')
|
||||
})
|
||||
})
|
@ -1,56 +0,0 @@
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
context('/demos/Examples/Default/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Default/Vue')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<h1>Example Text</h1>')
|
||||
cy.get('.ProseMirror').type('{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')
|
||||
})
|
||||
})
|
@ -1,188 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div 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>
|
||||
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
|
||||
code
|
||||
</button>
|
||||
<button @click="editor.chain().focus().unsetAllMarks().run()">
|
||||
clear marks
|
||||
</button>
|
||||
<button @click="editor.chain().focus().clearNodes().run()">
|
||||
clear nodes
|
||||
</button>
|
||||
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
|
||||
paragraph
|
||||
</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>
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
|
||||
h3
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
|
||||
h4
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
|
||||
h5
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
|
||||
h6
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
|
||||
bullet list
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
|
||||
ordered list
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
|
||||
code block
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
|
||||
blockquote
|
||||
</button>
|
||||
<button @click="editor.chain().focus().setHorizontalRule().run()">
|
||||
horizontal rule
|
||||
</button>
|
||||
<button @click="editor.chain().focus().setHardBreak().run()">
|
||||
hard break
|
||||
</button>
|
||||
<button @click="editor.chain().focus().undo().run()">
|
||||
undo
|
||||
</button>
|
||||
<button @click="editor.chain().focus().redo().run()">
|
||||
redo
|
||||
</button>
|
||||
</div>
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
],
|
||||
content: `
|
||||
<h2>
|
||||
Hi there,
|
||||
</h2>
|
||||
<p>
|
||||
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
That’s a bullet list with one …
|
||||
</li>
|
||||
<li>
|
||||
… or two list items.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:
|
||||
</p>
|
||||
<pre><code class="language-css">body {
|
||||
display: none;
|
||||
}</code></pre>
|
||||
<p>
|
||||
I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.
|
||||
</p>
|
||||
<blockquote>
|
||||
Wow, that’s amazing. Good work, boy! 👏
|
||||
<br />
|
||||
— Mom
|
||||
</blockquote>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,178 +0,0 @@
|
||||
<template>
|
||||
<node-view-wrapper class="draw">
|
||||
<input type="color" v-model="color">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="10"
|
||||
v-model="size"
|
||||
>
|
||||
<button @click="clear">
|
||||
clear
|
||||
</button>
|
||||
<svg viewBox="0 0 500 250" ref="canvas">
|
||||
<template v-for="item in node.attrs.lines">
|
||||
<path
|
||||
v-if="item.id !== id"
|
||||
:key="item.id"
|
||||
:d="item.path"
|
||||
:id="`id-${item.id}`"
|
||||
:stroke="item.color"
|
||||
:stroke-width="item.size"
|
||||
/>
|
||||
</template>
|
||||
</svg>
|
||||
</node-view-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper } from '@tiptap/vue-2'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import * as d3 from 'd3'
|
||||
import simplify from 'simplify-js'
|
||||
|
||||
const getRandomElement = list => {
|
||||
return list[Math.floor(Math.random() * list.length)]
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'Paper',
|
||||
|
||||
components: {
|
||||
NodeViewWrapper,
|
||||
},
|
||||
|
||||
props: {
|
||||
updateAttributes: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
|
||||
node: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
color: getRandomElement([
|
||||
'#A975FF',
|
||||
'#FB5151',
|
||||
'#FD9170',
|
||||
'#FFCB6B',
|
||||
'#68CEF8',
|
||||
'#80CBC4',
|
||||
'#9DEF8F',
|
||||
]),
|
||||
size: Math.ceil(Math.random() * Math.floor(10)),
|
||||
svg: null,
|
||||
path: null,
|
||||
points: [],
|
||||
drawing: false,
|
||||
id: uuid(),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onStartDrawing(event) {
|
||||
this.drawing = true
|
||||
this.points = []
|
||||
this.path = this.svg
|
||||
.append('path')
|
||||
.data([this.points])
|
||||
.attr('id', `id-${this.id}`)
|
||||
.attr('stroke', this.color)
|
||||
.attr('stroke-width', this.size)
|
||||
|
||||
const moveEvent = event.type === 'mousedown'
|
||||
? 'mousemove'
|
||||
: 'touchmove'
|
||||
|
||||
this.svg.on(moveEvent, this.onMove)
|
||||
},
|
||||
|
||||
onMove(event) {
|
||||
event.preventDefault()
|
||||
this.points.push(d3.pointers(event)[0])
|
||||
this.tick()
|
||||
},
|
||||
|
||||
onEndDrawing() {
|
||||
this.svg.on('mousemove', null)
|
||||
this.svg.on('touchmove', null)
|
||||
|
||||
if (!this.drawing) {
|
||||
return
|
||||
}
|
||||
|
||||
this.drawing = false
|
||||
this.svg.select(`#id-${this.id}`).remove()
|
||||
this.id = uuid()
|
||||
},
|
||||
|
||||
simplifyPoints(points) {
|
||||
return simplify(points.map(point => ({ x: point[0], y: point[1] }))).map(point => [point.x, point.y])
|
||||
},
|
||||
|
||||
tick() {
|
||||
requestAnimationFrame(() => {
|
||||
this.path.attr('d', points => {
|
||||
const path = d3.line().curve(d3.curveBasis)(points)
|
||||
// const simplifiedPath = d3.line().curve(d3.curveBasis)(this.simplifyPoints(points))
|
||||
const lines = this.node.attrs.lines.filter(item => item.id !== this.id)
|
||||
|
||||
this.updateAttributes({
|
||||
lines: [
|
||||
...lines,
|
||||
{
|
||||
id: this.id,
|
||||
color: this.color,
|
||||
size: this.size,
|
||||
path,
|
||||
// path: simplifiedPath,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return path
|
||||
})
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.updateAttributes({
|
||||
lines: [],
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.svg = d3.select(this.$refs.canvas)
|
||||
|
||||
this.svg
|
||||
.on('mousedown', this.onStartDrawing)
|
||||
.on('mouseup', this.onEndDrawing)
|
||||
.on('mouseleave', this.onEndDrawing)
|
||||
.on('touchstart', this.onStartDrawing)
|
||||
.on('touchend', this.onEndDrawing)
|
||||
.on('touchleave', this.onEndDrawing)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.draw {
|
||||
svg {
|
||||
background: #f1f3f5;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,35 +0,0 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { VueNodeViewRenderer } from '@tiptap/vue-2'
|
||||
import Component from './Component.vue'
|
||||
|
||||
export default Node.create({
|
||||
name: 'paper',
|
||||
|
||||
group: 'block',
|
||||
|
||||
atom: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
lines: {
|
||||
default: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'div[data-type="paper"]',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'paper' })]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return VueNodeViewRenderer(Component)
|
||||
},
|
||||
})
|
@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import Paper from './Paper.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document.extend({
|
||||
content: 'paper',
|
||||
}),
|
||||
Text,
|
||||
Paper,
|
||||
],
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
@ -1,98 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import TextAlign from '@tiptap/extension-text-align'
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
import './styles.scss'
|
||||
|
||||
const MenuBar = ({ editor }) => {
|
||||
if (!editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<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().toggleHeading({ level: 3 }).run()} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}>
|
||||
h3
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setParagraph().run()} className={editor.isActive('paragraph') ? 'is-active' : ''}>
|
||||
paragraph
|
||||
</button>
|
||||
<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>
|
||||
<button onClick={() => editor.chain().focus().toggleHighlight().run()} className={editor.isActive('highlight') ? 'is-active' : ''}>
|
||||
highlight
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setTextAlign('left').run()} className={editor.isActive({ textAlign: 'left' }) ? 'is-active' : ''}>
|
||||
left
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setTextAlign('center').run()} className={editor.isActive({ textAlign: 'center' }) ? 'is-active' : ''}>
|
||||
center
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setTextAlign('right').run()} className={editor.isActive({ textAlign: 'right' }) ? 'is-active' : ''}>
|
||||
right
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setTextAlign('justify').run()} className={editor.isActive({ textAlign: 'justify' }) ? 'is-active' : ''}>
|
||||
justify
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
TextAlign.configure({
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
Highlight,
|
||||
],
|
||||
content: `
|
||||
<h3 style="text-align:center">
|
||||
Devs Just Want to Have Fun by Cyndi Lauper
|
||||
</h3>
|
||||
<p style="text-align:center">
|
||||
I come home in the morning light<br>
|
||||
My mother says, <mark>“When you gonna live your life right?”</mark><br>
|
||||
Oh mother dear we’re not the fortunate ones<br>
|
||||
And devs, they wanna have fun<br>
|
||||
Oh devs just want to have fun</p>
|
||||
<p style="text-align:center">
|
||||
The phone rings in the middle of the night<br>
|
||||
My father yells, "What you gonna do with your life?"<br>
|
||||
Oh daddy dear, you know you’re still number one<br>
|
||||
But <s>girls</s>devs, they wanna have fun<br>
|
||||
Oh devs just want to have
|
||||
</p>
|
||||
<p style="text-align:center">
|
||||
That’s all they really want<br>
|
||||
Some fun<br>
|
||||
When the working day is done<br>
|
||||
Oh devs, they wanna have fun<br>
|
||||
Oh devs just wanna have fun<br>
|
||||
(devs, they wanna, wanna have fun, devs wanna have)
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MenuBar editor={editor} />
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
margin-top: 1rem;
|
||||
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #FAF594;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Vue')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,169 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div 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().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
|
||||
h3
|
||||
</button>
|
||||
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
|
||||
paragraph
|
||||
</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().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
|
||||
strike
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHighlight().run()" :class="{ 'is-active': editor.isActive('highlight') }">
|
||||
highlight
|
||||
</button>
|
||||
<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>
|
||||
</div>
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import TextAlign from '@tiptap/extension-text-align'
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
TextAlign.configure({
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
Highlight,
|
||||
],
|
||||
content: `
|
||||
<h3 style="text-align:center">
|
||||
Devs Just Want to Have Fun by Cyndi Lauper
|
||||
</h3>
|
||||
<p style="text-align:center">
|
||||
I come home in the morning light<br>
|
||||
My mother says, <mark>“When you gonna live your life right?”</mark><br>
|
||||
Oh mother dear we’re not the fortunate ones<br>
|
||||
And devs, they wanna have fun<br>
|
||||
Oh devs just want to have fun</p>
|
||||
<p style="text-align:center">
|
||||
The phone rings in the middle of the night<br>
|
||||
My father yells, "What you gonna do with your life?"<br>
|
||||
Oh daddy dear, you know you’re still number one<br>
|
||||
But <s>girls</s>devs, they wanna have fun<br>
|
||||
Oh devs just want to have
|
||||
</p>
|
||||
<p style="text-align:center">
|
||||
That’s all they really want<br>
|
||||
Some fun<br>
|
||||
When the working day is done<br>
|
||||
Oh devs, they wanna have fun<br>
|
||||
Oh devs just wanna have fun<br>
|
||||
(devs, they wanna, wanna have fun, devs wanna have)
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
margin-top: 1rem;
|
||||
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #FAF594;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,42 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
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'
|
||||
import './styles.scss'
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
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" />
|
||||
`,
|
||||
})
|
||||
|
||||
const addImage = () => {
|
||||
const url = window.prompt('URL')
|
||||
|
||||
if (url) {
|
||||
editor.chain().focus().setImage({ src: url }).run()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={addImage}>
|
||||
add image from URL
|
||||
</button>
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
|
||||
&.ProseMirror-selectednode {
|
||||
outline: 3px solid #68CEF8;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Vue')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<button @click="addImage">
|
||||
add image from URL
|
||||
</button>
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
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>
|
@ -1,37 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
import Typography from '@tiptap/extension-typography'
|
||||
import './styles.scss'
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Highlight,
|
||||
Typography,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
Markdown shortcuts make it easy to format the text while typing.
|
||||
</p>
|
||||
<p>
|
||||
To test that, start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels.
|
||||
</p>
|
||||
<p>
|
||||
Those conventions are called input rules in tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code, <code>~~tildes~~</code> to strike text, or <code>==equal signs==</code> to highlight text.
|
||||
</p>
|
||||
<p>
|
||||
You can overwrite existing input rules or add your own to nodes, marks and extensions.
|
||||
</p>
|
||||
<p>
|
||||
For example, we added the <code>Typography</code> extension here. Try typing <code>(c)</code> to see how it’s converted to a proper © character. You can also try <code>-></code>, <code>>></code>, <code>1/2</code>, <code>!=</code>, or <code>--</code>.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
|
||||
return (
|
||||
<EditorContent editor={editor} />
|
||||
)
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
context('/demos/Examples/MarkdownShortcuts/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/MarkdownShortcuts/Vue')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.clearContent()
|
||||
})
|
||||
})
|
||||
|
||||
it('should make a h1', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('# Headline')
|
||||
.find('h1')
|
||||
.should('contain', 'Headline')
|
||||
})
|
||||
|
||||
it('should make a h2', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('## Headline')
|
||||
.find('h2')
|
||||
.should('contain', 'Headline')
|
||||
})
|
||||
|
||||
it('should make a h3', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('### Headline')
|
||||
.find('h3')
|
||||
.should('contain', 'Headline')
|
||||
})
|
||||
|
||||
it('should make a h4', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('#### Headline')
|
||||
.find('h4')
|
||||
.should('contain', 'Headline')
|
||||
})
|
||||
|
||||
it('should make a h5', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('##### Headline')
|
||||
.find('h5')
|
||||
.should('contain', 'Headline')
|
||||
})
|
||||
|
||||
it('should make a h6', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('###### Headline')
|
||||
.find('h6')
|
||||
.should('contain', 'Headline')
|
||||
})
|
||||
|
||||
it('should create inline code', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('`$foobar`')
|
||||
.find('code')
|
||||
.should('contain', '$foobar')
|
||||
})
|
||||
|
||||
it('should create a code block without language', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('``` {enter}const foo = bar{enter}```')
|
||||
.find('pre')
|
||||
.should('contain', 'const foo = bar')
|
||||
})
|
||||
|
||||
it('should create a bullet list from asteriks', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('* foobar')
|
||||
.find('ul')
|
||||
.should('contain', 'foobar')
|
||||
})
|
||||
|
||||
it('should create a bullet list from dashes', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('- foobar')
|
||||
.find('ul')
|
||||
.should('contain', 'foobar')
|
||||
})
|
||||
|
||||
it('should create a bullet list from pluses', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('+ foobar')
|
||||
.find('ul')
|
||||
.should('contain', 'foobar')
|
||||
})
|
||||
|
||||
it('should create a ordered list', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('1. foobar')
|
||||
.find('ol')
|
||||
.should('contain', 'foobar')
|
||||
})
|
||||
|
||||
it('should create a blockquote', () => {
|
||||
cy.get('.ProseMirror')
|
||||
.type('> foobar')
|
||||
.find('blockquote')
|
||||
.should('contain', 'foobar')
|
||||
})
|
||||
})
|
@ -1,110 +0,0 @@
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
import Typography from '@tiptap/extension-typography'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Highlight,
|
||||
Typography,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
Markdown shortcuts make it easy to format the text while typing.
|
||||
</p>
|
||||
<p>
|
||||
To test that, start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels.
|
||||
</p>
|
||||
<p>
|
||||
Those conventions are called input rules in tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code, <code>~~tildes~~</code> to strike text, or <code>==equal signs==</code> to highlight text.
|
||||
</p>
|
||||
<p>
|
||||
You can overwrite existing input rules or add your own to nodes, marks and extensions.
|
||||
</p>
|
||||
<p>
|
||||
For example, we added the <code>Typography</code> extension here. Try typing <code>(c)</code> to see how it’s converted to a proper © character. You can also try <code>-></code>, <code>>></code>, <code>1/2</code>, <code>!=</code>, or <code>--</code>.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
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>
|
@ -1,73 +0,0 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
useEditor,
|
||||
EditorContent,
|
||||
BubbleMenu,
|
||||
FloatingMenu,
|
||||
} from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import './styles.scss'
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
Try to select <em>this text</em> to see what we call the bubble menu.
|
||||
</p>
|
||||
<p>
|
||||
Neat, isn’t it? Add an empty paragraph to see the floating menu.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{editor && <BubbleMenu className="bubble-menu" tippyOptions={{ duration: 100 }} 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>}
|
||||
|
||||
{editor && <FloatingMenu className="floating-menu" tippyOptions={{ duration: 100 }} 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} />
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/BubbleMenu/React', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/BubbleMenu/React')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,53 +0,0 @@
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bubble-menu {
|
||||
display: flex;
|
||||
background-color: #0D0D0D;
|
||||
padding: 0.2rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
color: #FFF;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
padding: 0 0.2rem;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.floating-menu {
|
||||
display: flex;
|
||||
background-color: #0D0D0D10;
|
||||
padding: 0.2rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
padding: 0 0.2rem;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/BubbleMenu/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/BubbleMenu/Vue')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<bubble-menu
|
||||
class="bubble-menu"
|
||||
:tippy-options="{ duration: 100 }"
|
||||
: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>
|
||||
|
||||
<floating-menu
|
||||
class="floating-menu"
|
||||
:tippy-options="{ duration: 100 }"
|
||||
: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,
|
||||
BubbleMenu,
|
||||
FloatingMenu,
|
||||
} from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
BubbleMenu,
|
||||
FloatingMenu,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
Try to select <em>this text</em> to see what we call the bubble menu.
|
||||
</p>
|
||||
<p>
|
||||
Neat, isn’t it? Add an empty paragraph to see the floating menu.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
.bubble-menu {
|
||||
display: flex;
|
||||
background-color: #0D0D0D;
|
||||
padding: 0.2rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
color: #FFF;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
padding: 0 0.2rem;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.floating-menu {
|
||||
display: flex;
|
||||
background-color: #0D0D0D10;
|
||||
padding: 0.2rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
padding: 0 0.2rem;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,28 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import './styles.scss'
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That’s it. It’s probably too much for real minimalists though.
|
||||
</p>
|
||||
<p>
|
||||
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
|
||||
return (
|
||||
<EditorContent editor={editor} />
|
||||
)
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/Minimal/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Minimal/Vue')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,53 +0,0 @@
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That’s it. It’s probably too much for real minimalists though.
|
||||
</p>
|
||||
<p>
|
||||
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,29 +0,0 @@
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
import findColors from './findColors'
|
||||
|
||||
export const ColorHighlighter = Extension.create({
|
||||
name: 'colorHighlighter',
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
state: {
|
||||
init(_, { doc }) {
|
||||
return findColors(doc)
|
||||
},
|
||||
apply(transaction, oldState) {
|
||||
return transaction.docChanged
|
||||
? findColors(transaction.doc)
|
||||
: oldState
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return this.getState(state)
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
@ -1,134 +0,0 @@
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { InputRule } from 'prosemirror-inputrules'
|
||||
|
||||
export const SmilieReplacer = Extension.create({
|
||||
name: 'smilieReplacer',
|
||||
|
||||
addInputRules() {
|
||||
return [
|
||||
new InputRule(/-___- $/, '😑 '),
|
||||
new InputRule(/:'-\) $/, '😂 '),
|
||||
new InputRule(/':-\) $/, '😅 '),
|
||||
new InputRule(/':-D $/, '😅 '),
|
||||
new InputRule(/>:-\) $/, '😆 '),
|
||||
new InputRule(/-__- $/, '😑 '),
|
||||
new InputRule(/':-\( $/, '😓 '),
|
||||
new InputRule(/:'-\( $/, '😢 '),
|
||||
new InputRule(/>:-\( $/, '😠 '),
|
||||
new InputRule(/O:-\) $/, '😇 '),
|
||||
new InputRule(/0:-3 $/, '😇 '),
|
||||
new InputRule(/0:-\) $/, '😇 '),
|
||||
new InputRule(/0;\^\) $/, '😇 '),
|
||||
new InputRule(/O;-\) $/, '😇 '),
|
||||
new InputRule(/0;-\) $/, '😇 '),
|
||||
new InputRule(/O:-3 $/, '😇 '),
|
||||
new InputRule(/:'\) $/, '😂 '),
|
||||
new InputRule(/:-D $/, '😃 '),
|
||||
new InputRule(/':\) $/, '😅 '),
|
||||
new InputRule(/'=\) $/, '😅 '),
|
||||
new InputRule(/':D $/, '😅 '),
|
||||
new InputRule(/'=D $/, '😅 '),
|
||||
new InputRule(/>:\) $/, '😆 '),
|
||||
new InputRule(/>;\) $/, '😆 '),
|
||||
new InputRule(/>=\) $/, '😆 '),
|
||||
new InputRule(/;-\) $/, '😉 '),
|
||||
new InputRule(/\*-\) $/, '😉 '),
|
||||
new InputRule(/;-\] $/, '😉 '),
|
||||
new InputRule(/;\^\) $/, '😉 '),
|
||||
new InputRule(/B-\) $/, '😎 '),
|
||||
new InputRule(/8-\) $/, '😎 '),
|
||||
new InputRule(/B-D $/, '😎 '),
|
||||
new InputRule(/8-D $/, '😎 '),
|
||||
new InputRule(/:-\* $/, '😘 '),
|
||||
new InputRule(/:\^\* $/, '😘 '),
|
||||
new InputRule(/:-\) $/, '🙂 '),
|
||||
new InputRule(/-_- $/, '😑 '),
|
||||
new InputRule(/:-X $/, '😶 '),
|
||||
new InputRule(/:-# $/, '😶 '),
|
||||
new InputRule(/:-x $/, '😶 '),
|
||||
new InputRule(/>.< $/, '😣 '),
|
||||
new InputRule(/:-O $/, '😮 '),
|
||||
new InputRule(/:-o $/, '😮 '),
|
||||
new InputRule(/O_O $/, '😮 '),
|
||||
new InputRule(/>:O $/, '😮 '),
|
||||
new InputRule(/:-P $/, '😛 '),
|
||||
new InputRule(/:-p $/, '😛 '),
|
||||
new InputRule(/:-Þ $/, '😛 '),
|
||||
new InputRule(/:-þ $/, '😛 '),
|
||||
new InputRule(/:-b $/, '😛 '),
|
||||
new InputRule(/>:P $/, '😜 '),
|
||||
new InputRule(/X-P $/, '😜 '),
|
||||
new InputRule(/x-p $/, '😜 '),
|
||||
new InputRule(/':\( $/, '😓 '),
|
||||
new InputRule(/'=\( $/, '😓 '),
|
||||
new InputRule(/>:\\ $/, '😕 '),
|
||||
new InputRule(/>:\/ $/, '😕 '),
|
||||
new InputRule(/:-\/ $/, '😕 '),
|
||||
new InputRule(/:-. $/, '😕 '),
|
||||
new InputRule(/>:\[ $/, '😞 '),
|
||||
new InputRule(/:-\( $/, '😞 '),
|
||||
new InputRule(/:-\[ $/, '😞 '),
|
||||
new InputRule(/:'\( $/, '😢 '),
|
||||
new InputRule(/;-\( $/, '😢 '),
|
||||
new InputRule(/#-\) $/, '😵 '),
|
||||
new InputRule(/%-\) $/, '😵 '),
|
||||
new InputRule(/X-\) $/, '😵 '),
|
||||
new InputRule(/>:\( $/, '😠 '),
|
||||
new InputRule(/0:3 $/, '😇 '),
|
||||
new InputRule(/0:\) $/, '😇 '),
|
||||
new InputRule(/O:\) $/, '😇 '),
|
||||
new InputRule(/O=\) $/, '😇 '),
|
||||
new InputRule(/O:3 $/, '😇 '),
|
||||
new InputRule(/<\/3 $/, '💔 '),
|
||||
new InputRule(/:D $/, '😃 '),
|
||||
new InputRule(/=D $/, '😃 '),
|
||||
new InputRule(/;\) $/, '😉 '),
|
||||
new InputRule(/\*\) $/, '😉 '),
|
||||
new InputRule(/;\] $/, '😉 '),
|
||||
new InputRule(/;D $/, '😉 '),
|
||||
new InputRule(/B\) $/, '😎 '),
|
||||
new InputRule(/8\) $/, '😎 '),
|
||||
new InputRule(/:\* $/, '😘 '),
|
||||
new InputRule(/=\* $/, '😘 '),
|
||||
new InputRule(/:\) $/, '🙂 '),
|
||||
new InputRule(/=\] $/, '🙂 '),
|
||||
new InputRule(/=\) $/, '🙂 '),
|
||||
new InputRule(/:\] $/, '🙂 '),
|
||||
new InputRule(/:X $/, '😶 '),
|
||||
new InputRule(/:# $/, '😶 '),
|
||||
new InputRule(/=X $/, '😶 '),
|
||||
new InputRule(/=x $/, '😶 '),
|
||||
new InputRule(/:x $/, '😶 '),
|
||||
new InputRule(/=# $/, '😶 '),
|
||||
new InputRule(/:O $/, '😮 '),
|
||||
new InputRule(/:o $/, '😮 '),
|
||||
new InputRule(/:P $/, '😛 '),
|
||||
new InputRule(/=P $/, '😛 '),
|
||||
new InputRule(/:p $/, '😛 '),
|
||||
new InputRule(/=p $/, '😛 '),
|
||||
new InputRule(/:Þ $/, '😛 '),
|
||||
new InputRule(/:þ $/, '😛 '),
|
||||
new InputRule(/:b $/, '😛 '),
|
||||
new InputRule(/d: $/, '😛 '),
|
||||
new InputRule(/:\/ $/, '😕 '),
|
||||
new InputRule(/:\\ $/, '😕 '),
|
||||
new InputRule(/=\/ $/, '😕 '),
|
||||
new InputRule(/=\\ $/, '😕 '),
|
||||
new InputRule(/:L $/, '😕 '),
|
||||
new InputRule(/=L $/, '😕 '),
|
||||
new InputRule(/:\( $/, '😞 '),
|
||||
new InputRule(/:\[ $/, '😞 '),
|
||||
new InputRule(/=\( $/, '😞 '),
|
||||
new InputRule(/;\( $/, '😢 '),
|
||||
new InputRule(/D: $/, '😨 '),
|
||||
new InputRule(/:\$ $/, '😳 '),
|
||||
new InputRule(/=\$ $/, '😳 '),
|
||||
new InputRule(/#\) $/, '😵 '),
|
||||
new InputRule(/%\) $/, '😵 '),
|
||||
new InputRule(/X\) $/, '😵 '),
|
||||
new InputRule(/:@ $/, '😠 '),
|
||||
new InputRule(/<3 $/, '❤️ '),
|
||||
new InputRule(/\/shrug $/, '¯\\_(ツ)_/¯'),
|
||||
]
|
||||
},
|
||||
})
|
@ -1,30 +0,0 @@
|
||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
import { Node } from 'prosemirror-model'
|
||||
|
||||
export default function (doc: Node): DecorationSet {
|
||||
const hexColor = /(#[0-9a-f]{3,6})\b/ig
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
doc.descendants((node, position) => {
|
||||
if (!node.text) {
|
||||
return
|
||||
}
|
||||
|
||||
Array
|
||||
.from(node.text.matchAll(hexColor))
|
||||
.forEach(match => {
|
||||
const color = match[0]
|
||||
const index = match.index || 0
|
||||
const from = position + index
|
||||
const to = from + color.length
|
||||
const decoration = Decoration.inline(from, to, {
|
||||
class: 'color',
|
||||
style: `--color: ${color}`,
|
||||
})
|
||||
|
||||
decorations.push(decoration)
|
||||
})
|
||||
})
|
||||
|
||||
return DecorationSet.create(doc, decorations)
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/Savvy', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Savvy')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,102 +0,0 @@
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import Code from '@tiptap/extension-code'
|
||||
import Typography from '@tiptap/extension-typography'
|
||||
import { ColorHighlighter } from './ColorHighlighter'
|
||||
import { SmilieReplacer } from './SmilieReplacer'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Code,
|
||||
Typography,
|
||||
ColorHighlighter,
|
||||
SmilieReplacer,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
→ With the Typography extension, tiptap understands »what you mean« and adds correct characters to your text — it’s like a “typography nerd” on your side.
|
||||
</p>
|
||||
<p>
|
||||
Try it out and type <code>(c)</code>, <code>-></code>, <code>>></code>, <code>1/2</code>, <code>!=</code>, <code>--</code> or <code>1x1</code> here:
|
||||
</p>
|
||||
<p></p>
|
||||
<p>
|
||||
Or add completely custom input rules. We added a custom extension here that replaces smilies like <code>:-)</code>, <code><3</code> or <code>>:P</code> with emojis. Try it out:
|
||||
</p>
|
||||
<p></p>
|
||||
<p>
|
||||
You can also teach the editor new things. For example to recognize hex colors and add a color swatch on the fly: #FFF, #0D0D0D, #616161, #A975FF, #FB5151, #FD9170, #FFCB6B, #68CEF8, #80cbc4, #9DEF8F
|
||||
</p>
|
||||
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: rgba(#616161, 0.1);
|
||||
color: #616161;
|
||||
}
|
||||
}
|
||||
|
||||
/* Color swatches */
|
||||
.color {
|
||||
white-space: nowrap;
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border: 1px solid rgba(128, 128, 128, 0.3);
|
||||
vertical-align: middle;
|
||||
margin-right: 0.1em;
|
||||
margin-bottom: 0.15em;
|
||||
border-radius: 2px;
|
||||
background-color: var(--color);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,192 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Table from '@tiptap/extension-table'
|
||||
import TableRow from '@tiptap/extension-table-row'
|
||||
import TableCell from '@tiptap/extension-table-cell'
|
||||
import TableHeader from '@tiptap/extension-table-header'
|
||||
import './styles.scss'
|
||||
|
||||
const CustomTableCell = TableCell.extend({
|
||||
addAttributes() {
|
||||
return {
|
||||
// extend the existing attributes …
|
||||
...this.parent?.(),
|
||||
|
||||
// and add a new one …
|
||||
backgroundColor: {
|
||||
default: null,
|
||||
parseHTML: element => {
|
||||
return {
|
||||
backgroundColor: element.getAttribute('data-background-color'),
|
||||
}
|
||||
},
|
||||
renderHTML: attributes => {
|
||||
return {
|
||||
'data-background-color': attributes.backgroundColor,
|
||||
style: `background-color: ${attributes.backgroundColor}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const tableHTML = `
|
||||
<table style="width:100%">
|
||||
<tr>
|
||||
<th>Firstname</th>
|
||||
<th>Lastname</th>
|
||||
<th>Age</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jill</td>
|
||||
<td>Smith</td>
|
||||
<td>50</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Eve</td>
|
||||
<td>Jackson</td>
|
||||
<td>94</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>John</td>
|
||||
<td>Doe</td>
|
||||
<td>80</td>
|
||||
</tr>
|
||||
</table>
|
||||
`
|
||||
|
||||
const MenuBar = ({ editor }) => {
|
||||
if (!editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}>
|
||||
insertTable
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().insertContent(tableHTML, {
|
||||
parseOptions: {
|
||||
preserveWhitespace: false,
|
||||
},
|
||||
}).run()}>
|
||||
insertHTMLTable
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().addColumnBefore().run()} disabled={!editor.can().addColumnBefore()}>
|
||||
addColumnBefore
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().addColumnAfter().run()} disabled={!editor.can().addColumnAfter()}>
|
||||
addColumnAfter
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().deleteColumn().run()} disabled={!editor.can().deleteColumn()}>
|
||||
deleteColumn
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().addRowBefore().run()} disabled={!editor.can().addRowBefore()}>
|
||||
addRowBefore
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().addRowAfter().run()} disabled={!editor.can().addRowAfter()}>
|
||||
addRowAfter
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().deleteRow().run()} disabled={!editor.can().deleteRow()}>
|
||||
deleteRow
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().deleteTable().run()} disabled={!editor.can().deleteTable()}>
|
||||
deleteTable
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().mergeCells().run()} disabled={!editor.can().mergeCells()}>
|
||||
mergeCells
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().splitCell().run()} disabled={!editor.can().splitCell()}>
|
||||
splitCell
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().toggleHeaderColumn().run()} disabled={!editor.can().toggleHeaderColumn()}>
|
||||
toggleHeaderColumn
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().toggleHeaderRow().run()} disabled={!editor.can().toggleHeaderRow()}>
|
||||
toggleHeaderRow
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().toggleHeaderCell().run()} disabled={!editor.can().toggleHeaderCell()}>
|
||||
toggleHeaderCell
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().mergeOrSplit().run()} disabled={!editor.can().mergeOrSplit()}>
|
||||
mergeOrSplit
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().setCellAttribute('backgroundColor', '#FAF594').run()} disabled={!editor.can().setCellAttribute('backgroundColor', '#FAF594')}>
|
||||
setCellAttribute
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().fixTables().run()} disabled={!editor.can().fixTables()}>
|
||||
fixTables
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().goToNextCell().run()} disabled={!editor.can().goToNextCell()}>
|
||||
goToNextCell
|
||||
</button>
|
||||
<button onClick={() => editor.chain().focus().goToPreviousCell().run()} disabled={!editor.can().goToPreviousCell()}>
|
||||
goToPreviousCell
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Table.configure({
|
||||
resizable: true,
|
||||
}),
|
||||
TableRow,
|
||||
TableHeader,
|
||||
// Default TableCell
|
||||
// TableCell,
|
||||
// Custom TableCell with backgroundColor attribute
|
||||
CustomTableCell,
|
||||
],
|
||||
content: `
|
||||
<h3>
|
||||
Have you seen our tables? They are amazing!
|
||||
</h3>
|
||||
<ul>
|
||||
<li>tables with rows, cells and headers (optional)</li>
|
||||
<li>support for <code>colgroup</code> and <code>rowspan</code></li>
|
||||
<li>and even resizable columns (optional)</li>
|
||||
</ul>
|
||||
<p>
|
||||
Here is an example:
|
||||
</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th colspan="3">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cyndi Lauper</td>
|
||||
<td>singer</td>
|
||||
<td>songwriter</td>
|
||||
<td>actress</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Philipp Kühn</td>
|
||||
<td>designer</td>
|
||||
<td>developer</td>
|
||||
<td>maker</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hans Pagel</td>
|
||||
<td>wrote this</td>
|
||||
<td colspan="2">that’s it</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MenuBar editor={editor} />
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
margin: 1rem 0;
|
||||
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Table-specific styling */
|
||||
.ProseMirror {
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
|
||||
td,
|
||||
th {
|
||||
min-width: 1em;
|
||||
border: 2px solid #ced4da;
|
||||
padding: 3px 5px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
> * {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
background-color: #f1f3f5;
|
||||
}
|
||||
|
||||
.selectedCell:after {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
background: rgba(200, 200, 255, 0.4);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.column-resize-handle {
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
top: 0;
|
||||
bottom: -2px;
|
||||
width: 4px;
|
||||
background-color: #adf;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tableWrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.resize-cursor {
|
||||
cursor: ew-resize;
|
||||
cursor: col-resize;
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/Tables/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Tables/Vue')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,285 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<button @click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()">
|
||||
insertTable
|
||||
</button>
|
||||
<button @click="editor.chain().focus().addColumnBefore().run()" :disabled="!editor.can().addColumnBefore()">
|
||||
addColumnBefore
|
||||
</button>
|
||||
<button @click="editor.chain().focus().addColumnAfter().run()" :disabled="!editor.can().addColumnAfter()">
|
||||
addColumnAfter
|
||||
</button>
|
||||
<button @click="editor.chain().focus().deleteColumn().run()" :disabled="!editor.can().deleteColumn()">
|
||||
deleteColumn
|
||||
</button>
|
||||
<button @click="editor.chain().focus().addRowBefore().run()" :disabled="!editor.can().addRowBefore()">
|
||||
addRowBefore
|
||||
</button>
|
||||
<button @click="editor.chain().focus().addRowAfter().run()" :disabled="!editor.can().addRowAfter()">
|
||||
addRowAfter
|
||||
</button>
|
||||
<button @click="editor.chain().focus().deleteRow().run()" :disabled="!editor.can().deleteRow()">
|
||||
deleteRow
|
||||
</button>
|
||||
<button @click="editor.chain().focus().deleteTable().run()" :disabled="!editor.can().deleteTable()">
|
||||
deleteTable
|
||||
</button>
|
||||
<button @click="editor.chain().focus().mergeCells().run()" :disabled="!editor.can().mergeCells()">
|
||||
mergeCells
|
||||
</button>
|
||||
<button @click="editor.chain().focus().splitCell().run()" :disabled="!editor.can().splitCell()">
|
||||
splitCell
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeaderColumn().run()" :disabled="!editor.can().toggleHeaderColumn()">
|
||||
toggleHeaderColumn
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeaderRow().run()" :disabled="!editor.can().toggleHeaderRow()">
|
||||
toggleHeaderRow
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeaderCell().run()" :disabled="!editor.can().toggleHeaderCell()">
|
||||
toggleHeaderCell
|
||||
</button>
|
||||
<button @click="editor.chain().focus().mergeOrSplit().run()" :disabled="!editor.can().mergeOrSplit()">
|
||||
mergeOrSplit
|
||||
</button>
|
||||
<button @click="editor.chain().focus().setCellAttribute('backgroundColor', '#FAF594').run()" :disabled="!editor.can().setCellAttribute('backgroundColor', '#FAF594')">
|
||||
setCellAttribute
|
||||
</button>
|
||||
<button @click="editor.chain().focus().fixTables().run()" :disabled="!editor.can().fixTables()">
|
||||
fixTables
|
||||
</button>
|
||||
<button @click="editor.chain().focus().goToNextCell().run()" :disabled="!editor.can().goToNextCell()">
|
||||
goToNextCell
|
||||
</button>
|
||||
<button @click="editor.chain().focus().goToPreviousCell().run()" :disabled="!editor.can().goToPreviousCell()">
|
||||
goToPreviousCell
|
||||
</button>
|
||||
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Table from '@tiptap/extension-table'
|
||||
import TableRow from '@tiptap/extension-table-row'
|
||||
import TableCell from '@tiptap/extension-table-cell'
|
||||
import TableHeader from '@tiptap/extension-table-header'
|
||||
|
||||
const CustomTableCell = TableCell.extend({
|
||||
addAttributes() {
|
||||
return {
|
||||
// extend the existing attributes …
|
||||
...this.parent?.(),
|
||||
|
||||
// and add a new one …
|
||||
backgroundColor: {
|
||||
default: null,
|
||||
parseHTML: element => {
|
||||
return {
|
||||
backgroundColor: element.getAttribute('data-background-color'),
|
||||
}
|
||||
},
|
||||
renderHTML: attributes => {
|
||||
return {
|
||||
'data-background-color': attributes.backgroundColor,
|
||||
style: `background-color: ${attributes.backgroundColor}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Table.configure({
|
||||
resizable: true,
|
||||
}),
|
||||
TableRow,
|
||||
TableHeader,
|
||||
// Default TableCell
|
||||
// TableCell,
|
||||
// Custom TableCell with backgroundColor attribute
|
||||
CustomTableCell,
|
||||
],
|
||||
content: `
|
||||
<h3>
|
||||
Have you seen our tables? They are amazing!
|
||||
</h3>
|
||||
<ul>
|
||||
<li>tables with rows, cells and headers (optional)</li>
|
||||
<li>support for <code>colgroup</code> and <code>rowspan</code></li>
|
||||
<li>and even resizable columns (optional)</li>
|
||||
</ul>
|
||||
<p>
|
||||
Here is an example:
|
||||
</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th colspan="3">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cyndi Lauper</td>
|
||||
<td>singer</td>
|
||||
<td>songwriter</td>
|
||||
<td>actress</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Philipp Kühn</td>
|
||||
<td>designer</td>
|
||||
<td>developer</td>
|
||||
<td>maker</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hans Pagel</td>
|
||||
<td>wrote this</td>
|
||||
<td colspan="2">that’s it</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
margin: 1rem 0;
|
||||
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Table-specific styling */
|
||||
.ProseMirror {
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
|
||||
td,
|
||||
th {
|
||||
min-width: 1em;
|
||||
border: 2px solid #ced4da;
|
||||
padding: 3px 5px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
> * {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
background-color: #f1f3f5;
|
||||
}
|
||||
|
||||
.selectedCell:after {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
background: rgba(200, 200, 255, 0.4);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.column-resize-handle {
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
top: 0;
|
||||
bottom: -2px;
|
||||
width: 4px;
|
||||
background-color: #adf;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tableWrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.resize-cursor {
|
||||
cursor: ew-resize;
|
||||
cursor: col-resize;
|
||||
}
|
||||
</style>
|
@ -1,43 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import TaskList from '@tiptap/extension-task-list'
|
||||
import TaskItem from '@tiptap/extension-task-item'
|
||||
import './styles.scss'
|
||||
|
||||
const CustomDocument = Document.extend({
|
||||
content: 'taskList',
|
||||
})
|
||||
|
||||
const CustomTaskItem = TaskItem.extend({
|
||||
content: 'inline*',
|
||||
})
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
CustomDocument,
|
||||
Paragraph,
|
||||
Text,
|
||||
TaskList,
|
||||
CustomTaskItem,
|
||||
],
|
||||
content: `
|
||||
<ul data-type="taskList">
|
||||
<li data-type="taskItem" data-checked="true">flour</li>
|
||||
<li data-type="taskItem" data-checked="true">baking powder</li>
|
||||
<li data-type="taskItem" data-checked="true">salt</li>
|
||||
<li data-type="taskItem" data-checked="false">sugar</li>
|
||||
<li data-type="taskItem" data-checked="false">milk</li>
|
||||
<li data-type="taskItem" data-checked="false">eggs</li>
|
||||
<li data-type="taskItem" data-checked="false">butter</li>
|
||||
</ul>
|
||||
`,
|
||||
})
|
||||
|
||||
return (
|
||||
<EditorContent editor={editor} />
|
||||
)
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
ul[data-type="taskList"] {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> label {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Examples/Tasks/Vue', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Examples/Tasks/Vue')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import TaskList from '@tiptap/extension-task-list'
|
||||
import TaskItem from '@tiptap/extension-task-item'
|
||||
|
||||
const CustomDocument = Document.extend({
|
||||
content: 'taskList',
|
||||
})
|
||||
|
||||
const CustomTaskItem = TaskItem.extend({
|
||||
content: 'inline*',
|
||||
})
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
CustomDocument,
|
||||
Paragraph,
|
||||
Text,
|
||||
TaskList,
|
||||
CustomTaskItem,
|
||||
],
|
||||
content: `
|
||||
<ul data-type="taskList">
|
||||
<li data-type="taskItem" data-checked="true">flour</li>
|
||||
<li data-type="taskItem" data-checked="true">baking powder</li>
|
||||
<li data-type="taskItem" data-checked="true">salt</li>
|
||||
<li data-type="taskItem" data-checked="false">sugar</li>
|
||||
<li data-type="taskItem" data-checked="false">milk</li>
|
||||
<li data-type="taskItem" data-checked="false">eggs</li>
|
||||
<li data-type="taskItem" data-checked="false">butter</li>
|
||||
</ul>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
ul[data-type="taskList"] {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> label {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,37 +0,0 @@
|
||||
export class AnnotationItem {
|
||||
private decoration!: any
|
||||
|
||||
constructor(decoration: any) {
|
||||
this.decoration = decoration
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.decoration.type.spec.id
|
||||
}
|
||||
|
||||
get from() {
|
||||
return this.decoration.from
|
||||
}
|
||||
|
||||
get to() {
|
||||
return this.decoration.to
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this.decoration.type.spec.data
|
||||
}
|
||||
|
||||
get HTMLAttributes() {
|
||||
return this.decoration.type.attrs
|
||||
}
|
||||
|
||||
toString() {
|
||||
return JSON.stringify({
|
||||
id: this.id,
|
||||
data: this.data,
|
||||
from: this.from,
|
||||
to: this.to,
|
||||
HTMLAttributes: this.HTMLAttributes,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import * as Y from 'yjs'
|
||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||
import { AnnotationState } from './AnnotationState'
|
||||
|
||||
export const AnnotationPluginKey = new PluginKey('annotation')
|
||||
|
||||
export interface AnnotationPluginOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
onUpdate: (items: [any?]) => {},
|
||||
map: Y.Map<any>,
|
||||
instance: string,
|
||||
}
|
||||
|
||||
export const AnnotationPlugin = (options: AnnotationPluginOptions) => new Plugin({
|
||||
key: AnnotationPluginKey,
|
||||
|
||||
state: {
|
||||
init() {
|
||||
return new AnnotationState({
|
||||
HTMLAttributes: options.HTMLAttributes,
|
||||
map: options.map,
|
||||
instance: options.instance,
|
||||
})
|
||||
},
|
||||
apply(transaction, pluginState, oldState, newState) {
|
||||
return pluginState.apply(transaction, newState)
|
||||
},
|
||||
},
|
||||
|
||||
props: {
|
||||
decorations(state) {
|
||||
const { decorations } = this.getState(state)
|
||||
const { selection } = state
|
||||
|
||||
if (!selection.empty) {
|
||||
return decorations
|
||||
}
|
||||
|
||||
const annotations = this
|
||||
.getState(state)
|
||||
.annotationsAt(selection.from)
|
||||
|
||||
options.onUpdate(annotations)
|
||||
|
||||
return decorations
|
||||
},
|
||||
},
|
||||
})
|
@ -1,151 +0,0 @@
|
||||
import * as Y from 'yjs'
|
||||
import { EditorState, Transaction } from 'prosemirror-state'
|
||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
import { ySyncPluginKey, relativePositionToAbsolutePosition, absolutePositionToRelativePosition } from 'y-prosemirror'
|
||||
import { AddAnnotationAction, DeleteAnnotationAction, UpdateAnnotationAction } from './collaboration-annotation'
|
||||
import { AnnotationPluginKey } from './AnnotationPlugin'
|
||||
import { AnnotationItem } from './AnnotationItem'
|
||||
|
||||
export interface AnnotationStateOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
map: Y.Map<any>,
|
||||
instance: string,
|
||||
}
|
||||
|
||||
export class AnnotationState {
|
||||
options: AnnotationStateOptions
|
||||
|
||||
decorations = DecorationSet.empty
|
||||
|
||||
constructor(options: AnnotationStateOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
randomId() {
|
||||
// TODO: That seems … to simple.
|
||||
return Math.floor(Math.random() * 0xffffffff).toString()
|
||||
}
|
||||
|
||||
findAnnotation(id: string) {
|
||||
const current = this.decorations.find()
|
||||
|
||||
for (let i = 0; i < current.length; i += 1) {
|
||||
if (current[i].spec.id === id) {
|
||||
return current[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addAnnotation(action: AddAnnotationAction, state: EditorState) {
|
||||
const ystate = ySyncPluginKey.getState(state)
|
||||
const { type, binding } = ystate
|
||||
const { map } = this.options
|
||||
const { from, to, data } = action
|
||||
const absoluteFrom = absolutePositionToRelativePosition(from, type, binding.mapping)
|
||||
const absoluteTo = absolutePositionToRelativePosition(to, type, binding.mapping)
|
||||
|
||||
map.set(this.randomId(), {
|
||||
from: absoluteFrom,
|
||||
to: absoluteTo,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
updateAnnotation(action: UpdateAnnotationAction) {
|
||||
const { map } = this.options
|
||||
|
||||
const annotation = map.get(action.id)
|
||||
|
||||
map.set(action.id, {
|
||||
from: annotation.from,
|
||||
to: annotation.to,
|
||||
data: action.data,
|
||||
})
|
||||
}
|
||||
|
||||
deleteAnnotation(id: string) {
|
||||
const { map } = this.options
|
||||
|
||||
map.delete(id)
|
||||
}
|
||||
|
||||
annotationsAt(position: number) {
|
||||
return this.decorations.find(position, position).map(decoration => {
|
||||
return new AnnotationItem(decoration)
|
||||
})
|
||||
}
|
||||
|
||||
createDecorations(state: EditorState) {
|
||||
const { map, HTMLAttributes } = this.options
|
||||
const ystate = ySyncPluginKey.getState(state)
|
||||
const { doc, type, binding } = ystate
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
map.forEach((annotation, id) => {
|
||||
const from = relativePositionToAbsolutePosition(doc, type, annotation.from, binding.mapping)
|
||||
const to = relativePositionToAbsolutePosition(doc, type, annotation.to, binding.mapping)
|
||||
|
||||
if (!from || !to) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`[${this.options.instance}] Decoration.inline()`, from, to, HTMLAttributes, { id, data: annotation.data })
|
||||
|
||||
if (from === to) {
|
||||
console.warn(`[${this.options.instance}] corrupt decoration `, annotation.from, from, annotation.to, to)
|
||||
}
|
||||
|
||||
decorations.push(
|
||||
Decoration.inline(from, to, HTMLAttributes, { id, data: annotation.data, inclusiveEnd: true }),
|
||||
)
|
||||
})
|
||||
|
||||
this.decorations = DecorationSet.create(state.doc, decorations)
|
||||
}
|
||||
|
||||
apply(transaction: Transaction, state: EditorState) {
|
||||
// Add/Remove annotations
|
||||
const action = transaction.getMeta(AnnotationPluginKey) as AddAnnotationAction | UpdateAnnotationAction | DeleteAnnotationAction
|
||||
|
||||
if (action && action.type) {
|
||||
console.log(`[${this.options.instance}] action: ${action.type}`)
|
||||
|
||||
if (action.type === 'addAnnotation') {
|
||||
this.addAnnotation(action, state)
|
||||
}
|
||||
|
||||
if (action.type === 'updateAnnotation') {
|
||||
this.updateAnnotation(action)
|
||||
}
|
||||
|
||||
if (action.type === 'deleteAnnotation') {
|
||||
this.deleteAnnotation(action.id)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (action.type === 'createDecorations') {
|
||||
this.createDecorations(state)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// Use Y.js to update positions
|
||||
const ystate = ySyncPluginKey.getState(state)
|
||||
|
||||
if (ystate.isChangeOrigin) {
|
||||
console.log(`[${this.options.instance}] isChangeOrigin: true → createDecorations`)
|
||||
this.createDecorations(state)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// Use ProseMirror to update positions
|
||||
console.log(`[${this.options.instance}] isChangeOrigin: false → ProseMirror mapping`)
|
||||
this.decorations = this.decorations.map(transaction.mapping, transaction.doc)
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
import * as Y from 'yjs'
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { AnnotationPlugin, AnnotationPluginKey } from './AnnotationPlugin'
|
||||
|
||||
export interface AddAnnotationAction {
|
||||
type: 'addAnnotation',
|
||||
data: any,
|
||||
from: number,
|
||||
to: number,
|
||||
}
|
||||
|
||||
export interface UpdateAnnotationAction {
|
||||
type: 'updateAnnotation',
|
||||
id: string,
|
||||
data: any,
|
||||
}
|
||||
|
||||
export interface DeleteAnnotationAction {
|
||||
type: 'deleteAnnotation',
|
||||
id: string,
|
||||
}
|
||||
|
||||
export interface AnnotationOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
/**
|
||||
* An event listener which receives annotations for the current selection.
|
||||
*/
|
||||
onUpdate: (items: [any?]) => {},
|
||||
/**
|
||||
* An initialized Y.js document.
|
||||
*/
|
||||
document: Y.Doc | null,
|
||||
/**
|
||||
* Name of a Y.js map, can be changed to sync multiple fields with one Y.js document.
|
||||
*/
|
||||
field: string,
|
||||
/**
|
||||
* A raw Y.js map, can be used instead of `document` and `field`.
|
||||
*/
|
||||
map: Y.Map<any> | null,
|
||||
instance: string,
|
||||
}
|
||||
|
||||
function getMapFromOptions(options: AnnotationOptions): Y.Map<any> {
|
||||
return options.map
|
||||
? options.map
|
||||
: options.document?.getMap(options.field) as Y.Map<any>
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
annotation: {
|
||||
addAnnotation: (data: any) => ReturnType,
|
||||
updateAnnotation: (id: string, data: any) => ReturnType,
|
||||
deleteAnnotation: (id: string) => ReturnType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const CollaborationAnnotation = Extension.create({
|
||||
name: 'annotation',
|
||||
|
||||
priority: 1000,
|
||||
|
||||
defaultOptions: <AnnotationOptions>{
|
||||
HTMLAttributes: {
|
||||
class: 'annotation',
|
||||
},
|
||||
onUpdate: decorations => decorations,
|
||||
document: null,
|
||||
field: 'annotations',
|
||||
map: null,
|
||||
instance: '',
|
||||
},
|
||||
|
||||
onCreate() {
|
||||
const map = getMapFromOptions(this.options)
|
||||
|
||||
map.observe(() => {
|
||||
console.log(`[${this.options.instance}] map updated → createDecorations`)
|
||||
|
||||
const transaction = this.editor.state.tr.setMeta(AnnotationPluginKey, {
|
||||
type: 'createDecorations',
|
||||
})
|
||||
|
||||
this.editor.view.dispatch(transaction)
|
||||
})
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
addAnnotation: (data: any) => ({ dispatch, state }) => {
|
||||
const { selection } = state
|
||||
|
||||
if (selection.empty) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (dispatch && data) {
|
||||
state.tr.setMeta(AnnotationPluginKey, <AddAnnotationAction>{
|
||||
type: 'addAnnotation',
|
||||
from: selection.from,
|
||||
to: selection.to,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
updateAnnotation: (id: string, data: any) => ({ dispatch, state }) => {
|
||||
if (dispatch) {
|
||||
state.tr.setMeta(AnnotationPluginKey, <UpdateAnnotationAction>{
|
||||
type: 'updateAnnotation',
|
||||
id,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
deleteAnnotation: id => ({ dispatch, state }) => {
|
||||
if (dispatch) {
|
||||
state.tr.setMeta(AnnotationPluginKey, <DeleteAnnotationAction>{
|
||||
type: 'deleteAnnotation',
|
||||
id,
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
AnnotationPlugin({
|
||||
HTMLAttributes: this.options.HTMLAttributes,
|
||||
onUpdate: this.options.onUpdate,
|
||||
map: getMapFromOptions(this.options),
|
||||
instance: this.options.instance,
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
@ -1,5 +0,0 @@
|
||||
import { CollaborationAnnotation } from './collaboration-annotation'
|
||||
|
||||
export * from './collaboration-annotation'
|
||||
|
||||
export default CollaborationAnnotation
|
@ -1,7 +0,0 @@
|
||||
context('/demos/Experiments/Annotation', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Experiments/Annotation')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
@ -1,151 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="editor">
|
||||
<h2>
|
||||
Original Editor
|
||||
</h2>
|
||||
<button @click="addComment" :disabled="!editor.can().addAnnotation()">
|
||||
comment
|
||||
</button>
|
||||
<editor-content :editor="editor" />
|
||||
<div v-for="comment in comments" :key="comment.id">
|
||||
{{ comment }}
|
||||
|
||||
<button @click="updateComment(comment.id)">
|
||||
update
|
||||
</button>
|
||||
|
||||
<button @click="deleteComment(comment.id)">
|
||||
remove
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h2>
|
||||
Another Editor
|
||||
</h2>
|
||||
<button @click="addAnotherComment" :disabled="!anotherEditor.can().addAnnotation()">
|
||||
comment
|
||||
</button>
|
||||
<editor-content :editor="anotherEditor" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
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 Bold from '@tiptap/extension-bold'
|
||||
import Heading from '@tiptap/extension-heading'
|
||||
import * as Y from 'yjs'
|
||||
import CollaborationAnnotation from './extension'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
anotherEditor: null,
|
||||
comments: [],
|
||||
ydoc: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.ydoc = new Y.Doc()
|
||||
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Bold,
|
||||
Heading,
|
||||
CollaborationAnnotation.configure({
|
||||
document: this.ydoc,
|
||||
onUpdate: items => { this.comments = items },
|
||||
instance: 'editor1',
|
||||
}),
|
||||
Collaboration.configure({
|
||||
document: this.ydoc,
|
||||
}),
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
Annotations can be used to add additional information to the content, for example comments. They live on a different level than the actual editor content.
|
||||
</p>
|
||||
<p>
|
||||
This example allows you to add plain text, but you’re free to add more complex data, for example JSON from another tiptap instance. :-)
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
|
||||
this.anotherEditor = new Editor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Bold,
|
||||
Heading,
|
||||
CollaborationAnnotation.configure({
|
||||
document: this.ydoc,
|
||||
instance: 'editor2',
|
||||
}),
|
||||
Collaboration.configure({
|
||||
document: this.ydoc,
|
||||
}),
|
||||
],
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
addComment() {
|
||||
const data = prompt('Comment', '')
|
||||
|
||||
this.editor.commands.addAnnotation(data)
|
||||
},
|
||||
updateComment(id) {
|
||||
const comment = this.comments.find(item => {
|
||||
return id === item.id
|
||||
})
|
||||
|
||||
const data = prompt('Comment', comment.data)
|
||||
|
||||
this.editor.commands.updateAnnotation(id, data)
|
||||
},
|
||||
deleteComment(id) {
|
||||
this.editor.commands.deleteAnnotation(id)
|
||||
},
|
||||
addAnotherComment() {
|
||||
const data = prompt('Comment', '')
|
||||
|
||||
this.anotherEditor.commands.addAnnotation(data)
|
||||
},
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
this.anotherEditor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.annotation {
|
||||
background: #9DEF8F;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<div class="items">
|
||||
<button
|
||||
class="item"
|
||||
:class="{ 'is-selected': index === selectedIndex }"
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
@click="selectItem(index)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
command: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: 0,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
items() {
|
||||
this.selectedIndex = 0
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onKeyDown({ event }) {
|
||||
if (event.key === 'ArrowUp') {
|
||||
this.upHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
this.downHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
this.enterHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
upHandler() {
|
||||
this.selectedIndex = ((this.selectedIndex + this.items.length) - 1) % this.items.length
|
||||
},
|
||||
|
||||
downHandler() {
|
||||
this.selectedIndex = (this.selectedIndex + 1) % this.items.length
|
||||
},
|
||||
|
||||
enterHandler() {
|
||||
this.selectItem(this.selectedIndex)
|
||||
},
|
||||
|
||||
selectItem(index) {
|
||||
const item = this.items[index]
|
||||
|
||||
if (item) {
|
||||
this.command(item)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.items {
|
||||
position: relative;
|
||||
border-radius: 0.25rem;
|
||||
background: white;
|
||||
color: rgba(black, 0.8);
|
||||
overflow: hidden;
|
||||
font-size: 0.9rem;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0, 0, 0, 0.1),
|
||||
0px 10px 20px rgba(0, 0, 0, 0.1),
|
||||
;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0.2rem 0.5rem;
|
||||
|
||||
&.is-selected,
|
||||
&:hover {
|
||||
color: #A975FF;
|
||||
background: rgba(#A975FF, 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,25 +0,0 @@
|
||||
import { Extension } from '@tiptap/core'
|
||||
import Suggestion from '@tiptap/suggestion'
|
||||
|
||||
export default Extension.create({
|
||||
name: 'mention',
|
||||
|
||||
defaultOptions: {
|
||||
suggestion: {
|
||||
char: '/',
|
||||
startOfLine: false,
|
||||
command: ({ editor, range, props }) => {
|
||||
props.command({ editor, range })
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
Suggestion({
|
||||
editor: this.editor,
|
||||
...this.options.suggestion,
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
@ -1,154 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tippy from 'tippy.js'
|
||||
import { Editor, EditorContent, VueRenderer } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Commands from './commands'
|
||||
import CommandsList from './CommandsList'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Commands.configure({
|
||||
suggestion: {
|
||||
items: query => {
|
||||
return [
|
||||
{
|
||||
title: 'H1',
|
||||
command: ({ editor, range }) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setNode('heading', { level: 1 })
|
||||
.run()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'H2',
|
||||
command: ({ editor, range }) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setNode('heading', { level: 2 })
|
||||
.run()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'bold',
|
||||
command: ({ editor, range }) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setMark('bold')
|
||||
.run()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'italic',
|
||||
command: ({ editor, range }) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setMark('italic')
|
||||
.run()
|
||||
},
|
||||
},
|
||||
].filter(item => item.title.toLowerCase().startsWith(query.toLowerCase())).slice(0, 10)
|
||||
},
|
||||
render: () => {
|
||||
let component
|
||||
let popup
|
||||
|
||||
return {
|
||||
onStart: props => {
|
||||
component = new VueRenderer(CommandsList, {
|
||||
parent: this,
|
||||
propsData: props,
|
||||
})
|
||||
|
||||
popup = tippy('body', {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.body,
|
||||
content: component.element,
|
||||
showOnCreate: true,
|
||||
interactive: true,
|
||||
trigger: 'manual',
|
||||
placement: 'bottom-start',
|
||||
})
|
||||
},
|
||||
onUpdate(props) {
|
||||
component.updateProps(props)
|
||||
|
||||
popup[0].setProps({
|
||||
getReferenceClientRect: props.clientRect,
|
||||
})
|
||||
},
|
||||
onKeyDown(props) {
|
||||
if (props.event.key === 'Escape') {
|
||||
popup[0].hide()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return component.ref?.onKeyDown(props)
|
||||
},
|
||||
onExit() {
|
||||
popup[0].destroy()
|
||||
component.destroy()
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
content: `
|
||||
<p>Type a slash</p>
|
||||
<p></p>
|
||||
<p></p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
color: #A975FF;
|
||||
background-color: rgba(#A975FF, 0.1);
|
||||
border-radius: 0.3rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,33 +0,0 @@
|
||||
import { Node } from '@tiptap/core'
|
||||
|
||||
export interface DetailsSummaryOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export default Node.create<DetailsSummaryOptions>({
|
||||
name: 'detailsSummary',
|
||||
|
||||
content: 'text*',
|
||||
|
||||
marks: '',
|
||||
|
||||
group: 'block',
|
||||
|
||||
isolating: true,
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{
|
||||
tag: 'summary',
|
||||
}]
|
||||
},
|
||||
|
||||
renderHTML() {
|
||||
return ['summary', 0]
|
||||
},
|
||||
})
|
@ -1,107 +0,0 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
|
||||
export interface DetailsOptions {
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
details: {
|
||||
/**
|
||||
* Set a details node
|
||||
*/
|
||||
setDetails: () => ReturnType,
|
||||
/**
|
||||
* Toggle a details node
|
||||
*/
|
||||
toggleDetails: () => ReturnType,
|
||||
/**
|
||||
* Unset a details node
|
||||
*/
|
||||
unsetDetails: () => ReturnType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Node.create<DetailsOptions>({
|
||||
name: 'details',
|
||||
|
||||
content: 'detailsSummary block+',
|
||||
|
||||
group: 'block',
|
||||
|
||||
// defining: true,
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'details',
|
||||
},
|
||||
{
|
||||
tag: 'div[data-type="details"]',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['details', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ({ HTMLAttributes }) => {
|
||||
const item = document.createElement('div')
|
||||
item.setAttribute('data-type', 'details')
|
||||
|
||||
const toggle = document.createElement('div')
|
||||
toggle.setAttribute('data-type', 'detailsToggle')
|
||||
item.append(toggle)
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.setAttribute('data-type', 'detailsContent')
|
||||
item.append(content)
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
if (item.hasAttribute('open')) {
|
||||
item.removeAttribute('open')
|
||||
} else {
|
||||
item.setAttribute('open', 'open')
|
||||
}
|
||||
})
|
||||
|
||||
Object.entries(HTMLAttributes).forEach(([key, value]) => {
|
||||
item.setAttribute(key, value)
|
||||
})
|
||||
|
||||
return {
|
||||
dom: item,
|
||||
contentDOM: content,
|
||||
ignoreMutation: (mutation: MutationRecord) => {
|
||||
return !item.contains(mutation.target) || item === mutation.target
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
setDetails: () => ({ commands }) => {
|
||||
// TODO: Doesn’t work
|
||||
return commands.wrapIn('details')
|
||||
},
|
||||
toggleDetails: () => ({ commands }) => {
|
||||
// TODO: Doesn’t work
|
||||
return commands.toggleWrap('details')
|
||||
},
|
||||
unsetDetails: () => ({ commands }) => {
|
||||
// TODO: Doesn’t work
|
||||
return commands.lift('details')
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
@ -1,100 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<button @click="editor.chain().focus().toggleDetails().run()" :class="{ 'is-active': editor.isActive('details') }">
|
||||
details
|
||||
</button>
|
||||
|
||||
<editor-content :editor="editor" />
|
||||
|
||||
<h2>HTML</h2>
|
||||
{{ editor.getHTML() }}
|
||||
|
||||
<h2>Issues</h2>
|
||||
<ul>
|
||||
<li>Commands don’t work</li>
|
||||
<li>Fails to open nested details</li>
|
||||
<li>Node can’t be deleted (if it’s the last node)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Details from './details'
|
||||
import DetailsSummary from './details-summary'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Details,
|
||||
DetailsSummary,
|
||||
],
|
||||
content: `
|
||||
<p>Here is a details list:</p>
|
||||
<details>
|
||||
<summary>An open details tag</summary>
|
||||
<p>More info about the details.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A closed details tag</summary>
|
||||
<p>More info about the details.</p>
|
||||
</details>
|
||||
<p>That’s it.</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
details,
|
||||
[data-type="details"] {
|
||||
display: flex;
|
||||
|
||||
[data-type="detailsContent"] > *:not(summary) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-type="detailsToggle"]::before {
|
||||
cursor: pointer;
|
||||
content: '▸';
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
[data-type="detailsContent"] > *:not(summary) {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
[data-type="detailsToggle"]::before {
|
||||
content: '▾';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,78 +0,0 @@
|
||||
import { Node } from '@tiptap/core'
|
||||
|
||||
export interface IframeOptions {
|
||||
allowFullscreen: boolean,
|
||||
HTMLAttributes: {
|
||||
[key: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
iframe: {
|
||||
/**
|
||||
* Add an iframe
|
||||
*/
|
||||
setIframe: (options: { src: string }) => ReturnType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Node.create({
|
||||
name: 'iframe',
|
||||
|
||||
group: 'block',
|
||||
|
||||
atom: true,
|
||||
|
||||
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]]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
setIframe: (options: { src: string }) => ({ tr, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const node = this.type.create(options)
|
||||
|
||||
if (dispatch) {
|
||||
tr.replaceRangeWith(selection.from, selection.to, node)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
@ -1,86 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<button @click="addIframe">
|
||||
add iframe
|
||||
</button>
|
||||
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Iframe from './iframe'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Iframe,
|
||||
],
|
||||
content: `
|
||||
<p>Here is an exciting video:</p>
|
||||
<iframe src="https://www.youtube.com/embed/XIMLoLxmTDw" frameborder="0" allowfullscreen></iframe>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
addIframe() {
|
||||
const url = window.prompt('URL')
|
||||
|
||||
if (url) {
|
||||
this.editor.chain().focus().setIframe({ src: url }).run()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "sass:math";
|
||||
::v-deep {
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.iframe-wrapper {
|
||||
position: relative;
|
||||
padding-bottom: math.div(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>
|
@ -1,200 +0,0 @@
|
||||
import {
|
||||
Node,
|
||||
nodeInputRule,
|
||||
mergeAttributes,
|
||||
findChildrenInRange,
|
||||
Tracker,
|
||||
} from '@tiptap/core'
|
||||
|
||||
export interface FigureOptions {
|
||||
HTMLAttributes: Record<string, any>,
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
figure: {
|
||||
/**
|
||||
* Add a figure element
|
||||
*/
|
||||
setFigure: (options: {
|
||||
src: string,
|
||||
alt?: string,
|
||||
title?: string,
|
||||
caption?: string,
|
||||
}) => ReturnType,
|
||||
|
||||
/**
|
||||
* Converts an image to a figure
|
||||
*/
|
||||
imageToFigure: () => ReturnType,
|
||||
|
||||
/**
|
||||
* Converts a figure to an image
|
||||
*/
|
||||
figureToImage: () => ReturnType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const inputRegex = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/
|
||||
|
||||
export const Figure = Node.create<FigureOptions>({
|
||||
name: 'figure',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
group: 'block',
|
||||
|
||||
content: 'inline*',
|
||||
|
||||
draggable: true,
|
||||
|
||||
isolating: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
src: {
|
||||
default: null,
|
||||
parseHTML: element => {
|
||||
return {
|
||||
src: element.querySelector('img')?.getAttribute('src'),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
alt: {
|
||||
default: null,
|
||||
parseHTML: element => {
|
||||
return {
|
||||
alt: element.querySelector('img')?.getAttribute('alt'),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
title: {
|
||||
default: null,
|
||||
parseHTML: element => {
|
||||
return {
|
||||
title: element.querySelector('img')?.getAttribute('title'),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'figure',
|
||||
contentElement: 'figcaption',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
'figure', this.options.HTMLAttributes,
|
||||
['img', mergeAttributes(HTMLAttributes, { draggable: false, contenteditable: false })],
|
||||
['figcaption', 0],
|
||||
]
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
setFigure: ({ caption, ...attrs }) => ({ chain }) => {
|
||||
return chain()
|
||||
.insertContent({
|
||||
type: this.name,
|
||||
attrs,
|
||||
content: caption
|
||||
? [{ type: 'text', text: caption }]
|
||||
: [],
|
||||
})
|
||||
// set cursor at end of caption field
|
||||
.command(({ tr, commands }) => {
|
||||
const { doc, selection } = tr
|
||||
const position = doc.resolve(selection.to - 2).end()
|
||||
|
||||
return commands.setTextSelection(position)
|
||||
})
|
||||
.run()
|
||||
},
|
||||
|
||||
imageToFigure: () => ({ tr, commands }) => {
|
||||
const { doc, selection } = tr
|
||||
const { from, to } = selection
|
||||
const images = findChildrenInRange(doc, { from, to }, node => node.type.name === 'image')
|
||||
|
||||
if (!images.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
const tracker = new Tracker(tr)
|
||||
|
||||
return commands.forEach(images, ({ node, pos }) => {
|
||||
const mapResult = tracker.map(pos)
|
||||
|
||||
if (mapResult.deleted) {
|
||||
return false
|
||||
}
|
||||
|
||||
const range = {
|
||||
from: mapResult.position,
|
||||
to: mapResult.position + node.nodeSize,
|
||||
}
|
||||
|
||||
return commands.insertContentAt(range, {
|
||||
type: this.name,
|
||||
attrs: {
|
||||
src: node.attrs.src,
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
figureToImage: () => ({ tr, commands }) => {
|
||||
const { doc, selection } = tr
|
||||
const { from, to } = selection
|
||||
const figures = findChildrenInRange(doc, { from, to }, node => node.type.name === this.name)
|
||||
|
||||
if (!figures.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
const tracker = new Tracker(tr)
|
||||
|
||||
return commands.forEach(figures, ({ node, pos }) => {
|
||||
const mapResult = tracker.map(pos)
|
||||
|
||||
if (mapResult.deleted) {
|
||||
return false
|
||||
}
|
||||
|
||||
const range = {
|
||||
from: mapResult.position,
|
||||
to: mapResult.position + node.nodeSize,
|
||||
}
|
||||
|
||||
return commands.insertContentAt(range, {
|
||||
type: 'image',
|
||||
attrs: {
|
||||
src: node.attrs.src,
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addInputRules() {
|
||||
return [
|
||||
nodeInputRule(inputRegex, this.type, match => {
|
||||
const [, alt, src, title] = match
|
||||
|
||||
return { src, alt, title }
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
@ -1,117 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<button @click="addFigure">
|
||||
figure
|
||||
</button>
|
||||
<button
|
||||
@click="editor.chain().focus().imageToFigure().run()"
|
||||
:disabled="!editor.can().imageToFigure()"
|
||||
>
|
||||
image to figure
|
||||
</button>
|
||||
<button
|
||||
@click="editor.chain().focus().figureToImage().run()"
|
||||
:disabled="!editor.can().figureToImage()"
|
||||
>
|
||||
figure to image
|
||||
</button>
|
||||
<editor-content :editor="editor" />
|
||||
|
||||
<h2>HTML</h2>
|
||||
{{ editor.getHTML() }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Image from '@tiptap/extension-image'
|
||||
import { Figure } from './figure'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
addFigure() {
|
||||
const url = window.prompt('URL')
|
||||
const caption = window.prompt('caption')
|
||||
|
||||
if (url) {
|
||||
this.editor
|
||||
.chain()
|
||||
.focus()
|
||||
.setFigure({ src: url, caption })
|
||||
.run()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Figure,
|
||||
Image,
|
||||
],
|
||||
content: `
|
||||
<p>Figure + Figcaption</p>
|
||||
<figure>
|
||||
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" alt="Random photo of something" title="Who’s dat?">
|
||||
<figcaption>
|
||||
<p>Amazing caption</p>
|
||||
</figcaption>
|
||||
</figure>
|
||||
<img src="https://source.unsplash.com/K9QHL52rE2k/800x400">
|
||||
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400">
|
||||
<img src="https://source.unsplash.com/K9QHL52rE2k/800x400">
|
||||
<p>That’s it.</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
figure {
|
||||
max-width: 25rem;
|
||||
border: 3px solid #0D0D0D;
|
||||
border-radius: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
margin-top: 0.25rem;
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
border: 2px dashed #0D0D0D20;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-width: min(100%, 25rem);
|
||||
height: auto;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,27 +0,0 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
|
||||
export const Figcaption = Node.create({
|
||||
name: 'figcaption',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
content: 'inline*',
|
||||
|
||||
selectable: false,
|
||||
|
||||
draggable: false,
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'figcaption',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['figcaption', mergeAttributes(HTMLAttributes), 0]
|
||||
},
|
||||
})
|
@ -1,56 +0,0 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { Plugin } from 'prosemirror-state'
|
||||
|
||||
export const Figure = Node.create({
|
||||
name: 'figure',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
group: 'block',
|
||||
|
||||
content: 'block figcaption',
|
||||
|
||||
draggable: true,
|
||||
|
||||
isolating: true,
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: `figure[data-type="${this.name}"]`,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['figure', mergeAttributes(HTMLAttributes, { 'data-type': this.name }), 0]
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
props: {
|
||||
handleDOMEvents: {
|
||||
// prevent dragging nodes out of the figure
|
||||
dragstart: (view, event) => {
|
||||
if (!event.target) {
|
||||
return false
|
||||
}
|
||||
|
||||
const pos = view.posAtDOM(event.target as HTMLElement, 0)
|
||||
const $pos = view.state.doc.resolve(pos)
|
||||
|
||||
if ($pos.parent.type === this.type) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
@ -1,331 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<button @click="addCapturedTable">
|
||||
add table
|
||||
</button>
|
||||
<button @click="addCapturedImage">
|
||||
add image
|
||||
</button>
|
||||
<button @click="removeCapturedTable">
|
||||
remove table
|
||||
</button>
|
||||
<button @click="removeCapturedImage">
|
||||
remove image
|
||||
</button>
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Image from '@tiptap/extension-image'
|
||||
import Table from '@tiptap/extension-table'
|
||||
import TableRow from '@tiptap/extension-table-row'
|
||||
import TableCell from '@tiptap/extension-table-cell'
|
||||
import TableHeader from '@tiptap/extension-table-header'
|
||||
import { Figure } from './figure'
|
||||
import { Figcaption } from './figcaption'
|
||||
|
||||
const ImageFigure = Figure.extend({
|
||||
name: 'capturedImage',
|
||||
content: 'figcaption image',
|
||||
})
|
||||
|
||||
const TableFigure = Figure.extend({
|
||||
name: 'capturedTable',
|
||||
content: 'figcaption table',
|
||||
})
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
addCapturedImage() {
|
||||
this.editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContent({
|
||||
type: 'capturedImage',
|
||||
content: [
|
||||
{
|
||||
type: 'figcaption',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'image caption',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
attrs: {
|
||||
src: 'https://source.unsplash.com/K9QHL52rE2k/800x400',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.run()
|
||||
},
|
||||
|
||||
addCapturedTable() {
|
||||
this.editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContent({
|
||||
type: 'capturedTable',
|
||||
content: [
|
||||
{
|
||||
type: 'figcaption',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'table caption',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'table',
|
||||
content: [
|
||||
{
|
||||
type: 'tableRow',
|
||||
content: [
|
||||
{
|
||||
type: 'tableCell',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'cell 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'tableCell',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'cell 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
.run()
|
||||
},
|
||||
|
||||
removeCapturedTable() {
|
||||
this.editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteNode('capturedTable')
|
||||
.run()
|
||||
},
|
||||
|
||||
removeCapturedImage() {
|
||||
this.editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteNode('capturedImage')
|
||||
.run()
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Table,
|
||||
TableRow,
|
||||
TableHeader,
|
||||
TableCell,
|
||||
ImageFigure,
|
||||
TableFigure,
|
||||
Figcaption,
|
||||
Image,
|
||||
],
|
||||
content: `
|
||||
<p>Some text</p>
|
||||
<figure data-type="capturedImage">
|
||||
<figcaption>
|
||||
Image caption
|
||||
</figcaption>
|
||||
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" alt="Random photo of something" title="Who’s dat?">
|
||||
</figure>
|
||||
<p>Some text</p>
|
||||
<img src="https://source.unsplash.com/K9QHL52rE2k/800x400">
|
||||
<p>Some text</p>
|
||||
<figure data-type="capturedTable">
|
||||
<figcaption>
|
||||
Table caption
|
||||
</figcaption>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th colspan="3">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cyndi Lauper</td>
|
||||
<td>singer</td>
|
||||
<td>songwriter</td>
|
||||
<td>actress</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Philipp Kühn</td>
|
||||
<td>designer</td>
|
||||
<td>developer</td>
|
||||
<td>maker</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hans Pagel</td>
|
||||
<td>wrote this</td>
|
||||
<td colspan="2">that’s it</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p>Some text</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th colspan="3">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cyndi Lauper</td>
|
||||
<td>singer</td>
|
||||
<td>songwriter</td>
|
||||
<td>actress</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Philipp Kühn</td>
|
||||
<td>designer</td>
|
||||
<td>developer</td>
|
||||
<td>maker</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hans Pagel</td>
|
||||
<td>wrote this</td>
|
||||
<td colspan="2">that’s it</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
figure {
|
||||
max-width: 25rem;
|
||||
border: 3px solid #0D0D0D;
|
||||
border-radius: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
margin: 0.25rem 0;
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
border: 2px dashed #0D0D0D20;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-width: min(100%, 25rem);
|
||||
height: auto;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
|
||||
td,
|
||||
th {
|
||||
min-width: 1em;
|
||||
border: 2px solid #ced4da;
|
||||
padding: 3px 5px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
> * {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
background-color: #f1f3f5;
|
||||
}
|
||||
|
||||
.selectedCell:after {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
background: rgba(200, 200, 255, 0.4);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.column-resize-handle {
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
top: 0;
|
||||
bottom: -2px;
|
||||
width: 4px;
|
||||
background-color: #adf;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,163 +0,0 @@
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { NodeSelection, Plugin } from 'prosemirror-state'
|
||||
import { serializeForClipboard } from 'prosemirror-view/src/clipboard'
|
||||
|
||||
function removeNode(node) {
|
||||
node.parentNode.removeChild(node)
|
||||
}
|
||||
|
||||
function absoluteRect(node) {
|
||||
const data = node.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
top: data.top,
|
||||
left: data.left,
|
||||
width: data.width,
|
||||
}
|
||||
}
|
||||
|
||||
export default Extension.create({
|
||||
addProseMirrorPlugins() {
|
||||
function blockPosAtCoords(coords, view) {
|
||||
const pos = view.posAtCoords(coords)
|
||||
let node = view.domAtPos(pos.pos)
|
||||
|
||||
node = node.node
|
||||
|
||||
while (node && node.parentNode) {
|
||||
if (node.parentNode?.classList?.contains('ProseMirror')) { // todo
|
||||
break
|
||||
}
|
||||
|
||||
node = node.parentNode
|
||||
}
|
||||
|
||||
if (node && node.nodeType === 1) {
|
||||
const desc = view.docView.nearestDesc(node, true)
|
||||
|
||||
if (!(!desc || desc === view.docView)) {
|
||||
return desc.posBefore
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function dragStart(e, view) {
|
||||
view.composing = true
|
||||
|
||||
if (!e.dataTransfer) {
|
||||
return
|
||||
}
|
||||
|
||||
const coords = { left: e.clientX + 50, top: e.clientY }
|
||||
const pos = blockPosAtCoords(coords, view)
|
||||
|
||||
if (pos != null) {
|
||||
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos)))
|
||||
|
||||
const slice = view.state.selection.content()
|
||||
|
||||
// console.log({
|
||||
// from: view.nodeDOM(view.state.selection.from),
|
||||
// to: view.nodeDOM(view.state.selection.to),
|
||||
// })
|
||||
const { dom, text } = serializeForClipboard(view, slice)
|
||||
|
||||
e.dataTransfer.clearData()
|
||||
e.dataTransfer.setData('text/html', dom.innerHTML)
|
||||
e.dataTransfer.setData('text/plain', text)
|
||||
|
||||
const el = document.querySelector('.ProseMirror-selectednode')
|
||||
e.dataTransfer?.setDragImage(el, 0, 0)
|
||||
|
||||
view.dragging = { slice, move: true }
|
||||
}
|
||||
}
|
||||
|
||||
let dropElement
|
||||
const WIDTH = 28
|
||||
|
||||
return [
|
||||
new Plugin({
|
||||
view(editorView) {
|
||||
const element = document.createElement('div')
|
||||
|
||||
element.draggable = 'true'
|
||||
element.classList.add('global-drag-handle')
|
||||
element.addEventListener('dragstart', e => dragStart(e, editorView))
|
||||
dropElement = element
|
||||
document.body.appendChild(dropElement)
|
||||
|
||||
return {
|
||||
// update(view, prevState) {
|
||||
// },
|
||||
destroy() {
|
||||
removeNode(dropElement)
|
||||
dropElement = null
|
||||
},
|
||||
}
|
||||
},
|
||||
props: {
|
||||
handleDrop(view, event, slice, moved) {
|
||||
if (moved) {
|
||||
// setTimeout(() => {
|
||||
// console.log('remove selection')
|
||||
// view.dispatch(view.state.tr.deleteSelection())
|
||||
// }, 50)
|
||||
}
|
||||
},
|
||||
// handlePaste() {
|
||||
// alert(2)
|
||||
// },
|
||||
handleDOMEvents: {
|
||||
// drop(view, event) {
|
||||
// setTimeout(() => {
|
||||
// const node = document.querySelector('.ProseMirror-hideselection')
|
||||
// if (node) {
|
||||
// node.classList.remove('ProseMirror-hideselection')
|
||||
// }
|
||||
// }, 50)
|
||||
// },
|
||||
mousemove(view, event) {
|
||||
const coords = {
|
||||
left: event.clientX + WIDTH + 50,
|
||||
top: event.clientY,
|
||||
}
|
||||
const pos = view.posAtCoords(coords)
|
||||
|
||||
if (pos) {
|
||||
let node = view.domAtPos(pos?.pos)
|
||||
|
||||
if (node) {
|
||||
node = node.node
|
||||
while (node && node.parentNode) {
|
||||
if (node.parentNode?.classList?.contains('ProseMirror')) { // todo
|
||||
break
|
||||
}
|
||||
node = node.parentNode
|
||||
}
|
||||
|
||||
if (node instanceof Element) {
|
||||
const cstyle = window.getComputedStyle(node)
|
||||
const lineHeight = parseInt(cstyle.lineHeight, 10)
|
||||
// const top = parseInt(cstyle.marginTop, 10) + parseInt(cstyle.paddingTop, 10)
|
||||
const top = 0
|
||||
const rect = absoluteRect(node)
|
||||
const win = node.ownerDocument.defaultView
|
||||
|
||||
rect.top += win.pageYOffset + ((lineHeight - 24) / 2) + top
|
||||
rect.left += win.pageXOffset
|
||||
rect.width = `${WIDTH}px`
|
||||
|
||||
dropElement.style.left = `${-WIDTH + rect.left}px`
|
||||
dropElement.style.top = `${rect.top}px`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
@ -1,135 +0,0 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import DragHandle from './DragHandle.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
DragHandle,
|
||||
],
|
||||
content: `
|
||||
<p>paragraph 1</p>
|
||||
<p>paragraph 2</p>
|
||||
<p>paragraph 3</p>
|
||||
<ul>
|
||||
<li>list item 1</li>
|
||||
<li>list item 2</li>
|
||||
</ul>
|
||||
<pre>code</pre>
|
||||
`,
|
||||
onUpdate: () => {
|
||||
console.log(this.editor.getHTML())
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.global-drag-handle {
|
||||
position: absolute;
|
||||
|
||||
&::after {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1rem;
|
||||
height: 1.25rem;
|
||||
content: '⠿';
|
||||
font-weight: 700;
|
||||
cursor: grab;
|
||||
background:#0D0D0D10;
|
||||
color: #0D0D0D50;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
.ProseMirror {
|
||||
padding: 0 1rem;
|
||||
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
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;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ProseMirror-selectednode {
|
||||
outline: 2px solid #70CFF8;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,103 +0,0 @@
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'
|
||||
import { Node as ProsemirrorNode } from 'prosemirror-model'
|
||||
import LinterPlugin, { Result as Issue } from './LinterPlugin'
|
||||
|
||||
interface IconDivElement extends HTMLDivElement {
|
||||
issue?: Issue
|
||||
}
|
||||
|
||||
function renderIcon(issue: Issue) {
|
||||
const icon: IconDivElement = document.createElement('div')
|
||||
|
||||
icon.className = 'lint-icon'
|
||||
icon.title = issue.message
|
||||
icon.issue = issue
|
||||
|
||||
return icon
|
||||
}
|
||||
|
||||
function runAllLinterPlugins(doc: ProsemirrorNode, plugins: Array<typeof LinterPlugin>) {
|
||||
const decorations: [any?] = []
|
||||
|
||||
const results = plugins.map(RegisteredLinterPlugin => {
|
||||
return new RegisteredLinterPlugin(doc).scan().getResults()
|
||||
}).flat()
|
||||
|
||||
results.forEach(issue => {
|
||||
decorations.push(Decoration.inline(issue.from, issue.to, {
|
||||
class: 'problem',
|
||||
}),
|
||||
Decoration.widget(issue.from, renderIcon(issue)))
|
||||
})
|
||||
|
||||
return DecorationSet.create(doc, decorations)
|
||||
}
|
||||
|
||||
export interface LinterOptions {
|
||||
plugins: Array<typeof LinterPlugin>,
|
||||
}
|
||||
|
||||
export const Linter = Extension.create({
|
||||
name: 'linter',
|
||||
|
||||
defaultOptions: <LinterOptions>{
|
||||
plugins: [],
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
const { plugins } = this.options
|
||||
|
||||
return [
|
||||
new Plugin({
|
||||
key: new PluginKey('linter'),
|
||||
state: {
|
||||
init(_, { doc }) {
|
||||
return runAllLinterPlugins(doc, plugins)
|
||||
},
|
||||
apply(transaction, oldState) {
|
||||
return transaction.docChanged
|
||||
? runAllLinterPlugins(transaction.doc, plugins)
|
||||
: oldState
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return this.getState(state)
|
||||
},
|
||||
handleClick(view, _, event) {
|
||||
const target = (event.target as IconDivElement)
|
||||
if (/lint-icon/.test(target.className) && target.issue) {
|
||||
const { from, to } = target.issue
|
||||
|
||||
view.dispatch(
|
||||
view.state.tr
|
||||
.setSelection(TextSelection.create(view.state.doc, from, to))
|
||||
.scrollIntoView(),
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
handleDoubleClick(view, _, event) {
|
||||
const target = (event.target as IconDivElement)
|
||||
if (/lint-icon/.test((event.target as HTMLElement).className) && target.issue) {
|
||||
const prob = target.issue
|
||||
|
||||
if (prob.fix) {
|
||||
prob.fix(view, prob)
|
||||
view.focus()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
@ -1,35 +0,0 @@
|
||||
import { Node as ProsemirrorNode } from 'prosemirror-model'
|
||||
|
||||
export interface Result {
|
||||
message: string,
|
||||
from: number,
|
||||
to: number,
|
||||
fix?: Function
|
||||
}
|
||||
|
||||
export default class LinterPlugin {
|
||||
protected doc
|
||||
|
||||
private results: Array<Result> = []
|
||||
|
||||
constructor(doc: ProsemirrorNode) {
|
||||
this.doc = doc
|
||||
}
|
||||
|
||||
record(message: string, from: number, to: number, fix?: Function) {
|
||||
this.results.push({
|
||||
message,
|
||||
from,
|
||||
to,
|
||||
fix,
|
||||
})
|
||||
}
|
||||
|
||||
scan() {
|
||||
return this
|
||||
}
|
||||
|
||||
getResults() {
|
||||
return this.results
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import { Linter } from './Linter'
|
||||
|
||||
export * from './Linter'
|
||||
export default Linter
|
||||
|
||||
export { BadWords } from './plugins/BadWords'
|
||||
export { Punctuation } from './plugins/Punctuation'
|
||||
export { HeadingLevel } from './plugins/HeadingLevel'
|
@ -1,25 +0,0 @@
|
||||
import LinterPlugin from '../LinterPlugin'
|
||||
|
||||
export class BadWords extends LinterPlugin {
|
||||
|
||||
public regex = /\b(obviously|clearly|evidently|simply)\b/ig
|
||||
|
||||
scan() {
|
||||
this.doc.descendants((node: any, position: number) => {
|
||||
if (!node.isText) {
|
||||
return
|
||||
}
|
||||
|
||||
const matches = this.regex.exec(node.text)
|
||||
|
||||
if (matches) {
|
||||
this.record(
|
||||
`Try not to say '${matches[0]}'`,
|
||||
position + matches.index, position + matches.index + matches[0].length,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { EditorView } from 'prosemirror-view'
|
||||
import LinterPlugin, { Result as Issue } from '../LinterPlugin'
|
||||
|
||||
export class HeadingLevel extends LinterPlugin {
|
||||
fixHeader(level: number) {
|
||||
return function ({ state, dispatch }: EditorView, issue: Issue) {
|
||||
dispatch(state.tr.setNodeMarkup(issue.from - 1, undefined, { level }))
|
||||
}
|
||||
}
|
||||
|
||||
scan() {
|
||||
let lastHeadLevel: number | null = null
|
||||
|
||||
this.doc.descendants((node, position) => {
|
||||
if (node.type.name === 'heading') {
|
||||
// Check whether heading levels fit under the current level
|
||||
const { level } = node.attrs
|
||||
|
||||
if (lastHeadLevel != null && level > lastHeadLevel + 1) {
|
||||
this.record(`Heading too small (${level} under ${lastHeadLevel})`,
|
||||
position + 1, position + 1 + node.content.size,
|
||||
this.fixHeader(lastHeadLevel + 1))
|
||||
}
|
||||
lastHeadLevel = level
|
||||
}
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
import { EditorView } from 'prosemirror-view'
|
||||
import LinterPlugin, { Result as Issue } from '../LinterPlugin'
|
||||
|
||||
export class Punctuation extends LinterPlugin {
|
||||
public regex = / ([,.!?:]) ?/g
|
||||
|
||||
fix(replacement: any) {
|
||||
return function ({ state, dispatch }: EditorView, issue: Issue) {
|
||||
dispatch(
|
||||
state.tr.replaceWith(
|
||||
issue.from, issue.to,
|
||||
state.schema.text(replacement),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
scan() {
|
||||
this.doc.descendants((node, position) => {
|
||||
if (!node.isText) return
|
||||
|
||||
if (!node.text) return
|
||||
|
||||
const matches = this.regex.exec(node.text)
|
||||
|
||||
if (matches) {
|
||||
this.record(
|
||||
'Suspicious spacing around punctuation',
|
||||
position + matches.index, position + matches.index + matches[0].length,
|
||||
this.fix(`${matches[1]} `),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Heading from '@tiptap/extension-heading'
|
||||
import Linter, { BadWords, Punctuation, HeadingLevel } from './extension'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Heading,
|
||||
Text,
|
||||
Linter.configure({
|
||||
plugins: [
|
||||
BadWords,
|
||||
Punctuation,
|
||||
HeadingLevel,
|
||||
],
|
||||
}),
|
||||
],
|
||||
content: `
|
||||
<h1>
|
||||
Lint example
|
||||
</h1>
|
||||
<p>
|
||||
This is a sentence ,but the comma clearly isn't in the right place.
|
||||
</p>
|
||||
<h3>
|
||||
Too-minor header
|
||||
</h3>
|
||||
<p>
|
||||
You can hover over the icons on the right to see what the problem is, click them to select the relevant text, and, obviously, double-click them to automatically fix it (if supported).
|
||||
</ul>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
.problem {
|
||||
background: #fdd;
|
||||
border-bottom: 1px solid #f22;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.lint-icon {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
cursor: pointer;
|
||||
border-radius: 100px;
|
||||
background: #f22;
|
||||
color: white;
|
||||
font-family: times, georgia, serif;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
width: 1.1em;
|
||||
height: 1.1em;
|
||||
text-align: center;
|
||||
padding-left: .5px;
|
||||
line-height: 1.1em
|
||||
}
|
||||
|
||||
.lint-icon:before {
|
||||
content: "!";
|
||||
}
|
||||
|
||||
.ProseMirror {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user