feat: save page

This commit is contained in:
NGPixel 2018-07-22 00:29:39 -04:00
parent c7b675bb1c
commit cb84df7a53
13 changed files with 192 additions and 66 deletions

View File

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

View File

@ -21,16 +21,27 @@
:size='60' :size='60'
color='#FFF' color='#FFF'
) )
.subheading Processing .subheading {{ $t('editor:save.processing') }}
.caption.blue--text.text--lighten-3 Please wait... .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> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { get } from 'vuex-pathify' import { get, sync } from 'vuex-pathify'
import { AtomSpinner } from 'epic-spinners' import { AtomSpinner } from 'epic-spinners'
import savePageMutation from 'gql/editor/save.gql' import createPageMutation from 'gql/editor/create.gql'
import editorStore from '@/store/editor' import editorStore from '@/store/editor'
@ -51,7 +62,9 @@ export default {
} }
}, },
computed: { computed: {
mode: get('editor/mode') mode: get('editor/mode'),
notification: get('notification'),
notificationState: sync('notification@isActive')
}, },
mounted() { mounted() {
if (this.mode === 'create') { if (this.mode === 'create') {
@ -77,12 +90,35 @@ export default {
}, },
async save() { async save() {
this.showProgressDialog('saving') this.showProgressDialog('saving')
// const resp = await this.$apollo.mutate({ if (this.$store.get('editor/mode') === 'create') {
// mutation: savePageMutation, const resp = await this.$apollo.mutate({
// variables: { 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()
} }
} }
} }

View File

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

View File

@ -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
}
}
}
}

View File

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

View File

@ -1,16 +1,12 @@
import _ from 'lodash' import _ from 'lodash'
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' 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) Vue.use(Vuex)
export default new Vuex.Store({ const state = {
strict: process.env.NODE_ENV !== 'production',
plugins: [
pathify.plugin
],
state: {
loadingStack: [], loadingStack: [],
notification: { notification: {
message: '', message: '',
@ -18,11 +14,19 @@ export default new Vuex.Store({
icon: 'cached', icon: 'cached',
isActive: false isActive: false
} }
}, }
export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
plugins: [
pathify.plugin
],
state,
getters: { getters: {
isLoading: state => { return state.loadingStack.length > 0 } isLoading: state => { return state.loadingStack.length > 0 }
}, },
mutations: { mutations: {
...make.mutations(state),
loadingStart (state, stackName) { loadingStart (state, stackName) {
state.loadingStack = _.union(state.loadingStack, [stackName]) state.loadingStack = _.union(state.loadingStack, [stackName])
}, },

View File

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

View File

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

View File

@ -23,13 +23,11 @@ module.exports = class User extends Model {
id: {type: 'integer'}, id: {type: 'integer'},
email: {type: 'string', format: 'email'}, email: {type: 'string', format: 'email'},
name: {type: 'string', minLength: 1, maxLength: 255}, name: {type: 'string', minLength: 1, maxLength: 255},
provider: {type: 'string', minLength: 1, maxLength: 255},
providerId: {type: 'number'}, providerId: {type: 'number'},
password: {type: 'string'}, password: {type: 'string'},
role: {type: 'string', enum: ['admin', 'guest', 'user']}, role: {type: 'string', enum: ['admin', 'guest', 'user']},
tfaIsActive: {type: 'boolean', default: false}, tfaIsActive: {type: 'boolean', default: false},
tfaSecret: {type: 'string'}, tfaSecret: {type: 'string'},
locale: {type: 'string'},
jobTitle: {type: 'string'}, jobTitle: {type: 'string'},
location: {type: 'string'}, location: {type: 'string'},
pictureUrl: {type: 'string'}, pictureUrl: {type: 'string'},
@ -52,6 +50,30 @@ module.exports = class User extends Model {
}, },
to: 'groups.id' 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'
}
} }
} }
} }

View File

@ -11,35 +11,44 @@ module.exports = {
}, },
PageQuery: { PageQuery: {
async list(obj, args, context, info) { async list(obj, args, context, info) {
return WIKI.db.groups.query().select( return WIKI.db.pages.query().select(
'groups.*', 'pages.*',
WIKI.db.groups.relatedQuery('users').count().as('userCount') WIKI.db.pages.relatedQuery('users').count().as('userCount')
) )
}, },
async single(obj, args, context, info) { async single(obj, args, context, info) {
return WIKI.db.groups.query().findById(args.id) return WIKI.db.pages.query().findById(args.id)
} }
}, },
PageMutation: { PageMutation: {
async create(obj, args) { async create(obj, args, context) {
const group = await WIKI.db.pages.query().insertAndFetch({ const page = await WIKI.db.pages.query().insertAndFetch({
name: args.name 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 { return {
responseResult: graphHelper.generateSuccess('Group created successfully.'), responseResult: graphHelper.generateSuccess('Page created successfully.'),
group page
} }
}, },
async delete(obj, args) { async delete(obj, args) {
await WIKI.db.groups.query().deleteById(args.id) await WIKI.db.groups.query().deleteById(args.id)
return { return {
responseResult: graphHelper.generateSuccess('Group has been deleted.') responseResult: graphHelper.generateSuccess('Page has been deleted.')
} }
}, },
async update(obj, args) { async update(obj, args) {
await WIKI.db.groups.query().patch({ name: args.name }).where('id', args.id) await WIKI.db.groups.query().patch({ name: args.name }).where('id', args.id)
return { return {
responseResult: graphHelper.generateSuccess('Group has been updated.') responseResult: graphHelper.generateSuccess('Page has been updated.')
} }
} }
}, },

View File

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

View File

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

View File

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