feat: save page - updated + page history

This commit is contained in:
NGPixel 2018-07-22 21:13:01 -04:00
parent 076e923d48
commit 803d86ff63
11 changed files with 289 additions and 53 deletions

View File

@ -6,7 +6,9 @@
v-icon(color='green', left) check v-icon(color='green', left) check
span.white--text(v-if='mode === "create"') {{ $t('common:actions.create') }} span.white--text(v-if='mode === "create"') {{ $t('common:actions.create') }}
span.white--text(v-else) {{ $t('common:actions.save') }} span.white--text(v-else) {{ $t('common:actions.save') }}
v-btn.is-icon(outline, color='red').mx-0: v-icon(color='red') close v-btn(outline, color='red').mx-0
v-icon(color='red', left) close
span.white--text {{ $t('common:actions.discard') }}
v-btn(outline, color='blue', @click.native.stop='openModal(`properties`)', dark) v-btn(outline, color='blue', @click.native.stop='openModal(`properties`)', dark)
v-icon(left) sort_by_alpha v-icon(left) sort_by_alpha
span.white--text {{ $t('editor:page') }} span.white--text {{ $t('editor:page') }}
@ -42,6 +44,7 @@ import { get, sync } from 'vuex-pathify'
import { AtomSpinner } from 'epic-spinners' import { AtomSpinner } from 'epic-spinners'
import createPageMutation from 'gql/editor/create.gql' import createPageMutation from 'gql/editor/create.gql'
import updatePageMutation from 'gql/editor/update.gql'
import editorStore from '@/store/editor' import editorStore from '@/store/editor'
@ -90,35 +93,79 @@ export default {
}, },
async save() { async save() {
this.showProgressDialog('saving') this.showProgressDialog('saving')
if (this.$store.get('editor/mode') === 'create') { try {
const resp = await this.$apollo.mutate({ if (this.$store.get('editor/mode') === 'create') {
mutation: createPageMutation, // --------------------------------------------
variables: { // -> CREATE PAGE
content: this.$store.get('editor/content'), // --------------------------------------------
description: this.$store.get('editor/description'),
editor: 'markdown', let resp = await this.$apollo.mutate({
locale: this.$store.get('editor/locale'), mutation: createPageMutation,
isPrivate: false, variables: {
isPublished: this.$store.get('editor/isPublished'), content: this.$store.get('editor/content'),
path: this.$store.get('editor/path'), description: this.$store.get('editor/description'),
publishEndDate: this.$store.get('editor/publishEndDate'), editor: 'markdown',
publishStartDate: this.$store.get('editor/publishStartDate'), locale: this.$store.get('editor/locale'),
tags: this.$store.get('editor/tags'), isPrivate: false,
title: this.$store.get('editor/title') isPublished: this.$store.get('editor/isPublished'),
} path: this.$store.get('editor/path'),
}) publishEndDate: this.$store.get('editor/publishEndDate'),
if (_.get(resp, 'data.pages.create.responseResult.succeeded')) { publishStartDate: this.$store.get('editor/publishStartDate'),
this.$store.commit('showNotification', { tags: this.$store.get('editor/tags'),
message: this.$t('editor:save.success'), title: this.$store.get('editor/title')
style: 'success', }
icon: 'check'
}) })
this.$store.set('editor/mode', 'update') resp = _.get(resp, 'data.pages.create', {})
if (_.get(resp, 'responseResult.succeeded')) {
this.$store.commit('showNotification', {
message: this.$t('editor:save.success'),
style: 'success',
icon: 'check'
})
this.$store.set('editor/id', _.get(resp, 'page.id'))
this.$store.set('editor/mode', 'update')
} else {
throw new Error(_.get(resp, 'responseResult.message'))
}
} else { } else {
// --------------------------------------------
// -> UPDATE EXISTING PAGE
// --------------------------------------------
let resp = await this.$apollo.mutate({
mutation: updatePageMutation,
variables: {
id: this.$store.get('editor/id'),
content: this.$store.get('editor/content'),
description: this.$store.get('editor/description'),
editor: 'markdown',
locale: this.$store.get('editor/locale'),
isPrivate: false,
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')
}
})
resp = _.get(resp, 'data.pages.update', {})
if (_.get(resp, 'responseResult.succeeded')) {
this.$store.commit('showNotification', {
message: this.$t('editor:save.success'),
style: 'success',
icon: 'check'
})
} else {
throw new Error(_.get(resp, 'responseResult.message'))
}
} }
} else { } catch (err) {
this.$store.commit('showNotification', {
message: err.message,
style: 'error',
icon: 'warning'
})
} }
this.hideProgressDialog() this.hideProgressDialog()
} }

