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:
parent
c7f8ae256b
commit
e6af4772fb
3 changed files with 161 additions and 176 deletions
|
@ -50,7 +50,7 @@
|
|||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
|
||||
import VueEasymde from './vue-easymde/vue-easymde.vue'
|
||||
import VueEasymde from './vue-easymde.vue'
|
||||
import {marked} from 'marked'
|
||||
import DOMPurify from 'dompurify'
|
||||
import hljs from 'highlight.js/lib/common'
|
||||
|
@ -316,7 +316,6 @@ export default defineComponent({
|
|||
|
||||
<style lang="scss">
|
||||
@import 'codemirror/lib/codemirror.css';
|
||||
@import './vue-easymde/vue-easymde.css';
|
||||
@import 'highlight.js/scss/base16/equilibrium-gray-light';
|
||||
|
||||
.editor {
|
||||
|
|
|
@ -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 {
|
||||
display: block;
|
||||
}
|
||||
|
@ -58,10 +205,6 @@
|
|||
|
||||
.editor-toolbar {
|
||||
position: relative;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
padding: 9px 10px;
|
||||
border-top: 1px solid #bbb;
|
||||
|
@ -89,11 +232,6 @@
|
|||
.editor-toolbar.fullscreen::before {
|
||||
width: 20px;
|
||||
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%);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -105,11 +243,6 @@
|
|||
.editor-toolbar.fullscreen::after {
|
||||
width: 20px;
|
||||
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%);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -372,3 +505,15 @@ span[data-img-src]::after{
|
|||
width: var(--width);
|
||||
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>
|
|
@ -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>
|
Loading…
Reference in a new issue