Added basic editor under /post/edit/p1

This commit is contained in:
Grzegorz Leoniec 2019-01-27 14:13:27 +01:00
parent 99fbd52f6e
commit 4745577290
No known key found for this signature in database
GPG Key ID: 3AA43686D4EB1377
5 changed files with 188 additions and 33 deletions

View File

@ -1,5 +1,33 @@
<template>
<div class="editor">
<editor-menu-bubble :editor="editor">
<div
slot-scope="{ commands, isActive, menu }"
class="menububble tooltip"
:class="{ 'is-active': menu.isActive }"
:style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"
>
<ds-button
class="menububble__button"
size="small"
:hover="isActive.bold()"
ghost
@click.prevent="commands.bold"
>
<ds-icon name="bold" />
</ds-button>
<ds-button
class="menububble__button"
size="small"
:hover="isActive.italic()"
ghost
@click.prevent="commands.italic"
>
<ds-icon name="italic" />
</ds-button>
</div>
</editor-menu-bubble>
<editor-floating-menu :editor="editor">
<div
slot-scope="{ commands, isActive, menu }"
@ -76,7 +104,13 @@
</template>
<script>
import { Editor, EditorContent, EditorFloatingMenu } from 'tiptap'
import stringHash from 'string-hash'
import {
Editor,
EditorContent,
EditorFloatingMenu,
EditorMenuBubble
} from 'tiptap'
import EventHandler from './plugins/eventHandler.js'
import {
Heading,
@ -95,18 +129,23 @@ import {
History
} from 'tiptap-extensions'
let throttleInputEvent
export default {
components: {
EditorContent,
EditorFloatingMenu
EditorFloatingMenu,
EditorMenuBubble
},
props: {
html: { type: String, default: '' },
value: { type: String, default: '' },
doc: { type: Object, default: () => {} }
},
data() {
return {
lastValueHash: null,
editor: new Editor({
content: this.value || '',
doc: this.doc,
extensions: [
new EventHandler(),
@ -128,19 +167,28 @@ export default {
emptyNodeText: 'Schreib etwas inspirerendes…'
}),
new History()
]
],
onUpdate: e => {
clearTimeout(throttleInputEvent)
throttleInputEvent = setTimeout(() => this.onUpdate(e), 300)
}
})
}
},
watch: {
html: {
value: {
immediate: true,
handler: function(html) {
html = html
.replace(/(<br\s*\/*>\s*){2,}/gim, '<br/>')
.replace(/<\/p>\s*(<br\s*\/*>\s*)+\s*<p>/gim, '</p><p>')
.replace(/<p>\s*(<br\s*\/*>\s*)+\s*<\/p>/gim, '')
this.editor.setContent(html)
handler: function(content, old) {
const contentHash = stringHash(content)
if (!content || contentHash === this.lastValueHash) {
return
}
this.lastValueHash = contentHash
// content = content
// .replace(/(<br\s*\/*>\s*){2,}/gim, '<br/>')
// .replace(/<\/p>\s*(<br\s*\/*>\s*)+\s*<p>/gim, '</p><p>')
// .replace(/<p>\s*(<br\s*\/*>\s*)+\s*<\/p>/gim, '')
this.editor.setContent(content)
}
}
},
@ -148,9 +196,13 @@ export default {
this.editor.destroy()
},
methods: {
onDialog({ mark, key, name, focus }) {
focus() // focus the editor if not already done to get the needed context
this.$emit('dialog', { mark, key, name, focus })
onUpdate(e) {
const content = e.getHTML()
const contentHash = stringHash(content)
if (contentHash !== this.lastValueHash) {
this.lastValueHash = contentHash
this.$emit('input', content)
}
}
}
}
@ -200,6 +252,35 @@ li > p {
visibility: visible;
}
}
.menububble {
position: absolute;
// margin-top: -0.5rem;
visibility: hidden;
opacity: 0;
transition: all 0.2s;
transition-delay: 150ms;
transform: translate(-50%, -25%);
background-color: $background-color-inverse-soft;
// color: $text-color-inverse;
border-radius: $border-radius-base;
padding: $space-xx-small;
box-shadow: $box-shadow-large;
.ds-button {
color: $text-color-inverse;
&.ds-button-hover,
&:hover {
color: $text-color-base;
}
}
&.is-active {
opacity: 1;
visibility: visible;
}
}
// p:not(.is-empty) {
// br:first-of-type,

View File

@ -1,5 +1,5 @@
import { Extension, Plugin } from 'tiptap'
import { Slice } from 'prosemirror-model'
// import { Slice, Fragment } from 'prosemirror-model'
export default class EventHandler extends Extension {
get name() {
@ -9,23 +9,58 @@ export default class EventHandler extends Extension {
return [
new Plugin({
props: {
handleDOMEvents: {
drop(view, event) {
event.stopImmediatePropagation()
},
paste(view, event, slice) {
console.log('#### PASTE', slice)
return 'ABC'
}
},
transformPasted(slice) {
console.log('#### transformPasted', slice)
return new Slice(slice.content, slice.openStart, slice.openEnd)
transformPastedText(text) {
// console.log('#### transformPastedText', text)
return text.trim()
},
transformPastedHTML(html) {
console.log('#### transformPastedHTML', html)
return 'HELLO WORLD'
html = html
.replace(/(<br\s*\/*>\s*){2,}/gim, '<br/>')
.replace(
/<\/(p|div|th|tr)>\s*(<br\s*\/*>\s*)+\s*<(p|div|th|tr)>/gim,
'</p><p>'
)
.replace(
/<(p|div|th|tr)>\s*(<br\s*\/*>\s*)+\s*<\/(p|div|th|tr)>/gim,
''
)
.replace(/<(p|div|th|tr)>\s*/gim, '<p>')
.replace(/\s*<\/(p|div|th|tr)>/gim, '</p>')
// console.log('#### transformPastedHTML', html)
return html
}
// transformPasted(slice) {
// // console.log('#### transformPasted', slice.content)
// let content = []
// let size = 0
// slice.content.forEach((node, offset, index) => {
// // console.log(node)
// // console.log('isBlock', node.type.isBlock)
// // console.log('childCount', node.content.childCount)
// // console.log('index', index)
// if (node.content.childCount) {
// content.push(node.content)
// size += node.content.size
// }
// })
// console.log(content)
// console.log(slice.content)
// let fragment = Fragment.fromArray(content)
// fragment.size = size
// console.log('#fragment', fragment, slice.content)
// console.log('----')
// console.log('#1', slice)
// // const newSlice = new Slice(fragment, slice.openStart, slice.openEnd)
// slice.fragment = fragment
// // slice.content.content = fragment.content
// // slice.content.size = fragment.size
// console.log('#2', slice)
// // console.log(newSlice)
// console.log('----')
// return slice
// // return newSlice
// }
}
})
]

