feat: mk editor toolbar + help dialog + fixes

pull/835/head
Nick 6 years ago
parent ca4e0ada30
commit de1dddbc90

@ -126,6 +126,6 @@ export default {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
font-family: 'Source Sans Pro', sans-serif; font-family: 'Roboto Mono', monospace;
} }
</style> </style>

@ -3,7 +3,7 @@
nav-header(dense) nav-header(dense)
template(slot='mid') template(slot='mid')
v-spacer v-spacer
.subheading {{currentPageTitle}} .subheading.grey--text {{currentPageTitle}}
v-spacer v-spacer
template(slot='actions') template(slot='actions')
v-btn( v-btn(
@ -32,7 +32,7 @@
v-icon(color='red', :left='$vuetify.breakpoint.lgAndUp') close v-icon(color='red', :left='$vuetify.breakpoint.lgAndUp') close
span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('editor:close') }} span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('editor:close') }}
v-content v-content
component(:is='currentEditor') component(:is='currentEditor', :save='save')
editor-modal-properties(v-model='dialogProps') editor-modal-properties(v-model='dialogProps')
editor-modal-editorselect(v-model='dialogEditorSelector') editor-modal-editorselect(v-model='dialogEditorSelector')
editor-modal-unsaved(v-model='dialogUnsaved', @discard='exitGo') editor-modal-unsaved(v-model='dialogUnsaved', @discard='exitGo')
@ -199,7 +199,7 @@ export default {
resp = _.get(resp, 'data.pages.create', {}) resp = _.get(resp, 'data.pages.create', {})
if (_.get(resp, 'responseResult.succeeded')) { if (_.get(resp, 'responseResult.succeeded')) {
this.$store.commit('showNotification', { this.$store.commit('showNotification', {
message: this.$t('editor:save.success'), message: this.$t('editor:save.createSuccess'),
style: 'success', style: 'success',
icon: 'check' icon: 'check'
}) })
@ -234,7 +234,7 @@ export default {
resp = _.get(resp, 'data.pages.update', {}) resp = _.get(resp, 'data.pages.update', {})
if (_.get(resp, 'responseResult.succeeded')) { if (_.get(resp, 'responseResult.succeeded')) {
this.$store.commit('showNotification', { this.$store.commit('showNotification', {
message: this.$t('editor:save.success'), message: this.$t('editor:save.updateSuccess'),
style: 'success', style: 'success',
icon: 'check' icon: 'check'
}) })

@ -124,8 +124,8 @@
v-icon crop_free v-icon crop_free
span Distraction Free Mode span Distraction Free Mode
v-tooltip(right, color='teal') v-tooltip(right, color='teal')
v-btn(icon, slot='activator', dark, disabled).mx-0 v-btn(icon, slot='activator', dark, @click='toggleHelp').mx-0
v-icon help v-icon(:color='helpShown ? `teal` : ``') help
span Markdown Formatting Help span Markdown Formatting Help
.editor-markdown-editor .editor-markdown-editor
.editor-markdown-editor-title(v-if='previewShown', @click='previewShown = false') Editor .editor-markdown-editor-title(v-if='previewShown', @click='previewShown = false') Editor
@ -141,11 +141,16 @@
.caption.px-3 /{{path}} .caption.px-3 /{{path}}
v-spacer v-spacer
.caption Markdown .caption Markdown
v-spacer
.caption Ln {{cursorPos.line + 1}}, Col {{cursorPos.ch + 1}}
markdown-help(v-if='helpShown')
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { get, sync } from 'vuex-pathify' import { get, sync } from 'vuex-pathify'
import markdownHelp from './markdown/help.vue'
// ======================================== // ========================================
// IMPORTS // IMPORTS
@ -232,7 +237,13 @@ md.renderer.rules.heading_open = injectLineNumbers
export default { export default {
components: { components: {
codemirror codemirror,
markdownHelp
},
props: {
save: {
type: Function
}
}, },
data() { data() {
return { return {
@ -251,8 +262,10 @@ export default {
}, },
viewportMargin: 50 viewportMargin: 50
}, },
cursorPos: { ch: 0, line: 1 },
previewShown: true, previewShown: true,
previewHTML: '' previewHTML: '',
helpShown: true
} }
}, },
computed: { computed: {
@ -281,13 +294,34 @@ export default {
} }
} }
_.set(keyBindings, `${CtrlKey}-S`, cm => { _.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.setSize(null, 'calc(100vh - 112px - 24px)')
cm.setOption('extraKeys', keyBindings) cm.setOption('extraKeys', keyBindings)
cm.on('cursorActivity', cm => { cm.on('cursorActivity', cm => {
this.toolbarSync(cm) this.positionSync(cm)
this.scrollSync(cm) this.scrollSync(cm)
}) })
this.onCmInput(this.code) this.onCmInput(this.code)
@ -298,20 +332,19 @@ export default {
this.previewHTML = md.render(newContent) this.previewHTML = md.render(newContent)
this.$nextTick(() => { this.$nextTick(() => {
Prism.highlightAllUnder(this.$refs.editorPreview) Prism.highlightAllUnder(this.$refs.editorPreview)
Array.from(this.$refs.editorPreview.querySelectorAll('pre.line-numbers')).map(pre => pre.classList.add('prismjs'))
this.scrollSync(this.cm) this.scrollSync(this.cm)
}) })
}, 500), }, 500),
/** /**
* Update toolbar state * Update cursor state
*/ */
toolbarSync(cm) { positionSync(cm) {
// const pos = cm.getCursor('start') this.cursorPos = cm.getCursor('head')
// const token = cm.getTokenAt(pos)
// if (!token.type) { return }
// console.info(token)
}, },
/**
* Wrap selection with start / end tags
*/
toggleMarkup({ start, end }) { toggleMarkup({ start, end }) {
if (!end) { end = start } if (!end) { end = start }
if (!this.cm.doc.somethingSelected()) { if (!this.cm.doc.somethingSelected()) {
@ -323,6 +356,9 @@ export default {
} }
this.cm.doc.replaceSelections(this.cm.doc.getSelections().map(s => start + s + end)) this.cm.doc.replaceSelections(this.cm.doc.getSelections().map(s => start + s + end))
}, },
/**
* Set current line as header
*/
setHeaderLine(lvl) { setHeaderLine(lvl) {
const curLine = this.cm.doc.getCursor('head').line const curLine = this.cm.doc.getCursor('head').line
let lineContent = this.cm.doc.getLine(curLine) let lineContent = this.cm.doc.getLine(curLine)
@ -333,11 +369,31 @@ export default {
lineContent = _.times(lvl, n => '#').join('') + ` ` + lineContent lineContent = _.times(lvl, n => '#').join('') + ` ` + lineContent
this.cm.doc.replaceRange(lineContent, { line: curLine, ch: 0 }, { line: curLine, ch: lineLength }) 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 }) { insertAfter({ content, newLine }) {
const curLine = this.cm.doc.getCursor('to').line const curLine = this.cm.doc.getCursor('to').line
const lineLength = this.cm.doc.getLine(curLine).length const lineLength = this.cm.doc.getLine(curLine).length
this.cm.doc.replaceRange(newLine ? `\n${content}\n` : content, { line: curLine, ch: lineLength + 1 }) this.cm.doc.replaceRange(newLine ? `\n${content}\n` : content, { line: curLine, ch: lineLength + 1 })
}, },
/**
* Insert content before current line
*/
insertBeforeEachLine({ content, after }) { insertBeforeEachLine({ content, after }) {
let lines = [] let lines = []
if (!this.cm.doc.somethingSelected()) { if (!this.cm.doc.somethingSelected()) {
@ -381,8 +437,8 @@ export default {
} }
} }
}, 500), }, 500),
toggleAround (before, after) { toggleHelp () {
this.helpShown = !this.helpShown
}, },
toggleFullscreen () { toggleFullscreen () {
this.cm.setOption('fullScreen', true) this.cm.setOption('fullScreen', true)

@ -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 &gt; Lorem ipsum
div &gt; dolor sit amet
div &gt; 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>

@ -22,14 +22,6 @@
font-style: normal; font-style: normal;
} }
@font-face {
font-family: 'Source Sans Pro';
src: url('/fonts/SourceSansPro-Bold.woff2') format('woff2'),
url('/fonts/SourceSansPro-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
}
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Roboto';
src: url('/fonts/Roboto-Regular.woff2') format('woff2'), src: url('/fonts/Roboto-Regular.woff2') format('woff2'),
@ -46,22 +38,6 @@
font-style: italic; font-style: italic;
} }
@font-face {
font-family: 'Source Sans Pro';
src: url('/fonts/SourceSansPro-BoldItalic.woff2') format('woff2'),
url('/fonts/SourceSansPro-BoldItalic.woff') format('woff');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'Source Sans Pro';
src: url('/fonts/SourceSansPro-Regular.woff2') format('woff2'),
url('/fonts/SourceSansPro-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Roboto';
src: url('/fonts/Roboto-Medium.woff2') format('woff2'), src: url('/fonts/Roboto-Medium.woff2') format('woff2'),
@ -71,9 +47,9 @@
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Roboto Mono';
src: url('/fonts/SourceSansPro-Italic.woff2') format('woff2'), src: url('/fonts/RobotoMono-Regular.woff2') format('woff2'),
url('/fonts/SourceSansPro-Italic.woff') format('woff'); url('/fonts/RobotoMono-Regular.woff') format('woff');
font-weight: normal; font-weight: normal;
font-style: italic; font-style: normal;
} }

@ -1,80 +0,0 @@
@font-face {
font-family: 'Roboto';
src: url('Roboto-MediumItalic.woff2') format('woff2'),
url('Roboto-MediumItalic.woff') format('woff');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: 'Roboto';
src: url('Roboto-Italic.woff2') format('woff2'),
url('Roboto-Italic.woff') format('woff');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'Roboto';
src: url('Roboto-Bold.woff2') format('woff2'),
url('Roboto-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Source Sans Pro';
src: url('SourceSansPro-Bold.woff2') format('woff2'),
url('SourceSansPro-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('Roboto-Regular.woff2') format('woff2'),
url('Roboto-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('Roboto-BoldItalic.woff2') format('woff2'),
url('Roboto-BoldItalic.woff') format('woff');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'Source Sans Pro';
src: url('SourceSansPro-BoldItalic.woff2') format('woff2'),
url('SourceSansPro-BoldItalic.woff') format('woff');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'Source Sans Pro';
src: url('SourceSansPro-Regular.woff2') format('woff2'),
url('SourceSansPro-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('Roboto-Medium.woff2') format('woff2'),
url('Roboto-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Source Sans Pro';
src: url('SourceSansPro-Italic.woff2') format('woff2'),
url('SourceSansPro-Italic.woff') format('woff');
font-weight: normal;
font-style: italic;
}

@ -256,7 +256,7 @@
background-color: mc('indigo', '50'); background-color: mc('indigo', '50');
padding: 0 5px; padding: 0 5px;
color: mc('indigo', '800'); color: mc('indigo', '800');
font-family: 'Source Code Pro', monospace; font-family: 'Roboto Mono', monospace;
font-weight: normal; font-weight: normal;
font-size: 1rem; font-size: 1rem;
box-shadow: none; box-shadow: none;
@ -281,7 +281,7 @@
box-shadow: initial; box-shadow: initial;
display: block; display: block;
font-size: .85rem; font-size: .85rem;
font-family: 'Source Code Pro', monospace; font-family: 'Roboto Mono', monospace;
&:after, &:before { &:after, &:before {
content: initial; content: initial;

Loading…
Cancel
Save