View File

@ -7,13 +7,24 @@
v-icon(color='white') sort_by_alpha v-icon(color='white') sort_by_alpha
.subheading.white--text.ml-2 Page Properties .subheading.white--text.ml-2 Page Properties
v-spacer v-spacer
v-btn( v-btn.mx-0(
outline outline
dark dark
@click.native='close' @click.native='close'
) )
v-icon(left) close v-icon(left) check
span Close span {{ $t('common:actions.ok') }}
v-menu
v-btn.is-icon(
slot='activator'
outline
dark
)
v-icon more_horiz
v-list
v-list-tile
v-list-tile-avatar: v-icon delete
v-list-tile-title Delete Page
v-card(tile) v-card(tile)
v-card-text v-card-text
v-subheader.pl-0 Page Info v-subheader.pl-0 Page Info

View File

@ -50,7 +50,7 @@
.body-1.pt-3 .body-1.pt-3
svg.icons.is-18.is-outlined.has-right-pad.is-text: use(xlink:href='#nc-cd-reader') svg.icons.is-18.is-outlined.has-right-pad.is-text: use(xlink:href='#nc-cd-reader')
span You are about to install Wiki.js #[strong {{wikiVersion}}]. span You are about to install Wiki.js #[strong {{wikiVersion}}].
v-divider v-divider.mt-3
v-form v-form
v-checkbox( v-checkbox(
color='primary', color='primary',
@ -67,7 +67,7 @@
hint='Check this box if you are upgrading from Wiki.js 1.x and wish to migrate your existing data.' hint='Check this box if you are upgrading from Wiki.js 1.x and wish to migrate your existing data.'
) )
v-divider v-divider
.text-xs-center .pt-3.text-xs-center
v-btn(color='primary', @click='proceedToSyscheck', :disabled='loading') Start v-btn(color='primary', @click='proceedToSyscheck', :disabled='loading') Start
//- ============================================== //- ==============================================
@ -94,7 +94,7 @@
v-list-tile-title {{rs.title}} v-list-tile-title {{rs.title}}
v-list-tile-sub-title {{rs.subtitle}} v-list-tile-sub-title {{rs.subtitle}}
v-divider v-divider
.text-xs-center .pt-3.text-xs-center
v-btn(@click='proceedToWelcome', :disabled='loading') Back v-btn(@click='proceedToWelcome', :disabled='loading') Back
v-btn(color='primary', @click='proceedToSyscheck', v-if='!loading && !syscheck.ok') Check Again v-btn(color='primary', @click='proceedToSyscheck', v-if='!loading && !syscheck.ok') Check Again
v-btn(color='red', dark, @click='proceedToGeneral', v-if='!loading && !syscheck.ok') Continue Anyway v-btn(color='red', dark, @click='proceedToGeneral', v-if='!loading && !syscheck.ok') Continue Anyway
@ -113,6 +113,8 @@
v-layout(row, wrap) v-layout(row, wrap)
v-flex(xs12, sm6).pr-3 v-flex(xs12, sm6).pr-3
v-text-field( v-text-field(
outline
background-color='grey lighten-2'
v-model='conf.title', v-model='conf.title',
label='Site Title', label='Site Title',
:counter='255', :counter='255',
@ -126,6 +128,8 @@
) )
v-flex.pr-3(xs12, sm6) v-flex.pr-3(xs12, sm6)
v-text-field( v-text-field(
outline
background-color='grey lighten-2'
v-model='conf.port', v-model='conf.port',
label='Server Port', label='Server Port',
persistent-hint, persistent-hint,
@ -139,6 +143,8 @@
v-layout(row, wrap).mt-3 v-layout(row, wrap).mt-3
v-flex(xs12, sm6).pr-3 v-flex(xs12, sm6).pr-3
v-text-field( v-text-field(
outline
background-color='grey lighten-2'
v-model='conf.pathContent', v-model='conf.pathContent',
label='Content Data Path', label='Content Data Path',
persistent-hint, persistent-hint,
@ -151,6 +157,8 @@
) )
v-flex(xs12, sm6) v-flex(xs12, sm6)
v-text-field( v-text-field(
outline
background-color='grey lighten-2'
v-model='conf.pathData', v-model='conf.pathData',
label='Temporary Data Path', label='Temporary Data Path',
persistent-hint, persistent-hint,
@ -170,15 +178,8 @@
persistent-hint, persistent-hint,
hint='Should the site be accessible (read only) without login.' hint='Should the site be accessible (read only) without login.'
) )
v-checkbox.mt-2(
color='primary',
v-model='conf.selfRegister',
label='Allow Self-Registration',
persistent-hint,
hint='Can users create their own account to gain access?'
)
v-divider v-divider
.text-xs-center .pt-3.text-xs-center
v-btn(@click='proceedToSyscheck', :disabled='loading') Back v-btn(@click='proceedToSyscheck', :disabled='loading') Back
v-btn(color='primary', @click='proceedToAdmin', :disabled='loading') Continue v-btn(color='primary', @click='proceedToAdmin', :disabled='loading') Continue
@ -196,7 +197,8 @@
v-layout(row, wrap) v-layout(row, wrap)
v-flex(xs12) v-flex(xs12)
v-text-field( v-text-field(
autofocus outline
background-color='grey lighten-2'
v-model='conf.adminEmail', v-model='conf.adminEmail',
label='Administrator Email', label='Administrator Email',
hint='The email address of the administrator account', hint='The email address of the administrator account',
@ -208,6 +210,8 @@
) )
v-flex.pr-3(xs6) v-flex.pr-3(xs6)
v-text-field( v-text-field(
outline
background-color='grey lighten-2'
ref='adminPassword', ref='adminPassword',
counter='255' counter='255'
v-model='conf.adminPassword', v-model='conf.adminPassword',
@ -224,6 +228,8 @@
) )
v-flex(xs6) v-flex(xs6)
v-text-field( v-text-field(
outline
background-color='grey lighten-2'
ref='adminPasswordConfirm', ref='adminPasswordConfirm',
counter='255' counter='255'
v-model='conf.adminPasswordConfirm', v-model='conf.adminPasswordConfirm',
@ -238,7 +244,7 @@
data-vv-scope='admin', data-vv-scope='admin',
:error-messages='errors.collect(`adminPasswordConfirm`)' :error-messages='errors.collect(`adminPasswordConfirm`)'
) )
.text-xs-center .pt-3.text-xs-center
v-btn(@click='proceedToGeneral', :disabled='loading') Back v-btn(@click='proceedToGeneral', :disabled='loading') Back
v-btn(color='primary', @click='proceedToUpgrade', :disabled='loading') Continue v-btn(color='primary', @click='proceedToUpgrade', :disabled='loading') Continue
@ -256,6 +262,8 @@
v-layout(row) v-layout(row)
v-flex(xs12) v-flex(xs12)
v-text-field( v-text-field(
outline
background-color='grey lighten-2'
v-model='conf.upgMongo', v-model='conf.upgMongo',
placeholder='mongodb://', placeholder='mongodb://',
label='Connection String to Wiki.js 1.x MongoDB database', label='Connection String to Wiki.js 1.x MongoDB database',
@ -267,7 +275,7 @@
data-vv-scope='upgrade', data-vv-scope='upgrade',
:error-messages='errors.collect(`upgMongo`)' :error-messages='errors.collect(`upgMongo`)'
) )
.text-xs-center .pt-3.text-xs-center
v-btn(@click='proceedToAdmin', :disabled='loading') Back v-btn(@click='proceedToAdmin', :disabled='loading') Back
v-btn(color='primary', @click='proceedToFinal', :disabled='loading') Continue v-btn(color='primary', @click='proceedToFinal', :disabled='loading') Continue
@ -290,7 +298,7 @@
v-alert(type='success', outline, :value='!loading && final.ok') Wiki.js was configured successfully and is now ready for use. v-alert(type='success', outline, :value='!loading && final.ok') Wiki.js was configured successfully and is now ready for use.
v-alert(type='error', outline, :value='!loading && !final.ok') {{ final.error }} v-alert(type='error', outline, :value='!loading && !final.ok') {{ final.error }}
v-divider v-divider
.text-xs-center .pt-3.text-xs-center
v-btn(@click='!conf.upgrade ? proceedToAdmin() : proceedToUpgrade()', :disabled='loading') Back v-btn(@click='!conf.upgrade ? proceedToAdmin() : proceedToUpgrade()', :disabled='loading') Back
v-btn(color='primary', @click='proceedToFinal', v-if='!loading && !final.ok') Try Again v-btn(color='primary', @click='proceedToFinal', v-if='!loading && !final.ok') Try Again
v-btn(color='success', @click='finish', v-if='loading || final.ok', :disabled='loading') Continue v-btn(color='success', @click='finish', v-if='loading || final.ok', :disabled='loading') Continue
@ -342,7 +350,6 @@ export default {
pathContent: './content', pathContent: './content',
port: siteConfig.port || 80, port: siteConfig.port || 80,
public: (siteConfig.public === true), public: (siteConfig.public === true),
selfRegister: (siteConfig.selfRegister === true),
telemetry: true, telemetry: true,
title: siteConfig.title || 'Wiki', title: siteConfig.title || 'Wiki',
upgrade: false, upgrade: false,

View File

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

View File

@ -1,6 +1,7 @@
import { make } from 'vuex-pathify' import { make } from 'vuex-pathify'
const state = { const state = {
id: 0,
content: '', content: '',
description: '', description: '',
isPublished: true, isPublished: true,

View File

@ -68,6 +68,19 @@ exports.up = knex => {
table.string('createdAt').notNullable() table.string('createdAt').notNullable()
table.string('updatedAt').notNullable() table.string('updatedAt').notNullable()
}) })
// PAGE HISTORY ------------------------
.createTable('pageHistory', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('publishStartDate')
table.string('publishEndDate')
table.text('content')
table.string('createdAt').notNullable()
})
// PAGES ------------------------------- // PAGES -------------------------------
.createTable('pages', table => { .createTable('pages', table => {
table.increments('id').primary() table.increments('id').primary()
@ -126,6 +139,12 @@ exports.up = knex => {
// ===================================== // =====================================
// RELATION TABLES // RELATION TABLES
// ===================================== // =====================================
// PAGE HISTORY TAGS ---------------------------
.createTable('pageHistoryTags', table => {
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
})
// PAGE TAGS --------------------------- // PAGE TAGS ---------------------------
.createTable('pageTags', table => { .createTable('pageTags', table => {
table.increments('id').primary() table.increments('id').primary()
@ -149,10 +168,17 @@ exports.up = knex => {
table.integer('pageId').unsigned().references('id').inTable('pages') table.integer('pageId').unsigned().references('id').inTable('pages')
table.integer('authorId').unsigned().references('id').inTable('users') table.integer('authorId').unsigned().references('id').inTable('users')
}) })
.table('pageHistory', table => {
table.integer('pageId').unsigned().references('id').inTable('pages')
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 2).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
})
.table('pages', table => { .table('pages', table => {
table.string('editorKey').references('key').inTable('editors') table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 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.integer('creatorId').unsigned().references('id').inTable('users')
}) })
.table('users', table => { .table('users', table => {
table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local') table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local')

View File

@ -0,0 +1,100 @@
const Model = require('objection').Model
/* global WIKI */
/**
* Page History model
*/
module.exports = class PageHistory extends Model {
static get tableName() { return 'pageHistory' }
static get jsonSchema () {
return {
type: 'object',
required: ['path', 'title'],
properties: {
id: {type: 'integer'},
path: {type: 'string'},
title: {type: 'string'},
description: {type: 'string'},
isPublished: {type: 'boolean'},
publishStartDate: {type: 'string'},
publishEndDate: {type: 'string'},
content: {type: 'string'},
createdAt: {type: 'string'}
}
}
}
static get relationMappings() {
return {
tags: {
relation: Model.ManyToManyRelation,
modelClass: require('./tags'),
join: {
from: 'pageHistory.id',
through: {
from: 'pageHistoryTags.pageId',
to: 'pageHistoryTags.tagId'
},
to: 'tags.id'
}
},
page: {
relation: Model.BelongsToOneRelation,
modelClass: require('./pages'),
join: {
from: 'pageHistory.pageId',
to: 'pages.id'
}
},
author: {
relation: Model.BelongsToOneRelation,
modelClass: require('./users'),
join: {
from: 'pageHistory.authorId',
to: 'users.id'
}
},
editor: {
relation: Model.BelongsToOneRelation,
modelClass: require('./editors'),
join: {
from: 'pageHistory.editorKey',
to: 'editors.key'
}
},
locale: {
relation: Model.BelongsToOneRelation,
modelClass: require('./locales'),
join: {
from: 'pageHistory.localeCode',
to: 'locales.code'
}
}
}
}
$beforeInsert() {
this.createdAt = new Date().toISOString()
}
static async addVersion(opts) {
await WIKI.db.pageHistory.query().insert({
pageId: opts.id,
authorId: opts.authorId,
content: opts.content,
description: opts.description,
editorKey: opts.editorKey,
isPrivate: opts.isPrivate,
isPublished: opts.isPublished,
localeCode: opts.localeCode,
path: opts.path,
publishEndDate: opts.publishEndDate,
publishStartDate: opts.publishStartDate,
title: opts.title
})
}
}

View File

@ -51,6 +51,14 @@ module.exports = class Page extends Model {
to: 'users.id' to: 'users.id'
} }
}, },
creator: {
relation: Model.BelongsToOneRelation,
modelClass: require('./users'),
join: {
from: 'pages.creatorId',
to: 'users.id'
}
},
editor: { editor: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: require('./editors'), modelClass: require('./editors'),
@ -82,6 +90,7 @@ module.exports = class Page extends Model {
const page = await WIKI.db.pages.query().insertAndFetch({ const page = await WIKI.db.pages.query().insertAndFetch({
authorId: opts.authorId, authorId: opts.authorId,
content: opts.content, content: opts.content,
creatorId: opts.authorId,
description: opts.description, description: opts.description,
editorKey: opts.editor, editorKey: opts.editor,
isPrivate: opts.isPrivate, isPrivate: opts.isPrivate,
@ -92,7 +101,26 @@ module.exports = class Page extends Model {
publishStartDate: opts.publishStartDate, publishStartDate: opts.publishStartDate,
title: opts.title title: opts.title
}) })
await WIKI.db.storage.createPage(page) await WIKI.db.storage.pageEvent({
event: 'created',
page
})
return page
}
static async updatePage(opts) {
const ogPage = await WIKI.db.pages.query().findById(opts.id)
if (!ogPage) {
throw new Error('Invalid Page Id')
}
await WIKI.db.pageHistory.addVersion(ogPage)
const page = await WIKI.db.pages.query().patchAndFetch({
title: opts.title
}).where('id', opts.id)
await WIKI.db.storage.pageEvent({
event: 'updated',
page
})
return page return page
} }
} }

View File

@ -87,12 +87,12 @@ module.exports = class Storage extends Model {
} }
} }
static async createPage(page) { static async pageEvent(event, page) {
const targets = await WIKI.db.storage.query().where('isEnabled', true) const targets = await WIKI.db.storage.query().where('isEnabled', true)
if (targets && targets.length > 0) { if (targets && targets.length > 0) {
_.forEach(targets, target => { _.forEach(targets, target => {
WIKI.queue.job.syncStorage.add({ WIKI.queue.job.syncStorage.add({
event: 'created', event,
target, target,
page page
}, { }, {

View File

@ -24,7 +24,6 @@ module.exports = {
async create(obj, args, context) { async create(obj, args, context) {
const page = await WIKI.db.pages.createPage({ const page = await WIKI.db.pages.createPage({
...args, ...args,
isPrivate: false,
authorId: context.req.user.id authorId: context.req.user.id
}) })
return { return {
@ -38,10 +37,14 @@ module.exports = {
responseResult: graphHelper.generateSuccess('Page has been deleted.') responseResult: graphHelper.generateSuccess('Page has been deleted.')
} }
}, },
async update(obj, args) { async update(obj, args, context) {
await WIKI.db.groups.query().patch({ name: args.name }).where('id', args.id) const page = await WIKI.db.pages.updatePage({
...args,
authorId: context.req.user.id
})
return { return {
responseResult: graphHelper.generateSuccess('Page has been updated.') responseResult: graphHelper.generateSuccess('Page has been updated.'),
page
} }
} }
}, },

View File

@ -52,6 +52,7 @@ type PageMutation {
content: String content: String
description: String description: String
editor: String editor: String
isPrivate: Boolean
isPublished: Boolean isPublished: Boolean
locale: String locale: String
path: String path: String
@ -59,7 +60,7 @@ type PageMutation {
publishStartDate: Date publishStartDate: Date
tags: [String] tags: [String]
title: String title: String
): DefaultResponse ): PageResponse
delete( delete(
id: Int! id: Int!