Przeglądaj źródła

feat: save page

NGPixel 6 lat temu
rodzic
commit
cb84df7a53

+ 0 - 1
.vscode/settings.json

@@ -2,7 +2,6 @@
   "eslint.enable": true,
   "eslint.autoFixOnSave": true,
   "puglint.enable": true,
-  "standard.enable": false,
   "editor.formatOnSave": false,
   "editor.tabSize": 2,
   "eslint.validate": [

+ 46 - 10
client/components/editor.vue

@@ -21,16 +21,27 @@
               :size='60'
               color='#FFF'
               )
-            .subheading Processing
-            .caption.blue--text.text--lighten-3 Please wait...
+            .subheading {{ $t('editor:save.processing') }}
+            .caption.blue--text.text--lighten-3 {{ $t('editor:save.pleaseWait') }}
+
+    v-snackbar(
+      :color='notification.style'
+      bottom,
+      right,
+      multi-line,
+      v-model='notificationState'
+    )
+      .text-xs-left
+        v-icon.mr-3(dark) {{ notification.icon }}
+        span {{ notification.message }}
 </template>
 
 <script>
 import _ from 'lodash'
-import { get } from 'vuex-pathify'
+import { get, sync } from 'vuex-pathify'
 import { AtomSpinner } from 'epic-spinners'
 
-import savePageMutation from 'gql/editor/save.gql'
+import createPageMutation from 'gql/editor/create.gql'
 
 import editorStore from '@/store/editor'
 
@@ -51,7 +62,9 @@ export default {
     }
   },
   computed: {
-    mode: get('editor/mode')
+    mode: get('editor/mode'),
+    notification: get('notification'),
+    notificationState: sync('notification@isActive')
   },
   mounted() {
     if (this.mode === 'create') {
@@ -77,12 +90,35 @@ export default {
     },
     async save() {
       this.showProgressDialog('saving')
-      // const resp = await this.$apollo.mutate({
-      //   mutation: savePageMutation,
-      //   variables: {
+      if (this.$store.get('editor/mode') === 'create') {
+        const resp = await this.$apollo.mutate({
+          mutation: createPageMutation,
+          variables: {
+            description: this.$store.get('editor/description'),
+            editor: 'markdown',
+            locale: this.$store.get('editor/locale'),
+            isPublished: this.$store.get('editor/isPublished'),
+            path: this.$store.get('editor/path'),
+            publishEndDate: this.$store.get('editor/publishEndDate'),
+            publishStartDate: this.$store.get('editor/publishStartDate'),
+            tags: this.$store.get('editor/tags'),
+            title: this.$store.get('editor/title')
+          }
+        })
+        if (_.get(resp, 'data.pages.create.responseResult.succeeded')) {
+          this.$store.commit('showNotification', {
+            message: this.$t('editor:save.success'),
+            style: 'success',
+            icon: 'check'
+          })
+          this.$store.set('editor/mode', 'update')
+        } else {
+
+        }
+      } else {
 
-      //   }
-      // })
+      }
+      this.hideProgressDialog()
     }
   }
 }

+ 39 - 12
client/components/editor/editor-modal-properties.vue

@@ -32,30 +32,46 @@
           counter='255'
           v-model='description'
           )
-        v-text-field(
-          outline
-          background-color='grey lighten-2'
-          label='Path'
-          prefix='/'
-          append-icon='folder'
-          v-model='path'
-          )
       v-divider
-      v-card-text
-        v-subheader.pl-0 Tags
+      v-card-text.grey.lighten-5
+        v-subheader.pl-0 Path &amp; Categorization
+        v-container.pa-0(fluid, grid-list-lg)
+          v-layout(row, wrap)
+            v-flex(xs12, md2)
+              v-select(
+                outline
+                background-color='grey lighten-2'
+                label='Locale'
+                suffix='/'
+                :items='namespaces'
+                v-model='locale'
+                hide-details
+              )
+            v-flex(xs12, md10)
+              v-text-field(
+                outline
+                background-color='grey lighten-2'
+                label='Path'
+                append-icon='folder'
+                v-model='path'
+                hint='Do not include any leading or trailing slashes.'
+                persistent-hint
+                @click:append='showPathSelector'
+                )
         v-combobox(
           background-color='grey lighten-2'
           chips
           deletable-chips
-          hide-details
           label='Tags'
           outline
           multiple
           v-model='tags'
           single-line
+          hint='Use tags to categorize your pages and make them easier to find.'
+          persistent-hint
           )
       v-divider
-      v-card-text.pb-5
+      v-card-text.pb-5.grey.lighten-4
         v-subheader.pl-0 Publishing State
         v-container.pa-0(fluid, grid-list-lg)
           v-layout(row, wrap)
@@ -64,6 +80,8 @@
                 label='Published'
                 v-model='isPublished'
                 color='primary'
