Browse Source

feat: delete page

Nicolas Giard 6 years ago
parent
commit
658c105ab5

+ 1 - 0
.gitignore

@@ -29,6 +29,7 @@ npm-debug.log*
 /repo
 /data
 /uploads
+/content
 *.sqlite
 
 # IDE exclude

+ 3 - 1
CHANGELOG.md

@@ -5,8 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ## [2.0.0-beta.12] - 2018-01-27
 ### Added
 - Added Patreon link in Contribute admin page
-- Added Theme Code Injection feature
+- Added Theme Code Injection functionality
 - Added Theme CSS Injection code minification
+- Added Page Delete functionality
 
 ### Fixed
 - Fixed root admin refresh token fail
@@ -14,6 +15,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Changed
 - Moved Insert Media button in Markdown editor
+- Use semver for DB migrations ordering
 
 ## [2.0.0-beta.11] - 2018-01-20
 - First beta release

+ 4 - 1
Makefile

@@ -35,9 +35,12 @@ docker-dev-rebuild: ## Rebuild dockerized dev image
 
 docker-dev-clean: ## Clean DB, redis and data folders
 	rm -rf ./data
-	docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . exec db psql --dbname=wiki --username=postgres --command='DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public'
+	docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . exec db psql --dbname=wiki --username=wikijs --command='DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public'
 	docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . exec redis redis-cli flushall
 
+docker-dev-bash: ## Rebuild dockerized dev image
+	docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . exec wiki bash
+
 docker-build: ## Run assets generation build in docker
 	docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . run wiki yarn build
 	docker-compose -f ./dev/docker/docker-compose.yml -p wiki --project-directory . down

+ 1 - 1
client/components/admin.vue

