Reputation: 51
I am using merkedjs, heighlight.js and marked-heighlight for a vue markdown editor (just some project to learn the composition-api). The markdown is parsed by markedjs and code is inside a <code></code> block, but it is not displayed correctly. Also, the syntax highlighting is not working.
Can not really find an answer, most posts are deprecated using the highlight option (removed), or for tutorials the code block is just displayed correctly. Can anyone help?
UI Screenshot
Parent Component
<template>
<div class="new-post__view" ref="root">
<PostWriter :post="newPost" />
</div>
</template>
<script setup lang="ts">
import { Ref, ref } from 'vue';
import { DateTime } from 'luxon';
import PostWriter from '@/components/PostWriter.vue';
import { TimelinePost } from '@/models/timelinePost';
const root: Ref<HTMLElement | null> = ref(null);
const newPost: TimelinePost = {
id: '',
title: '',
created: DateTime.now(),
markdown: '## test \n ```\nfunction () => { console.log("test") }\n```\n- [ ] test'
}
</script>
<style scoped lang="scss">
.new-post__view {
@include view;
display: flex;
align-items: start;
justify-content: center;
}
</style>
Component
<template>
<div class="post-writer" ref="root">
<h3>{{ $t('links.new-post') }}</h3>
<div class="post-writer__title">
<label for="post-writer-title">{{ $t('views.post-writer.title') }}</label>
<input type="text" id="post-writer-title" v-model="title" />
</div>
<div class="post-writer__content">
<div class="post-writer__editor">
<label for="post-writer-editor">{{ $t('views.post-writer.editor') }}</label>
<div id="post-writer-editor" ref="contentEditor" contenteditable @input="handleInput()" />
</div>
<div class="post-writer__preview">
<label for="post-writer-preview">{{ $t('views.post-writer.preview') }}</label>
<div id="post-writer-preview" v-html="parsedContent"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Ref, ref, onMounted, watch } from 'vue';
import { TimelinePost } from '@/models/timelinePost';
import { Marked } from 'marked';
import { markedHighlight } from 'marked-highlight';
import hljs from 'highlight.js';
const root: Ref<HTMLElement | null> = ref(null);
const contentEditor: Ref<HTMLElement | null> = ref(null);
// eslint-disable-next-line -- compiler macro
const props = defineProps<{
post: TimelinePost;
}>();
const title: Ref<string> = ref(props.post.title);
const content: Ref<string> = ref(props.post.markdown);
const parsedContent: Ref<string> = ref('');
const marked = new Marked(
{
gfm: true,
breaks: true
},
markedHighlight({
langPrefix: 'hljs language-',
highlight(code: string, lang: string) {
const language: string = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
})
);
watch(content, async (newContent) => {
parsedContent.value = await marked.parse(newContent);
}, { immediate: true });
async function handleInput(): Promise<void> {
if (!contentEditor.value) {
throw Error('ContentEditor/Preview DOM note(s) was not found');
}
content.value = contentEditor.value.innerText;
}
onMounted(() => {
if (!contentEditor.value) {
throw Error('ContentEditor/Preview DOM note(s) was not found');
}
contentEditor.value.innerText = content.value;
});
</script>
<style scoped lang="scss">
@import 'highlight.js/styles/atom-one-dark.css';
.post-writer {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 1rem;
gap: 2rem;
width: 100%;
h3 {
margin: 0;
}
.post-writer__title {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: 0.5rem;
width: 100%;
#post-writer-title {
background-color: $color-white;
border: 1px solid $color-gray;
border-radius: 5px;
height: 1.25rem;
padding: 0.2rem 0.5rem;
width: calc(100% - 1rem);
outline: none;
}
}
.post-writer__content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 2rem;
width: 100%;
.post-writer__editor {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 50%;
:focus {
outline: none;
}
}
.post-writer__preview {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 50%;
}
label {
margin-bottom: 0.5rem;
cursor: pointer;
}
#post-writer-editor,
#post-writer-preview {
@include custom-scrollbar;
background-color: $color-white;
border: 1px solid $color-gray;
border-radius: 5px;
width: calc(100% - 2rem);
min-height: 15rem;
height: calc(100% - 2rem);
padding: 1rem;
text-align: start;
}
#post-writer-editor {
line-height: 1.5;
}
}
@media (max-width: 800px) {
.post-writer__content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
.post-writer__editor {
width: 100%;
}
.post-writer__preview {
width: 100%;
}
}
}
}
</style>
package.json
{
"name": "web-app",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test": "vitest watch --environment happy-dom",
"test-cov": "vitest watch --environment happy-dom --coverage",
"server": "ts-node src/server/server.ts"
},
"dependencies": {
"highlight.js": "^11.9.0",
"lodash": "^4.17.21",
"luxon": "^3.4.4",
"marked": "^12.0.2",
"marked-highlight": "^2.1.1",
"pinia": "^2.1.7",
"uuid": "^9.0.1",
"vue": "^3.2.13",
"vue-i18n": "^9.10.2",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@testing-library/vue": "^8.0.3",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/lodash": "^4.17.0",
"@types/luxon": "^3.4.2",
"@types/marked": "^6.0.0",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vitest/coverage-v8": "^1.4.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"@vue/test-utils": "^2.4.5",
"cors": "^2.8.5",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"express": "^4.19.2",
"happy-dom": "^14.3.1",
"msw": "^2.2.10",
"sass": "^1.71.1",
"sass-loader": "^10.5.2",
"ts-node": "^10.9.2",
"typescript": "^5.1.6",
"vite": "^5.2.3",
"vitest": "^1.4.0",
"whatwg-fetch": "^3.6.20"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended"
],
"parserOptions": {
"ecmaVersion": 2020
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}
Been using markedjs docs And marked-heighlight docs
Upvotes: 1
Views: 199
Reputation: 51
This works for the highlighting:
const marked = new Marked(
{
gfm: true,
breaks: true
},
markedHighlight({
langPrefix: 'hljs language-',
highlight(code: string) {
return hljs.highlightAuto(code).value;
}
})
);
For the code block, the marked demo (https://marked.js.org/demo/) does not show code blocks anymore, seems to be the new default styling.
Edit: Got the code block working:
const marked = new Marked(
{
gfm: true,
breaks: true
},
markedHighlight({
highlight(code: string) {
return `<pre><code class="hljs">${hljs.highlightAuto(code).value}</code></pre>`;
}
})
);
Upvotes: 1