From 7a6af370bc413bbeb776a46283bbc02043ac9c72 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 11 Aug 2022 00:30:40 +0300 Subject: [PATCH 1/5] feat(markdown): add optional codemirror as code block renderer --- src/main/store/module/preferences.ts | 3 +- src/renderer/App.vue | 5 +- src/renderer/assets/scss/markdown.scss | 3 + .../components/markdown/TheMarkdown.vue | 100 ++++++++++++++++-- .../preferences/MarkdownPreferences.vue | 41 +++++++ src/renderer/composable/codemirror.ts | 16 +++ src/renderer/store/app.ts | 3 +- src/renderer/views/Preferences.vue | 6 ++ src/shared/types/main/store.d.ts | 1 + 9 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 src/renderer/components/preferences/MarkdownPreferences.vue create mode 100644 src/renderer/composable/codemirror.ts diff --git a/src/main/store/module/preferences.ts b/src/main/store/module/preferences.ts index aba8fbd9..c9183ff2 100644 --- a/src/main/store/module/preferences.ts +++ b/src/main/store/module/preferences.ts @@ -34,7 +34,8 @@ export default new Store({ width: 600 }, markdown: { - presentationScale: 1.3 + presentationScale: 1.3, + codeRenderer: 'highlight.js' }, language: 'en' } diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 9d1bc17f..a486f072 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -76,7 +76,10 @@ const init = async () => { appStore.sizes.sidebar = store.app.get('sidebarWidth') appStore.sizes.snippetList = store.app.get('snippetListWidth') appStore.screenshot = store.preferences.get('screenshot') - appStore.markdown = store.preferences.get('markdown') + appStore.markdown = { + ...appStore.markdown, + ...store.preferences.get('markdown') + } snippetStore.sort = store.app.get('sort') diff --git a/src/renderer/assets/scss/markdown.scss b/src/renderer/assets/scss/markdown.scss index 23026fa7..808dcf80 100644 --- a/src/renderer/assets/scss/markdown.scss +++ b/src/renderer/assets/scss/markdown.scss @@ -951,5 +951,8 @@ pre { background-color: rgba(0, 0, 0, 0.15); } + .CodeMirror-gutter { + background-color: rgba(0, 0, 0, 0.03) !important; + } } } \ No newline at end of file diff --git a/src/renderer/components/markdown/TheMarkdown.vue b/src/renderer/components/markdown/TheMarkdown.vue index b4f72a00..eb223aa6 100644 --- a/src/renderer/components/markdown/TheMarkdown.vue +++ b/src/renderer/components/markdown/TheMarkdown.vue @@ -1,7 +1,7 @@ @@ -11,11 +11,14 @@ import { useAppStore } from '@/store/app' import { useSnippetStore } from '@/store/snippets' import sanitizeHtml from 'sanitize-html' import hljs from 'highlight.js' -import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' +import { computed, onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue' import { ipc, store } from '@/electron' import { marked } from 'marked' import mermaid from 'mermaid' import { useHljsTheme } from '@/composable' +import { useCodemirror } from '@/composable/codemirror' + +import { nanoid } from 'nanoid' const isDev = import.meta.env.DEV @@ -24,6 +27,12 @@ interface Props { scale?: number } +interface Editors { + id: string + value: string + lang: string +} + const props = withDefaults(defineProps(), { scale: 1 }) @@ -31,10 +40,15 @@ const props = withDefaults(defineProps(), { const appStore = useAppStore() const snippetStore = useSnippetStore() +const renderedHtml = ref() + +const editors: Editors[] = [] + const forceRefresh = ref() const preTagBg = computed(() => appStore.isLightTheme ? '#fff' : 'var(--color-contrast-high)' ) +const fontFamily = computed(() => appStore.editor.fontFamily) const init = () => { const renderer: marked.RendererObject = { @@ -42,10 +56,22 @@ const init = () => { if (lang === 'mermaid') { return `
${code}

` } else { - const language = hljs.getLanguage(lang) ? lang : 'plaintext' - return `
${
-          hljs.highlight(code, { language }).value
-        }
` + if (appStore.markdown.codeRenderer === 'highlight.js') { + const language = hljs.getLanguage(lang) ? lang : 'plaintext' + return `
${
+            hljs.highlight(code, { language }).value
+          }
` + } else { + const id = nanoid(6) + + editors.push({ + id, + value: code, + lang + }) + + return `
` + } } }, link (href: string, title: string, text: string) { @@ -69,10 +95,12 @@ const initMermaid = () => { onMounted(() => { initMermaid() + render() }) -const getRenderer = () => { +const render = () => { const raw = marked.parse(props.value) + let html = sanitizeHtml(raw, { allowedTags: [ 'h1', @@ -152,7 +180,8 @@ const getRenderer = () => { 'class', 'type', 'checked', - 'disabled' + 'disabled', + 'id' ] } }) @@ -164,11 +193,9 @@ const getRenderer = () => { ? html.replace(re, `src="file://${path}/`) : html.replace(re, `src="${path}/`) - return html + renderedHtml.value = html } -const renderer = computed(() => getRenderer()) - const openExternal = (e: Event) => { const el = e.target as HTMLAnchorElement e.preventDefault() @@ -209,6 +236,24 @@ watch( { immediate: true } ) +watch(renderedHtml, () => { + nextTick(() => { + editors.forEach(i => { + useCodemirror(i.id, { + value: i.value, + mode: i.lang + }) + }) + }) +}) + +watch( + () => props.value, + () => { + render() + } +) + init() onMounted(() => { @@ -238,5 +283,38 @@ window.addEventListener('resize', () => { :deep(.ps) { height: v-bind(height); } + :deep(.CodeMirror) { + height: 100%; + padding: var(--spacing-xs); + font-family: v-bind(fontFamily); + } + :deep(.CodeMirror-line) { + padding: 0 var(--spacing-sm); + &:first-child { + padding-top: var(--spacing-xs); + } + &:last-child { + padding-bottom: var(--spacing-xs); + } + // padding: var(--spacing-xs); + } + :deep(.CodeMirror-code) { + // padding: var(--spacing-xs); + } + // :deep(.CodeMirror-overlayscroll-vertical div) { + // background-color: rgba(121, 121, 121, 0.8); + // width: 5px; + // // opacity: v-bind(scrollBarOpacity); + // transition: opacity 0.3s; + // } + // :deep(.CodeMirror-overlayscroll-horizontal div) { + // background-color: rgba(121, 121, 121, 0.8); + // height: 5px; + // // opacity: v-bind(scrollBarOpacity); + // transition: opacity 0.3s; + // } + // :deep(.CodeMirror-scrollbar-filler) { + // background-color: transparent; + // } } diff --git a/src/renderer/components/preferences/MarkdownPreferences.vue b/src/renderer/components/preferences/MarkdownPreferences.vue new file mode 100644 index 00000000..1468c7eb --- /dev/null +++ b/src/renderer/components/preferences/MarkdownPreferences.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/renderer/composable/codemirror.ts b/src/renderer/composable/codemirror.ts new file mode 100644 index 00000000..d0480e3b --- /dev/null +++ b/src/renderer/composable/codemirror.ts @@ -0,0 +1,16 @@ +import { getThemeName } from '@/components/editor/themes' +import { useAppStore } from '@/store/app' +import type { EditorConfiguration } from 'codemirror' +import CodeMirror from 'codemirror' + +export const useCodemirror = (elemId: string, options: EditorConfiguration) => { + const appStore = useAppStore() + + CodeMirror(document.getElementById(elemId)!, { + ...options, + theme: getThemeName(appStore.theme) || 'GitHub', + scrollbarStyle: 'null', + readOnly: true, + cursorBlinkRate: -1 + }) +} diff --git a/src/renderer/store/app.ts b/src/renderer/store/app.ts index f5e209ba..bdead39f 100644 --- a/src/renderer/store/app.ts +++ b/src/renderer/store/app.ts @@ -35,7 +35,8 @@ const CODE_PREVIEW_DEFAULTS: CodePreviewSettings = { } const MARKDOWN_DEFAULTS: MarkdownSettings = { - presentationScale: 1.3 + presentationScale: 1.3, + codeRenderer: 'highlight.js' } export const useAppStore = defineStore('app', { diff --git a/src/renderer/views/Preferences.vue b/src/renderer/views/Preferences.vue index a286afe0..2a1d0f44 100644 --- a/src/renderer/views/Preferences.vue +++ b/src/renderer/views/Preferences.vue @@ -23,6 +23,12 @@ > + + + Date: Thu, 11 Aug 2022 01:00:49 +0300 Subject: [PATCH 2/5] feat(i18n): update locales --- src/main/services/i18n/locales/en/preferences.json | 4 ++++ src/main/services/i18n/locales/en/special.json | 6 +++++- src/main/services/i18n/locales/ru/preferences.json | 4 ++++ src/main/services/i18n/locales/ru/special.json | 6 +++++- .../components/preferences/MarkdownPreferences.vue | 12 ++++++++++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main/services/i18n/locales/en/preferences.json b/src/main/services/i18n/locales/en/preferences.json index 3daeac30..93f3daa0 100644 --- a/src/main/services/i18n/locales/en/preferences.json +++ b/src/main/services/i18n/locales/en/preferences.json @@ -41,5 +41,9 @@ }, "language": { "label": "Language" + }, + "markdown": { + "label": "Markdown", + "codeRenderer": "Code block Renderer" } } diff --git a/src/main/services/i18n/locales/en/special.json b/src/main/services/i18n/locales/en/special.json index d183cfee..e5be9f03 100644 --- a/src/main/services/i18n/locales/en/special.json +++ b/src/main/services/i18n/locales/en/special.json @@ -10,7 +10,11 @@ "Snippets with unsupported languages will be set to default Plain Text." ] }, - "htmlCssPreview": "Add fragments with HTML & CSS languages to view result." + "htmlCssPreview": "Add fragments with HTML & CSS languages to view result.", + "codeBlockRenderer": [ + "When using Codemirror, the language to be set for the code block must correspond to one of the values of the", + "languages" + ] }, "success": { "migrate": "DB successfully migrated." diff --git a/src/main/services/i18n/locales/ru/preferences.json b/src/main/services/i18n/locales/ru/preferences.json index 25bbd075..e1bd7b24 100644 --- a/src/main/services/i18n/locales/ru/preferences.json +++ b/src/main/services/i18n/locales/ru/preferences.json @@ -41,5 +41,9 @@ }, "language": { "label": "Язык" + }, + "markdown": { + "label": "Markdown", + "codeRenderer": "Рендерер блоков кода" } } diff --git a/src/main/services/i18n/locales/ru/special.json b/src/main/services/i18n/locales/ru/special.json index 17065a98..6db12bcf 100644 --- a/src/main/services/i18n/locales/ru/special.json +++ b/src/main/services/i18n/locales/ru/special.json @@ -10,7 +10,11 @@ "Сниппеты с неподдерживаемыми языками будут установлены на стандартный простой текст." ] }, - "htmlCssPreview": "Добавьте фрагменты с языками HTML и CSS для просмотра результата." + "htmlCssPreview": "Добавьте фрагменты с языками HTML и CSS для просмотра результата.", + "codeBlockRenderer": [ + "При использовании Codemirror, устанавливаемый язык для блока кода должен соответствовать одному из значений", + "языков" + ] }, "success": { "migrate": "БД успешно перенесена." diff --git a/src/renderer/components/preferences/MarkdownPreferences.vue b/src/renderer/components/preferences/MarkdownPreferences.vue index 1468c7eb..8fe46786 100644 --- a/src/renderer/components/preferences/MarkdownPreferences.vue +++ b/src/renderer/components/preferences/MarkdownPreferences.vue @@ -6,6 +6,17 @@ v-model="renderer" :options="rendererOptions" /> + @@ -15,6 +26,7 @@ import { i18n, store } from '@/electron' import { useAppStore } from '@/store/app' import { computed } from 'vue' +import { onClickUrl } from '@/composable' const appStore = useAppStore() From 81d92f2e297846b4b81b98351cf5af31fa33a334 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 11 Aug 2022 01:01:15 +0300 Subject: [PATCH 3/5] chore(style): add links style --- src/renderer/assets/scss/themes.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/renderer/assets/scss/themes.scss b/src/renderer/assets/scss/themes.scss index 98871901..7e3e1b81 100644 --- a/src/renderer/assets/scss/themes.scss +++ b/src/renderer/assets/scss/themes.scss @@ -298,3 +298,12 @@ --color-tag-delete: var(--color-contrast-lower-alt3); } + +[data-theme^="dark"] { + a { + color: var(--color-contrast-medium); + &:hover { + color: var(--color-text); + } + } +} \ No newline at end of file From 36f2b6335fdeca733a5141fe77293a846eaadd58 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 11 Aug 2022 01:01:57 +0300 Subject: [PATCH 4/5] polish(markdown): codemirror font size --- .../components/markdown/TheMarkdown.vue | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/renderer/components/markdown/TheMarkdown.vue b/src/renderer/components/markdown/TheMarkdown.vue index eb223aa6..dba80cb0 100644 --- a/src/renderer/components/markdown/TheMarkdown.vue +++ b/src/renderer/components/markdown/TheMarkdown.vue @@ -286,6 +286,7 @@ window.addEventListener('resize', () => { :deep(.CodeMirror) { height: 100%; padding: var(--spacing-xs); + font-size: 0.85em; font-family: v-bind(fontFamily); } :deep(.CodeMirror-line) { @@ -296,25 +297,6 @@ window.addEventListener('resize', () => { &:last-child { padding-bottom: var(--spacing-xs); } - // padding: var(--spacing-xs); } - :deep(.CodeMirror-code) { - // padding: var(--spacing-xs); - } - // :deep(.CodeMirror-overlayscroll-vertical div) { - // background-color: rgba(121, 121, 121, 0.8); - // width: 5px; - // // opacity: v-bind(scrollBarOpacity); - // transition: opacity 0.3s; - // } - // :deep(.CodeMirror-overlayscroll-horizontal div) { - // background-color: rgba(121, 121, 121, 0.8); - // height: 5px; - // // opacity: v-bind(scrollBarOpacity); - // transition: opacity 0.3s; - // } - // :deep(.CodeMirror-scrollbar-filler) { - // background-color: transparent; - // } } From a9f5627c7d34fc26dd3912072f2be0670281e742 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 11 Aug 2022 01:04:15 +0300 Subject: [PATCH 5/5] feat(composable): add click handler --- src/renderer/composable/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderer/composable/index.ts b/src/renderer/composable/index.ts index 6c2753eb..401c62bb 100644 --- a/src/renderer/composable/index.ts +++ b/src/renderer/composable/index.ts @@ -191,3 +191,8 @@ export const checkForRemoteNotification = async () => { checkAndShow() }, 1000 * 60 * 180) // 3 часа } + +export const onClickUrl = (url: string) => { + ipc.invoke('main:open-url', url) + track('app/open-url', url) +}