Skip to content

Commit 4a76beb

Browse files
feat(markdown): add optional codemirror as code block renderer (#218)
1 parent fd6f4a2 commit 4a76beb

15 files changed

Lines changed: 190 additions & 16 deletions

File tree

src/main/services/i18n/locales/en/preferences.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,9 @@
4141
},
4242
"language": {
4343
"label": "Language"
44+
},
45+
"markdown": {
46+
"label": "Markdown",
47+
"codeRenderer": "Code block Renderer"
4448
}
4549
}

src/main/services/i18n/locales/en/special.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
"Snippets with unsupported languages will be set to default Plain Text."
1111
]
1212
},
13-
"htmlCssPreview": "Add fragments with HTML & CSS languages to view result."
13+
"htmlCssPreview": "Add fragments with HTML & CSS languages to view result.",
14+
"codeBlockRenderer": [
15+
"When using Codemirror, the language to be set for the code block must correspond to one of the values of the",
16+
"languages"
17+
]
1418
},
1519
"success": {
1620
"migrate": "DB successfully migrated."

src/main/services/i18n/locales/ru/preferences.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,9 @@
4141
},
4242
"language": {
4343
"label": "Язык"
44+
},
45+
"markdown": {
46+
"label": "Markdown",
47+
"codeRenderer": "Рендерер блоков кода"
4448
}
4549
}

src/main/services/i18n/locales/ru/special.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
"Сниппеты с неподдерживаемыми языками будут установлены на стандартный простой текст."
1111
]
1212
},
13-
"htmlCssPreview": "Добавьте фрагменты с языками HTML и CSS для просмотра результата."
13+
"htmlCssPreview": "Добавьте фрагменты с языками HTML и CSS для просмотра результата.",
14+
"codeBlockRenderer": [
15+
"При использовании Codemirror, устанавливаемый язык для блока кода должен соответствовать одному из значений",
16+
"языков"
17+
]
1418
},
1519
"success": {
1620
"migrate": "БД успешно перенесена."

src/main/store/module/preferences.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ export default new Store<PreferencesStore>({
3434
width: 600
3535
},
3636
markdown: {
37-
presentationScale: 1.3
37+
presentationScale: 1.3,
38+
codeRenderer: 'highlight.js'
3839
},
3940
language: 'en'
4041
}

src/renderer/App.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ const init = async () => {
7676
appStore.sizes.sidebar = store.app.get('sidebarWidth')
7777
appStore.sizes.snippetList = store.app.get('snippetListWidth')
7878
appStore.screenshot = store.preferences.get('screenshot')
79-
appStore.markdown = store.preferences.get('markdown')
79+
appStore.markdown = {
80+
...appStore.markdown,
81+
...store.preferences.get('markdown')
82+
}
8083
8184
snippetStore.sort = store.app.get('sort')
8285

src/renderer/assets/scss/markdown.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,5 +951,8 @@
951951
pre {
952952
background-color: rgba(0, 0, 0, 0.15);
953953
}
954+
.CodeMirror-gutter {
955+
background-color: rgba(0, 0, 0, 0.03) !important;
956+
}
954957
}
955958
}

src/renderer/assets/scss/themes.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,12 @@
298298

299299
--color-tag-delete: var(--color-contrast-lower-alt3);
300300
}
301+
302+
[data-theme^="dark"] {
303+
a {
304+
color: var(--color-contrast-medium);
305+
&:hover {
306+
color: var(--color-text);
307+
}
308+
}
309+
}

