feat: mk editor toolbar + help dialog + fixes
This commit is contained in:
@@ -124,8 +124,8 @@
|
||||
v-icon crop_free
|
||||
span Distraction Free Mode
|
||||
v-tooltip(right, color='teal')
|
||||
v-btn(icon, slot='activator', dark, disabled).mx-0
|
||||
v-icon help
|
||||
v-btn(icon, slot='activator', dark, @click='toggleHelp').mx-0
|
||||
v-icon(:color='helpShown ? `teal` : ``') help
|
||||
span Markdown Formatting Help
|
||||
.editor-markdown-editor
|
||||
.editor-markdown-editor-title(v-if='previewShown', @click='previewShown = false') Editor
|
||||
@@ -141,11 +141,16 @@
|
||||
.caption.px-3 /{{path}}
|
||||
v-spacer
|
||||
.caption Markdown
|
||||
v-spacer
|
||||
.caption Ln {{cursorPos.line + 1}}, Col {{cursorPos.ch + 1}}
|
||||
|
||||
markdown-help(v-if='helpShown')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { get, sync } from 'vuex-pathify'
|
||||
import markdownHelp from './markdown/help.vue'
|
||||
|
||||
// ========================================
|
||||
// IMPORTS
|
||||
@@ -232,7 +237,13 @@ md.renderer.rules.heading_open = injectLineNumbers
|
||||
|
||||
export default {
|
||||
components: {
|
||||
codemirror
|
||||
codemirror,
|
||||
markdownHelp
|
||||
},
|
||||
props: {
|
||||
save: {
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -251,8 +262,10 @@ export default {
|
||||
},
|
||||
viewportMargin: 50
|
||||
},
|
||||
cursorPos: { ch: 0, line: 1 },
|
||||
previewShown: true,
|
||||
previewHTML: ''
|
||||
previewHTML: '',
|
||||
helpShown: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -281,13 +294,34 @@ export default {
|
||||
}
|
||||
}
|
||||
_.set(keyBindings, `${CtrlKey}-S`, cm => {
|
||||
self.$parent.save()
|
||||
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
|
||||
})
|
||||
|
||||
cm.setSize(null, 'calc(100vh - 112px - 24px)')
|
||||
cm.setOption('extraKeys', keyBindings)
|
||||
cm.on('cursorActivity', cm => {
|
||||
this.toolbarSync(cm)
|
||||
this.positionSync(cm)
|
||||
this.scrollSync(cm)
|
||||
})
|
||||
this.onCmInput(this.code)
|
||||
@@ -298,20 +332,19 @@ export default {
|
||||
this.previewHTML = md.render(newContent)
|
||||
this.$nextTick(() => {
|
||||
Prism.highlightAllUnder(this.$refs.editorPreview)
|
||||
Array.from(this.$refs.editorPreview.querySelectorAll('pre.line-numbers')).map(pre => pre.classList.add('prismjs'))
|
||||
this.scrollSync(this.cm)
|
||||
})
|
||||
}, 500),
|
||||
/**
|
||||
* Update toolbar state
|
||||
* Update cursor state
|
||||
*/
|
||||
toolbarSync(cm) {
|
||||
// const pos = cm.getCursor('start')
|
||||
// const token = cm.getTokenAt(pos)
|
||||
|
||||
// if (!token.type) { return }
|
||||
|
||||
// console.info(token)
|
||||
positionSync(cm) {
|
||||
this.cursorPos = cm.getCursor('head')
|
||||
},
|
||||
/**
|
||||
* Wrap selection with start / end tags
|
||||
*/
|
||||
toggleMarkup({ start, end }) {
|
||||
if (!end) { end = start }
|
||||
if (!this.cm.doc.somethingSelected()) {
|
||||
@@ -323,6 +356,9 @@ export default {
|
||||
}
|
||||
this.cm.doc.replaceSelections(this.cm.doc.getSelections().map(s => start + s + end))
|
||||
},
|
||||
/**
|
||||
* Set current line as header
|
||||
*/
|
||||
setHeaderLine(lvl) {
|
||||
const curLine = this.cm.doc.getCursor('head').line
|
||||
let lineContent = this.cm.doc.getLine(curLine)
|
||||
@@ -333,11 +369,31 @@ export default {
|
||||
lineContent = _.times(lvl, n => '#').join('') + ` ` + lineContent
|
||||
this.cm.doc.replaceRange(lineContent, { line: curLine, ch: 0 }, { line: curLine, ch: lineLength })
|
||||
},
|
||||
/**
|
||||
* Get the header lever of the current line
|
||||
*/
|
||||
getHeaderLevel(cm) {
|
||||
const curLine = this.cm.doc.getCursor('head').line
|
||||
let lineContent = this.cm.doc.getLine(curLine)
|
||||
let lvl = 0
|
||||
|
||||
const result = lineContent.match(/^(#+) /)
|
||||
if (result) {
|
||||
lvl = _.get(result, '[1]', '').length
|
||||
}
|
||||
return lvl
|
||||
},
|
||||
/**
|
||||
* Insert content after current line
|
||||
*/
|
||||
insertAfter({ content, newLine }) {
|
||||
const curLine = this.cm.doc.getCursor('to').line
|
||||
const lineLength = this.cm.doc.getLine(curLine).length
|
||||
this.cm.doc.replaceRange(newLine ? `\n${content}\n` : content, { line: curLine, ch: lineLength + 1 })
|
||||
},
|
||||
/**
|
||||
* Insert content before current line
|
||||
*/
|
||||
insertBeforeEachLine({ content, after }) {
|
||||
let lines = []
|
||||
if (!this.cm.doc.somethingSelected()) {
|
||||
@@ -381,8 +437,8 @@ export default {
|
||||
}
|
||||
}
|
||||
}, 500),
|
||||
toggleAround (before, after) {
|
||||
|
||||
toggleHelp () {
|
||||
this.helpShown = !this.helpShown
|
||||
},
|
||||
toggleFullscreen () {
|
||||
this.cm.setOption('fullScreen', true)
|
||||
|
335
client/components/editor/markdown/help.vue
Normal file
335
client/components/editor/markdown/help.vue
Normal file
@@ -0,0 +1,335 @@
|
||||
<template lang='pug'>
|
||||
v-card.editor-markdown-help.animated.fadeInLeft(flat, tile)
|
||||
v-container.pa-3(grid-list-lg, fluid)
|
||||
v-layout(row, wrap)
|
||||
v-flex(xs4)
|
||||
v-card.radius-7.animated.fadeInUp(light)
|
||||
v-card-text
|
||||
.d-flex
|
||||
v-toolbar.radius-7(color='teal lighten-5', dense, flat, height='44')
|
||||
v-icon.mr-3(color='teal') info
|
||||
.body-2.teal--text Markdown Reference
|
||||
.body-2.mt-3 Bold
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div **Lorem ipsum**
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption: strong Lorem ipsum
|
||||
.body-2.mt-3 Italic
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div *Lorem ipsum*
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption: em Lorem ipsum
|
||||
.body-2.mt-3 Strikethrough
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div ~~Lorem ipsum~~
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption(style='text-decoration: line-through;') Lorem ipsum
|
||||
.body-2.mt-3 Headers
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div # Header 1
|
||||
div ## Header 2
|
||||
div ### Header 3
|
||||
div #### Header 4
|
||||
div ##### Header 5
|
||||
div ###### Header 6
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
div(style='font-weight: 700; font-size: 24px;') Header 1
|
||||
div(style='font-weight: 700; font-size: 22px;') Header 2
|
||||
div(style='font-weight: 700; font-size: 20px;') Header 3
|
||||
div(style='font-weight: 700; font-size: 18px;') Header 4
|
||||
div(style='font-weight: 700; font-size: 16px;') Header 5
|
||||
div(style='font-weight: 700; font-size: 14px;') Header 6
|
||||
.body-2.mt-3 Unordered Lists
|
||||
.caption.grey--text.text--darken-1: em You can also use the asterisk symbol instead of the dash.
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div - Unordered List Item 1
|
||||
div - Unordered List Item 2
|
||||
div - Unordered List Item 3
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
ul
|
||||
li Unordered List Item 1
|
||||
li Unordered List Item 2
|
||||
li Unordered List Item 3
|
||||
.body-2.mt-3 Ordered Lists
|
||||
.caption.grey--text.text--darken-1: em Even though we prefix all lines with #[strong 1.], the output will be correctly numbered automatically.
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div 1. Ordered List Item 1
|
||||
div 1. Ordered List Item 2
|
||||
div 1. Ordered List Item 3
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
ol
|
||||
li Unordered List Item 1
|
||||
li Unordered List Item 2
|
||||
li Unordered List Item 3
|
||||
|
||||
v-flex(xs4)
|
||||
v-card.radius-7.animated.fadeInUp.wait-p1s(light)
|
||||
v-card-text
|
||||
.d-flex
|
||||
v-toolbar.radius-7(color='teal lighten-5', dense, flat, height='44')
|
||||
v-icon.mr-3(color='teal') info
|
||||
.body-2.teal--text Markdown Reference (continued)
|
||||
.body-2.mt-3 Superscript
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div Lorem ^ipsum^
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption Lorem #[sup ipsum]
|
||||
.body-2.mt-3 Subscript
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div Lorem ~ipsum~
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption: em Lorem #[sub ipsum]
|
||||
.body-2.mt-3 Horizontal Line
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div Lorem ipsum
|
||||
div ---
|
||||
div Dolor sit amet
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption Lorem ipsum
|
||||
v-divider.my-2
|
||||
.caption Dolor sit amet
|
||||
.body-2.mt-3 Inline Code
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div Lorem `ipsum dolor sit` amet
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
.caption Lorem #[code ipsum dolor sit] amet
|
||||
.body-2.mt-3 Code Blocks
|
||||
.caption.grey--text.text--darken-1: em In the example below, #[strong js] defines the syntax highlighting language to use. It can be omitted.
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div ```js
|
||||
div function main () {
|
||||
div.pl-3 echo 'Lorem ipsum'
|
||||
div }
|
||||
div ```
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text.contents
|
||||
pre.prismjs.line-numbers.language-js
|
||||
code.language-js
|
||||
span.token.keyword function
|
||||
span.token.function main
|
||||
span.token.punctuation (
|
||||
span.token.punctuation )
|
||||
span.token.punctuation {#[br]
|
||||
| echo
|
||||
span.token.string 'Lorem ipsum'#[br]
|
||||
span.token.punctuation }
|
||||
span.line-numbers-rows(aria-hidden='true')
|
||||
span
|
||||
span
|
||||
span
|
||||
.body-2.mt-3 Blockquotes
|
||||
v-layout(row)
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-source(flat)
|
||||
v-card-text
|
||||
div > Lorem ipsum
|
||||
div > dolor sit amet
|
||||
div > consectetur adipiscing elit
|
||||
v-icon chevron_right
|
||||
v-flex(xs6)
|
||||
v-card.editor-markdown-help-result(flat)
|
||||
v-card-text
|
||||
blockquote(style='border: 1px solid #263238; border-radius: .5rem; padding: 1rem 24px;') Lorem ipsum#[br]dolor sit amet#[br]consectetur adipiscing elit
|
||||
|
||||
v-flex(xs4)
|
||||
v-card.radius-7.animated.fadeInUp.wait-p2s(light)
|
||||
v-card-text
|
||||
v-toolbar.radius-7(color='teal lighten-5', dense, flat)
|
||||
v-icon.mr-3(color='teal') keyboard
|
||||
.body-2.teal--text Keyboard Shortcuts
|
||||
v-list.editor-markdown-help-kbd(two-line, dense)
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Bold
|
||||
v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd B]
|
||||
v-divider
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Italic
|
||||
v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd I]
|
||||
v-divider
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Increase Header Level
|
||||
v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + #[kbd Right]
|
||||
v-divider
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Decrease Header Level
|
||||
v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + #[kbd Left]
|
||||
v-divider
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Save
|
||||
v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd S]
|
||||
v-divider
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Undo
|
||||
v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd Z]
|
||||
v-divider
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Redo
|
||||
v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd Y]
|
||||
v-divider
|
||||
v-list-tile
|
||||
v-list-tile-content
|
||||
v-list-tile-title.body-2 Distraction Free Mode
|
||||
v-list-tile-sub-title Press <kbd>Esc</kbd> to exit.
|
||||
v-list-tile-action #[kbd F11]
|
||||
|
||||
v-card.radius-7.animated.fadeInUp.wait-p3s.mt-3(light)
|
||||
v-card-text
|
||||
v-toolbar.radius-7(color='teal lighten-5', dense, flat)
|
||||
v-icon.mr-3(color='teal') mouse
|
||||
.body-2.teal--text Multi-Selection
|
||||
v-list.editor-markdown-help-kbd(two-line, dense)
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Multiple Cursors
|
||||
v-list-tile-action #[kbd {{ctrlKey}}] + Left Click
|
||||
v-divider
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Select Region
|
||||
v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + Left Click
|
||||
v-divider
|
||||
v-list-tile
|
||||
v-list-tile-content.body-2 Deselect
|
||||
v-list-tile-action #[kbd Esc]
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
ctrlKey() { return /Mac/.test(navigator.platform) ? 'Cmd' : 'Ctrl' },
|
||||
altKey() { return /Mac/.test(navigator.platform) ? 'Option' : 'Alt' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
.editor-markdown-help {
|
||||
position: fixed;
|
||||
top: 112px;
|
||||
left: 64px;
|
||||
z-index: 10;
|
||||
width: calc(100vw - 64px - 17px);
|
||||
height: calc(100vh - 112px - 24px);
|
||||
background-color: rgba(darken(mc('grey', '900'), 3%), .9) !important;
|
||||
overflow: auto;
|
||||
|
||||
&-source {
|
||||
background-color: mc('blue-grey', '900') !important;
|
||||
border-radius: 7px;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-size: 14px;
|
||||
color: #FFF !important;
|
||||
}
|
||||
|
||||
&-result {
|
||||
background-color: mc('blue-grey', '50') !important;
|
||||
border-radius: 7px;
|
||||
font-size: 14px;
|
||||
|
||||
code {
|
||||
display: inline-block;
|
||||
background-color: mc('pink', '50');
|
||||
box-shadow: none;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.contents {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.prismjs {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-kbd {
|
||||
.v-list__tile__action {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
kbd {
|
||||
margin: 0 5px;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
padding: 0.1em 0.5em;
|
||||
margin: 0 0.2em;
|
||||
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 0 0 2px #fff inset;
|
||||
background-color: #f7f7f7;
|
||||
color: mc('grey', '700');
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-ref {
|
||||
.v-list__tile__action {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user