gancio/components/Editor.vue

320 lines
7.9 KiB
Vue
Raw Normal View History

2020-01-15 23:18:00 +01:00
<template lang='pug'>
2021-03-24 21:05:21 +01:00
.editor.grey.darken-4(:class='focused')
2020-11-17 00:32:14 +01:00
.label {{label}}
2020-08-16 14:07:25 +02:00
editor-menu-bar.menubar.is-hidden(:editor='editor'
:keep-in-bounds='true' v-slot='{ commands, isActive, getMarkAttrs, focused }')
2020-11-17 00:32:14 +01:00
v-btn-toggle(dense)
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.bold() }"
2020-07-28 12:24:39 +02:00
@click="commands.bold")
v-icon mdi-format-bold
2020-08-16 14:07:25 +02:00
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.underline() }"
2020-07-28 12:24:39 +02:00
@click="commands.underline")
2020-08-16 14:07:25 +02:00
v-icon mdi-format-underline
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.strike() }"
@click="commands.strike")
v-icon mdi-format-strikethrough-variant
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.italic() }"
@click="commands.italic")
2020-07-28 12:24:39 +02:00
v-icon mdi-format-italic
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.heading({level: 1}) }"
@click="commands.heading({level: 1})")
v-icon mdi-format-header-1
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.heading({level: 2}) }"
@click="commands.heading({level: 2})")
v-icon mdi-format-header-2
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.heading({level: 3}) }"
@click="commands.heading({level: 3})")
v-icon mdi-format-header-3
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.code() }"
@click="commands.code")
v-icon mdi-code-tags
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.blockquote() }"
@click="commands.blockquote")
v-icon mdi-format-quote-open
v-btn(icon text tabindex='-1'
2020-08-16 14:07:25 +02:00
:class="{ primary: isActive.bullet_list() }"
@click="commands.bullet_list")
v-icon mdi-format-list-bulleted
v-btn(icon text tabindex='-1' :class='{ primary: isActive.link() }'
@click='commands.link({href: getMarkAttrs("link") && getMarkAttrs("link").href ? "" : "https://"}); $refs.link.focus();')
2020-08-16 14:07:25 +02:00
v-icon mdi-link
v-text-field.pt-0.ml-1(v-show='isActive.link()' ref='link' @focus='focus' @blur='blur' hide-details
:value='isActive.link() && getMarkAttrs("link") && getMarkAttrs("link").href || ""'
@keypress.enter='commands.link({ href: $event.target.value}); editor.focus()')
2020-08-16 14:07:25 +02:00
editor-content.content(:editor='editor' spellcheck='false' :style="{ 'max-height': maxHeight }")
2020-01-15 23:18:00 +01:00
</template>
<script>
2021-03-16 19:54:26 +01:00
import debounce from 'lodash/debounce'
2020-07-05 23:51:45 +02:00
import { Editor, EditorContent, EditorMenuBar, EditorMenuBubble } from 'tiptap'
2020-01-15 23:18:00 +01:00
import {
Blockquote,
BulletList,
CodeBlock,
HardBreak,
Heading,
ListItem,
OrderedList,
Bold,
Code,
Italic,
Link,
History,
Strike,
Underline,
Placeholder
2020-01-15 23:18:00 +01:00
} from 'tiptap-extensions'
export default {
name: 'Editor',
components: { EditorContent, EditorMenuBar, EditorMenuBubble },
props: {
2020-10-14 21:13:20 +02:00
label: { type: String, default: 'Editor' },
2020-01-15 23:18:00 +01:00
value: { type: String, default: '' },
border: { type: Boolean, default: false },
2020-08-16 14:07:25 +02:00
noSave: { type: Boolean, default: false },
maxHeight: { type: String, Number, default: '' },
placeholder: { type: String, default: '' }
2020-01-15 23:18:00 +01:00
},
data () {
return {
2020-07-28 12:24:39 +02:00
options: [],
2020-01-15 23:18:00 +01:00
linkActive: false,
editor: null,
blurring: false,
2020-11-17 00:32:14 +01:00
update: false,
focused: ''
}
},
watch: {
value () {
if (this.update) {
this.update = false
return
}
this.editor.setContent(this.value)
2020-01-15 23:18:00 +01:00
}
},
mounted () {
this.editor = new Editor({
onFocus: () => this.focus(),
onBlur: () => this.blur(),
2021-03-16 19:54:26 +01:00
onUpdate: debounce(({ getHTML }) => {
this.update = true
this.$emit('input', getHTML())
}, 1000),
2020-01-15 23:18:00 +01:00
content: this.value,
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3, 4, 5, 6] }),
new OrderedList(),
new ListItem(),
new Code(),
new History(),
new Link({ openOnClick: false, target: '_blank' }),
2020-01-15 23:18:00 +01:00
new Bold(),
new Italic(),
new Strike(),
new Underline(),
new Placeholder({
emptyEditorClass: 'is-editor-empty',
emptyNodeClass: 'is-empty',
emptyNodeText: this.placeholder,
showOnlyWhenEditable: true,
2020-11-17 00:32:14 +01:00
showOnlyCurrent: true
})
2020-01-15 23:18:00 +01:00
]
})
},
beforeDestroy () {
if (this.editor) { this.editor.destroy() }
},
methods: {
blur () {
this.blurring = true
window.setTimeout(() => {
if (this.blurring) {
this.focused = ''
this.blurring = false
}
}, 200)
},
focus () {
this.focused = 'editor--focused'
this.$nextTick(() => {
this.blurring = false
})
}
2020-01-15 23:18:00 +01:00
}
}
</script>
2020-01-21 22:59:54 +01:00
<style lang='less'>
2020-11-17 00:32:14 +01:00
2020-07-28 12:24:39 +02:00
.editor {
2020-11-17 00:32:14 +01:00
margin-top: 4px;
padding-top: 12px;
padding-bottom: 22px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #FF4500 transparent;
scroll-behavior: smooth;
font-family: sans-serif;
font-size: 1.1em;
.editor p.is-editor-empty:first-child::before {
content: attr(data-empty-text);
float: left;
color: #aaa;
// opacity: .4;
pointer-events: none;
height: 0;
font-style: italic;
}
2020-10-14 21:13:20 +02:00
.label {
left: 0px;
2020-11-17 00:32:14 +01:00
position: relative;
transform-origin: top left;
transition: transform .3s, scale .3s, color .3s;
transform: translateY(20px);
2020-10-14 21:13:20 +02:00
}
2020-11-17 00:32:14 +01:00
&.editor--focused {
.label {
color: #FF4500;
transform: translateY(0px) scale(0.75);
}
.menubar {
opacity: 1 !important;
}
.ProseMirror::after {
width : 100% !important;
transform: scaleX(1) !important;
}
2020-08-16 14:07:25 +02:00
}
2020-11-17 00:32:14 +01:00
2020-08-16 14:07:25 +02:00
.menubar {
2020-11-17 00:32:14 +01:00
transition: opacity .5s;
opacity: 0;
2020-08-16 14:07:25 +02:00
// position: absolute;
}
2020-11-17 00:32:14 +01:00
.focused .ProseMirror::after {
width: 100%;
}
2020-08-16 14:07:25 +02:00
.ProseMirror {
padding: 15px;
outline: 0;
2020-11-17 00:32:14 +01:00
&::before {
bottom: 0px;
content: "";
left: 0;
position: absolute;
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
width: 100%;
border-width: thin 0 0 0;
border-style: solid;
height: 0px;
border-color: rgba(255, 255, 255, 0.7);
}
&::after {
bottom: 0px;
content: "";
left: 0;
position: absolute;
height: 0px;
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
width: 100%;
border-width: 2px 0 0 0;
border-style: solid;
border-color: #FF4500;
transform: scaleX(0);
}
2020-08-16 14:07:25 +02:00
}
2020-07-28 12:24:39 +02:00
}
2020-07-25 21:41:22 +02:00
// position: relative;
// overflow-y: auto;
// padding-top: 1.7em;
2020-01-15 23:18:00 +01:00
2020-07-25 21:41:22 +02:00
// &.with-border {
// border: 1px solid #ddd;
// border-radius: 5px;
// }
2020-01-15 23:18:00 +01:00
2020-07-25 21:41:22 +02:00
// .content {
// padding: 0px 5px 0px 5px;
// flex: 1;
// scrollbar-width: thin;
// overflow-y: auto;
// }
2020-01-15 23:18:00 +01:00
2020-07-25 21:41:22 +02:00
// .menububble {
// position: absolute;
// display: flex;
// overflow: hidden;
// opacity: 0;
// z-index: 1;
// background: #dddddd;
// transform: translateX(-50%);
// border-radius: 3px;
// padding: 0.07rem;
// transition: opacity 0.2s, visibility 0.2s, left .2s, bottom .2s;
// visibility: hidden;
2020-01-15 23:18:00 +01:00
2020-07-25 21:41:22 +02:00
// &.is-active {
// opacity: 1;
// visibility: visible;
// }
// input {
// padding: 0;
// margin: 1px;
// display: block;
// border: 0;
// color: #444;
// font-size: .8em;
// border-radius: 3px;
// line-height: 100%;
// transition: width .2s;
// padding-left: 5px;
// flex-grow: 1;
// }
2020-01-15 23:18:00 +01:00
2020-07-25 21:41:22 +02:00
// .fa-icon {
// width: auto;
// font-size: 10px;
// height: 1.4em; /* or any other relative font sizes */
// /* You would have to include the following two lines to make this work in Safari */
// // max-width: 100%;
// max-height: 100%;
// }
2020-01-15 23:18:00 +01:00
2020-07-25 21:41:22 +02:00
// }
// }
2020-01-15 23:18:00 +01:00
</style>