feat: branch off / create from template

This commit is contained in:
NGPixel 2020-03-01 18:22:26 -05:00 committed by Nicolas Giard
parent e85de92715
commit 13a995133b
7 changed files with 142 additions and 21 deletions

View File

@ -171,7 +171,7 @@ export default {
this.initContentParsed = this.initContent ? Base64.decode(this.initContent) : '' this.initContentParsed = this.initContent ? Base64.decode(this.initContent) : ''
this.$store.set('editor/content', this.initContentParsed) this.$store.set('editor/content', this.initContentParsed)
if (this.mode === 'create') { if (this.mode === 'create' && !this.initEditor) {
_.delay(() => { _.delay(() => {
this.dialogEditorSelector = true this.dialogEditorSelector = true
}, 500) }, 500)

View File

@ -474,7 +474,7 @@ export default {
mounted() { mounted() {
this.$store.set('editor/editorKey', 'markdown') this.$store.set('editor/editorKey', 'markdown')
if (this.mode === 'create') { if (this.mode === 'create' && !this.$store.get('editor/content')) {
this.$store.set('editor/content', '# Header\nYour content here') this.$store.set('editor/content', '# Header\nYour content here')
} }

View File

@ -122,6 +122,8 @@
v-btn(text, @click='isRestoreConfirmDialogShown = false', :disabled='restoreLoading') {{$t('common:actions.cancel')}} v-btn(text, @click='isRestoreConfirmDialogShown = false', :disabled='restoreLoading') {{$t('common:actions.cancel')}}
v-btn(color='orange darken-2', dark, @click='restoreConfirm', :loading='restoreLoading') {{$t('history:restore.confirmButton')}} v-btn(color='orange darken-2', dark, @click='restoreConfirm', :loading='restoreLoading') {{$t('history:restore.confirmButton')}}
page-selector(mode='create', v-model='branchOffOpts.modal', :open-handler='branchOffHandle', :path='branchOffOpts.path', :locale='branchOffOpts.locale')
nav-footer nav-footer
notify notify
search-results search-results
@ -211,6 +213,12 @@ export default {
versionId: 0, versionId: 0,
versionDate: '' versionDate: ''
}, },
branchOffOpts: {
versionId: 0,
locale: 'en',
path: 'new-page',
modal: false
},
isRestoreConfirmDialogShown: false, isRestoreConfirmDialogShown: false,
restoreLoading: false restoreLoading: false
} }
@ -408,7 +416,16 @@ export default {
this.restoreLoading = false this.restoreLoading = false
}, },
branchOff (versionId) { branchOff (versionId) {
const pathParts = this.path.split('/')
this.branchOffOpts = {
versionId: versionId,
locale: this.locale,
path: (pathParts.length > 1) ? _.initial(pathParts).join('/') + `/new-page` : `new-page`,
modal: true
}
},
branchOffHandle ({ locale, path }) {
window.location.assign(`/e/${locale}/${path}?from=${this.pageId},${this.branchOffOpts.versionId}`)
}, },
toggleViewMode () { toggleViewMode () {
this.viewMode = (this.viewMode === 'line-by-line') ? 'side-by-side' : 'line-by-line' this.viewMode = (this.viewMode === 'line-by-line') ? 'side-by-side' : 'line-by-line'

View File

@ -5,6 +5,8 @@ const _ = require('lodash')
/* global WIKI */ /* global WIKI */
const tmplCreateRegex = /^[0-9]+(,[0-9]+)?$/
/** /**
* Robots.txt * Robots.txt
*/ */
@ -89,13 +91,16 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
return res.redirect(`/e/${pageArgs.locale}/${pageArgs.path}`) return res.redirect(`/e/${pageArgs.locale}/${pageArgs.path}`)
} }
// -> Set Editor Lang
_.set(res, 'locals.siteConfig.lang', pageArgs.locale) _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
_.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl') _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
// -> Check for reserved path
if (pageHelper.isReservedPath(pageArgs.path)) { if (pageHelper.isReservedPath(pageArgs.path)) {
return next(new Error('Cannot create this page because it starts with a system reserved path.')) return next(new Error('Cannot create this page because it starts with a system reserved path.'))
} }
// -> Get page data from DB
let page = await WIKI.models.pages.getPageFromDb({ let page = await WIKI.models.pages.getPageFromDb({
path: pageArgs.path, path: pageArgs.path,
locale: pageArgs.locale, locale: pageArgs.locale,
@ -112,11 +117,13 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
} }
if (page) { if (page) {
// -> EDIT MODE
if (!WIKI.auth.checkAccess(req.user, ['write:pages', 'manage:pages'], pageArgs)) { if (!WIKI.auth.checkAccess(req.user, ['write:pages', 'manage:pages'], pageArgs)) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'edit' }) return res.render('unauthorized', { action: 'edit' })
} }
// -> Get page tags
await page.$relatedQuery('tags') await page.$relatedQuery('tags')
page.tags = _.map(page.tags, 'tag') page.tags = _.map(page.tags, 'tag')
@ -126,6 +133,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false' page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
page.content = Buffer.from(page.content).toString('base64') page.content = Buffer.from(page.content).toString('base64')
} else { } else {
// -> CREATE MODE
if (!WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) { if (!WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'create' }) return res.render('unauthorized', { action: 'create' })
@ -137,7 +145,54 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
localeCode: pageArgs.locale, localeCode: pageArgs.locale,
editorKey: null, editorKey: null,
mode: 'create', mode: 'create',
content: null content: null,
title: null,
description: null
}
// -> From Template
if (req.query.from && tmplCreateRegex.test(req.query.from)) {
let tmplPageId = 0
let tmplVersionId = 0
if (req.query.from.indexOf(',')) {
const q = req.query.from.split(',')
tmplPageId = _.toSafeInteger(q[0])
tmplVersionId = _.toSafeInteger(q[1])
} else {
tmplPageId = _.toSafeInteger(req.query.from)
}
if (tmplVersionId > 0) {
// -> From Page Version
const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: tmplPageId, versionId: tmplVersionId })
if (!pageVersion) {
_.set(res.locals, 'pageMeta.title', 'Page Not Found')
return res.status(404).render('notfound', { action: 'template' })
}
if (!WIKI.auth.checkAccess(req.user, ['read:history'], { path: pageVersion.path, locale: pageVersion.locale })) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'sourceVersion' })
}
page.content = Buffer.from(pageVersion.content).toString('base64')
page.editorKey = pageVersion.editor
page.title = pageVersion.title
page.description = pageVersion.description
} else {
// -> From Page Live
const pageOriginal = await WIKI.models.pages.query().findById(tmplPageId)
if (!pageOriginal) {
_.set(res.locals, 'pageMeta.title', 'Page Not Found')
return res.status(404).render('notfound', { action: 'template' })
}
if (!WIKI.auth.checkAccess(req.user, ['read:source'], { path: pageOriginal.path, locale: pageOriginal.locale })) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'source' })
}
page.content = Buffer.from(pageOriginal.content).toString('base64')
page.editorKey = pageOriginal.editorKey
page.title = pageOriginal.title
page.description = pageOriginal.description
}
} }
} }
res.render('editor', { page, injectCode }) res.render('editor', { page, injectCode })
@ -163,6 +218,11 @@ router.get(['/h', '/h/*'], async (req, res, next) => {
isPrivate: false isPrivate: false
}) })
if (!page) {
_.set(res.locals, 'pageMeta.title', 'Page Not Found')
return res.status(404).render('notfound', { action: 'history' })
}
pageArgs.tags = _.get(page, 'tags', []) pageArgs.tags = _.get(page, 'tags', [])
if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) { if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {

View File

@ -15,20 +15,36 @@ module.exports = {
* PAGE HISTORY * PAGE HISTORY
*/ */
async history(obj, args, context, info) { async history(obj, args, context, info) {
return WIKI.models.pageHistory.getHistory({ const page = await WIKI.models.pages.query().select('path', 'localeCode').findById(args.id)
pageId: args.id, if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
offsetPage: args.offsetPage || 0, path: page.path,
offsetSize: args.offsetSize || 100 locale: page.localeCode
}) })) {
return WIKI.models.pageHistory.getHistory({
pageId: args.id,
offsetPage: args.offsetPage || 0,
offsetSize: args.offsetSize || 100
})
} else {
throw new WIKI.Error.PageHistoryForbidden()
}
}, },
/** /**
* PAGE VERSION * PAGE VERSION
*/ */
async version(obj, args, context, info) { async version(obj, args, context, info) {
return WIKI.models.pageHistory.getVersion({ const page = await WIKI.models.pages.query().select('path', 'localeCode').findById(args.pageId)
pageId: args.pageId, if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
versionId: args.versionId path: page.path,
}) locale: page.localeCode
})) {
return WIKI.models.pageHistory.getVersion({
pageId: args.pageId,
versionId: args.versionId
})
} else {
throw new WIKI.Error.PageHistoryForbidden()
}
}, },
/** /**
* SEARCH PAGES * SEARCH PAGES
@ -123,10 +139,17 @@ module.exports = {
async single (obj, args, context, info) { async single (obj, args, context, info) {
let page = await WIKI.models.pages.getPageFromDb(args.id) let page = await WIKI.models.pages.getPageFromDb(args.id)
if (page) { if (page) {
return { if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
...page, path: page.path,
locale: page.localeCode, locale: page.localeCode
editor: page.editorKey })) {
return {
...page,
locale: page.localeCode,
editor: page.editorKey
}
} else {
throw new WIKI.Error.PageViewForbidden()
} }
} else { } else {
throw new WIKI.Error.PageNotFound() throw new WIKI.Error.PageNotFound()

View File

@ -137,6 +137,10 @@ module.exports = {
message: 'Page content cannot be empty.', message: 'Page content cannot be empty.',
code: 6004 code: 6004
}), }),
PageHistoryForbidden: CustomError('PageHistoryForbidden', {
message: 'You are not authorized to view the history of this page.',
code: 6012
}),
PageIllegalPath: CustomError('PageIllegalPath', { PageIllegalPath: CustomError('PageIllegalPath', {
message: 'Page path cannot contains illegal characters.', message: 'Page path cannot contains illegal characters.',
code: 6005 code: 6005
@ -161,6 +165,10 @@ module.exports = {
message: 'You are not authorized to update this page.', message: 'You are not authorized to update this page.',
code: 6009 code: 6009
}), }),
PageViewForbidden: CustomError('PageViewForbidden', {
message: 'You are not authorized to view this page.',
code: 6013
}),
SearchActivationFailed: CustomError('SearchActivationFailed', { SearchActivationFailed: CustomError('SearchActivationFailed', {
message: 'Search Engine activation failed.', message: 'Search Engine activation failed.',
code: 4002 code: 4002

View File

@ -84,6 +84,9 @@ module.exports = class PageHistory extends Model {
this.createdAt = new Date().toISOString() this.createdAt = new Date().toISOString()
} }
/**
* Create Page Version
*/
static async addVersion(opts) { static async addVersion(opts) {
await WIKI.models.pageHistory.query().insert({ await WIKI.models.pageHistory.query().insert({
pageId: opts.id, pageId: opts.id,
@ -105,6 +108,9 @@ module.exports = class PageHistory extends Model {
}) })
} }
/**
* Get Page Version
*/
static async getVersion({ pageId, versionId }) { static async getVersion({ pageId, versionId }) {
const version = await WIKI.models.pageHistory.query() const version = await WIKI.models.pageHistory.query()
.column([ .column([
@ -134,13 +140,20 @@ module.exports = class PageHistory extends Model {
'pageHistory.id': versionId, 'pageHistory.id': versionId,
'pageHistory.pageId': pageId 'pageHistory.pageId': pageId
}).first() }).first()
return { if (version) {
...version, return {
updatedAt: version.createdAt, ...version,
tags: [] updatedAt: version.createdAt || null,
tags: []
}
} else {
return null
} }
} }
/**
* Get History Trail of a Page
*/
static async getHistory({ pageId, offsetPage = 0, offsetSize = 100 }) { static async getHistory({ pageId, offsetPage = 0, offsetSize = 100 }) {
const history = await WIKI.models.pageHistory.query() const history = await WIKI.models.pageHistory.query()
.column([ .column([