View File

@ -53,6 +53,7 @@
"nuxt-env": "^0.0.4",
"nuxt-sass-resources-loader": "^2.0.5",
"portal-vue": "~1.5.1",
"string-hash": "^1.1.3",
"tiptap": "~1.8.0",
"tiptap-extensions": "~1.8.0",
"v-tooltip": "^2.0.0-rc.33",

View File

@ -6,8 +6,20 @@
<ds-flex-item :width="{ base: '100%', sm: 3, md: 5, lg: 3 }">
<ds-card>
<no-ssr>
<hc-editor :html="htmlContent" />
<hc-editor v-model="content" />
</no-ssr>
<div
slot="footer"
style="text-align: right"
>
<ds-button
icon="check"
primary
@click="save"
>
Speichern
</ds-button>
</div>
</ds-card>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }">
@ -24,11 +36,32 @@ export default {
components: {
HcEditor
},
computed: {
htmlContent() {
return this.Post ? this.Post[0].content : ''
data() {
return {
content: ''
}
},
watch: {
Post: {
immediate: true,
handler: function(post) {
console.log('try to set content', this.content, post)
if (!post || !post[0].content) {
return
}
console.log(post[0].content)
this.content = post[0].content
}
}
},
methods: {
save() {
console.log(this.content)
}
//onUpdate(data) {
// console.log('onUpdate', data)
//}
},
apollo: {
Post: {
query() {

View File

@ -10852,6 +10852,11 @@ string-argv@0.0.2:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736"
integrity sha1-2sMECGkMIfPDYwo/86BYd73L1zY=
string-hash@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=
string-length@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"