Add Markdown syntax highlighting (#2068)

Closes #2058.

Co-authored-by: Kailash Nadh <kailash@nadh.in>
This commit is contained in:
Bowrna 2024-10-13 16:58:48 +05:30 committed by GitHub
parent b0f3891629
commit 5074987544
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 88 additions and 3 deletions

View file

@ -18,6 +18,7 @@
"codeflask": "^1.4.1",
"dayjs": "^1.11.10",
"indent.js": "^0.3.5",
"prismjs":"^1.29.0",
"qs": "^6.10.1",
"textversionjs": "^1.1.3",
"tinymce": "^5.10.9",

View file

@ -243,7 +243,7 @@ body.is-noscroll {
}
/* WYSIWYG / HTML code editor */
.html-editor {
.html-editor, .markdown-editor {
position: relative;
width: 100%;
min-height: 250px;

View file

@ -80,8 +80,11 @@
<!-- raw html editor //-->
<html-editor v-if="form.format === 'html'" v-model="form.body" />
<!-- plain text / markdown editor //-->
<b-input v-if="form.format === 'plain' || form.format === 'markdown'" v-model="form.body" @input="onEditorChange"
<!-- markdown editor //-->
<markdown-editor v-if="form.format === 'markdown'" v-model="form.body" />
<!-- plain text //-->
<b-input v-if="form.format === 'plain'" v-model="form.body" @input="onEditorChange"
type="textarea" name="content" ref="plainEditor" class="plain-editor" />
<!-- campaign preview //-->
@ -136,6 +139,7 @@ import { colors, uris } from '../constants';
import Media from '../views/Media.vue';
import CampaignPreview from './CampaignPreview.vue';
import HTMLEditor from './HTMLEditor.vue';
import MarkdownEditor from './MarkdownEditor.vue';
const turndown = new TurndownService();
@ -158,6 +162,7 @@ export default {
Media,
CampaignPreview,
'html-editor': HTMLEditor,
'markdown-editor': MarkdownEditor,
TinyMce,
},

View file

@ -0,0 +1,79 @@
<template>
<div ref="markdownEditor" id="markdown-editor" class="markdown-editor" />
</template>
<script>
import 'prismjs/components/prism-markdown';
import CodeFlask from 'codeflask';
import { colors } from '../constants';
export default {
props: {
value: { type: String, default: '' },
language: { type: String, default: 'markdown' },
disabled: Boolean,
},
data() {
return {
data: '',
flask: null,
};
},
methods: {
initMarkdownEditor(body) {
// CodeFlask editor is rendered in a shadow DOM tree to keep its styles
// sandboxed away from the global styles.
const el = document.createElement('code-flask');
el.attachShadow({ mode: 'open' });
el.shadowRoot.innerHTML = `
<style>
.codeflask .codeflask__flatten { font-size: 15px; }
.codeflask .token.tag { font-weight: bold; color: ${colors.primary} !important; }
.codeflask .token { color: ${colors.primary} !important; }
.codeflask .token.heading { font-weight: bold; }
.codeflask .token.important,.token.bold,.token.strong { font-weight: bold; }
.codeflask .token.em,.token.italic { font-style: italic; }
.codeflask .token.comment { color: slategray; }
.codeflask .token.url { color: ${colors.primary}; text-decoration: underline; }
</style>
<div id="area"></area>
`;
this.$refs.markdownEditor.appendChild(el);
this.flask = new CodeFlask(el.shadowRoot.getElementById('area'), {
language: this.$props.language || 'markdown',
lineNumbers: false,
styleParent: el.shadowRoot,
readonly: this.disabled,
});
this.flask.onUpdate((v) => {
this.data = v;
this.$emit('input', v);
});
// Set the initial value.
this.flask.updateCode(body);
this.$nextTick(() => {
document.querySelector('code-flask').shadowRoot.querySelector('textarea').focus();
});
},
},
mounted() {
this.initMarkdownEditor(this.$props.value || '');
},
watch: {
value(newVal) {
if (newVal !== this.data) {
this.flask.updateCode(newVal);
}
},
},
};
</script>