浏览代码

feat: admin rendering UI + module configuration UI + UI fixes

Nicolas Giard 6 年之前
父节点
当前提交
0b93544677

+ 11 - 0
client/components/admin.vue

@@ -14,6 +14,9 @@
         v-list-tile(to='/locale')
           v-list-tile-avatar: v-icon language
           v-list-tile-title {{ $t('admin:locale.title') }}
+        v-list-tile(to='/pages')
+          v-list-tile-avatar: v-icon insert_drive_file
+          v-list-tile-title {{ $t('admin:pages.title') }}
         v-list-tile(to='/stats')
           v-list-tile-avatar: v-icon show_chart
           v-list-tile-title {{ $t('admin:stats.title') }}
@@ -90,6 +93,7 @@ const router = new VueRouter({
     { path: '/dashboard', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-dashboard.vue') },
     { path: '/general', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-general.vue') },
     { path: '/locale', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-locale.vue') },
+    { path: '/pages', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages.vue') },
     { path: '/stats', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-stats.vue') },
     { path: '/theme', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-theme.vue') },
     { path: '/groups', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups.vue') },
@@ -154,4 +158,11 @@ export default {
   }
 }
 
+.admin-header-icon {
+  position: absolute;
+  top: 1rem;
+  right: 1rem;
+
+}
+
 </style>

+ 1 - 0
client/components/admin/admin-api.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card(flat)
     v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') call_split
       .headline.blue--text.text--darken-2 API
       .subheading.grey--text Manage keys to access the API
     v-card

+ 1 - 0
client/components/admin/admin-auth.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
     .pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') lock_outline
       .headline.primary--text Authentication
       .subheading.grey--text Configure the authentication settings of your wiki
     v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)

+ 1 - 0
client/components/admin/admin-contribute.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card(flat)
     v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') favorite
       .headline.primary--text {{ $t('admin:contribute.title') }}
       .subheading.grey--text {{ $t('admin:contribute.subtitle') }}
     v-card.pa-3

+ 1 - 0
client/components/admin/admin-dev.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   div
     v-card(flat, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') weekend
       .headline.primary--text Developer Tools
       .subheading.grey--text ¯\_(ツ)_/¯
     v-tabs(

+ 1 - 0
client/components/admin/admin-editor.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
     .pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') transform
       .headline.primary--text Editor
       .subheading.grey--text Configure the content editor
     v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)

+ 1 - 0
client/components/admin/admin-general.vue

@@ -2,6 +2,7 @@
   v-container(fluid, fill-height, grid-list-lg)
     v-layout(row wrap)
       v-flex(xs12)
+        .admin-header-icon: v-icon(size='80', color='grey lighten-2') widgets
         .headline.primary--text {{ $t('admin:general.title') }}
         .subheading.grey--text {{ $t('admin:general.subtitle') }}
         v-form.pt-3

+ 1 - 0
client/components/admin/admin-groups-edit.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card
     v-card(flat, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') people
       .headline.blue--text.text--darken-2 Edit Group
       .subheading.grey--text {{name}}
       v-btn(color='primary', fab, absolute, bottom, right, small, to='/groups'): v-icon arrow_upward

+ 1 - 0
client/components/admin/admin-groups.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card(flat)
     v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') people
       .headline.blue--text.text--darken-2 Groups
       .subheading.grey--text Manage groups and their permissions
     v-card

+ 1 - 0
client/components/admin/admin-locale.vue

@@ -2,6 +2,7 @@
   v-container(fluid, fill-height, grid-list-lg)
     v-layout(row wrap)
       v-flex(xs12)
+        .admin-header-icon: v-icon(size='80', color='grey lighten-2') language
         .headline.primary--text {{ $t('admin:locale.title') }}
         .subheading.grey--text {{ $t('admin:locale.subtitle') }}
         v-form.pt-3

+ 1 - 0
client/components/admin/admin-logging.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
     .pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') graphic_eq
       .headline.primary--text Logging
       .subheading.grey--text Configure the system logger(s)
     v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)

+ 79 - 0
client/components/admin/admin-pages.vue

@@ -0,0 +1,79 @@
+<template lang='pug'>
+  v-card(flat)
+    v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') insert_drive_file
+      .headline.blue--text.text--darken-2 Pages
+      .subheading.grey--text Manage pages
+    v-card
+      v-card-title
+        v-btn(color='primary', dark, slot='activator')
+          v-icon(left) add
+          | New Page
+        v-btn(icon, @click='refresh')
+          v-icon.grey--text refresh
+        v-spacer
+        v-text-field(solo, append-icon='search', label='Search', single-line, hide-details, v-model='search')
+      v-data-table(
+        :items='groups'
+        :headers='headers'
+        :search='search'
+        :pagination.sync='pagination'
+        :rows-per-page-items='[15]'
+        hide-actions
+      )
+        template(slot='items', slot-scope='props')
+          tr.is-clickable(:active='props.selected', @click='$router.push("/e/" + props.item.id)')
+            td.text-xs-right {{ props.item.id }}
+            td {{ props.item.name }}
+            td {{ props.item.userCount }}
+            td {{ props.item.createdAt | moment('calendar') }}
+            td {{ props.item.updatedAt | moment('calendar') }}
+        template(slot='no-data')
+          v-alert.ma-3(icon='warning', :value='true', outline) No pages to display.
+      .text-xs-center.py-2(v-if='groups.length > 15')
+        v-pagination(v-model='pagination.page', :length='pages')
+</template>
+
+<script>
+
+export default {
+  data() {
+    return {
+      selectedGroup: {},
+      pagination: {},
+      groups: [],
+      headers: [
+        { text: 'ID', value: 'id', width: 50, align: 'right' },
+        { text: 'Title', value: 'title' },
+        { text: 'Path', value: 'path' },
+        { text: 'Created', value: 'createdAt', width: 250 },
+        { text: 'Last Updated', value: 'updatedAt', width: 250 }
+      ],
+      search: ''
+    }
+  },
+  computed: {
+    pages () {
+      if (this.pagination.rowsPerPage == null || this.pagination.totalItems == null) {
+        return 0
+      }
+
+      return Math.ceil(this.pagination.totalItems / this.pagination.rowsPerPage)
+    }
+  },
+  methods: {
+    async refresh() {
+      // await this.$apollo.queries.groups.refetch()
+      this.$store.commit('showNotification', {
+        message: 'Pages have been refreshed.',
+        style: 'success',
+        icon: 'cached'
+      })
+    }
+  }
+}
+</script>
+
+<style lang='scss'>
+
+</style>

+ 103 - 60
client/components/admin/admin-rendering.vue

@@ -2,6 +2,7 @@
   v-container(fluid, fill-height, grid-list-lg)
     v-layout(row wrap)
       v-flex(xs12)
+        .admin-header-icon: v-icon(size='80', color='grey lighten-2') system_update_alt
         .headline.primary--text Rendering
         .subheading.grey--text Configure how content is rendered
         v-layout.mt-3(row wrap)
@@ -17,7 +18,7 @@
             v-expansion-panel.adm-rendering-pipeline(v-model='selectedCore')
               v-expansion-panel-content(
                 hide-actions
-                v-for='core in cores'
+                v-for='core in renderers'
                 :key='core.key'
                 )
                 v-toolbar(
@@ -27,24 +28,27 @@
                   dark
                   flat
                   )
+                  v-spacer
                   .body-2 {{core.input}}
                   v-icon.mx-2 arrow_forward
                   .caption {{core.output}}
-                v-list(two-line, dense)
+                  v-spacer
+                v-list.py-0(two-line, dense)
                   template(v-for='(rdr, n) in core.children')
                     v-list-tile(
                       avatar
                       :key='rdr.key'
-                      @click=''
+                      @click='selectRenderer(rdr.key)'
+                      :class='currentRenderer.key === rdr.key ? "blue lighten-5" : ""'
                       )
                       v-list-tile-avatar
-                        v-icon(color='grey') {{rdr.icon}}
+                        v-icon(:color='currentRenderer.key === rdr.key ? "primary" : "grey"') {{rdr.icon}}
                       v-list-tile-content
                         v-list-tile-title {{rdr.title}}
                         v-list-tile-sub-title {{rdr.description}}
                       v-list-tile-avatar
-                        v-icon(color='green', small, v-if='rdr.isEnabled') lens
-                        v-icon(color='red', small, v-else) trip_origin
+                        status-indicator(v-if='rdr.isEnabled', positive, pulse)
+                        status-indicator(v-else, negative, pulse)
                     v-divider.my-0(v-if='n < core.children.length - 1')
 
           v-flex(lg9 xs12)
@@ -55,90 +59,129 @@
                 flat
                 dense
                 )
-                v-icon.mr-2 settings_applications
-                .subheading Markdown
-                v-icon chevron_right
-                .subheading Core
+                v-icon.mr-2 {{currentRenderer.icon}}
+                .subheading {{currentRenderer.title}}
                 v-spacer
-                v-btn(flat, disabled)
-                  v-icon(left) wrap_text
-                  span Bypass
-                v-btn(flat, disabled)
-                  v-icon(left) clear
-                  span Remove
-              v-card-text
-                v-switch(
-                  v-model='linkify'
-                  label='Automatically convert links'
-                  color='primary'
-                  persistent-hint
-                  hint='Links will automatically be converted to clickable links.'
-                  )
-                v-divider.mt-3
-                v-switch(
-                  v-model='linkify'
-                  label='Automatically convert line breaks'
-                  color='primary'
-                  persistent-hint
-                  hint='Add linebreaks within paragraphs.'
-                  )
-                v-divider.mt-3
-                v-switch(
-                  v-model='linkify'
-                  label='Highlight code blocks'
-                  color='primary'
-                  persistent-hint
-                  hint='Add syntax coloring to code blocks.'
+                .pt-3.mt-1
+                  v-switch(
+                    dark
+                    color='white'
+                    label='Enabled'
+                    v-model='currentRenderer.isEnabled'
+                    )
+              v-card-text.pb-4.pt-2.pl-4
+                v-subheader.pl-0 Rendering Module Configuration
+                .body-1.ml-3(v-if='!currentRenderer.config || currentRenderer.config.length < 1') This rendering module has no configuration options you can modify.
+                template(v-else, v-for='(cfg, idx) in currentRenderer.config')
+                  v-select(
+                    v-if='cfg.value.type === "string" && cfg.value.enum'
+                    outline
+                    background-color='grey lighten-2'
+                    :items='cfg.value.enum'
+                    :key='cfg.key'
+                    :label='cfg.value.title'
+                    v-model='cfg.value.value'
+                    :hint='cfg.value.hint ? cfg.value.hint : ""'
+                    persistent-hint
+                    :class='cfg.value.hint ? "mb-2" : ""'
                   )
-                v-select.mt-3(
-                  :items='["Light", "Dark"]'
-                  v-model='codeTheme'
-                  label='Code Color Theme'
-                  outline
-                  background-color='grey lighten-2'
-                )
+                  v-switch(
+                    v-else-if='cfg.value.type === "boolean"'
+                    :key='cfg.key'
+                    :label='cfg.value.title'
+                    v-model='cfg.value.value'
+                    color='primary'
+                    :hint='cfg.value.hint ? cfg.value.hint : ""'
+                    persistent-hint
+                    )
+                  v-text-field(
+                    v-else
+                    outline
+                    background-color='grey lighten-2'
+                    :key='cfg.key'
+                    :label='cfg.value.title'
+                    v-model='cfg.value.value'
+                    :hint='cfg.value.hint ? cfg.value.hint : ""'
+                    persistent-hint
+                    :class='cfg.value.hint ? "mb-2" : ""'
+                    )
+                  v-divider.my-3(v-if='idx < currentRenderer.config.length - 1')
               v-card-chin
                 v-btn(
                   color='primary'
                   )
                   v-icon(left) check
                   span Apply Configuration
+                v-spacer
+                .caption.pr-3.grey--text Module: {{ currentRenderer.key }}
 </template>
 
 <script>
 import _ from 'lodash'
+import { DepGraph } from 'dependency-graph'
+
+import { StatusIndicator } from 'vue-status-indicator'
 
 import renderersQuery from 'gql/admin/rendering/rendering-query-renderers.gql'
 
 export default {
+  components: {
+    StatusIndicator
+  },
   data() {
     return {
-      selectedCore: 0,
-      linkify: true,
-      codeTheme: 'Light',
-      renderers: []
-    }
-  },
-  computed: {
-    cores() {
-      return _.filter(this.renderers, ['dependsOn', null]).map(core => {
-        core.children = _.concat([_.cloneDeep(core)], _.filter(this.renderers, ['dependsOn', core.key]))
-        return core
-      })
+      selectedCore: -1,
+      renderers: [],
+      currentRenderer: {}
     }
   },
   watch: {
     renderers(newValue, oldValue) {
       _.delay(() => {
-        this.selectedCore = _.findIndex(this.cores, ['key', 'markdownCore'])
+        this.selectedCore = _.findIndex(newValue, ['key', 'markdownCore'])
+        this.selectRenderer('markdownCore')
       }, 500)
     }
   },
+  methods: {
+    selectRenderer (key) {
+      this.renderers.map(rdr => {
+        if (_.some(rdr.children, ['key', key])) {
+          this.currentRenderer = _.find(rdr.children, ['key', key])
+        }
+      })
+    }
+  },
   apollo: {
     renderers: {
       query: renderersQuery,
       fetchPolicy: 'network-only',
-      update: (data) => _.cloneDeep(data.rendering.renderers).map(str => ({...str, config: str.config.map(cfg => ({...cfg, value: JSON.parse(cfg.value)}))})),
+      update: (data) => {
+        let renderers = _.cloneDeep(data.rendering.renderers).map(str => ({...str, config: str.config.map(cfg => ({...cfg, value: JSON.parse(cfg.value)}))}))
+        // Build tree
+        const graph = new DepGraph({ circular: true })
+        const rawCores = _.filter(renderers, ['dependsOn', null]).map(core => {
+          core.children = _.concat([_.cloneDeep(core)], _.filter(renderers, ['dependsOn', core.key]))
+          return core
+        })
+        // Build dependency graph
+        rawCores.map(core => { graph.addNode(core.key) })
+        rawCores.map(core => {
+          rawCores.map(coreTarget => {
+            if (core.key !== coreTarget.key) {
+              if (core.output === coreTarget.input) {
+                graph.addDependency(core.key, coreTarget.key)
+              }
+            }
+          })
+        })
+        // Reorder cores in reverse dependency order
+        let orderedCores = []
+        _.reverse(graph.overallOrder()).map(coreKey => {
+          orderedCores.push(_.find(rawCores, ['key', coreKey]))
+        })
+        return orderedCores
+      },
       watchLoading (isLoading) {
         this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-rendering-refresh')
       }

+ 1 - 0
client/components/admin/admin-search.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
     .pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') search
       .headline.primary--text Search Engine
       .subheading.grey--text Configure the search capabilities of your wiki
     v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)

+ 1 - 0
client/components/admin/admin-stats.vue

@@ -2,6 +2,7 @@
   v-container(fluid, fill-height)
     v-layout(row wrap)
       v-flex(xs12)
+        .admin-header-icon: v-icon(size='80', color='grey lighten-2') show_chart
         .headline.primary--text Statistics
         .subheading.grey--text Useful information about your wiki
         .pa-3

+ 1 - 0
client/components/admin/admin-storage.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
     .pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') storage
       .headline.primary--text Storage
       .subheading.grey--text Set backup and sync targets for your content
     v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)

+ 1 - 0
client/components/admin/admin-system.vue

@@ -2,6 +2,7 @@
   v-container(fluid, fill-height, grid-list-lg)
     v-layout(row, wrap)
       v-flex(xs12)
+        .admin-header-icon: v-icon(size='80', color='grey lighten-2') tune
         .headline.primary--text {{ $t('admin:system.title') }}
         .subheading.grey--text {{ $t('admin:system.subtitle') }}
         v-layout.mt-3(row wrap)

+ 1 - 0
client/components/admin/admin-theme.vue

@@ -2,6 +2,7 @@
   v-container(fluid, fill-height, grid-list-lg)
     v-layout(row wrap)
       v-flex(xs12)
+        .admin-header-icon: v-icon(size='80', color='grey lighten-2') palette
         .headline.primary--text Theme
         .subheading.grey--text Modify the look &amp; feel of your wiki
         v-form.pt-3

+ 1 - 0
client/components/admin/admin-users.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   v-card(flat)
     v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') perm_identity
       .headline.blue--text.text--darken-2 Users
       .subheading.grey--text Manage users
     v-card

+ 1 - 0
client/components/admin/admin-utilities.vue

@@ -1,6 +1,7 @@
 <template lang='pug'>
   div
     v-card(flat, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
+      .admin-header-icon: v-icon(size='80', color='grey lighten-2') build
       .headline.primary--text Utilities
       .subheading.grey--text Maintenance and troubleshooting tools
     v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)

+ 1 - 1
client/components/common/nav-header.vue

@@ -19,7 +19,7 @@
     v-menu(open-on-hover, offset-y, bottom, left, min-width='250')
       v-toolbar-side-icon(slot='activator')
         v-icon view_module
-      v-list(dense).py-0
+      v-list(dense, :light='!$vuetify.dark').py-0
         v-list-tile(avatar, href='/')
           v-list-tile-avatar: v-icon(color='blue') home
           v-list-tile-content Home

+ 1 - 1
server/modules/renderer/html-blockquotes/definition.yml

@@ -1,6 +1,6 @@
 key: htmlBlockquotes
 title: Blockquotes
-description: Embed audio players for audio content
+description: Parse blockquotes box styling
 author: requarks.io
 icon: insert_comment
 enabledDefault: true