src/renderer/components/markdown/TheMarkdown.vue

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div class="markdown markdown-body">
33
<PerfectScrollbar>
4-
<div v-html="renderer" />
4+
<div v-html="renderedHtml" />
55
</PerfectScrollbar>
66
</div>
77
</template>
@@ -11,11 +11,14 @@ import { useAppStore } from '@/store/app'
1111
import { useSnippetStore } from '@/store/snippets'
1212
import sanitizeHtml from 'sanitize-html'
1313
import hljs from 'highlight.js'
14-
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
14+
import { computed, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue'
1515
import { ipc, store } from '@/electron'
1616
import { marked } from 'marked'
1717
import mermaid from 'mermaid'
1818
import { useHljsTheme } from '@/composable'
19+
import { useCodemirror } from '@/composable/codemirror'
20+
21+
import { nanoid } from 'nanoid'
1922
2023
const isDev = import.meta.env.DEV
2124
@@ -24,28 +27,51 @@ interface Props {
2427
scale?: number
2528
}
2629
30+
interface Editors {
31+
id: string
32+
value: string
33+
lang: string
34+
}
35+
2736
const props = withDefaults(defineProps<Props>(), {
2837
scale: 1
2938
})
3039
3140
const appStore = useAppStore()
3241
const snippetStore = useSnippetStore()
3342
43+
const renderedHtml = ref()
44+
45+
const editors: Editors[] = []
46+
3447
const forceRefresh = ref()
3548
const preTagBg = computed(() =>
3649
appStore.isLightTheme ? '#fff' : 'var(--color-contrast-high)'
3750
)
51+
const fontFamily = computed(() => appStore.editor.fontFamily)
3852
3953
const init = () => {
4054
const renderer: marked.RendererObject = {
4155
code (code: string, lang: string) {
4256
if (lang === 'mermaid') {
4357
return `<div class="mermaid">${code}</div><br>`
4458
} else {
45-
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
46-
return `<pre><code class="language-${lang}">${
47-
hljs.highlight(code, { language }).value
48-
}</code></pre>`
59+
if (appStore.markdown.codeRenderer === 'highlight.js') {
60+
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
61+
return `<pre><code class="language-${lang}">${
62+
hljs.highlight(code, { language }).value
63+
}</code></pre>`
64+
} else {
65+
const id = nanoid(6)
66+
67+
editors.push({
68+
id,
69+
value: code,
70+
lang
71+
})
72+
73+
return `<div id="${id}"></div>`
74+
}
4975
}
5076
},
5177
link (href: string, title: string, text: string) {
@@ -69,10 +95,12 @@ const initMermaid = () => {
6995
7096
onMounted(() => {
7197
initMermaid()
98+
render()
7299
})
73100
74-
const getRenderer = () => {
101+
const render = () => {
75102
const raw = marked.parse(props.value)
103+
76104
let html = sanitizeHtml(raw, {
77105
allowedTags: [
78106
'h1',
@@ -152,7 +180,8 @@ const getRenderer = () => {
152180
'class',
153181
'type',
154182
'checked',
155-
'disabled'
183+
'disabled',
184+
'id'
156185
]
157186
}
158187
})
@@ -164,11 +193,9 @@ const getRenderer = () => {
164193
? html.replace(re, `src="file://${path}/`)
165194
: html.replace(re, `src="${path}/`)
166195
167-
return html
196+
renderedHtml.value = html
168197
}
169198
170-
const renderer = computed(() => getRenderer())
171-
172199
const openExternal = (e: Event) => {
173200
const el = e.target as HTMLAnchorElement
174201
e.preventDefault()
@@ -209,6 +236,24 @@ watch(
209236
{ immediate: true }
210237
)
211238
239+
watch(renderedHtml, () => {
240+
nextTick(() => {
241+
editors.forEach(i => {
242+
useCodemirror(i.id, {
243+
value: i.value,
244+
mode: i.lang
245+
})
246+
})
247+
})
248+
})
249+
250+
watch(
251+
() => props.value,
252+
() => {
253+
render()
254+
}
255+
)
256+
212257
init()
213258
214259
onMounted(() => {
@@ -238,5 +283,20 @@ window.addEventListener('resize', () => {
238283
:deep(.ps) {
239284
height: v-bind(height);
240285
}
286+
:deep(.CodeMirror) {
287+
height: 100%;
288+
padding: var(--spacing-xs);
289+
font-size: 0.85em;
290+
font-family: v-bind(fontFamily);
291+
}
292+
:deep(.CodeMirror-line) {
293+
padding: 0 var(--spacing-sm);
294+
&:first-child {
295+
padding-top: var(--spacing-xs);
296+
}
297+
&:last-child {
298+
padding-bottom: var(--spacing-xs);
299+
}
300+
}
241301
}
242302
</style>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<template>
2+
<div class="language-preferences">
3+
<AppForm>
4+
<AppFormItem :label="i18n.t('preferences:markdown.codeRenderer')">
5+
<AppSelect
6+
v-model="renderer"
7+
:options="rendererOptions"
8+
/>
9+
<template #desc>
10+
{{ i18n.t('special:description.codeBlockRenderer.0') }}
11+
<a
12+
href="#"
13+
@click="
14+
onClickUrl(
15+
'https://github.com/massCodeIO/massCode/blob/master/src/renderer/components/editor/languages.ts'
16+
)
17+
"
18+
>{{ i18n.t('special:description.codeBlockRenderer.1') }}</a>.
19+
</template>
20+
</AppFormItem>
21+
</AppForm>
22+
</div>
23+
</template>
24+
25+
<script setup lang="ts">
26+
import { i18n, store } from '@/electron'
27+
import { useAppStore } from '@/store/app'
28+
import { computed } from 'vue'
29+
import { onClickUrl } from '@/composable'
30+
31+
const appStore = useAppStore()
32+
33+
const renderer = computed({
34+
get: () => appStore.markdown.codeRenderer,
35+
set: v => {
36+
appStore.markdown.codeRenderer = v
37+
store.preferences.set('markdown', { ...appStore.markdown })
38+
}
39+
})
40+
41+
const rendererOptions = [
42+
{
43+
label: 'Codemirror',
44+
value: 'codemirror'
45+
},
46+
{
47+
label: 'Highlight.js',
48+
value: 'highlight.js'
49+
}
50+
]
51+
</script>
52+
53+
<style lang="scss" scoped></style>

0 commit comments

Comments
 (0)