|
@@ -160,7 +160,7 @@
|
|
|
v-icon(:color='helpShown ? `teal` : ``') mdi-help-circle
|
|
|
span {{$t('editor:markup.markdownFormattingHelp')}}
|
|
|
.editor-markdown-editor
|
|
|
- codemirror(ref='cm', v-model='code', :options='cmOptions', @ready='onCmReady', @input='onCmInput')
|
|
|
+ textarea(ref='cm')
|
|
|
transition(name='editor-markdown-preview')
|
|
|
.editor-markdown-preview(v-if='previewShown')
|
|
|
.editor-markdown-preview-content.contents(ref='editorPreviewContainer')
|
|
@@ -188,7 +188,7 @@ import markdownHelp from './markdown/help.vue'
|
|
|
// ========================================
|
|
|
|
|
|
// Code Mirror
|
|
|
-import { codemirror } from 'vue-codemirror'
|
|
|
+import CodeMirror from 'codemirror'
|
|
|
import 'codemirror/lib/codemirror.css'
|
|
|
|
|
|
// Language
|
|
@@ -261,6 +261,7 @@ function injectLineNumbers (tokens, idx, options, env, slf) {
|
|
|
}
|
|
|
md.renderer.rules.paragraph_open = injectLineNumbers
|
|
|
md.renderer.rules.heading_open = injectLineNumbers
|
|
|
+md.renderer.rules.blockquote_open = injectLineNumbers
|
|
|
|
|
|
// ========================================
|
|
|
// Vue Component
|
|
@@ -268,7 +269,6 @@ md.renderer.rules.heading_open = injectLineNumbers
|
|
|
|
|
|
export default {
|
|
|
components: {
|
|
|
- codemirror,
|
|
|
markdownHelp
|
|
|
},
|
|
|
props: {
|
|
@@ -280,22 +280,7 @@ export default {
|
|
|
data() {
|
|
|
return {
|
|
|
fabInsertMenu: false,
|
|
|
- code: this.$store.get('editor/content'),
|
|
|
- cmOptions: {
|
|
|
- tabSize: 2,
|
|
|
- mode: 'text/markdown',
|
|
|
- theme: 'wikijs-dark',
|
|
|
- lineNumbers: true,
|
|
|
- lineWrapping: true,
|
|
|
- line: true,
|
|
|
- styleActiveLine: true,
|
|
|
- highlightSelectionMatches: {
|
|
|
- annotateScrollbar: true
|
|
|
- },
|
|
|
- viewportMargin: 50,
|
|
|
- inputStyle: 'contenteditable',
|
|
|
- allowDropFileTypes: ['image/jpg', 'image/png', 'image/svg', 'image/jpeg', 'image/gif']
|
|
|
- },
|
|
|
+ cm: null,
|
|
|
cursorPos: { ch: 0, line: 1 },
|
|
|
previewShown: true,
|
|
|
previewHTML: '',
|
|
@@ -303,9 +288,6 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
- cm() {
|
|
|
- return this.$refs.cm.codemirror
|
|
|
- },
|
|
|
isMobile() {
|
|
|
return this.$vuetify.breakpoint.smAndDown
|
|
|
},
|
|
@@ -325,53 +307,6 @@ export default {
|
|
|
this.activeModal = ''
|
|
|
this.helpShown = false
|
|
|
},
|
|
|
- onCmReady(cm) {
|
|
|
- const keyBindings = {
|
|
|
- 'F11' (cm) {
|
|
|
- cm.setOption('fullScreen', !cm.getOption('fullScreen'))
|
|
|
- },
|
|
|
- 'Esc' (cm) {
|
|
|
- if (cm.getOption('fullScreen')) cm.setOption('fullScreen', false)
|
|
|
- }
|
|
|
- }
|
|
|
- _.set(keyBindings, `${CtrlKey}-S`, cm => {
|
|
|
- this.save()
|
|
|
- return false
|
|
|
- })
|
|
|
- _.set(keyBindings, `${CtrlKey}-B`, cm => {
|
|
|
- this.toggleMarkup({ start: `**` })
|
|
|
- return false
|
|
|
- })
|
|
|
- _.set(keyBindings, `${CtrlKey}-I`, cm => {
|
|
|
- this.toggleMarkup({ start: `*` })
|
|
|
- return false
|
|
|
- })
|
|
|
- _.set(keyBindings, `${CtrlKey}-Alt-Right`, cm => {
|
|
|
- let lvl = this.getHeaderLevel(cm)
|
|
|
- if (lvl >= 6) { lvl = 5 }
|
|
|
- this.setHeaderLine(lvl + 1)
|
|
|
- return false
|
|
|
- })
|
|
|
- _.set(keyBindings, `${CtrlKey}-Alt-Left`, cm => {
|
|
|
- let lvl = this.getHeaderLevel(cm)
|
|
|
- if (lvl <= 1) { lvl = 2 }
|
|
|
- this.setHeaderLine(lvl - 1)
|
|
|
- return false
|
|
|
- })
|
|
|
-
|
|
|
- if (this.$vuetify.breakpoint.mdAndUp) {
|
|
|
- cm.setSize(null, 'calc(100vh - 112px - 24px)')
|
|
|
- } else {
|
|
|
- cm.setSize(null, 'calc(100vh - 112px - 16px)')
|
|
|
- }
|
|
|
- cm.setOption('extraKeys', keyBindings)
|
|
|
- cm.on('cursorActivity', cm => {
|
|
|
- this.positionSync(cm)
|
|
|
- this.scrollSync(cm)
|
|
|
- })
|
|
|
- cm.on('paste', this.onCmPaste)
|
|
|
- this.onCmInput(this.code)
|
|
|
- },
|
|
|
onCmInput: _.debounce(function (newContent) {
|
|
|
linesMap = []
|
|
|
this.$store.set('editor/content', newContent)
|
|
@@ -497,7 +432,7 @@ export default {
|
|
|
let currentLine = cm.getCursor().line
|
|
|
if (currentLine < 3) {
|
|
|
this.Velocity(this.$refs.editorPreview, 'stop', true)
|
|
|
- this.Velocity(this.$refs.editorPreview.firstChild, 'scroll', { offset: '-50', duration: 1000, container: this.$refs.editorPreview })
|
|
|
+ this.Velocity(this.$refs.editorPreview.firstChild, 'scroll', { offset: '-50', duration: 1000, container: this.$refs.editorPreviewContainer })
|
|
|
} else {
|
|
|
let closestLine = _.findLast(linesMap, n => n <= currentLine)
|
|
|
let destElm = this.$refs.editorPreview.querySelector(`[data-line='${closestLine}']`)
|
|
@@ -513,9 +448,94 @@ export default {
|
|
|
},
|
|
|
toggleFullscreen () {
|
|
|
this.cm.setOption('fullScreen', true)
|
|
|
+ },
|
|
|
+ refresh() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.cm.refresh()
|
|
|
+ })
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
+ // Initialize CodeMirror
|
|
|
+
|
|
|
+ this.cm = CodeMirror.fromTextArea(this.$refs.cm, {
|
|
|
+ tabSize: 2,
|
|
|
+ mode: 'text/markdown',
|
|
|
+ theme: 'wikijs-dark',
|
|
|
+ lineNumbers: true,
|
|
|
+ lineWrapping: true,
|
|
|
+ line: true,
|
|
|
+ styleActiveLine: true,
|
|
|
+ highlightSelectionMatches: {
|
|
|
+ annotateScrollbar: true
|
|
|
+ },
|
|
|
+ viewportMargin: 50,
|
|
|
+ inputStyle: 'contenteditable',
|
|
|
+ allowDropFileTypes: ['image/jpg', 'image/png', 'image/svg', 'image/jpeg', 'image/gif']
|
|
|
+ })
|
|
|
+ this.cm.setValue(this.$store.get('editor/content'))
|
|
|
+ this.cm.on('change', c => {
|
|
|
+ this.$store.set('editor/content', c.getValue())
|
|
|
+ this.onCmInput(this.$store.get('editor/content'))
|
|
|
+ })
|
|
|
+ if (this.$vuetify.breakpoint.mdAndUp) {
|
|
|
+ this.cm.setSize(null, 'calc(100vh - 112px - 24px)')
|
|
|
+ } else {
|
|
|
+ this.cm.setSize(null, 'calc(100vh - 112px - 16px)')
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set Keybindings
|
|
|
+
|
|
|
+ const keyBindings = {
|
|
|
+ 'F11' (c) {
|
|
|
+ c.setOption('fullScreen', !c.getOption('fullScreen'))
|
|
|
+ },
|
|
|
+ 'Esc' (c) {
|
|
|
+ if (c.getOption('fullScreen')) c.setOption('fullScreen', false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _.set(keyBindings, `${CtrlKey}-S`, c => {
|
|
|
+ this.save()
|
|
|
+ return false
|
|
|
+ })
|
|
|
+ _.set(keyBindings, `${CtrlKey}-B`, c => {
|
|
|
+ this.toggleMarkup({ start: `**` })
|
|
|
+ return false
|
|
|
+ })
|
|
|
+ _.set(keyBindings, `${CtrlKey}-I`, c => {
|
|
|
+ this.toggleMarkup({ start: `*` })
|
|
|
+ return false
|
|
|
+ })
|
|
|
+ _.set(keyBindings, `${CtrlKey}-Alt-Right`, c => {
|
|
|
+ let lvl = this.getHeaderLevel(c)
|
|
|
+ if (lvl >= 6) { lvl = 5 }
|
|
|
+ this.setHeaderLine(lvl + 1)
|
|
|
+ return false
|
|
|
+ })
|
|
|
+ _.set(keyBindings, `${CtrlKey}-Alt-Left`, c => {
|
|
|
+ let lvl = this.getHeaderLevel(c)
|
|
|
+ if (lvl <= 1) { lvl = 2 }
|
|
|
+ this.setHeaderLine(lvl - 1)
|
|
|
+ return false
|
|
|
+ })
|
|
|
+ this.cm.setOption('extraKeys', keyBindings)
|
|
|
+
|
|
|
+ // Handle cursor movement
|
|
|
+
|
|
|
+ this.cm.on('cursorActivity', c => {
|
|
|
+ this.positionSync(c)
|
|
|
+ this.scrollSync(c)
|
|
|
+ })
|
|
|
+
|
|
|
+ // Handle special paste
|
|
|
+
|
|
|
+ this.cm.on('paste', this.onCmPaste)
|
|
|
+
|
|
|
+ // Render initial preview
|
|
|
+
|
|
|
+ this.onCmInput(this.$store.get('editor/content'))
|
|
|
+ this.refresh()
|
|
|
+
|
|
|
this.$root.$on('editorInsert', opts => {
|
|
|
switch (opts.kind) {
|
|
|
case 'IMAGE':
|
|
@@ -718,10 +738,10 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
|
|
|
background: mc('blue','800');
|
|
|
}
|
|
|
.cm-s-wikijs-dark .CodeMirror-line::selection, .cm-s-wikijs-dark .CodeMirror-line > span::selection, .cm-s-wikijs-dark .CodeMirror-line > span > span::selection {
|
|
|
- background: mc('red', '500');
|
|
|
+ background: mc('amber', '500');
|
|
|
}
|
|
|
.cm-s-wikijs-dark .CodeMirror-line::-moz-selection, .cm-s-wikijs-dark .CodeMirror-line > span::-moz-selection, .cm-s-wikijs-dark .CodeMirror-line > span > span::-moz-selection {
|
|
|
- background: mc('red', '500');
|
|
|
+ background: mc('amber', '500');
|
|
|
}
|
|
|
.cm-s-wikijs-dark .CodeMirror-gutters {
|
|
|
background: darken(mc('grey','900'), 6%);
|