@@ -203,7 +203,7 @@ export default {
 <style lang='scss'>
 
 .admin {
-  &.theme--light {
+  &.theme--light .application--wrap {
     background-color: lighten(mc('grey', '200'), 2%);
   }
 }

+ 14 - 0
client/components/admin/admin-groups-edit-permissions.vue

@@ -79,6 +79,20 @@ export default {
               restrictedForSystem: false,
               disabled: false
             },
+            {
+              permission: 'read:source',
+              hint: 'Can view pages source, as specified in the Page Rules',
+              warning: false,
+              restrictedForSystem: false,
+              disabled: false
+            },
+            {
+              permission: 'read:history',
+              hint: 'Can view pages history, as specified in the Page Rules',
+              warning: false,
+              restrictedForSystem: false,
+              disabled: false
+            },
             {
               permission: 'read:assets',
               hint: 'Can view / use assets (such as images and files), as specified in the Page Rules',

+ 2 - 0
client/components/admin/admin-groups-edit-rules.vue

@@ -206,6 +206,8 @@ export default {
         { text: 'Create Pages', value: 'write:pages', icon: 'insert_drive_file' },
         { text: 'Edit + Move Pages', value: 'manage:pages', icon: 'insert_drive_file' },
         { text: 'Delete Pages', value: 'delete:pages', icon: 'insert_drive_file' },
+        { text: 'View Pages Source', value: 'read:source', icon: 'code' },
+        { text: 'View Pages History', value: 'read:history', icon: 'restore' },
         { text: 'Read / Use Assets', value: 'read:assets', icon: 'camera' },
         { text: 'Upload Assets', value: 'write:assets', icon: 'camera' },
         { text: 'Edit + Delete Assets', value: 'manage:assets', icon: 'camera' },

+ 7 - 6
client/components/common/nav-header.vue

@@ -131,6 +131,7 @@
             span Login
 
     page-selector(mode='create', v-model='newPageModal', :open-handler='pageNewCreate')
+    page-delete(v-model='deletePageModal', v-if='path && path.length')
 </template>
 
 <script>
@@ -139,6 +140,9 @@ import _ from 'lodash'
 import Cookies from 'js-cookie'
 
 export default {
+  components: {
+    PageDelete: () => import('./page-delete.vue')
+  },
   props: {
     dense: {
       type: Boolean,
@@ -155,7 +159,8 @@ export default {
       searchIsLoading: false,
       searchIsShown: true,
       search: '',
-      newPageModal: false
+      newPageModal: false,
+      deletePageModal: false
     }
   },
   computed: {
@@ -233,11 +238,7 @@ export default {
       })
     },
     pageDelete () {
-      this.$store.commit('showNotification', {
-        style: 'indigo',
-        message: `Coming soon...`,
-        icon: 'directions_boat'
-      })
+      this.deletePageModal = true
     },
     assets () {
       this.$store.commit('showNotification', {

+ 110 - 0
client/components/common/page-delete.vue

@@ -0,0 +1,110 @@
+<template lang='pug'>
+  v-dialog(v-model='isShown', max-width='550', persistent)
+    v-card.wiki-form
+      .dialog-header.is-short.is-red
+        v-icon.mr-2(color='white') highlight_off
+        span Delete Page
+      v-card-text
+        .body-2 Are you sure you want to delete page #[span.red--text.text--darken-2 {{pageTitle}}]?
+        .caption The page can be restored from the administration area.
+        v-chip.mt-3.ml-0.mr-1(label, color='red lighten-4', disabled, small)
+          .caption.red--text.text--darken-2 {{pageLocale.toUpperCase()}}
+        v-chip.mt-3.mx-0(label, color='red lighten-5', disabled, small)
+          span.red--text.text--darken-2 /{{pagePath}}
+      v-card-chin
+        v-spacer
+        v-btn(flat, @click='discard', :disabled='loading') Cancel
+        v-btn(color='red darken-2', @click='deletePage', :loading='loading').white--text Delete
+</template>
+
+<script>
+import _ from 'lodash'
+import { get } from 'vuex-pathify'
+
+import deletePageMutation from 'gql/common/common-pages-mutation-delete.gql'
+
+export default {
+  props: {
+    value: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      loading: false
+    }
+  },
+  computed: {
+    isShown: {
+      get() { return this.value },
+      set(val) { this.$emit('input', val) }
+    },
+    pageTitle: get('page/title'),
+    pagePath: get('page/path'),
+    pageLocale: get('page/locale'),
+    pageId: get('page/id')
+  },
+  watch: {
+    isShown(newValue, oldValue) {
+      if (newValue) {
+        document.body.classList.add('page-deleted-pending')
+      }
+    }
+  },
+  methods: {
+    discard() {
+      document.body.classList.remove('page-deleted-pending')
+      this.isShown = false
+    },
+    async deletePage() {
+      this.loading = true
+      this.$store.commit(`loadingStart`, 'page-delete')
+      this.$nextTick(async () => {
+        try {
+          const resp = await this.$apollo.mutate({
+            mutation: deletePageMutation,
+            variables: {
+              id: this.pageId
+            }
+          })
+          if (_.get(resp, 'data.pages.delete.responseResult.succeeded', false)) {
+            this.isShown = false
+            _.delay(() => {
+              document.body.classList.add('page-deleted')
+              _.delay(() => {
+                window.location.assign('/')
+              }, 1200)
+            }, 400)
+          } else {
+            throw new Error(_.get(resp, 'data.pages.delete.responseResult.message', 'An unexpected error occured.'))
+          }
+        } catch (err) {
+          this.$store.commit('pushGraphError', err)
+        }
+        this.$store.commit(`loadingStop`, 'page-delete')
+        this.loading = false
+      })
+    }
+  }
+}
+</script>
+
+<style lang='scss'>
+  body.page-deleted-pending {
+    .application {
+      background-color: mc('grey', '900');
+    }
+    .application--wrap {
+      transform: translateZ(-5vw) rotateX(2deg);
+      border-radius: 7px;
+      overflow: hidden;
+    }
+  }
+  body.page-deleted {
+    .application--wrap {
+      transform: translateZ(-1000vw) rotateX(60deg);
+      opacity: 0;
+    }
+  }
+</style>

+ 5 - 1
client/components/editor.vue

@@ -14,7 +14,7 @@
           outline
           color='blue'
           @click.native.stop='openPropsModal'
-          :class='{ "is-icon": $vuetify.breakpoint.mdAndDown, "mx-0": !welcomeMode, "ml-0": !welcomeMode }'
+          :class='{ "is-icon": $vuetify.breakpoint.mdAndDown, "mx-0": !welcomeMode, "ml-0": welcomeMode }'
           )
           v-icon(color='blue', :left='$vuetify.breakpoint.lgAndUp') sort_by_alpha
           span.white--text(v-if='$vuetify.breakpoint.lgAndUp') {{ $t('editor:page') }}
@@ -282,6 +282,10 @@ export default {
   .editor {
     background-color: mc('grey', '900') !important;
     min-height: 100vh;
+
+    .application--wrap {
+      background-color: mc('grey', '900');
+    }
   }
 
   .atom-spinner.is-inline {

+ 12 - 0
client/graph/common/common-pages-mutation-delete.gql

@@ -0,0 +1,12 @@
+mutation($id: Int!) {
+  pages {
+    delete(id: $id) {
+      responseResult {
+        succeeded
+        errorCode
+        slug
+        message
+      }
+    }
+  }
+}

+ 13 - 0
client/scss/base/base.scss

@@ -1,6 +1,8 @@
 html {
   box-sizing: border-box;
   height: 100%;
+  perspective: 50vw;
+  background-color: mc('grey', '900');
 }
 *, *:before, *:after {
   box-sizing: inherit;
@@ -19,6 +21,17 @@ html {
   }
 }
 
+.application--wrap {
+  transition: all 1.2s ease;
+  transform-style: preserve-3d;
+  transform-origin: 50% 50%;
+  background-color: #FFF;
+
+  @at-root .theme--dark & {
+    background-color: mc('grey', '900');
+  }
+}
+
 
 @for $i from 0 through 25 {
   .radius-#{$i} {

+ 5 - 0
client/themes/default/components/page.vue

@@ -132,6 +132,10 @@ export default {
     StatusIndicator
   },
   props: {
+    pageId: {
+      type: Number,
+      default: 0
+    },
     locale: {
       type: String,
       default: 'en'
@@ -229,6 +233,7 @@ export default {
     this.$store.commit('page/SET_CREATED_AT', this.createdAt)
     this.$store.commit('page/SET_DESCRIPTION', this.description)
     this.$store.commit('page/SET_IS_PUBLISHED', this.isPublished)
+    this.$store.commit('page/SET_ID', this.pageId)
     this.$store.commit('page/SET_LOCALE', this.locale)
     this.$store.commit('page/SET_PATH', this.path)
     this.$store.commit('page/SET_TAGS', this.tags)

+ 1 - 1
dev/docker/Dockerfile

@@ -5,7 +5,7 @@ FROM node:10-alpine
 LABEL maintainer "requarks.io"
 
 RUN apk update && \
-    apk add bash curl git python make g++ --no-cache && \
+    apk add bash curl git python make g++ nano --no-cache && \
     mkdir -p /wiki
 
 WORKDIR /wiki

+ 0 - 3
dev/examples/docker-compose.yml

@@ -1,6 +1,3 @@
-# -- DEV DOCKER-COMPOSE --
-# -- DO NOT USE IN PRODUCTION! --
-
 version: "3"
 services:
 

+ 100 - 100
package.json

@@ -40,17 +40,16 @@
   },
   "dependencies": {
     "apollo-fetch": "0.7.0",
-    "apollo-server": "2.2.2",
-    "apollo-server-express": "2.2.2",
+    "apollo-server": "2.3.2",
+    "apollo-server-express": "2.3.2",
     "auto-load": "3.0.4",
     "axios": "0.18.0",
-    "js-base64": "2.5.0",
     "bcryptjs-then": "1.0.1",
     "bluebird": "3.5.3",
     "body-parser": "1.18.3",
     "bugsnag": "2.4.3",
-    "bull": "3.5.2",
-    "chalk": "2.4.1",
+    "bull": "3.6.0",
+    "chalk": "2.4.2",
     "cheerio": "1.0.0-rc.2",
     "child-process-promise": "2.2.1",
     "chokidar": "2.0.4",
@@ -60,36 +59,37 @@
     "cookie-parser": "1.4.3",
     "cors": "2.8.5",
     "custom-error-instance": "2.1.1",
-    "dependency-graph": "0.7.2",
-    "diff": "3.5.0",
-    "diff2html": "2.5.0",
+    "dependency-graph": "0.8.0",
+    "diff": "4.0.1",
+    "diff2html": "2.7.0",
     "dotize": "^0.2.0",
     "execa": "1.0.0",
     "express": "4.16.4",
     "express-brute": "1.0.1",
     "express-brute-redis": "0.0.1",
     "express-session": "1.15.6",
-    "file-type": "10.4.0",
-    "filesize": "3.6.1",
-    "follow-redirects": "1.5.9",
+    "file-type": "10.7.1",
+    "filesize": "4.0.0",
+    "follow-redirects": "1.6.1",
     "fs-extra": "7.0.1",
-    "getos": "3.1.0",
-    "graphql": "14.0.2",
+    "getos": "3.1.1",
+    "graphql": "14.1.1",
     "graphql-list-fields": "2.0.2",
     "graphql-subscriptions": "1.0.0",
-    "graphql-tools": "4.0.3",
+    "graphql-tools": "4.0.4",
     "highlight.js": "9.13.1",
-    "i18next": "12.0.0",
-    "i18next-express-middleware": "1.5.0",
+    "i18next": "14.0.1",
+    "i18next-express-middleware": "1.7.1",
     "i18next-localstorage-cache": "1.1.1",
-    "i18next-node-fs-backend": "2.1.0",
-    "image-size": "0.6.3",
-    "ioredis": "4.2.0",
+    "i18next-node-fs-backend": "2.1.1",
+    "image-size": "0.7.1",
+    "ioredis": "4.5.1",
+    "js-base64": "2.5.1",
     "js-binary": "1.2.0",
-    "js-yaml": "3.12.0",
+    "js-yaml": "3.12.1",
     "jsonwebtoken": "8.4.0",
     "klaw": "3.0.0",
-    "knex": "0.15.2",
+    "knex": "0.16.3",
     "lodash": "4.17.11",
     "markdown-it": "8.4.2",
     "markdown-it-abbr": "1.0.4",
@@ -107,18 +107,18 @@
     "markdown-it-task-lists": "2.1.1",
     "mathjax-node": "2.1.1",
     "mime-types": "2.1.21",
-    "moment": "2.22.2",
+    "moment": "2.24.0",
     "moment-timezone": "0.5.23",
-    "mongodb": "3.1.10",
-    "mssql": "4.2.3",
+    "mongodb": "3.1.13",
+    "mssql": "4.3.0",
     "multer": "1.4.1",
     "mysql2": "1.6.4",
-    "nanoid": "2.0.0",
+    "nanoid": "2.0.1",
     "node-2fa": "1.1.2",
     "node-cache": "4.2.0",
-    "nodemailer": "4.7.0",
+    "nodemailer": "5.1.1",
     "oauth2orize": "1.11.0",
-    "objection": "1.4.0",
+    "objection": "1.5.3",
     "ora": "3.0.0",
     "passport": "0.4.0",
     "passport-auth0": "1.1.0",
@@ -126,23 +126,23 @@
     "passport-cas": "0.1.1",
     "passport-discord": "0.1.3",
     "passport-dropbox-oauth2": "1.1.0",
-    "passport-facebook": "2.1.1",
+    "passport-facebook": "3.0.0",
     "passport-github2": "0.1.11",
     "passport-google-oauth20": "1.0.0",
     "passport-jwt": "4.0.0",
-    "passport-ldapauth": "2.1.0",
+    "passport-ldapauth": "2.1.1",
     "passport-local": "1.0.0",
     "passport-oauth2": "1.4.0",
     "passport-okta-oauth": "0.0.1",
     "passport-openidconnect": "0.0.2",
-    "passport-saml": "0.35.0",
+    "passport-saml": "1.0.0",
     "passport-slack": "0.0.7",
     "passport-twitch": "1.0.3",
     "passport-windowslive": "1.0.2",
-    "pem-jwk": "1.5.1",
-    "pg": "7.6.1",
+    "pem-jwk": "2.0.0",
+    "pg": "7.8.0",
     "pg-hstore": "2.3.2",
-    "pm2": "3.2.2",
+    "pm2": "3.2.9",
     "pug": "2.0.3",
     "qr-image": "3.2.0",
     "raven": "2.6.4",
@@ -154,73 +154,73 @@
     "scim-query-filter-parser": "1.1.0",
     "semver": "5.6.0",
     "serve-favicon": "2.5.0",
-    "sqlite3": "4.0.4",
+    "sqlite3": "4.0.6",
     "subscriptions-transport-ws": "0.9.15",
     "twemoji": "11.2.0",
     "uslug": "1.0.4",
     "uuid": "3.3.2",
     "validate.js": "0.12.0",
-    "validator": "10.9.0",
+    "validator": "10.11.0",
     "validator-as-promised": "1.0.2",
     "winston": "3.1.0",
-    "yargs": "12.0.4"
+    "yargs": "12.0.5"
   },
   "devDependencies": {
-    "@babel/cli": "^7.1.5",
-    "@babel/core": "^7.1.6",
-    "@babel/plugin-proposal-class-properties": "^7.1.0",
-    "@babel/plugin-proposal-decorators": "^7.1.6",
-    "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
-    "@babel/plugin-proposal-function-sent": "^7.1.0",
-    "@babel/plugin-proposal-json-strings": "^7.0.0",
-    "@babel/plugin-proposal-numeric-separator": "^7.0.0",
-    "@babel/plugin-proposal-throw-expressions": "^7.0.0",
-    "@babel/plugin-syntax-dynamic-import": "^7.0.0",
-    "@babel/plugin-syntax-import-meta": "^7.0.0",
-    "@babel/polyfill": "^7.0.0",
-    "@babel/preset-env": "^7.1.6",
-    "@panter/vue-i18next": "0.13.0",
-    "animated-number-vue": "0.1.3",
-    "apollo-cache-inmemory": "1.3.10",
-    "apollo-client": "2.4.6",
-    "apollo-link": "1.2.3",
-    "apollo-link-batch-http": "1.2.3",
-    "apollo-link-error": "1.1.1",
-    "apollo-link-http": "1.5.5",
+    "@babel/cli": "^7.2.3",
+    "@babel/core": "^7.2.2",
+    "@babel/plugin-proposal-class-properties": "^7.3.0",
+    "@babel/plugin-proposal-decorators": "^7.3.0",
+    "@babel/plugin-proposal-export-namespace-from": "^7.2.0",
+    "@babel/plugin-proposal-function-sent": "^7.2.0",
+    "@babel/plugin-proposal-json-strings": "^7.2.0",
+    "@babel/plugin-proposal-numeric-separator": "^7.2.0",
+    "@babel/plugin-proposal-throw-expressions": "^7.2.0",
+    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
+    "@babel/plugin-syntax-import-meta": "^7.2.0",
+    "@babel/polyfill": "^7.2.5",
+    "@babel/preset-env": "^7.3.1",
+    "@panter/vue-i18next": "0.15.0",
+    "animated-number-vue": "0.1.4",
+    "apollo-cache-inmemory": "1.4.2",
+    "apollo-client": "2.4.12",
+    "apollo-link": "1.2.6",
+    "apollo-link-batch-http": "1.2.6",
+    "apollo-link-error": "1.1.5",
+    "apollo-link-http": "1.5.9",
     "apollo-link-persisted-queries": "0.2.2",
-    "apollo-link-ws": "1.0.9",
-    "apollo-utilities": "1.0.25",
-    "autoprefixer": "9.3.1",
+    "apollo-link-ws": "1.0.12",
+    "apollo-utilities": "1.1.2",
+    "autoprefixer": "9.4.6",
     "babel-eslint": "10.0.1",
-    "babel-jest": "23.6.0",
-    "babel-loader": "^8.0.4",
+    "babel-jest": "24.0.0",
+    "babel-loader": "^8.0.5",
     "babel-plugin-graphql-tag": "1.6.0",
     "babel-plugin-lodash": "3.3.4",
     "babel-plugin-transform-imports": "1.5.1",
     "brace": "0.11.1",
-    "cache-loader": "1.2.5",
+    "cache-loader": "2.0.1",
     "chart.js": "2.7.3",
-    "clean-webpack-plugin": "1.0.0",
+    "clean-webpack-plugin": "1.0.1",
     "copy-webpack-plugin": "4.6.0",
-    "core-js": "2.5.7",
-    "css-loader": "1.0.1",
-    "cssnano": "4.1.7",
+    "core-js": "2.6.3",
+    "css-loader": "2.1.0",
+    "cssnano": "4.1.8",
     "duplicate-package-checker-webpack-plugin": "3.0.0",
     "epic-spinners": "1.0.4",
-    "eslint": "5.9.0",
+    "eslint": "5.12.1",
     "eslint-config-requarks": "1.0.7",
     "eslint-config-standard": "12.0.0",
-    "eslint-plugin-import": "2.14.0",
-    "eslint-plugin-node": "8.0.0",
+    "eslint-plugin-import": "2.15.0",
+    "eslint-plugin-node": "8.0.1",
     "eslint-plugin-promise": "4.0.1",
     "eslint-plugin-standard": "4.0.0",
-    "eslint-plugin-vue": "4.7.1",
-    "file-loader": "2.0.0",
+    "eslint-plugin-vue": "5.1.0",
+    "file-loader": "3.0.1",
     "filesize.js": "1.0.2",
-    "grapesjs": "0.14.40",
+    "grapesjs": "0.14.50",
     "graphiql": "0.12.0",
     "graphql-persisted-document-loader": "1.0.1",
-    "graphql-tag": "^2.10.0",
+    "graphql-tag": "^2.10.1",
     "graphql-voyager": "1.0.0-rc.26",
     "hammerjs": "2.0.8",
     "html-webpack-plugin": "3.2.0",
@@ -228,8 +228,8 @@
     "i18next-xhr-backend": "1.5.1",
     "ignore-loader": "0.1.2",
     "js-cookie": "2.2.0",
-    "mini-css-extract-plugin": "0.4.4",
-    "node-sass": "4.9.4",
+    "mini-css-extract-plugin": "0.5.0",
+    "node-sass": "4.11.0",
     "offline-plugin": "5.0.6",
     "optimize-css-assets-webpack-plugin": "5.0.1",
     "postcss-cssnext": "3.1.0",
@@ -237,14 +237,14 @@
     "postcss-flexibility": "2.0.0",
     "postcss-import": "12.0.1",
     "postcss-loader": "3.0.0",
-    "postcss-preset-env": "6.4.0",
-    "postcss-selector-parser": "5.0.0-rc.4",
+    "postcss-preset-env": "6.5.0",
+    "postcss-selector-parser": "5.0.0",
     "pug-lint": "2.5.0",
     "pug-loader": "2.4.0",
     "pug-plain-loader": "1.0.0",
-    "raw-loader": "0.5.1",
-    "react": "16.6.3",
-    "react-dom": "16.6.3",
+    "raw-loader": "1.0.0",
+    "react": "16.7.0",
+    "react-dom": "16.7.0",
     "resolve-url-loader": "3.0.0",
     "sass-loader": "7.1.0",
     "sass-resources-loader": "2.0.0",
@@ -255,40 +255,40 @@
     "stylus-loader": "3.0.2",
     "twemoji-awesome": "1.0.6",
     "url-loader": "1.1.2",
-    "vee-validate": "2.1.3",
+    "vee-validate": "2.1.5",
     "velocity-animate": "1.5.2",
-    "viz.js": "2.1.1",
-    "vue": "2.5.17",
-    "vue-apollo": "3.0.0-beta.26",
+    "viz.js": "2.1.2",
+    "vue": "2.5.22",
+    "vue-apollo": "3.0.0-beta.27",
     "vue-chartjs": "3.4.0",
     "vue-clipboards": "1.2.4",
     "vue-codemirror": "4.0.6",
     "vue-hot-reload-api": "2.3.1",
-    "vue-loader": "15.4.2",
-    "vue-material-design-icons": "2.4.0",
+    "vue-loader": "15.6.1",
+    "vue-material-design-icons": "2.6.0",
     "vue-moment": "4.0.0",
-    "vue-router": "3.0.1",
+    "vue-router": "3.0.2",
     "vue-simple-breakpoints": "1.0.3",
     "vue-status-indicator": "1.1.1",
-    "vue-template-compiler": "2.5.17",
+    "vue-template-compiler": "2.5.22",
     "vue-tour": "1.1.0",
     "vue2-animate": "2.1.0",
-    "vuedraggable": "2.16.0",
-    "vuescroll": "4.9.0-beta.14",
-    "vuetify": "1.3.8",
-    "vuex": "3.0.1",
+    "vuedraggable": "2.17.0",
+    "vuescroll": "4.9.6",
+    "vuetify": "1.4.3",
+    "vuex": "3.1.0",
     "vuex-pathify": "1.1.3",
     "vuex-persistedstate": "2.5.4",
-    "webpack": "4.25.1",
+    "webpack": "4.29.0",
     "webpack-bundle-analyzer": "3.0.3",
-    "webpack-cli": "3.1.2",
-    "webpack-dev-middleware": "3.4.0",
+    "webpack-cli": "3.2.1",
+    "webpack-dev-middleware": "3.5.1",
     "webpack-hot-middleware": "2.24.3",
-    "webpack-merge": "4.1.4",
-    "webpack-subresource-integrity": "1.3.0",
+    "webpack-merge": "4.2.1",
+    "webpack-subresource-integrity": "1.3.1",
     "whatwg-fetch": "3.0.0",
-    "write-file-webpack-plugin": "4.4.1",
-    "xterm": "3.8.0",
+    "write-file-webpack-plugin": "4.5.0",
+    "xterm": "3.10.1",
     "zxcvbn": "4.4.2"
   },
   "browserslist": [

+ 6 - 2
server/core/db.js

@@ -5,6 +5,8 @@ const Promise = require('bluebird')
 const Knex = require('knex')
 const Objection = require('objection')
 
+const migrationSource = require('../db/migrator-source')
+
 /* global WIKI */
 
 /**
@@ -89,12 +91,14 @@ module.exports = {
 
     // Set init tasks
 
+    console.info(migrationSource)
+
     let initTasks = {
       // -> Migrate DB Schemas
       async syncSchemas() {
         return self.knex.migrate.latest({
-          directory: path.join(WIKI.SERVERPATH, 'db/migrations'),
-          tableName: 'migrations'
+          tableName: 'migrations',
+          migrationSource
         })
       }
     }

+ 5 - 0
server/core/system.js

@@ -1,6 +1,8 @@
 const _ = require('lodash')
 const cfgHelper = require('../helpers/config')
 const Promise = require('bluebird')
+const fs = require('fs-extra')
+const path = require('path')
 
 /* global WIKI */
 
@@ -22,6 +24,9 @@ module.exports = {
       }
     })
 
+    // Clear content cache
+    fs.emptyDir(path.join(WIKI.ROOTPATH, 'data/cache'))
+
     return this
   },
   /**

+ 15 - 0
server/db/migrations/2.0.0-beta.11.js

@@ -0,0 +1,15 @@
+exports.up = knex => {
+  return knex.schema
+    .table('pageHistory', table => {
+      table.string('action').defaultTo('updated')
+      table.dropForeign('pageId')
+    })
+}
+
+exports.down = knex => {
+  return knex.schema
+    .table('pageHistory', table => {
+      table.dropColumn('action')
+      table.integer('pageId').unsigned().references('id').inTable('pages')
+    })
+}

+ 28 - 0
server/db/migrator-source.js

@@ -0,0 +1,28 @@
+const path = require('path')
+const fs = require('fs-extra')
+const semver = require('semver')
+
+/* global WIKI */
+
+module.exports = {
+  /**
+   * Gets the migration names
+   * @returns Promise<string[]>
+   */
+  async getMigrations() {
+    const absoluteDir = path.join(WIKI.SERVERPATH, 'db/migrations')
+    const migrationFiles = await fs.readdirAsync(absoluteDir)
+    return migrationFiles.sort(semver.compare).map(m => ({
+      file: m,
+      directory: absoluteDir
+    }))
+  },
+
+  getMigrationName(migration) {
+    return migration.file;
+  },
+
+  getMigration(migration) {
+    return require(path.join(WIKI.SERVERPATH, 'db/migrations', migration.file));
+  }
+}

+ 5 - 2
server/graph/resolvers/page.js

@@ -29,8 +29,11 @@ module.exports = {
         page
       }
     },
-    async delete(obj, args) {
-      await WIKI.models.groups.query().deleteById(args.id)
+    async delete(obj, args, context) {
+      await WIKI.models.pages.deletePage({
+        ...args,
+        authorId: context.req.user.id
+      })
       return {
         responseResult: graphHelper.generateSuccess('Page has been deleted.')
       }

+ 4 - 1
server/models/pageHistory.js

@@ -99,7 +99,8 @@ module.exports = class PageHistory extends Model {
       path: opts.path,
       publishEndDate: opts.publishEndDate || '',
       publishStartDate: opts.publishStartDate || '',
-      title: opts.title
+      title: opts.title,
+      action: opts.action || 'updated'
     })
   }
 
@@ -109,6 +110,7 @@ module.exports = class PageHistory extends Model {
         'pageHistory.id',
         'pageHistory.path',
         'pageHistory.authorId',
+        'pageHistory.action',
         'pageHistory.createdAt',
         {
           authorName: 'author.name'
@@ -130,6 +132,7 @@ module.exports = class PageHistory extends Model {
           'pageHistory.id',
           'pageHistory.path',
           'pageHistory.authorId',
+          'pageHistory.action',
           'pageHistory.createdAt',
           {
             authorName: 'author.name'

+ 27 - 1
server/models/pages.js

@@ -96,6 +96,7 @@ module.exports = class Page extends Model {
 
   static get cacheSchema() {
     return new JSBinType({
+      id: 'uint',
       authorId: 'uint',
       authorName: 'string',
       createdAt: 'string',
@@ -150,7 +151,10 @@ module.exports = class Page extends Model {
     if (!ogPage) {
       throw new Error('Invalid Page Id')
     }
-    await WIKI.models.pageHistory.addVersion(ogPage)
+    await WIKI.models.pageHistory.addVersion({
+      ...ogPage,
+      action: 'updated'
+    })
     await WIKI.models.pages.query().patch({
       authorId: opts.authorId,
       content: opts.content,
@@ -174,6 +178,23 @@ module.exports = class Page extends Model {
     return page
   }
 
+  static async deletePage(opts) {
+    const page = await WIKI.models.pages.query().findById(opts.id)
+    if (!page) {
+      throw new Error('Invalid Page Id')
+    }
+    await WIKI.models.pageHistory.addVersion({
+      ...page,
+      action: 'deleted'
+    })
+    await WIKI.models.pages.query().delete().where('id', page.id)
+    await WIKI.models.pages.deletePageFromCache(page)
+    await WIKI.models.storage.pageEvent({
+      event: 'deleted',
+      page
+    })
+  }
+
   static async renderPage(page) {
     const pipeline = await WIKI.models.renderers.getRenderingPipeline(page.contentType)
     WIKI.queue.job.renderPage.add({
@@ -232,6 +253,7 @@ module.exports = class Page extends Model {
   static async savePageToCache(page) {
     const cachePath = path.join(process.cwd(), `data/cache/${page.hash}.bin`)
     await fs.outputFile(cachePath, WIKI.models.pages.cacheSchema.encode({
+      id: page.id,
       authorId: page.authorId,
       authorName: page.authorName,
       createdAt: page.createdAt,
@@ -270,4 +292,8 @@ module.exports = class Page extends Model {
       throw err
     }
   }
+
+  static async deletePageFromCache(page) {
+    return fs.remove(path.join(process.cwd(), `data/cache/${page.hash}.bin`))
+  }
 }

+ 1 - 0
server/views/page.pug

@@ -20,6 +20,7 @@ block body
       :author-id=page.authorId
       :is-published=page.isPublished.toString()
       :toc=page.toc
+      :page-id=page.id
       )
       template(slot='sidebar')
         each navItem in sidebar

File diff suppressed because it is too large
+ 381 - 326
yarn.lock


Some files were not shown because too many files changed in this diff