Bläddra i källkod

feat: editor props scripts + styles code editor

NGPixel 5 år sedan
förälder
incheckning
718c14dd74

+ 1 - 1
client/components/editor.vue

@@ -203,7 +203,7 @@ export default {
     this.checkoutDateActive = this.checkoutDate
     this.checkoutDateActive = this.checkoutDate
 
 
     if (this.effectivePermissions) {
     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() {
   mounted() {

+ 0 - 80
client/components/editor/editor-markdown.vue

@@ -936,86 +936,6 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
   .CodeMirror-selection-highlight-scrollbar {
   .CodeMirror-selection-highlight-scrollbar {
     background-color: mc('green', '600');
     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
 // HINT DROPDOWN

+ 108 - 38
client/components/editor/editor-modal-properties.vue

@@ -17,12 +17,13 @@
         v-icon(left) mdi-check
         v-icon(left) mdi-check
         span {{ $t('common:actions.ok') }}
         span {{ $t('common:actions.ok') }}
     v-card(tile)
     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.info')}}
         v-tab {{$t('editor:props.scheduling')}}
         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 {{$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
           v-card-text.pt-5
             .overline.pb-5 {{$t('editor:props.pageInfo')}}
             .overline.pb-5 {{$t('editor:props.pageInfo')}}
             v-text-field(
             v-text-field(
@@ -88,16 +89,15 @@
               hide-no-data
               hide-no-data
               :search-input.sync='newTagSearch'
               :search-input.sync='newTagSearch'
               )
               )
-        v-tab-item
+        v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
           v-card-text
           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(
             v-switch(
               :label='$t(`editor:props.publishToggle`)'
               :label='$t(`editor:props.publishToggle`)'
               v-model='isPublished'
               v-model='isPublished'
               color='primary'
               color='primary'
               :hint='$t(`editor:props.publishToggleHint`)'
               :hint='$t(`editor:props.publishToggleHint`)'
               persistent-hint
               persistent-hint
-              disabled
               inset
               inset
               )
               )
           v-divider
           v-divider
@@ -111,7 +111,7 @@
                     v-model='isPublishStartShown'
                     v-model='isPublishStartShown'
                     :return-value.sync='publishStartDate'
                     :return-value.sync='publishStartDate'
                     width='460px'
                     width='460px'
-                    :disabled='!isPublished || true'
+                    :disabled='!isPublished'
                     )
                     )
                     template(v-slot:activator='{ on }')
                     template(v-slot:activator='{ on }')
                       v-text-field(
                       v-text-field(
@@ -124,7 +124,7 @@
                         clearable
                         clearable
                         :hint='$t(`editor:props.publishStartHint`)'
                         :hint='$t(`editor:props.publishStartHint`)'
                         persistent-hint
                         persistent-hint
-                        :disabled='!isPublished || true'
+                        :disabled='!isPublished'
                         )
                         )
                     v-date-picker(
                     v-date-picker(
                       v-model='publishStartDate'
                       v-model='publishStartDate'
@@ -152,7 +152,7 @@
                     v-model='isPublishEndShown'
                     v-model='isPublishEndShown'
                     :return-value.sync='publishEndDate'
                     :return-value.sync='publishEndDate'
                     width='460px'
                     width='460px'
-                    :disabled='!isPublished || true'
+                    :disabled='!isPublished'
                     )
                     )
                     template(v-slot:activator='{ on }')
                     template(v-slot:activator='{ on }')
                       v-text-field(
                       v-text-field(
@@ -165,7 +165,7 @@
                         clearable
                         clearable
                         :hint='$t(`editor:props.publishEndHint`)'
                         :hint='$t(`editor:props.publishEndHint`)'
                         persistent-hint
                         persistent-hint
-                        :disabled='!isPublished || true'
+                        :disabled='!isPublished'
                         )
                         )
                     v-date-picker(
                     v-date-picker(
                       v-model='publishEndDate'
                       v-model='publishEndDate'
@@ -187,35 +187,21 @@
                         @click='$refs.menuPublishEnd.save(publishEndDate)'
                         @click='$refs.menuPublishEnd.save(publishEndDate)'
                         ) {{$t('common:actions.ok')}}
                         ) {{$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
           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(
             v-switch(
               :label='$t(`editor:props.allowComments`)'
               :label='$t(`editor:props.allowComments`)'
               v-model='isPublished'
               v-model='isPublished'
               color='primary'
               color='primary'
               :hint='$t(`editor:props.allowCommentsHint`)'
               :hint='$t(`editor:props.allowCommentsHint`)'
               persistent-hint
               persistent-hint
-              disabled
               inset
               inset
               )
               )
             v-switch(
             v-switch(
@@ -233,7 +219,6 @@
               color='primary'
               color='primary'
               :hint='$t(`editor:props.displayAuthorHint`)'
               :hint='$t(`editor:props.displayAuthorHint`)'
               persistent-hint
               persistent-hint
-              disabled
               inset
               inset
               )
               )
             v-switch(
             v-switch(
@@ -242,10 +227,15 @@
               color='primary'
               color='primary'
               :hint='$t(`editor:props.displaySharingBarHint`)'
               :hint='$t(`editor:props.displaySharingBarHint`)'
               persistent-hint
               persistent-hint
-              disabled
               inset
               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')
     page-selector(:mode='pageSelectorMode', v-model='pageSelectorShown', :path='path', :locale='locale', :open-handler='setPath')
 </template>
 </template>
 
 
@@ -254,6 +244,11 @@ import _ from 'lodash'
 import { sync, get } from 'vuex-pathify'
 import { sync, get } from 'vuex-pathify'
 import gql from 'graphql-tag'
 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 */
 /* global siteLangs, siteConfig */
 
 
 export default {
 export default {
@@ -263,7 +258,7 @@ export default {
       default: false
       default: false
     }
     }
   },
   },
-  data() {
+  data () {
     return {
     return {
       isPublishStartShown: false,
       isPublishStartShown: false,
       isPublishEndShown: false,
       isPublishEndShown: false,
@@ -271,7 +266,9 @@ export default {
       namespaces: siteLangs.length ? siteLangs.map(ns => ns.code) : [siteConfig.lang],
       namespaces: siteLangs.length ? siteLangs.map(ns => ns.code) : [siteConfig.lang],
       newTag: '',
       newTag: '',
       newTagSuggestions: [],
       newTagSuggestions: [],
-      newTagSearch: ''
+      newTagSearch: '',
+      currentTab: 0,
+      cm: null
     }
     }
   },
   },
   computed: {
   computed: {
@@ -288,20 +285,23 @@ export default {
     isPublished: sync('page/isPublished'),
     isPublished: sync('page/isPublished'),
     publishStartDate: sync('page/publishStartDate'),
     publishStartDate: sync('page/publishStartDate'),
     publishEndDate: sync('page/publishEndDate'),
     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 () {
     pageSelectorMode () {
       return (this.mode === 'create') ? 'create' : 'move'
       return (this.mode === 'create') ? 'create' : 'move'
     }
     }
   },
   },
   watch: {
   watch: {
-    value(newValue, oldValue) {
+    value (newValue, oldValue) {
       if (newValue) {
       if (newValue) {
         _.delay(() => {
         _.delay(() => {
           this.$refs.iptTitle.focus()
           this.$refs.iptTitle.focus()
-          // this.$tours['editorPropertiesTour'].start()
         }, 500)
         }, 500)
       }
       }
     },
     },
-    newTag(newValue, oldValue) {
+    newTag (newValue, oldValue) {
       const tagClean = _.trim(newValue || '').toLowerCase()
       const tagClean = _.trim(newValue || '').toLowerCase()
       if (tagClean && tagClean.length > 0) {
       if (tagClean && tagClean.length > 0) {
         if (!_.includes(this.tags, tagClean)) {
         if (!_.includes(this.tags, tagClean)) {
@@ -311,6 +311,24 @@ export default {
           this.newTag = null
           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: {
   methods: {
@@ -326,6 +344,42 @@ export default {
     setPath({ path, locale }) {
     setPath({ path, locale }) {
       this.locale = locale
       this.locale = locale
       this.path = path
       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: {
   apollo: {
@@ -355,4 +409,20 @@ export default {
 
 
 <style lang='scss'>
 <style lang='scss'>
 
 
+.editor-props-codeeditor {
+  background-color: mc('grey', '900');
+  min-height: 500px;
+
+  > textarea {
+    visibility: hidden;
+  }
+
+  &-hint {
+    background-color: mc('grey', '900');
+    border-top: 1px solid lighten(mc('grey', '900'), 5%);
+    color: mc('grey', '500');
+    padding: 5px 10px;
+  }
+}
+
 </style>
 </style>

+ 1 - 0
client/scss/app.scss

@@ -8,6 +8,7 @@
 @import '~katex/dist/katex.min.css';
 @import '~katex/dist/katex.min.css';
 @import '~diff2html/bundles/css/diff2html.min.css';
 @import '~diff2html/bundles/css/diff2html.min.css';
 
 
+@import 'components/codemirror';
 @import 'components/katex';
 @import 'components/katex';
 @import 'components/v-btn';
 @import 'components/v-btn';
 @import 'components/v-data-table';
 @import 'components/v-data-table';

+ 79 - 0
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;
+}

+ 5 - 1
client/store/page.js

@@ -15,6 +15,8 @@ const state = {
   title: '',
   title: '',
   updatedAt: '',
   updatedAt: '',
   mode: '',
   mode: '',
+  scriptJs: '',
+  scriptCss: '',
   effectivePermissions: {
   effectivePermissions: {
     comments: {
     comments: {
       read: false,
       read: false,
@@ -30,7 +32,9 @@ const state = {
     pages: {
     pages: {
       write: false,
       write: false,
       manage: false,
       manage: false,
-      delete: false
+      delete: false,
+      script: false,
+      style: false
     },
     },
     system: {
     system: {
       manage: false
       manage: false

+ 3 - 1
server/controllers/common.js

@@ -23,7 +23,9 @@ const getPageEffectivePermissions = (req, page) => {
     pages: {
     pages: {
       write: WIKI.auth.checkAccess(req.user, ['write:pages'], page),
       write: WIKI.auth.checkAccess(req.user, ['write:pages'], page),
       manage: WIKI.auth.checkAccess(req.user, ['manage: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: {
     system: {
       manage: WIKI.auth.checkAccess(req.user, ['manage:system'], page)
       manage: WIKI.auth.checkAccess(req.user, ['manage:system'], page)