+                hint='Unpublished pages can still be seen by users having write permissions.'
+                persistent-hint
                 )
             v-flex(xs12, md4)
               v-dialog(
@@ -165,6 +183,7 @@ export default {
       isShown: false,
       isPublishStartShown: false,
       isPublishEndShown: false,
+      namespaces: ['en'],
       tourSteps: [
         {
           target: '.dialog-header',
@@ -176,6 +195,7 @@ export default {
   computed: {
     title: sync('editor/title'),
     description: sync('editor/description'),
+    locale: sync('editor/locale'),
     tags: sync('editor/tags'),
     path: sync('editor/path'),
     isPublished: sync('editor/isPublished'),
@@ -193,6 +213,13 @@ export default {
     close() {
       this.isShown = false
       this.$parent.$parent.closeModal()
+    },
+    showPathSelector() {
+      this.$store.commit('showNotification', {
+        message: 'Coming soon!',
+        style: 'purple',
+        icon: 'directions_boat'
+      })
     }
   }
 }

+ 15 - 0
client/graph/editor/create.gql

@@ -0,0 +1,15 @@
+mutation ($description: String, $editor: String, $isPublished: Boolean!, $locale: String!, $path: String!, $publishEndDate: Date, $publishStartDate: Date, $tags: [String], $title: String!) {
+  pages {
+    create(description: $description, editor: $editor, isPublished: $isPublished, locale: $locale, path: $path, publishEndDate: $publishEndDate, publishStartDate: $publishStartDate, tags: $tags, title: $title) {
+      responseResult {
+        succeeded
+        errorCode
+        slug
+        message
+      }
+      page {
+        id
+      }
+    }
+  }
+}

+ 0 - 7
client/graph/editor/save.gql

@@ -1,7 +0,0 @@
-mutation {
-  page {
-    create {
-      page
-    }
-  }
-}

+ 14 - 10
client/store/index.js

@@ -1,28 +1,32 @@
 import _ from 'lodash'
 import Vue from 'vue'
 import Vuex from 'vuex'
-import pathify from 'vuex-pathify'
+import pathify from 'vuex-pathify' // eslint-disable-line import/no-duplicates
+import { make } from 'vuex-pathify' // eslint-disable-line import/no-duplicates
 
 Vue.use(Vuex)
 
+const state = {
+  loadingStack: [],
+  notification: {
+    message: '',
+    style: 'primary',
+    icon: 'cached',
+    isActive: false
+  }
+}
+
 export default new Vuex.Store({
   strict: process.env.NODE_ENV !== 'production',
   plugins: [
     pathify.plugin
   ],
-  state: {
-    loadingStack: [],
-    notification: {
-      message: '',
-      style: 'primary',
-      icon: 'cached',
-      isActive: false
-    }
-  },
+  state,
   getters: {
     isLoading: state => { return state.loadingStack.length > 0 }
   },
   mutations: {
+    ...make.mutations(state),
     loadingStart (state, stackName) {
       state.loadingStack = _.union(state.loadingStack, [stackName])
     },

+ 5 - 5
server/db/migrations/2.0.0.js

@@ -150,16 +150,16 @@ exports.up = knex => {
       table.integer('authorId').unsigned().references('id').inTable('users')
     })
     .table('pages', table => {
-      table.string('editor').references('key').inTable('editors')
-      table.string('locale', 2).references('code').inTable('locales')
+      table.string('editorKey').references('key').inTable('editors')
+      table.string('localeCode', 2).references('code').inTable('locales')
       table.integer('authorId').unsigned().references('id').inTable('users')
     })
     .table('users', table => {
-      table.string('provider').references('key').inTable('authentication').notNullable().defaultTo('local')
-      table.string('locale', 2).references('code').inTable('locales').notNullable().defaultTo('en')
+      table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local')
+      table.string('localeCode', 2).references('code').inTable('locales').notNullable().defaultTo('en')
       table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown')
 
-      table.unique(['provider', 'email'])
+      table.unique(['providerKey', 'email'])
     })
 }
 

+ 9 - 1
server/db/models/pages.js

@@ -49,11 +49,19 @@ module.exports = class Page extends Model {
           to: 'users.id'
         }
       },
+      editor: {
+        relation: Model.BelongsToOneRelation,
+        modelClass: require('./editors'),
+        join: {
+          from: 'pages.editorKey',
+          to: 'editors.key'
+        }
+      },
       locale: {
         relation: Model.BelongsToOneRelation,
         modelClass: require('./locales'),
         join: {
-          from: 'users.locale',
+          from: 'pages.localeCode',
           to: 'locales.code'
         }
       }

+ 24 - 2
server/db/models/users.js

@@ -23,13 +23,11 @@ module.exports = class User extends Model {
         id: {type: 'integer'},
         email: {type: 'string', format: 'email'},
         name: {type: 'string', minLength: 1, maxLength: 255},
-        provider: {type: 'string', minLength: 1, maxLength: 255},
         providerId: {type: 'number'},
         password: {type: 'string'},
         role: {type: 'string', enum: ['admin', 'guest', 'user']},
         tfaIsActive: {type: 'boolean', default: false},
         tfaSecret: {type: 'string'},
-        locale: {type: 'string'},
         jobTitle: {type: 'string'},
         location: {type: 'string'},
         pictureUrl: {type: 'string'},
@@ -52,6 +50,30 @@ module.exports = class User extends Model {
           },
           to: 'groups.id'
         }
+      },
+      provider: {
+        relation: Model.BelongsToOneRelation,
+        modelClass: require('./authentication'),
+        join: {
+          from: 'users.providerKey',
+          to: 'authentication.key'
+        }
+      },
+      defaultEditor: {
+        relation: Model.BelongsToOneRelation,
+        modelClass: require('./editors'),
+        join: {
+          from: 'users.editorKey',
+          to: 'editors.key'
+        }
+      },
+      locale: {
+        relation: Model.BelongsToOneRelation,
+        modelClass: require('./locales'),
+        join: {
+          from: 'users.localeCode',
+          to: 'locales.code'
+        }
       }
     }
   }

+ 20 - 11
server/graph/resolvers/page.js

@@ -11,35 +11,44 @@ module.exports = {
   },
   PageQuery: {
     async list(obj, args, context, info) {
-      return WIKI.db.groups.query().select(
-        'groups.*',
-        WIKI.db.groups.relatedQuery('users').count().as('userCount')
+      return WIKI.db.pages.query().select(
+        'pages.*',
+        WIKI.db.pages.relatedQuery('users').count().as('userCount')
       )
     },
     async single(obj, args, context, info) {
-      return WIKI.db.groups.query().findById(args.id)
+      return WIKI.db.pages.query().findById(args.id)
     }
   },
   PageMutation: {
-    async create(obj, args) {
-      const group = await WIKI.db.pages.query().insertAndFetch({
-        name: args.name
+    async create(obj, args, context) {
+      const page = await WIKI.db.pages.query().insertAndFetch({
+        path: args.path,
+        title: args.title,
+        description: args.description,
+        isPrivate: false,
+        isPublished: args.isPublished,
+        publishStartDate: args.publishStartDate,
+        publishEndDate: args.publishEndDate,
+        localeCode: args.locale,
+        editorKey: args.editor,
+        authorId: context.req.user.id
       })
       return {
-        responseResult: graphHelper.generateSuccess('Group created successfully.'),
-        group
+        responseResult: graphHelper.generateSuccess('Page created successfully.'),
+        page
       }
     },
     async delete(obj, args) {
       await WIKI.db.groups.query().deleteById(args.id)
       return {
-        responseResult: graphHelper.generateSuccess('Group has been deleted.')
+        responseResult: graphHelper.generateSuccess('Page has been deleted.')
       }
     },
     async update(obj, args) {
       await WIKI.db.groups.query().patch({ name: args.name }).where('id', args.id)
       return {
-        responseResult: graphHelper.generateSuccess('Group has been updated.')
+        responseResult: graphHelper.generateSuccess('Page has been updated.')
       }
     }
   },

+ 17 - 4
server/graph/schemas/page.graphql

@@ -21,7 +21,10 @@ type PageQuery {
   ): [PageMinimal]
 
   single(
-    id: Int!
+    id: Int
+    path: String
+    locale: String
+    isPrivate: Boolean
   ): Page
 }
 
@@ -32,8 +35,10 @@ type PageQuery {
 type PageMutation {
   create(
     description: String
-    isPublished: Boolean
-    locale: String
+    editor: String
+    isPublished: Boolean!
+    isPrivate: Boolean
+    locale: String!
     path: String!
     publishEndDate: Date
     publishStartDate: Date
@@ -43,7 +48,15 @@ type PageMutation {
 
   update(
     id: Int!
-    name: String!
+    description: String
+    editor: String
+    isPublished: Boolean
+    locale: String
+    path: String
+    publishEndDate: Date
+    publishStartDate: Date
+    tags: [String]
+    title: String
   ): DefaultResponse
 
   delete(

+ 1 - 1
server/modules/authentication/local/authentication.js

@@ -15,7 +15,7 @@ module.exports = {
       }, (uEmail, uPassword, done) => {
         WIKI.db.users.query().findOne({
           email: uEmail,
-          provider: 'local'
+          providerKey: 'local'
         }).then((user) => {
           if (user) {
             return user.verifyPassword(uPassword).then(() => {

+ 2 - 2
server/setup.js

@@ -325,7 +325,7 @@ module.exports = () => {
       // Create root administrator
       WIKI.logger.info('Creating root administrator...')
       await WIKI.db.users.query().delete().where({
-        provider: 'local',
+        providerKey: 'local',
         email: req.body.adminEmail
       })
       await WIKI.db.users.query().insert({
@@ -342,7 +342,7 @@ module.exports = () => {
       // Create Guest account
       WIKI.logger.info('Creating guest account...')
       const guestUsr = await WIKI.db.users.query().findOne({
-        provider: 'local',
+        providerKey: 'local',
         email: 'guest@example.com'
       })
       if (!guestUsr) {