Dominik Pschenitschni
f4a69f8bec
All checks were successful
continuous-integration/drone/pr Build is passing
There are lots of error logs in sentry for cases where easymde is called but not defined. This tries to solve this.
523 lines
10 KiB
Vue
523 lines
10 KiB
Vue
<template>
|
|
<div class="vue-easymde" ref="easymdeRef">
|
|
<textarea
|
|
class="vue-simplemde-textarea"
|
|
:name="name"
|
|
:value="modelValue"
|
|
@input="handleInput(($event.target as HTMLTextAreaElement).value)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {ref, watch, onMounted, onDeactivated, onBeforeUnmount, nextTick, shallowReactive, type ShallowReactive, type PropType} from 'vue'
|
|
import EasyMDE, {toggleFullScreen} from 'easymde'
|
|
import {marked} from 'marked'
|
|
import type CodeMirror from 'codemirror'
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
name: {
|
|
type: String,
|
|
},
|
|
previewClass: {
|
|
type: String,
|
|
},
|
|
autoinit: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
highlight: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
sanitize: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
configs: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
previewRender: {
|
|
type: Function as PropType<EasyMDE.Options['previewRender']>,
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits(['update:modelValue', 'blur', 'initialized'])
|
|
|
|
const isValueUpdateFromInner = ref(false)
|
|
let easymde: ShallowReactive<EasyMDE> | undefined
|
|
|
|
onMounted(() => {
|
|
if (props.autoinit) initialize()
|
|
})
|
|
|
|
onDeactivated(() => {
|
|
if (easymde === undefined) return
|
|
if (easymde.isFullscreenActive()) toggleFullScreen(easymde)
|
|
easymde.toTextArea
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
if (easymde) {
|
|
easymde.toTextArea()
|
|
easymde.cleanup()
|
|
easymde = undefined
|
|
}
|
|
})
|
|
|
|
const easymdeRef = ref<HTMLElement | null>(null)
|
|
|
|
function initialize() {
|
|
const configs: EasyMDE.Options = Object.assign({
|
|
element: easymdeRef.value?.firstElementChild as HTMLElement,
|
|
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(easymde, className)
|
|
|
|
// Binding event
|
|
easymde.codemirror.on('change', handleCodemirrorInput)
|
|
easymde.codemirror.on('blur', handleCodemirrorBlur)
|
|
|
|
nextTick(() => emit('initialized', easymde))
|
|
}
|
|
|
|
function addPreviewClass(easymde: EasyMDE, 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 handleInput(val: string) {
|
|
isValueUpdateFromInner.value = true
|
|
emit('update:modelValue', val)
|
|
}
|
|
|
|
function handleCodemirrorInput(instance: CodeMirror.Editor, changeObj: CodeMirror.EditorChange) {
|
|
if (changeObj.origin === 'setValue' || easymde === undefined) {
|
|
return
|
|
}
|
|
handleInput(easymde.value())
|
|
}
|
|
|
|
function handleCodemirrorBlur() {
|
|
if (easymde === undefined) {
|
|
return
|
|
}
|
|
isValueUpdateFromInner.value = true
|
|
emit('blur', easymde.value())
|
|
}
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(val) => {
|
|
if (isValueUpdateFromInner.value) {
|
|
isValueUpdateFromInner.value = false
|
|
} else {
|
|
easymde?.value(val)
|
|
}
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.EasyMDEContainer {
|
|
display: block;
|
|
}
|
|
|
|
.EasyMDEContainer.sided--no-fullscreen {
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.EasyMDEContainer .CodeMirror {
|
|
box-sizing: border-box;
|
|
height: auto;
|
|
border: 1px solid #ddd;
|
|
border-bottom-left-radius: 4px;
|
|
border-bottom-right-radius: 4px;
|
|
padding: 10px;
|
|
font: inherit;
|
|
z-index: 0;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.EasyMDEContainer .CodeMirror-scroll {
|
|
cursor: text;
|
|
}
|
|
|
|
.EasyMDEContainer .CodeMirror-fullscreen {
|
|
background: #fff;
|
|
position: fixed !important;
|
|
top: 50px;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
height: auto;
|
|
z-index: 8;
|
|
border-right: none !important;
|
|
border-bottom-right-radius: 0 !important;
|
|
}
|
|
|
|
.EasyMDEContainer .CodeMirror-sided {
|
|
width: 50% !important;
|
|
}
|
|
|
|
.EasyMDEContainer.sided--no-fullscreen .CodeMirror-sided {
|
|
border-right: none!important;
|
|
border-bottom-right-radius: 0px;
|
|
position: relative;
|
|
flex: 1 1 auto;
|
|
}
|
|
|
|
.EasyMDEContainer .CodeMirror-placeholder {
|
|
opacity: .5;
|
|
}
|
|
|
|
.EasyMDEContainer .CodeMirror-focused .CodeMirror-selected {
|
|
background: #d9d9d9;
|
|
}
|
|
|
|
.editor-toolbar {
|
|
position: relative;
|
|
user-select: none;
|
|
padding: 9px 10px;
|
|
border-top: 1px solid #bbb;
|
|
border-left: 1px solid #bbb;
|
|
border-right: 1px solid #bbb;
|
|
border-top-left-radius: 4px;
|
|
border-top-right-radius: 4px;
|
|
}
|
|
|
|
.editor-toolbar.fullscreen {
|
|
width: 100%;
|
|
height: 50px;
|
|
padding-top: 10px;
|
|
padding-bottom: 10px;
|
|
box-sizing: border-box;
|
|
background: #fff;
|
|
border: 0;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
opacity: 1;
|
|
z-index: 9;
|
|
}
|
|
|
|
.editor-toolbar.fullscreen::before {
|
|
width: 20px;
|
|
height: 50px;
|
|
background: linear-gradient(to right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.editor-toolbar.fullscreen::after {
|
|
width: 20px;
|
|
height: 50px;
|
|
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
|
|
position: fixed;
|
|
top: 0;
|
|
right: 0;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.EasyMDEContainer.sided--no-fullscreen .editor-toolbar {
|
|
width: 100%;
|
|
}
|
|
|
|
.editor-toolbar button, .editor-toolbar .easymde-dropdown {
|
|
background: transparent;
|
|
display: inline-block;
|
|
text-align: center;
|
|
text-decoration: none !important;
|
|
height: 30px;
|
|
margin: 0;
|
|
padding: 0;
|
|
border: 1px solid transparent;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.editor-toolbar button {
|
|
width: 30px;
|
|
}
|
|
|
|
.editor-toolbar button.active,
|
|
.editor-toolbar button:hover {
|
|
background: #fcfcfc;
|
|
border-color: #95a5a6;
|
|
}
|
|
|
|
.editor-toolbar i.separator {
|
|
display: inline-block;
|
|
width: 0;
|
|
border-left: 1px solid #d9d9d9;
|
|
border-right: 1px solid #fff;
|
|
color: transparent;
|
|
text-indent: -10px;
|
|
margin: 0 6px;
|
|
}
|
|
|
|
.editor-toolbar button:after {
|
|
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
|
|
font-size: 65%;
|
|
vertical-align: text-bottom;
|
|
position: relative;
|
|
top: 2px;
|
|
}
|
|
|
|
.editor-toolbar button.heading-1:after {
|
|
content: "1";
|
|
}
|
|
|
|
.editor-toolbar button.heading-2:after {
|
|
content: "2";
|
|
}
|
|
|
|
.editor-toolbar button.heading-3:after {
|
|
content: "3";
|
|
}
|
|
|
|
.editor-toolbar button.heading-bigger:after {
|
|
content: "▲";
|
|
}
|
|
|
|
.editor-toolbar button.heading-smaller:after {
|
|
content: "▼";
|
|
}
|
|
|
|
.editor-toolbar.disabled-for-preview button:not(.no-disable) {
|
|
opacity: .6;
|
|
pointer-events: none;
|
|
}
|
|
|
|
@media only screen and (max-width: 700px) {
|
|
.editor-toolbar i.no-mobile {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.editor-statusbar {
|
|
padding: 8px 10px;
|
|
font-size: 12px;
|
|
color: #959694;
|
|
text-align: right;
|
|
}
|
|
|
|
.EasyMDEContainer.sided--no-fullscreen .editor-statusbar {
|
|
width: 100%;
|
|
}
|
|
|
|
.editor-statusbar span {
|
|
display: inline-block;
|
|
min-width: 4em;
|
|
margin-left: 1em;
|
|
}
|
|
|
|
.editor-statusbar .lines:before {
|
|
content: 'lines: '
|
|
}
|
|
|
|
.editor-statusbar .words:before {
|
|
content: 'words: '
|
|
}
|
|
|
|
.editor-statusbar .characters:before {
|
|
content: 'characters: '
|
|
}
|
|
|
|
.editor-preview-full {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
top: 0;
|
|
left: 0;
|
|
z-index: 7;
|
|
overflow: auto;
|
|
display: none;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.editor-preview-side {
|
|
position: fixed;
|
|
bottom: 0;
|
|
width: 50%;
|
|
top: 50px;
|
|
right: 0;
|
|
z-index: 9;
|
|
overflow: auto;
|
|
display: none;
|
|
box-sizing: border-box;
|
|
border: 1px solid #ddd;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.editor-preview-active-side {
|
|
display: block
|
|
}
|
|
|
|
.EasyMDEContainer.sided--no-fullscreen .editor-preview-active-side {
|
|
flex: 1 1 auto;
|
|
height: auto;
|
|
position: static;
|
|
}
|
|
|
|
.editor-preview-active {
|
|
display: block
|
|
}
|
|
|
|
.editor-preview {
|
|
padding: 10px;
|
|
background: #fafafa;
|
|
}
|
|
|
|
.editor-preview > p {
|
|
margin-top: 0
|
|
}
|
|
|
|
.editor-preview pre {
|
|
background: #eee;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.editor-preview table td,
|
|
.editor-preview table th {
|
|
border: 1px solid #ddd;
|
|
padding: 5px;
|
|
}
|
|
|
|
.cm-s-easymde .cm-tag {
|
|
color: #63a35c;
|
|
}
|
|
|
|
.cm-s-easymde .cm-attribute {
|
|
color: #795da3;
|
|
}
|
|
|
|
.cm-s-easymde .cm-string {
|
|
color: #183691;
|
|
}
|
|
|
|
.cm-s-easymde .cm-header-1 {
|
|
font-size: 200%;
|
|
line-height: 200%;
|
|
}
|
|
|
|
.cm-s-easymde .cm-header-2 {
|
|
font-size: 160%;
|
|
line-height: 160%;
|
|
}
|
|
|
|
.cm-s-easymde .cm-header-3 {
|
|
font-size: 125%;
|
|
line-height: 125%;
|
|
}
|
|
|
|
.cm-s-easymde .cm-header-4 {
|
|
font-size: 110%;
|
|
line-height: 110%;
|
|
}
|
|
|
|
.cm-s-easymde .cm-comment {
|
|
background: rgba(0, 0, 0, .05);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.cm-s-easymde .cm-link {
|
|
color: #7f8c8d;
|
|
}
|
|
|
|
.cm-s-easymde .cm-url {
|
|
color: #aab2b3;
|
|
}
|
|
|
|
.cm-s-easymde .cm-quote {
|
|
color: #7f8c8d;
|
|
font-style: italic;
|
|
}
|
|
|
|
.editor-toolbar .easymde-dropdown {
|
|
position: relative;
|
|
background: linear-gradient(to bottom right, #fff 0%, #fff 84%, #333 50%, #333 100%);
|
|
border-radius: 0;
|
|
border: 1px solid #fff;
|
|
}
|
|
|
|
.editor-toolbar .easymde-dropdown:hover {
|
|
background: linear-gradient(to bottom right, #fff 0%, #fff 84%, #333 50%, #333 100%);
|
|
}
|
|
|
|
.easymde-dropdown-content {
|
|
display: block;
|
|
visibility: hidden;
|
|
position: absolute;
|
|
background-color: #f9f9f9;
|
|
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
|
|
padding: 8px;
|
|
z-index: 2;
|
|
top: 30px;
|
|
}
|
|
|
|
.easymde-dropdown:active .easymde-dropdown-content,
|
|
.easymde-dropdown:focus .easymde-dropdown-content {
|
|
visibility: visible;
|
|
}
|
|
|
|
span[data-img-src]::after{
|
|
content: '';
|
|
background-image: var(--bg-image);
|
|
display: block;
|
|
max-height: 100%;
|
|
max-width: 100%;
|
|
background-size: contain;
|
|
height: 0;
|
|
padding-top: var(--height);
|
|
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>
|