From 718c14dd7450c64c2645139329b2f020b56b8303 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Fri, 19 Jun 2020 21:00:44 -0400 Subject: [PATCH] feat: editor props scripts + styles code editor --- client/components/editor.vue | 2 +- client/components/editor/editor-markdown.vue | 80 ---------- .../editor/editor-modal-properties.vue | 146 +++++++++++++----- client/scss/app.scss | 1 + client/scss/components/codemirror.scss | 79 ++++++++++ client/store/page.js | 6 +- server/controllers/common.js | 4 +- 7 files changed, 197 insertions(+), 121 deletions(-) create mode 100644 client/scss/components/codemirror.scss diff --git a/client/components/editor.vue b/client/components/editor.vue index e59badf0..36ae0937 100644 --- a/client/components/editor.vue +++ b/client/components/editor.vue @@ -203,7 +203,7 @@ export default { this.checkoutDateActive = this.checkoutDate if (this.effectivePermissions) { - this.$store.set('page/effectivePermissions',JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) + this.$store.set('page/effectivePermissions', JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) } }, mounted() { diff --git a/client/components/editor/editor-markdown.vue b/client/components/editor/editor-markdown.vue index 1b1c483b..02c0c50b 100644 --- a/client/components/editor/editor-markdown.vue +++ b/client/components/editor/editor-markdown.vue @@ -936,86 +936,6 @@ $editor-height-mobile: calc(100vh - 112px - 16px); .CodeMirror-selection-highlight-scrollbar { background-color: mc('green', '600'); } - - .cm-s-wikijs-dark.CodeMirror { - background: darken(mc('grey','900'), 3%); - color: #e0e0e0; - } - .cm-s-wikijs-dark div.CodeMirror-selected { - background: mc('blue','800'); - } - .cm-s-wikijs-dark .cm-matchhighlight { - 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('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('amber', '500'); - } - .cm-s-wikijs-dark .CodeMirror-gutters { - background: darken(mc('grey','900'), 6%); - border-right: 1px solid mc('grey','900'); - } - .cm-s-wikijs-dark .CodeMirror-guttermarker { - color: #ac4142; - } - .cm-s-wikijs-dark .CodeMirror-guttermarker-subtle { - color: #505050; - } - .cm-s-wikijs-dark .CodeMirror-linenumber { - color: mc('grey','800'); - } - .cm-s-wikijs-dark .CodeMirror-cursor { - border-left: 1px solid #b0b0b0; - } - .cm-s-wikijs-dark span.cm-comment { - color: mc('orange','800'); - } - .cm-s-wikijs-dark span.cm-atom { - color: #aa759f; - } - .cm-s-wikijs-dark span.cm-number { - color: #aa759f; - } - .cm-s-wikijs-dark span.cm-property, .cm-s-wikijs-dark span.cm-attribute { - color: #90a959; - } - .cm-s-wikijs-dark span.cm-keyword { - color: #ac4142; - } - .cm-s-wikijs-dark span.cm-string { - color: #f4bf75; - } - .cm-s-wikijs-dark span.cm-variable { - color: #90a959; - } - .cm-s-wikijs-dark span.cm-variable-2 { - color: #6a9fb5; - } - .cm-s-wikijs-dark span.cm-def { - color: #d28445; - } - .cm-s-wikijs-dark span.cm-bracket { - color: #e0e0e0; - } - .cm-s-wikijs-dark span.cm-tag { - color: #ac4142; - } - .cm-s-wikijs-dark span.cm-link { - color: #aa759f; - } - .cm-s-wikijs-dark span.cm-error { - background: #ac4142; - color: #b0b0b0; - } - .cm-s-wikijs-dark .CodeMirror-activeline-background { - background: mc('grey','900'); - } - .cm-s-wikijs-dark .CodeMirror-matchingbracket { - text-decoration: underline; - color: white !important; - } } // HINT DROPDOWN diff --git a/client/components/editor/editor-modal-properties.vue b/client/components/editor/editor-modal-properties.vue index 88962a43..cbc96a02 100644 --- a/client/components/editor/editor-modal-properties.vue +++ b/client/components/editor/editor-modal-properties.vue @@ -17,12 +17,13 @@ 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-tabs(color='white', background-color='blue darken-1', dark, centered, v-model='currentTab') v-tab {{$t('editor:props.info')}} v-tab {{$t('editor:props.scheduling')}} - v-tab(disabled) {{$t('editor:props.scripts')}} + v-tab(:disabled='!hasScriptPermission') {{$t('editor:props.scripts')}} v-tab {{$t('editor:props.social')}} - v-tab-item + v-tab(:disabled='!hasStylePermission') {{$t('editor:props.styles')}} + v-tab-item(transition='fade-transition', reverse-transition='fade-transition') v-card-text.pt-5 .overline.pb-5 {{$t('editor:props.pageInfo')}} v-text-field( @@ -88,16 +89,15 @@ hide-no-data :search-input.sync='newTagSearch' ) - v-tab-item + v-tab-item(transition='fade-transition', reverse-transition='fade-transition') v-card-text - .overline.pb-5 {{$t('editor:props.publishState')}} #[v-chip.ml-3(label, color='grey', small, outlined).white--text coming soon] + .overline {{$t('editor:props.publishState')}} v-switch( :label='$t(`editor:props.publishToggle`)' v-model='isPublished' color='primary' :hint='$t(`editor:props.publishToggleHint`)' persistent-hint - disabled inset ) v-divider @@ -111,7 +111,7 @@ v-model='isPublishStartShown' :return-value.sync='publishStartDate' width='460px' - :disabled='!isPublished || true' + :disabled='!isPublished' ) template(v-slot:activator='{ on }') v-text-field( @@ -124,7 +124,7 @@ clearable :hint='$t(`editor:props.publishStartHint`)' persistent-hint - :disabled='!isPublished || true' + :disabled='!isPublished' ) v-date-picker( v-model='publishStartDate' @@ -152,7 +152,7 @@ v-model='isPublishEndShown' :return-value.sync='publishEndDate' width='460px' - :disabled='!isPublished || true' + :disabled='!isPublished' ) template(v-slot:activator='{ on }') v-text-field( @@ -165,7 +165,7 @@ clearable :hint='$t(`editor:props.publishEndHint`)' persistent-hint - :disabled='!isPublished || true' + :disabled='!isPublished' ) v-date-picker( v-model='publishEndDate' @@ -187,35 +187,21 @@ @click='$refs.menuPublishEnd.save(publishEndDate)' ) {{$t('common:actions.ok')}} - v-tab-item - v-card-text - .overline.pb-3 {{$t('editor:props.js')}} - v-textarea( - outlined - rows='5' - :hint='$t(`editor:props.jsHint`)' - persistent-hint - ) - v-divider - v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d3` : `lighten-5`') - .overline.pb-3 {{$t('editor:props.css')}} - v-textarea( - outlined - rows='5' - :hint='$t(`editor:props.cssHint`)' - persistent-hint - ) + v-tab-item(:transition='false', :reverse-transition='false') + .editor-props-codeeditor + textarea(ref='codejs') + .editor-props-codeeditor-hint + .caption {{$t('editor:props.jsHint')}} - v-tab-item + v-tab-item(transition='fade-transition', reverse-transition='fade-transition') v-card-text - .overline.pb-5 {{$t('editor:props.socialFeatures')}} #[v-chip.ml-3(label, color='grey', small, outlined).white--text coming soon] + .overline {{$t('editor:props.socialFeatures')}} v-switch( :label='$t(`editor:props.allowComments`)' v-model='isPublished' color='primary' :hint='$t(`editor:props.allowCommentsHint`)' persistent-hint - disabled inset ) v-switch( @@ -233,7 +219,6 @@ color='primary' :hint='$t(`editor:props.displayAuthorHint`)' persistent-hint - disabled inset ) v-switch( @@ -242,10 +227,15 @@ color='primary' :hint='$t(`editor:props.displaySharingBarHint`)' persistent-hint - disabled inset ) + v-tab-item(:transition='false', :reverse-transition='false') + .editor-props-codeeditor + textarea(ref='codecss') + .editor-props-codeeditor-hint + .caption {{$t('editor:props.cssHint')}} + page-selector(:mode='pageSelectorMode', v-model='pageSelectorShown', :path='path', :locale='locale', :open-handler='setPath') @@ -254,6 +244,11 @@ import _ from 'lodash' import { sync, get } from 'vuex-pathify' import gql from 'graphql-tag' +import CodeMirror from 'codemirror' +import 'codemirror/lib/codemirror.css' +import 'codemirror/mode/javascript/javascript.js' +import 'codemirror/mode/css/css.js' + /* global siteLangs, siteConfig */ export default { @@ -263,7 +258,7 @@ export default { default: false } }, - data() { + data () { return { isPublishStartShown: false, isPublishEndShown: false, @@ -271,7 +266,9 @@ export default { namespaces: siteLangs.length ? siteLangs.map(ns => ns.code) : [siteConfig.lang], newTag: '', newTagSuggestions: [], - newTagSearch: '' + newTagSearch: '', + currentTab: 0, + cm: null } }, computed: { @@ -288,20 +285,23 @@ export default { isPublished: sync('page/isPublished'), publishStartDate: sync('page/publishStartDate'), publishEndDate: sync('page/publishEndDate'), + scriptJs: sync('page/scriptJs'), + scriptCss: sync('page/scriptCss'), + hasScriptPermission: get('page/effectivePermissions@pages.script'), + hasStylePermission: get('page/effectivePermissions@pages.style'), pageSelectorMode () { return (this.mode === 'create') ? 'create' : 'move' } }, watch: { - value(newValue, oldValue) { + value (newValue, oldValue) { if (newValue) { _.delay(() => { this.$refs.iptTitle.focus() - // this.$tours['editorPropertiesTour'].start() }, 500) } }, - newTag(newValue, oldValue) { + newTag (newValue, oldValue) { const tagClean = _.trim(newValue || '').toLowerCase() if (tagClean && tagClean.length > 0) { if (!_.includes(this.tags, tagClean)) { @@ -311,6 +311,24 @@ export default { this.newTag = null }) } + }, + currentTab (newValue, oldValue) { + if (this.cm) { + this.cm.toTextArea() + } + if (newValue === 2) { + this.$nextTick(() => { + setTimeout(() => { + this.loadEditor(this.$refs.codejs, 'javascript') + }, 100) + }) + } else if (newValue === 4) { + this.$nextTick(() => { + setTimeout(() => { + this.loadEditor(this.$refs.codecss, 'css') + }, 100) + }) + } } }, methods: { @@ -326,6 +344,42 @@ export default { setPath({ path, locale }) { this.locale = locale this.path = path + }, + loadEditor(ref, mode) { + this.cm = CodeMirror.fromTextArea(ref, { + tabSize: 2, + mode: `text/${mode}`, + theme: 'wikijs-dark', + lineNumbers: true, + lineWrapping: true, + line: true, + styleActiveLine: true, + viewportMargin: 50, + inputStyle: 'contenteditable', + direction: 'ltr' + }) + switch (mode) { + case 'javascript': + this.cm.setValue(this.scriptJs) + this.cm.on('change', c => { + this.scriptJs = c.getValue() + }) + break + case 'css': + this.cm.setValue(this.scriptCss) + this.cm.on('change', c => { + this.scriptCss = c.getValue() + }) + break + default: + console.warn('Invalid Editor Mode') + break + } + this.cm.setSize(null, '500px') + this.$nextTick(() => { + this.cm.refresh() + this.cm.focus() + }) } }, apollo: { @@ -355,4 +409,20 @@ export default { diff --git a/client/scss/app.scss b/client/scss/app.scss index e6389909..566934ec 100644 --- a/client/scss/app.scss +++ b/client/scss/app.scss @@ -8,6 +8,7 @@ @import '~katex/dist/katex.min.css'; @import '~diff2html/bundles/css/diff2html.min.css'; +@import 'components/codemirror'; @import 'components/katex'; @import 'components/v-btn'; @import 'components/v-data-table'; diff --git a/client/scss/components/codemirror.scss b/client/scss/components/codemirror.scss new file mode 100644 index 00000000..89a9470f --- /dev/null +++ b/client/scss/components/codemirror.scss @@ -0,0 +1,79 @@ +.cm-s-wikijs-dark.CodeMirror { + background: darken(mc('grey','900'), 3%); + color: #e0e0e0; +} +.cm-s-wikijs-dark div.CodeMirror-selected { + background: mc('blue','800'); +} +.cm-s-wikijs-dark .cm-matchhighlight { + 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('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('amber', '500'); +} +.cm-s-wikijs-dark .CodeMirror-gutters { + background: darken(mc('grey','900'), 6%); + border-right: 1px solid mc('grey','900'); +} +.cm-s-wikijs-dark .CodeMirror-guttermarker { + color: #ac4142; +} +.cm-s-wikijs-dark .CodeMirror-guttermarker-subtle { + color: #505050; +} +.cm-s-wikijs-dark .CodeMirror-linenumber { + color: mc('grey','800'); +} +.cm-s-wikijs-dark .CodeMirror-cursor { + border-left: 1px solid #b0b0b0; +} +.cm-s-wikijs-dark span.cm-comment { + color: mc('orange','800'); +} +.cm-s-wikijs-dark span.cm-atom { + color: #aa759f; +} +.cm-s-wikijs-dark span.cm-number { + color: #aa759f; +} +.cm-s-wikijs-dark span.cm-property, .cm-s-wikijs-dark span.cm-attribute { + color: #90a959; +} +.cm-s-wikijs-dark span.cm-keyword { + color: #ac4142; +} +.cm-s-wikijs-dark span.cm-string { + color: #f4bf75; +} +.cm-s-wikijs-dark span.cm-variable { + color: #90a959; +} +.cm-s-wikijs-dark span.cm-variable-2 { + color: #6a9fb5; +} +.cm-s-wikijs-dark span.cm-def { + color: #d28445; +} +.cm-s-wikijs-dark span.cm-bracket { + color: #e0e0e0; +} +.cm-s-wikijs-dark span.cm-tag { + color: #ac4142; +} +.cm-s-wikijs-dark span.cm-link { + color: #aa759f; +} +.cm-s-wikijs-dark span.cm-error { + background: #ac4142; + color: #b0b0b0; +} +.cm-s-wikijs-dark .CodeMirror-activeline-background { + background: mc('grey','900'); +} +.cm-s-wikijs-dark .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/client/store/page.js b/client/store/page.js index fe39e301..e6b0992e 100644 --- a/client/store/page.js +++ b/client/store/page.js @@ -15,6 +15,8 @@ const state = { title: '', updatedAt: '', mode: '', + scriptJs: '', + scriptCss: '', effectivePermissions: { comments: { read: false, @@ -30,7 +32,9 @@ const state = { pages: { write: false, manage: false, - delete: false + delete: false, + script: false, + style: false }, system: { manage: false diff --git a/server/controllers/common.js b/server/controllers/common.js index 4ecc6a4d..3d61ba10 100644 --- a/server/controllers/common.js +++ b/server/controllers/common.js @@ -23,7 +23,9 @@ const getPageEffectivePermissions = (req, page) => { pages: { write: WIKI.auth.checkAccess(req.user, ['write:pages'], page), manage: WIKI.auth.checkAccess(req.user, ['manage:pages'], page), - delete: WIKI.auth.checkAccess(req.user, ['delete:pages'], page) + delete: WIKI.auth.checkAccess(req.user, ['delete:pages'], page), + script: WIKI.auth.checkAccess(req.user, ['write:scripts'], page), + style: WIKI.auth.checkAccess(req.user, ['write:styles'], page) }, system: { manage: WIKI.auth.checkAccess(req.user, ['manage:system'], page)