|
|
@ -160,7 +160,7 @@
|
|
|
|
v-icon(:color='helpShown ? `teal` : ``') mdi-help-circle
|
|
|
|
v-icon(:color='helpShown ? `teal` : ``') mdi-help-circle
|
|
|
|
span {{$t('editor:markup.markdownFormattingHelp')}}
|
|
|
|
span {{$t('editor:markup.markdownFormattingHelp')}}
|
|
|
|
.editor-markdown-editor
|
|
|
|
.editor-markdown-editor
|
|
|
|
codemirror(ref='cm', v-model='code', :options='cmOptions', @ready='onCmReady', @input='onCmInput')
|
|
|
|
textarea(ref='cm')
|
|
|
|
transition(name='editor-markdown-preview')
|
|
|
|
transition(name='editor-markdown-preview')
|
|
|
|
.editor-markdown-preview(v-if='previewShown')
|
|
|
|
.editor-markdown-preview(v-if='previewShown')
|
|
|
|
.editor-markdown-preview-content.contents(ref='editorPreviewContainer')
|
|
|
|
.editor-markdown-preview-content.contents(ref='editorPreviewContainer')
|
|
|
@ -188,7 +188,7 @@ import markdownHelp from './markdown/help.vue'
|
|
|
|
// ========================================
|
|
|
|
// ========================================
|
|
|
|
|
|
|
|
|
|
|
|
// Code Mirror
|
|
|
|
// Code Mirror
|
|
|
|
import { codemirror } from 'vue-codemirror'
|
|
|
|
import CodeMirror from 'codemirror'
|
|
|
|
import 'codemirror/lib/codemirror.css'
|
|
|
|
import 'codemirror/lib/codemirror.css'
|
|
|
|
|
|
|
|
|
|
|
|
// Language
|
|
|
|
// Language
|
|
|
@ -261,6 +261,7 @@ function injectLineNumbers (tokens, idx, options, env, slf) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
md.renderer.rules.paragraph_open = injectLineNumbers
|
|
|
|
md.renderer.rules.paragraph_open = injectLineNumbers
|
|
|
|
md.renderer.rules.heading_open = injectLineNumbers
|
|
|
|
md.renderer.rules.heading_open = injectLineNumbers
|
|
|
|
|
|
|
|
md.renderer.rules.blockquote_open = injectLineNumbers
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
// ========================================
|
|
|
|
// Vue Component
|
|
|
|
// Vue Component
|
|
|
@ -268,7 +269,6 @@ md.renderer.rules.heading_open = injectLineNumbers
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
export default {
|
|
|
|
components: {
|
|
|
|
components: {
|
|
|
|
codemirror,
|
|
|
|
|
|
|
|
markdownHelp
|
|
|
|
markdownHelp
|
|
|
|
},
|
|
|
|
},
|
|
|
|
props: {
|
|
|
|
props: {
|
|
|
@ -280,22 +280,7 @@ export default {
|
|
|
|
data() {
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
fabInsertMenu: false,
|
|
|
|
fabInsertMenu: false,
|
|
|
|
code: this.$store.get('editor/content'),
|
|
|
|
cm: null,
|
|
|
|
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']
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
cursorPos: { ch: 0, line: 1 },
|
|
|
|
cursorPos: { ch: 0, line: 1 },
|
|
|
|
previewShown: true,
|
|
|
|
previewShown: true,
|
|
|
|
previewHTML: '',
|
|
|
|
previewHTML: '',
|
|
|
@ -303,9 +288,6 @@ export default {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
computed: {
|
|
|
|
cm() {
|
|
|
|
|
|
|
|
return this.$refs.cm.codemirror
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
isMobile() {
|
|
|
|
isMobile() {
|
|
|
|
return this.$vuetify.breakpoint.smAndDown
|
|
|
|
return this.$vuetify.breakpoint.smAndDown
|
|
|
|
},
|
|
|
|
},
|
|
|
@ -325,53 +307,6 @@ export default {
|
|
|
|
this.activeModal = ''
|
|
|
|
this.activeModal = ''
|
|
|
|
this.helpShown = false
|
|
|
|
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) {
|
|
|
|
onCmInput: _.debounce(function (newContent) {
|
|
|
|
linesMap = []
|
|
|
|
linesMap = []
|
|
|
|
this.$store.set('editor/content', newContent)
|
|
|
|
this.$store.set('editor/content', newContent)
|
|
|
@ -497,7 +432,7 @@ export default {
|
|
|
|
let currentLine = cm.getCursor().line
|
|
|
|
let currentLine = cm.getCursor().line
|
|
|
|
if (currentLine < 3) {
|
|
|
|
if (currentLine < 3) {
|
|
|
|
this.Velocity(this.$refs.editorPreview, 'stop', true)
|
|
|
|
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 {
|
|
|
|
} else {
|
|
|
|
let closestLine = _.findLast(linesMap, n => n <= currentLine)
|
|
|
|
let closestLine = _.findLast(linesMap, n => n <= currentLine)
|
|
|
|
let destElm = this.$refs.editorPreview.querySelector(`[data-line='${closestLine}']`)
|
|
|
|
let destElm = this.$refs.editorPreview.querySelector(`[data-line='${closestLine}']`)
|
|
|
@ -513,9 +448,94 @@ export default {
|
|
|
|
},
|
|
|
|
},
|
|
|
|
toggleFullscreen () {
|
|
|
|
toggleFullscreen () {
|
|
|
|
this.cm.setOption('fullScreen', true)
|
|
|
|
this.cm.setOption('fullScreen', true)
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
refresh() {
|
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
|
|
this.cm.refresh()
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
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 => {
|
|
|
|
this.$root.$on('editorInsert', opts => {
|
|
|
|
switch (opts.kind) {
|
|
|
|
switch (opts.kind) {
|
|
|
|
case 'IMAGE':
|
|
|
|
case 'IMAGE':
|
|
|
@ -718,10 +738,10 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
|
|
|
|
background: mc('blue','800');
|
|
|
|
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 {
|
|
|
|
.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 {
|
|
|
|
.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 {
|
|
|
|
.cm-s-wikijs-dark .CodeMirror-gutters {
|
|
|
|
background: darken(mc('grey','900'), 6%);
|
|
|
|
background: darken(mc('grey','900'), 6%);
|
|
|
|