feat: vue-easymde script setup (#1983)

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/1983
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
Dominik Pschenitschni 2022-05-22 20:44:22 +00:00 committed by konrad
parent c7f8ae256b
commit e6af4772fb
3 changed files with 161 additions and 176 deletions

View file

@ -50,7 +50,7 @@
<script lang="ts"> <script lang="ts">
import {defineComponent} from 'vue' import {defineComponent} from 'vue'
import VueEasymde from './vue-easymde/vue-easymde.vue' import VueEasymde from './vue-easymde.vue'
import {marked} from 'marked' import {marked} from 'marked'
import DOMPurify from 'dompurify' import DOMPurify from 'dompurify'
import hljs from 'highlight.js/lib/common' import hljs from 'highlight.js/lib/common'
@ -316,7 +316,6 @@ export default defineComponent({
<style lang="scss"> <style lang="scss">
@import 'codemirror/lib/codemirror.css'; @import 'codemirror/lib/codemirror.css';
@import './vue-easymde/vue-easymde.css';
@import 'highlight.js/scss/base16/equilibrium-gray-light'; @import 'highlight.js/scss/base16/equilibrium-gray-light';
.editor { .editor {

View file

@ -1,3 +1,150 @@
<template>
<div class="vue-easymde" ref="easymdeRef">
<textarea
class="vue-simplemde-textarea"
:name="name"
:value="modelValue"
@input="handleInput($event.target.value)"
/>
</div>
</template>
<script setup lang="ts">
import {ref, watch, onMounted, onDeactivated, onBeforeUnmount, nextTick, shallowReactive} from 'vue'
import type { ShallowReactive } from 'vue'
import EasyMDE from 'easymde'
import {marked} from 'marked'
const props = defineProps({
modelValue: String,
name: String,
previewClass: String,
autoinit: {
type: Boolean,
default: true,
},
highlight: {
type: Boolean,
default: false,
},
sanitize: {
type: Boolean,
default: false,
},
configs: {
type: Object,
default: () => ({}),
},
previewRender: {
type: Function,
},
})
const emit = defineEmits(['update:modelValue', 'blur', 'initialized'])
const isValueUpdateFromInner = ref(false)
let easymde: ShallowReactive<EasyMDE> | undefined
onMounted(() => {
if (props.autoinit) initialize()
})
onDeactivated(() => {
if (!easymde) return
const isFullScreen = easymde.codemirror.getOption('fullScreen')
if (isFullScreen) easymde.toggleFullScreen()
})
onBeforeUnmount(() => {
if (easymde) {
easymde.toTextArea()
easymde.cleanup()
easymde = undefined
}
})
const easymdeRef = ref<HTMLElement | null>(null)
function initialize() {
const configs = Object.assign({
element: easymdeRef.value?.firstElementChild,
initialValue: props.modelValue,
previewRender: props.previewRender,
renderingConfig: {},
}, props.configs)
// Synchronize the values of value and initialValue
if (configs.initialValue) {
emit('update:modelValue', configs.initialValue)
}
// Determine whether to enable code highlighting
if (props.highlight) {
configs.renderingConfig.codeSyntaxHighlighting = true
}
// Set whether to render the input html
marked.setOptions({ sanitize: props.sanitize })
// Instantiated editor
easymde = shallowReactive(new EasyMDE(configs))
// Add a custom previewClass
const className = props.previewClass || ''
addPreviewClass(className)
// Binding event
bindingEvents()
nextTick(() => emit('initialized', easymde))
}
function addPreviewClass(className: string) {
const wrapper = easymde.codemirror.getWrapperElement()
const preview = document.createElement('div')
wrapper.nextSibling.className += ` ${className}`
preview.className = `editor-preview ${className}`
wrapper.appendChild(preview)
}
function bindingEvents() {
easymde.codemirror.on('change', handleCodemirrorInput)
easymde.codemirror.on('blur', handleCodemirrorBlur)
}
function handleCodemirrorInput(instance, changeObj) {
if (changeObj.origin === 'setValue') {
return
}
const val = easymde.value()
handleInput(val)
}
function handleCodemirrorBlur() {
const val = easymde.value()
isValueUpdateFromInner.value = true
emit('blur', val)
}
function handleInput(val) {
isValueUpdateFromInner.value = true
emit('update:modelValue', val)
}
watch(
() => props.modelValue,
(val) => {
if (isValueUpdateFromInner.value) {
isValueUpdateFromInner.value = false
} else {
easymde.value(val)
}
},
)
</script>
<style lang="scss">
.EasyMDEContainer { .EasyMDEContainer {
display: block; display: block;
} }
@ -58,10 +205,6 @@
.editor-toolbar { .editor-toolbar {
position: relative; position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none; user-select: none;
padding: 9px 10px; padding: 9px 10px;
border-top: 1px solid #bbb; border-top: 1px solid #bbb;
@ -89,11 +232,6 @@
.editor-toolbar.fullscreen::before { .editor-toolbar.fullscreen::before {
width: 20px; width: 20px;
height: 50px; height: 50px;
background: -moz-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 1)), color-stop(100%, rgba(255, 255, 255, 0)));
background: -webkit-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
background: -o-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
background: -ms-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
background: linear-gradient(to right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); background: linear-gradient(to right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
position: fixed; position: fixed;
top: 0; top: 0;
@ -105,11 +243,6 @@
.editor-toolbar.fullscreen::after { .editor-toolbar.fullscreen::after {
width: 20px; width: 20px;
height: 50px; height: 50px;
background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(100%, rgba(255, 255, 255, 1)));
background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
position: fixed; position: fixed;
top: 0; top: 0;
@ -371,4 +504,16 @@ span[data-img-src]::after{
padding-top: var(--height); padding-top: var(--height);
width: var(--width); width: var(--width);
background-repeat: no-repeat; background-repeat: no-repeat;
} }
</style>
<style lang="scss" scoped>
.vue-easymde .markdown-body {
padding: 0.5em
}
.vue-easymde .editor-preview-active,
.vue-easymde .editor-preview-active-side {
display: block;
}
</style>

View file

@ -1,159 +0,0 @@
<template>
<div class="vue-easymde">
<textarea
class="vue-simplemde-textarea"
:name="name"
:value="modelValue"
@input="handleInput($event.target.value)"
/>
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
import EasyMDE from 'easymde'
import {marked} from 'marked'
export default defineComponent({
name: 'vue-easymde',
props: {
modelValue: String,
name: String,
previewClass: String,
autoinit: {
type: Boolean,
default: true,
},
highlight: {
type: Boolean,
default: false,
},
sanitize: {
type: Boolean,
default: false,
},
configs: {
type: Object,
default() {
return {}
},
},
previewRender: {
type: Function,
},
},
emits: ['update:modelValue', 'blur', 'initialized'],
data() {
return {
isValueUpdateFromInner: false,
easymde: null,
}
},
mounted() {
if (this.autoinit) this.initialize()
},
deactivated() {
const editor = this.easymde
if (!editor) return
const isFullScreen = editor.codemirror.getOption('fullScreen')
if (isFullScreen) editor.toggleFullScreen()
},
beforeUnmount() {
if (this.easymde) {
this.easymde.toTextArea()
this.easymde.cleanup()
this.easymde = null
}
},
methods: {
initialize() {
const configs = Object.assign({
element: this.$el.firstElementChild,
initialValue: this.modelValue,
previewRender: this.previewRender,
renderingConfig: {},
}, this.configs)
// Synchronize the values of value and initialValue
if (configs.initialValue) {
this.$emit('update:modelValue', configs.initialValue)
}
// Determine whether to enable code highlighting
if (this.highlight) {
configs.renderingConfig.codeSyntaxHighlighting = true
}
// Set whether to render the input html
marked.setOptions({ sanitize: this.sanitize })
// Instantiated editor
this.easymde = new EasyMDE(configs)
// Add a custom previewClass
const className = this.previewClass || ''
this.addPreviewClass(className)
// Binding event
this.bindingEvents()
this.$nextTick(() => {
this.$emit('initialized', this.easymde)
})
},
addPreviewClass(className) {
const wrapper = this.easymde.codemirror.getWrapperElement()
const preview = document.createElement('div')
wrapper.nextSibling.className += ` ${className}`
preview.className = `editor-preview ${className}`
wrapper.appendChild(preview)
},
bindingEvents() {
this.easymde.codemirror.on('change', this.handleCodemirrorInput)
this.easymde.codemirror.on('blur', this.handleCodemirrorBlur)
},
handleCodemirrorInput(instance, changeObj) {
if (changeObj.origin === 'setValue') {
return
}
const val = this.easymde.value()
this.handleInput(val)
},
handleCodemirrorBlur() {
const val = this.easymde.value()
this.isValueUpdateFromInner = true
this.$emit('blur', val)
},
handleInput(val) {
this.isValueUpdateFromInner = true
this.$emit('update:modelValue', val)
},
},
watch: {
modelValue(val) {
if (this.isValueUpdateFromInner) {
this.isValueUpdateFromInner = false
} else {
this.easymde.value(val)
}
},
},
})
</script>
<style lang="scss" scoped>
.vue-easymde .markdown-body {
padding: 0.5em
}
.vue-easymde .editor-preview-active, .vue-easymde .editor-preview-active-side {
display: block;
}
</style>