瀏覽代碼

feat: branch off / create from template

NGPixel 5 年之前
父節點
當前提交
13a995133b

+ 1 - 1
client/components/editor.vue

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

+ 1 - 1
client/components/editor/editor-markdown.vue

@@ -474,7 +474,7 @@ export default {
   mounted() {
     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')
     }
 

+ 18 - 1
client/components/history.vue

@@ -122,6 +122,8 @@
           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')}}
 
+    page-selector(mode='create', v-model='branchOffOpts.modal', :open-handler='branchOffHandle', :path='branchOffOpts.path', :locale='branchOffOpts.locale')
+
     nav-footer
     notify
     search-results
@@ -211,6 +213,12 @@ export default {
         versionId: 0,
         versionDate: ''
       },
+      branchOffOpts: {
+        versionId: 0,
+        locale: 'en',
+        path: 'new-page',
+        modal: false
+      },
       isRestoreConfirmDialogShown: false,
       restoreLoading: false
     }
@@ -408,7 +416,16 @@ export default {
       this.restoreLoading = false
     },
     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 () {
       this.viewMode = (this.viewMode === 'line-by-line') ? 'side-by-side' : 'line-by-line'

+ 61 - 1
server/controllers/common.js

@@ -5,6 +5,8 @@ const _ = require('lodash')
 
 /* global WIKI */
 
+const tmplCreateRegex = /^[0-9]+(,[0-9]+)?$/
+
 /**
  * Robots.txt
  */
@@ -89,13 +91,16 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
     return res.redirect(`/e/${pageArgs.locale}/${pageArgs.path}`)
   }
 
+  // -> Set Editor Lang
   _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
   _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
 
+  // -> Check for reserved path
   if (pageHelper.isReservedPath(pageArgs.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({
     path: pageArgs.path,
     locale: pageArgs.locale,
@@ -112,11 +117,13 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
   }
 
   if (page) {
+    // -> EDIT MODE
     if (!WIKI.auth.checkAccess(req.user, ['write:pages', 'manage:pages'], pageArgs)) {
       _.set(res.locals, 'pageMeta.title', 'Unauthorized')
       return res.render('unauthorized', { action: 'edit' })
     }
 
+    // -> Get page tags
     await page.$relatedQuery('tags')
     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.content = Buffer.from(page.content).toString('base64')
   } else {
+    // -> CREATE MODE
     if (!WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) {
       _.set(res.locals, 'pageMeta.title', 'Unauthorized')
       return res.render('unauthorized', { action: 'create' })
@@ -137,7 +145,54 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
       localeCode: pageArgs.locale,
       editorKey: null,
       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 })
@@ -163,6 +218,11 @@ router.get(['/h', '/h/*'], async (req, res, next) => {
     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', [])
 
   if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {

+ 36 - 13
server/graph/resolvers/page.js

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

+ 8 - 0
server/helpers/error.js

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

+ 17 - 4
server/models/pageHistory.js

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