feat: editor page props UI improvements + codemirror fix

This commit is contained in:
Nick 2019-08-31 22:10:58 -04:00
parent cc469d8785
commit 8e80b7471d
7 changed files with 313 additions and 262 deletions

View File

@ -56,7 +56,7 @@
v-card.radius-7.animated.fadeInUp.wait-p2s
v-toolbar(:color='$vuetify.theme.dark ? `grey darken-2` : `grey lighten-5`', dense, flat)
v-spacer
.overline Recent Pages
.overline {{$t('admin:dashboard.recentPages')}}
v-spacer
v-data-table.pb-2(
:items='recentPages'
@ -72,27 +72,28 @@
td.admin-pages-path
v-chip(label, small, :color='$vuetify.theme.dark ? `grey darken-4` : `grey lighten-4`') {{ props.item.locale }}
span.ml-2.grey--text(:class='$vuetify.theme.dark ? `text--lighten-1` : `text--darken-2`') / {{ props.item.path }}
td.text-right(width='250') {{ props.item.updatedAt | moment('calendar') }}
td.text-right.caption(width='250') {{ props.item.updatedAt | moment('calendar') }}
v-flex(xs12, xl6)
v-card.radius-7.animated.fadeInUp.wait-p4s
v-toolbar(:color='$vuetify.theme.dark ? `grey darken-2` : `grey lighten-5`', dense, flat)
v-spacer
.overline Most Popular Pages
.overline {{$t('admin:dashboard.mostPopularPages')}}
v-spacer
v-data-table.pb-2(
:items='popularPages'
:headers='headers'
:loading='popularPagesLoading'
hide-default-footer
hide-default-header
)
template(slot='items' slot-scope='props')
td(width='20', style='padding-right: 0;'): v-icon insert_drive_file
template(slot='item', slot-scope='props')
tr.is-clickable(:active='props.selected', @click='$router.push(`/pages/` + props.item.id)')
td
.body-2.primary--text {{ props.item.title }}
.caption.grey--text.text--darken-2 {{ props.item.description }}
td.caption /{{ props.item.path }}
td.grey--text.text--darken-2(width='250')
.caption: strong Updated {{ props.item.updatedAt | moment('from') }}
.caption Created {{ props.item.createdAt | moment('calendar') }}
.body-2: strong {{ props.item.title }}
td.admin-pages-path
v-chip(label, small, :color='$vuetify.theme.dark ? `grey darken-4` : `grey lighten-4`') {{ props.item.locale }}
span.ml-2.grey--text(:class='$vuetify.theme.dark ? `text--lighten-1` : `text--darken-2`') / {{ props.item.path }}
td.text-right.caption(width='250') {{ props.item.updatedAt | moment('calendar') }}
v-flex(xs12)
v-card.dashboard-contribute.animated.fadeInUp.wait-p4s
@ -123,6 +124,7 @@ export default {
recentPages: [],
recentPagesLoading: false,
popularPages: [],
popularPagesLoading: false,
headers: [
{ text: 'ID', value: 'id', width: 80 },
{ text: 'Title', value: 'title' },

View File

@ -13,7 +13,7 @@ import _ from 'lodash'
// ========================================
// Code Mirror
import { codemirror } from 'vue-codemirror'
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
// Language
@ -41,9 +41,6 @@ const CtrlKey = /Mac/.test(navigator.platform) ? 'Cmd' : 'Ctrl'
// ========================================
export default {
components: {
codemirror
},
data() {
return {
code: '<h1>Title</h1>\n\n<p>Some text here</p>',

View File

@ -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%);

View File

@ -66,15 +66,15 @@
@click.right.prevent=''
:class='currentFileId === props.item.id ? ($vuetify.theme.dark ? `grey darken-3-d5` : `teal lighten-5`) : ``'
)
td.text-xs-right(v-if='$vuetify.breakpoint.smAndUp') {{ props.item.id }}
td.caption(v-if='$vuetify.breakpoint.smAndUp') {{ props.item.id }}
td
.body-2(:class='currentFileId === props.item.id ? `teal--text` : ``') {{ props.item.filename }}
.body-2: strong(:class='currentFileId === props.item.id ? `teal--text` : ``') {{ props.item.filename }}
.caption.grey--text {{ props.item.description }}
td.text-xs-center(v-if='$vuetify.breakpoint.lgAndUp')
v-chip.ma-0(small, :color='$vuetify.theme.dark ? `grey darken-4` : `grey lighten-4`')
.caption {{props.item.ext.toUpperCase().substring(1)}}
td(v-if='$vuetify.breakpoint.mdAndUp') {{ props.item.fileSize | prettyBytes }}
td(v-if='$vuetify.breakpoint.mdAndUp') {{ props.item.createdAt | moment('from') }}
v-chip.ma-0(x-small, :color='$vuetify.theme.dark ? `grey darken-4` : `grey lighten-4`')
.overline {{props.item.ext.toUpperCase().substring(1)}}
td.caption(v-if='$vuetify.breakpoint.mdAndUp') {{ props.item.fileSize | prettyBytes }}
td.caption(v-if='$vuetify.breakpoint.mdAndUp') {{ props.item.createdAt | moment('from') }}
td(v-if='$vuetify.breakpoint.smAndUp')
v-menu(offset-x, min-width='200')
template(v-slot:activator='{ on }')
@ -293,12 +293,12 @@ export default {
},
headers() {
return _.compact([
this.$vuetify.breakpoint.smAndUp && { text: this.$t('editor:assets.headerId'), value: 'id', width: 50, align: 'right' },
this.$vuetify.breakpoint.smAndUp && { text: this.$t('editor:assets.headerId'), value: 'id', width: 80 },
{ text: this.$t('editor:assets.headerFilename'), value: 'filename' },
this.$vuetify.breakpoint.lgAndUp && { text: this.$t('editor:assets.headerType'), value: 'ext', width: 50 },
this.$vuetify.breakpoint.lgAndUp && { text: this.$t('editor:assets.headerType'), value: 'ext', width: 90 },
this.$vuetify.breakpoint.mdAndUp && { text: this.$t('editor:assets.headerFileSize'), value: 'fileSize', width: 110 },
this.$vuetify.breakpoint.mdAndUp && { text: this.$t('editor:assets.headerAdded'), value: 'createdAt', width: 175 },
this.$vuetify.breakpoint.smAndUp && { text: this.$t('editor:assets.headerActions'), value: '', width: 40, sortable: false, align: 'right' }
this.$vuetify.breakpoint.smAndUp && { text: this.$t('editor:assets.headerActions'), value: '', width: 80, sortable: false, align: 'right' }
])
},
isFolderNameValid() {

View File

@ -2,7 +2,7 @@
v-dialog(
v-model='isShown'
persistent
width='1100'
width='1000'
:fullscreen='$vuetify.breakpoint.smAndDown'
)
.dialog-header
@ -17,6 +17,11 @@
v-icon(left) mdi-check
span {{ $t('common:actions.ok') }}
v-card(tile)
v-tabs(color='white', background-color='blue darken-1', dark, centered)
v-tab {{$t('editor:props.info')}}
v-tab {{$t('editor:props.scheduling')}}
v-tab {{$t('editor:props.social')}}
v-tab-item
v-card-text.pt-5
.overline.pb-5 {{$t('editor:props.pageInfo')}}
v-text-field(
@ -36,7 +41,7 @@
)
v-divider
v-card-text.grey.pt-5(:class='darkMode ? `darken-3-d3` : `lighten-5`')
.overline.pb-5 {{$t('editor:props.pathCategorization')}}
.overline.pb-5 {{$t('editor:props.path')}}
v-container.pa-0(fluid, grid-list-lg)
v-layout(row, wrap)
v-flex(xs12, md2)
@ -60,6 +65,9 @@
@click:append='showPathSelector'
:disabled='mode !== "create"'
)
v-divider
v-card-text.grey.pt-5(:class='darkMode ? `darken-3-d5` : `lighten-4`')
.overline.pb-5 {{$t('editor:props.categorization')}}
v-combobox(
chips
deletable-chips
@ -67,17 +75,14 @@
outlined
multiple
v-model='tags'
single-line
:hint='`COMING SOON - ` + $t(`editor:props.tagsHint`)'
:hint='$t(`editor:props.tagsHint`)'
persistent-hint
disabled
clearable
height='130'
)
v-divider
v-card-text.py-5.grey(:class='darkMode ? `darken-3-d5` : `lighten-4`')
v-tab-item
v-card-text
.overline.pb-5 {{$t('editor:props.publishState')}} #[v-chip.ml-3(label, color='grey', small, outlined).white--text coming soon]
v-container.pa-0(fluid, grid-list-lg)
v-layout(row, wrap)
v-flex(xs12, md4)
v-switch(
:label='$t(`editor:props.publishToggle`)'
v-model='isPublished'
@ -86,7 +91,11 @@
persistent-hint
disabled
)
v-flex(xs12, md4)
v-divider
v-card-text.grey.pt-5(:class='darkMode ? `darken-3-d3` : `lighten-5`')
v-container.pa-0(fluid, grid-list-lg)
v-row
v-col(cols='6')
v-dialog(
ref='menuPublishStart'
:close-on-content-click='false'
@ -128,7 +137,7 @@
color='primary'
@click='$refs.menuPublishStart.save(publishStartDate)'
) {{$t('common:actions.ok')}}
v-flex(xs12, md4)
v-col(cols='6')
v-dialog(
ref='menuPublishEnd'
:close-on-content-click='false'
@ -171,6 +180,42 @@
@click='$refs.menuPublishEnd.save(publishEndDate)'
) {{$t('common:actions.ok')}}
v-tab-item
v-card-text
.overline.pb-5 {{$t('editor:props.socialFeatures')}} #[v-chip.ml-3(label, color='grey', small, outlined).white--text coming soon]
v-switch(
:label='$t(`editor:props.allowComments`)'
v-model='isPublished'
color='primary'
:hint='$t(`editor:props.allowCommentsHint`)'
persistent-hint
disabled
)
v-switch(
:label='$t(`editor:props.allowRatings`)'
v-model='isPublished'
color='primary'
:hint='$t(`editor:props.allowRatingsHint`)'
persistent-hint
disabled
)
v-switch(
:label='$t(`editor:props.displayAuthor`)'
v-model='isPublished'
color='primary'
:hint='$t(`editor:props.displayAuthorHint`)'
persistent-hint
disabled
)
v-switch(
:label='$t(`editor:props.displaySharingBar`)'
v-model='isPublished'
color='primary'
:hint='$t(`editor:props.displaySharingBarHint`)'
persistent-hint
disabled
)
page-selector(mode='create', v-model='pageSelectorShown', :path='path', :locale='locale', :open-handler='setPath')
v-tour(name='editorPropertiesTour', :steps='tourSteps')
</template>

View File

@ -206,6 +206,7 @@
"cache-loader": "4.1.0",
"chart.js": "2.8.0",
"clean-webpack-plugin": "3.0.0",
"codemirror": "5.48.4",
"copy-webpack-plugin": "5.0.4",
"core-js": "3.2.1",
"css-loader": "3.2.0",
@ -274,7 +275,6 @@
"vue-apollo": "3.0.0-rc.2",
"vue-chartjs": "3.4.2",
"vue-clipboards": "1.3.0",
"vue-codemirror": "4.0.6",
"vue-filepond": "5.1.3",
"vue-hot-reload-api": "2.3.3",
"vue-loader": "15.7.1",

View File

@ -4250,10 +4250,10 @@ codemirror-graphql@^0.9.0:
graphql-language-service-interface "^2.1.0"
graphql-language-service-parser "^1.3.0"
codemirror@^5.41.0:
version "5.43.0"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.43.0.tgz#2454b5e0f7005dc9945ab7b0d9594ccf233da040"
integrity sha512-mljwQWUaWIf85I7QwTBryF2ASaIvmYAL4s5UCanCJFfKeXOKhrqdHWdHiZWAMNT+hjLTCnVx2S/SYTORIgxsgA==
codemirror@5.48.4:
version "5.48.4"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.48.4.tgz#4210fbe92be79a88f0eea348fab3ae78da85ce47"
integrity sha512-pUhZXDQ6qXSpWdwlgAwHEkd4imA0kf83hINmUEzJpmG80T/XLtDDEzZo8f6PQLuRCcUQhmzqqIo3ZPTRaWByRA==
codemirror@^5.47.0:
version "5.48.0"
@ -5283,11 +5283,6 @@ dicer@0.3.0:
dependencies:
streamsearch "0.1.2"
diff-match-patch@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1"
integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==
diff-sequences@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
@ -15030,14 +15025,6 @@ vue-clipboards@1.3.0:
dependencies:
clipboard "^1.7.1"
vue-codemirror@4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/vue-codemirror/-/vue-codemirror-4.0.6.tgz#b786bb80d8d762a93aab8e46f79a81006f0437c4"
integrity sha512-ilU7Uf0mqBNSSV3KT7FNEeRIxH4s1fmpG4TfHlzvXn0QiQAbkXS9lLfwuZpaBVEnpP5CSE62iGJjoliTuA8poQ==
dependencies:
codemirror "^5.41.0"
diff-match-patch "^1.0.0"
vue-eslint-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1"