Prechádzať zdrojové kódy

feat: TOC, scroll to header, page UI improvements

Nicolas Giard 6 rokov pred
rodič
commit
3abc254685

+ 0 - 2
client/client-app.js

@@ -21,7 +21,6 @@ import Hammer from 'hammerjs'
 import moment from 'moment'
 import moment from 'moment'
 import VueMoment from 'vue-moment'
 import VueMoment from 'vue-moment'
 import VueTour from 'vue-tour'
 import VueTour from 'vue-tour'
-import VueTreeNavigation from 'vue-tree-navigation'
 import store from './store'
 import store from './store'
 import Cookies from 'js-cookie'
 import Cookies from 'js-cookie'
 
 
@@ -149,7 +148,6 @@ Vue.use(VeeValidate, { events: '' })
 Vue.use(Vuetify)
 Vue.use(Vuetify)
 Vue.use(VueMoment, { moment })
 Vue.use(VueMoment, { moment })
 Vue.use(VueTour)
 Vue.use(VueTour)
-Vue.use(VueTreeNavigation)
 
 
 Vue.prototype.Velocity = Velocity
 Vue.prototype.Velocity = Velocity
 
 

+ 3 - 2
client/components/common/page-selector.vue

@@ -1,7 +1,8 @@
 <template lang="pug">
 <template lang="pug">
   v-dialog(v-model='isShown', lazy, max-width='850px')
   v-dialog(v-model='isShown', lazy, max-width='850px')
     v-card.page-selector
     v-card.page-selector
-      .dialog-header
+      .dialog-header.is-dark
+        v-icon.mr-2(color='white') find_in_page
         span Select Page Location
         span Select Page Location
         v-spacer
         v-spacer
         v-progress-circular(
         v-progress-circular(
@@ -59,7 +60,7 @@
             v-list-tile
             v-list-tile
               v-list-tile-avatar: v-icon insert_drive_file
               v-list-tile-avatar: v-icon insert_drive_file
               v-list-tile-title File D
               v-list-tile-title File D
-      v-card-text.grey.lighten-2.pa-2
+      v-card-text.grey.lighten-1.pa-2
         v-text-field(
         v-text-field(
           solo
           solo
           hide-details
           hide-details

+ 4 - 4
client/components/editor.vue

@@ -224,8 +224,8 @@ export default {
               isPrivate: false,
               isPrivate: false,
               isPublished: this.$store.get('page/isPublished'),
               isPublished: this.$store.get('page/isPublished'),
               path: this.$store.get('page/path'),
               path: this.$store.get('page/path'),
-              publishEndDate: this.$store.get('page/publishEndDate'),
-              publishStartDate: this.$store.get('page/publishStartDate'),
+              publishEndDate: this.$store.get('page/publishEndDate') || '',
+              publishStartDate: this.$store.get('page/publishStartDate') || '',
               tags: this.$store.get('page/tags'),
               tags: this.$store.get('page/tags'),
               title: this.$store.get('page/title')
               title: this.$store.get('page/title')
             }
             }
@@ -258,8 +258,8 @@ export default {
               isPrivate: false,
               isPrivate: false,
               isPublished: this.$store.get('page/isPublished'),
               isPublished: this.$store.get('page/isPublished'),
               path: this.$store.get('page/path'),
               path: this.$store.get('page/path'),
-              publishEndDate: this.$store.get('page/publishEndDate'),
-              publishStartDate: this.$store.get('page/publishStartDate'),
+              publishEndDate: this.$store.get('page/publishEndDate') || '',
+              publishStartDate: this.$store.get('page/publishStartDate') || '',
               tags: this.$store.get('page/tags'),
               tags: this.$store.get('page/tags'),
               title: this.$store.get('page/title')
               title: this.$store.get('page/title')
             }
             }

+ 1 - 1
client/scss/components/markdown-content.scss

@@ -12,7 +12,7 @@
   }
   }
 
 
   > * + h1, > * + h2, > * + h3, > * + h4 {
   > * + h1, > * + h2, > * + h3, > * + h4 {
-    margin-top: 1rem;
+    margin-top: 3rem;
   }
   }
   h1 {
   h1 {
     font-size: 1.5rem;
     font-size: 1.5rem;

+ 6 - 0
client/scss/components/v-dialog.scss

@@ -14,4 +14,10 @@
     background: radial-gradient(ellipse at top, mc('red', '500'), mc('red', '700')),
     background: radial-gradient(ellipse at top, mc('red', '500'), mc('red', '700')),
               radial-gradient(ellipse at bottom, mc('red', '800'), mc('red', '700'));
               radial-gradient(ellipse at bottom, mc('red', '800'), mc('red', '700'));
   }
   }
+
+  &.is-dark {
+    background-color: mc('grey', '900');
+    background: radial-gradient(ellipse at top, mc('grey', '800'), mc('grey', '900')),
+              radial-gradient(ellipse at bottom, mc('grey', '800'), mc('grey', '900'));
+  }
 }
 }

+ 2 - 0
client/store/page.js

@@ -9,6 +9,8 @@ const state = {
   isPublished: true,
   isPublished: true,
   locale: 'en',
   locale: 'en',
   path: '',
   path: '',
+  publishEndDate: '',
+  publishStartDate: '',
   tags: [],
   tags: [],
   title: '',
   title: '',
   updatedAt: ''
   updatedAt: ''

+ 50 - 51
client/themes/default/components/page.vue

@@ -1,5 +1,5 @@
 <template lang="pug">
 <template lang="pug">
-  v-app
+  v-app(v-scroll='upBtnScroll')
     nav-header
     nav-header
     v-navigation-drawer.primary(
     v-navigation-drawer.primary(
       dark
       dark
@@ -15,23 +15,24 @@
         slot(name='sidebar')
         slot(name='sidebar')
 
 
     v-content
     v-content
-      v-toolbar(color='grey lighten-3', flat, dense)
-        v-btn.pl-0(v-if='$vuetify.breakpoint.xsOnly', flat, @click='toggleNavigation')
-          v-icon(color='grey darken-2', left) menu
-          span Navigation
-        v-breadcrumbs.breadcrumbs-nav.pl-0(
-          v-else
-          :items='breadcrumbs'
-          divider='/'
-          )
-          template(slot='item', slot-scope='props')
-            v-icon(v-if='props.item.path === "/"', small) home
-            v-btn.ma-0(v-else, :href='props.item.path', small, flat) {{props.item.name}}
-        template(v-if='!isPublished')
-          v-spacer
-          .caption.red--text Unpublished
-          status-indicator.ml-3(negative, pulse)
-      v-divider
+      template(v-if='path !== `home`')
+        v-toolbar(color='grey lighten-3', flat, dense)
+          v-btn.pl-0(v-if='$vuetify.breakpoint.xsOnly', flat, @click='toggleNavigation')
+            v-icon(color='grey darken-2', left) menu
+            span Navigation
+          v-breadcrumbs.breadcrumbs-nav.pl-0(
+            v-else
+            :items='breadcrumbs'
+            divider='/'
+            )
+            template(slot='item', slot-scope='props')
+              v-icon(v-if='props.item.path === "/"', small) home
+              v-btn.ma-0(v-else, :href='props.item.path', small, flat) {{props.item.name}}
+          template(v-if='!isPublished')
+            v-spacer
+            .caption.red--text Unpublished
+            status-indicator.ml-3(negative, pulse)
+        v-divider
       v-layout(row)
       v-layout(row)
         v-flex(xs12, lg9, xl10)
         v-flex(xs12, lg9, xl10)
           v-toolbar(color='grey lighten-4', flat, :height='90')
           v-toolbar(color='grey lighten-4', flat, :height='90')
@@ -54,10 +55,20 @@
                 v-icon(color='grey') edit
                 v-icon(color='grey') edit
               span Edit Page
               span Edit Page
           v-divider
           v-divider
-          v-list.grey.lighten-3.pb-3(dense)
-            v-subheader.pl-4.primary--text Table of contents
-            vue-tree-navigation.treenav(:items='toc', :defaultOpenLevel='1')
-          v-divider
+          template(v-if='toc.length')
+            v-list.grey.lighten-3.pb-3(dense)
+              v-subheader.pl-4.primary--text Table of contents
+              template(v-for='(tocItem, tocIdx) in toc')
+                v-list-tile(@click='$vuetify.goTo(tocItem.anchor, scrollOpts)')
+                  v-icon(color='grey') arrow_right
+                  v-list-tile-title.pl-3 {{tocItem.title}}
+                v-divider.ml-4(v-if='tocIdx < toc.length - 1 || tocItem.children.length')
+                template(v-for='tocSubItem in tocItem.children')
+                  v-list-tile(@click='$vuetify.goTo(tocSubItem.anchor, scrollOpts)')
+                    v-icon.pl-3(color='grey lighten-1') arrow_right
+                    v-list-tile-title.pl-3.caption {{tocSubItem.title}}
+                  v-divider(inset, v-if='tocIdx < toc.length - 1')
+            v-divider
           v-list.grey.lighten-4(dense)
           v-list.grey.lighten-4(dense)
             v-subheader.pl-4.yellow--text.text--darken-4 Rating
             v-subheader.pl-4.yellow--text.text--darken-4 Rating
             .text-xs-center
             .text-xs-center
@@ -97,6 +108,9 @@
               span Print Format
               span Print Format
             v-spacer
             v-spacer
     nav-footer
     nav-footer
+    v-fab-transition
+      v-btn(v-if='upBtnShown', fab, fixed, bottom, right, small, @click='$vuetify.goTo(0, scrollOpts)', color='primary')
+        v-icon arrow_upward
 </template>
 </template>
 
 
 <script>
 <script>
@@ -147,46 +161,27 @@ export default {
     isPublished: {
     isPublished: {
       type: Boolean,
       type: Boolean,
       default: false
       default: false
+    },
+    toc: {
+      type: Array,
+      default: () => []
     }
     }
   },
   },
   data() {
   data() {
     return {
     return {
       navOpen: false,
       navOpen: false,
+      upBtnShown: false,
+      scrollOpts: {
+        duration: 1500,
+        offset: -75,
+        easing: 'easeInOutCubic'
+      },
       breadcrumbs: [
       breadcrumbs: [
         { path: '/', name: 'Home' },
         { path: '/', name: 'Home' },
         { path: '/universe', name: 'Universe' },
         { path: '/universe', name: 'Universe' },
         { path: '/universe/galaxy', name: 'Galaxy' },
         { path: '/universe/galaxy', name: 'Galaxy' },
         { path: '/universe/galaxy/solar-system', name: 'Solar System' },
         { path: '/universe/galaxy/solar-system', name: 'Solar System' },
         { path: '/universe/galaxy/solar-system/planet-earth', name: 'Planet Earth' }
         { path: '/universe/galaxy/solar-system/planet-earth', name: 'Planet Earth' }
-      ],
-      toc: [
-        {
-          name: 'Introduction',
-          element: 'introduction'
-        },
-        {
-          name: 'Cities',
-          element: 'cities',
-          children: [
-            {
-              name: 'New York',
-              element: 'contact',
-              children: [
-                { name: 'E-mail', element: 'email' },
-                { name: 'Phone', element: 'phone' }
-              ]
-            },
-            {
-              name: 'Chicago',
-              element: 'contact',
-              children: [
-                { name: 'E-mail', element: 'email' },
-                { name: 'Phone', element: 'phone' }
-              ]
-            }
-          ]
-        },
-        { name: 'Population', external: 'https://github.com' }
       ]
       ]
     }
     }
   },
   },
@@ -222,6 +217,10 @@ export default {
   methods: {
   methods: {
     toggleNavigation () {
     toggleNavigation () {
       this.navOpen = !this.navOpen
       this.navOpen = !this.navOpen
+    },
+    upBtnScroll () {
+      const scrollOffset = window.pageYOffset || document.documentElement.scrollTop
+      this.upBtnShown = scrollOffset > window.innerHeight * 0.33
     }
     }
   }
   }
 }
 }

+ 2 - 1
client/themes/default/scss/app.scss

@@ -2,6 +2,7 @@
 
 
 .contents {
 .contents {
   color: mc('grey', '800');
   color: mc('grey', '800');
+  padding-bottom: 50px;
 
 
   h1, h2, h3, h4, h5, h6 {
   h1, h2, h3, h4, h5, h6 {
     position: relative;
     position: relative;
@@ -26,7 +27,7 @@
   h1 {
   h1 {
     padding-left: 24px;
     padding-left: 24px;
     color: mc('blue', '800');
     color: mc('blue', '800');
-    margin-top: 1rem;
+    margin-top: 2rem;
     position: relative;
     position: relative;
 
 
     &::after {
     &::after {

+ 50 - 50
package.json

@@ -42,15 +42,15 @@
     "node": ">=10.10"
     "node": ">=10.10"
   },
   },
   "dependencies": {
   "dependencies": {
-    "apollo-server": "2.1.0",
-    "apollo-server-express": "2.1.0",
-    "auto-load": "3.0.1",
+    "apollo-server": "2.2.2",
+    "apollo-server-express": "2.2.2",
+    "auto-load": "3.0.4",
     "axios": "0.18.0",
     "axios": "0.18.0",
     "bcryptjs-then": "1.0.1",
     "bcryptjs-then": "1.0.1",
-    "bluebird": "3.5.2",
+    "bluebird": "3.5.3",
     "body-parser": "1.18.3",
     "body-parser": "1.18.3",
     "bugsnag": "2.4.3",
     "bugsnag": "2.4.3",
-    "bull": "3.4.8",
+    "bull": "3.5.2",
     "chalk": "2.4.1",
     "chalk": "2.4.1",
     "cheerio": "1.0.0-rc.2",
     "cheerio": "1.0.0-rc.2",
     "child-process-promise": "2.2.1",
     "child-process-promise": "2.2.1",
@@ -58,34 +58,34 @@
     "compression": "1.7.3",
     "compression": "1.7.3",
     "connect-redis": "3.4.0",
     "connect-redis": "3.4.0",
     "cookie-parser": "1.4.3",
     "cookie-parser": "1.4.3",
-    "cors": "2.8.4",
+    "cors": "2.8.5",
     "dependency-graph": "0.7.2",
     "dependency-graph": "0.7.2",
-    "diff2html": "2.4.0",
+    "diff2html": "2.5.0",
     "dotize": "^0.2.0",
     "dotize": "^0.2.0",
     "execa": "1.0.0",
     "execa": "1.0.0",
     "express": "4.16.4",
     "express": "4.16.4",
     "express-brute": "1.0.1",
     "express-brute": "1.0.1",
     "express-brute-redis": "0.0.1",
     "express-brute-redis": "0.0.1",
     "express-session": "1.15.6",
     "express-session": "1.15.6",
-    "file-type": "10.2.0",
+    "file-type": "10.4.0",
     "filesize": "3.6.1",
     "filesize": "3.6.1",
     "follow-redirects": "1.5.9",
     "follow-redirects": "1.5.9",
-    "fs-extra": "7.0.0",
+    "fs-extra": "7.0.1",
     "getos": "3.1.0",
     "getos": "3.1.0",
     "graphql": "14.0.2",
     "graphql": "14.0.2",
     "graphql-list-fields": "2.0.2",
     "graphql-list-fields": "2.0.2",
     "graphql-subscriptions": "1.0.0",
     "graphql-subscriptions": "1.0.0",
-    "graphql-tools": "4.0.2",
+    "graphql-tools": "4.0.3",
     "highlight.js": "9.13.1",
     "highlight.js": "9.13.1",
     "i18next": "12.0.0",
     "i18next": "12.0.0",
-    "i18next-express-middleware": "1.4.1",
+    "i18next-express-middleware": "1.5.0",
     "i18next-localstorage-cache": "1.1.1",
     "i18next-localstorage-cache": "1.1.1",
     "i18next-node-fs-backend": "2.1.0",
     "i18next-node-fs-backend": "2.1.0",
     "image-size": "0.6.3",
     "image-size": "0.6.3",
     "ioredis": "4.2.0",
     "ioredis": "4.2.0",
     "js-binary": "1.2.0",
     "js-binary": "1.2.0",
     "js-yaml": "3.12.0",
     "js-yaml": "3.12.0",
-    "jsonwebtoken": "8.3.0",
+    "jsonwebtoken": "8.4.0",
     "klaw": "3.0.0",
     "klaw": "3.0.0",
     "knex": "0.15.2",
     "knex": "0.15.2",
     "lodash": "4.17.11",
     "lodash": "4.17.11",
@@ -106,11 +106,11 @@
     "mathjax-node": "2.1.1",
     "mathjax-node": "2.1.1",
     "mime-types": "2.1.21",
     "mime-types": "2.1.21",
     "moment": "2.22.2",
     "moment": "2.22.2",
-    "moment-timezone": "0.5.21",
-    "mongodb": "3.1.8",
-    "mssql": "4.2.2",
+    "moment-timezone": "0.5.23",
+    "mongodb": "3.1.10",
+    "mssql": "4.2.3",
     "multer": "1.4.1",
     "multer": "1.4.1",
-    "mysql2": "1.6.1",
+    "mysql2": "1.6.4",
     "node-2fa": "1.1.2",
     "node-2fa": "1.1.2",
     "node-cache": "4.2.0",
     "node-cache": "4.2.0",
     "oauth2orize": "1.11.0",
     "oauth2orize": "1.11.0",
@@ -135,7 +135,7 @@
     "passport-slack": "0.0.7",
     "passport-slack": "0.0.7",
     "passport-twitch": "1.0.3",
     "passport-twitch": "1.0.3",
     "passport-windowslive": "1.0.2",
     "passport-windowslive": "1.0.2",
-    "pg": "7.6.0",
+    "pg": "7.6.1",
     "pg-hstore": "2.3.2",
     "pg-hstore": "2.3.2",
     "pm2": "3.2.2",
     "pm2": "3.2.2",
     "pug": "2.0.3",
     "pug": "2.0.3",
@@ -148,20 +148,20 @@
     "scim-query-filter-parser": "1.1.0",
     "scim-query-filter-parser": "1.1.0",
     "semver": "5.6.0",
     "semver": "5.6.0",
     "serve-favicon": "2.5.0",
     "serve-favicon": "2.5.0",
-    "sqlite3": "4.0.3",
+    "sqlite3": "4.0.4",
     "subscriptions-transport-ws": "0.9.15",
     "subscriptions-transport-ws": "0.9.15",
     "uslug": "1.0.4",
     "uslug": "1.0.4",
     "uuid": "3.3.2",
     "uuid": "3.3.2",
-    "validator": "10.8.0",
+    "validator": "10.9.0",
     "validator-as-promised": "1.0.2",
     "validator-as-promised": "1.0.2",
     "winston": "3.1.0",
     "winston": "3.1.0",
-    "yargs": "12.0.2"
+    "yargs": "12.0.4"
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@babel/cli": "^7.1.2",
-    "@babel/core": "^7.1.2",
+    "@babel/cli": "^7.1.5",
+    "@babel/core": "^7.1.6",
     "@babel/plugin-proposal-class-properties": "^7.1.0",
     "@babel/plugin-proposal-class-properties": "^7.1.0",
-    "@babel/plugin-proposal-decorators": "^7.1.2",
+    "@babel/plugin-proposal-decorators": "^7.1.6",
     "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
     "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
     "@babel/plugin-proposal-function-sent": "^7.1.0",
     "@babel/plugin-proposal-function-sent": "^7.1.0",
     "@babel/plugin-proposal-json-strings": "^7.0.0",
     "@babel/plugin-proposal-json-strings": "^7.0.0",
@@ -170,20 +170,20 @@
     "@babel/plugin-syntax-dynamic-import": "^7.0.0",
     "@babel/plugin-syntax-dynamic-import": "^7.0.0",
     "@babel/plugin-syntax-import-meta": "^7.0.0",
     "@babel/plugin-syntax-import-meta": "^7.0.0",
     "@babel/polyfill": "^7.0.0",
     "@babel/polyfill": "^7.0.0",
-    "@babel/preset-env": "^7.1.0",
+    "@babel/preset-env": "^7.1.6",
     "@panter/vue-i18next": "0.13.0",
     "@panter/vue-i18next": "0.13.0",
-    "@vue/cli": "3.0.5",
+    "@vue/cli": "3.1.3",
     "animated-number-vue": "0.1.3",
     "animated-number-vue": "0.1.3",
-    "apollo-cache-inmemory": "1.3.7",
-    "apollo-client": "2.4.4",
+    "apollo-cache-inmemory": "1.3.10",
+    "apollo-client": "2.4.6",
     "apollo-fetch": "0.7.0",
     "apollo-fetch": "0.7.0",
     "apollo-link": "1.2.3",
     "apollo-link": "1.2.3",
     "apollo-link-batch-http": "1.2.3",
     "apollo-link-batch-http": "1.2.3",
     "apollo-link-error": "1.1.1",
     "apollo-link-error": "1.1.1",
     "apollo-link-http": "1.5.5",
     "apollo-link-http": "1.5.5",
-    "apollo-link-persisted-queries": "0.2.1",
+    "apollo-link-persisted-queries": "0.2.2",
     "apollo-link-ws": "1.0.9",
     "apollo-link-ws": "1.0.9",
-    "apollo-utilities": "1.0.24",
+    "apollo-utilities": "1.0.25",
     "autoprefixer": "9.3.1",
     "autoprefixer": "9.3.1",
     "babel-eslint": "10.0.1",
     "babel-eslint": "10.0.1",
     "babel-jest": "23.6.0",
     "babel-jest": "23.6.0",
@@ -192,15 +192,16 @@
     "babel-plugin-lodash": "3.3.4",
     "babel-plugin-lodash": "3.3.4",
     "babel-plugin-transform-imports": "1.5.1",
     "babel-plugin-transform-imports": "1.5.1",
     "brace": "0.11.1",
     "brace": "0.11.1",
-    "cache-loader": "1.2.2",
+    "cache-loader": "1.2.5",
     "chart.js": "2.7.3",
     "chart.js": "2.7.3",
-    "clean-webpack-plugin": "0.1.19",
-    "copy-webpack-plugin": "4.5.4",
-    "css-loader": "1.0.0",
+    "clean-webpack-plugin": "1.0.0",
+    "copy-webpack-plugin": "4.6.0",
+    "core-js": "2.5.7",
+    "css-loader": "1.0.1",
     "cssnano": "4.1.7",
     "cssnano": "4.1.7",
     "duplicate-package-checker-webpack-plugin": "3.0.0",
     "duplicate-package-checker-webpack-plugin": "3.0.0",
     "epic-spinners": "1.0.4",
     "epic-spinners": "1.0.4",
-    "eslint": "5.8.0",
+    "eslint": "5.9.0",
     "eslint-config-requarks": "1.0.7",
     "eslint-config-requarks": "1.0.7",
     "eslint-config-standard": "12.0.0",
     "eslint-config-standard": "12.0.0",
     "eslint-plugin-import": "2.14.0",
     "eslint-plugin-import": "2.14.0",
@@ -210,7 +211,7 @@
     "eslint-plugin-vue": "4.7.1",
     "eslint-plugin-vue": "4.7.1",
     "file-loader": "2.0.0",
     "file-loader": "2.0.0",
     "filesize.js": "1.0.2",
     "filesize.js": "1.0.2",
-    "grapesjs": "0.14.33",
+    "grapesjs": "0.14.40",
     "graphiql": "0.12.0",
     "graphiql": "0.12.0",
     "graphql-persisted-document-loader": "1.0.1",
     "graphql-persisted-document-loader": "1.0.1",
     "graphql-tag": "^2.10.0",
     "graphql-tag": "^2.10.0",
@@ -223,62 +224,61 @@
     "js-cookie": "2.2.0",
     "js-cookie": "2.2.0",
     "mini-css-extract-plugin": "0.4.4",
     "mini-css-extract-plugin": "0.4.4",
     "node-sass": "4.9.4",
     "node-sass": "4.9.4",
-    "offline-plugin": "5.0.5",
+    "offline-plugin": "5.0.6",
     "optimize-css-assets-webpack-plugin": "5.0.1",
     "optimize-css-assets-webpack-plugin": "5.0.1",
     "postcss-cssnext": "3.1.0",
     "postcss-cssnext": "3.1.0",
     "postcss-flexbugs-fixes": "4.1.0",
     "postcss-flexbugs-fixes": "4.1.0",
     "postcss-flexibility": "2.0.0",
     "postcss-flexibility": "2.0.0",
     "postcss-import": "12.0.1",
     "postcss-import": "12.0.1",
     "postcss-loader": "3.0.0",
     "postcss-loader": "3.0.0",
-    "postcss-preset-env": "6.3.0",
+    "postcss-preset-env": "6.4.0",
     "postcss-selector-parser": "5.0.0-rc.4",
     "postcss-selector-parser": "5.0.0-rc.4",
     "pug-lint": "2.5.0",
     "pug-lint": "2.5.0",
     "pug-loader": "2.4.0",
     "pug-loader": "2.4.0",
     "pug-plain-loader": "1.0.0",
     "pug-plain-loader": "1.0.0",
     "raw-loader": "0.5.1",
     "raw-loader": "0.5.1",
-    "react": "16.6.0",
-    "react-dom": "16.6.0",
+    "react": "16.6.3",
+    "react-dom": "16.6.3",
     "resolve-url-loader": "3.0.0",
     "resolve-url-loader": "3.0.0",
     "sass-loader": "7.1.0",
     "sass-loader": "7.1.0",
-    "sass-resources-loader": "1.3.4",
-    "script-ext-html-webpack-plugin": "2.0.1",
+    "sass-resources-loader": "2.0.0",
+    "script-ext-html-webpack-plugin": "2.1.3",
     "simple-progress-webpack-plugin": "1.1.2",
     "simple-progress-webpack-plugin": "1.1.2",
     "style-loader": "0.23.1",
     "style-loader": "0.23.1",
     "stylus": "0.54.5",
     "stylus": "0.54.5",
     "stylus-loader": "3.0.2",
     "stylus-loader": "3.0.2",
     "twemoji-awesome": "1.0.6",
     "twemoji-awesome": "1.0.6",
     "url-loader": "1.1.2",
     "url-loader": "1.1.2",
-    "vee-validate": "2.1.1",
+    "vee-validate": "2.1.3",
     "velocity-animate": "1.5.2",
     "velocity-animate": "1.5.2",
-    "viz.js": "2.0.0",
+    "viz.js": "2.1.1",
     "vue": "2.5.17",
     "vue": "2.5.17",
-    "vue-apollo": "3.0.0-beta.25",
+    "vue-apollo": "3.0.0-beta.26",
     "vue-chartjs": "3.4.0",
     "vue-chartjs": "3.4.0",
     "vue-clipboards": "1.2.4",
     "vue-clipboards": "1.2.4",
-    "vue-codemirror": "4.0.5",
+    "vue-codemirror": "4.0.6",
     "vue-hot-reload-api": "2.3.1",
     "vue-hot-reload-api": "2.3.1",
     "vue-loader": "15.4.2",
     "vue-loader": "15.4.2",
-    "vue-material-design-icons": "2.3.0",
+    "vue-material-design-icons": "2.4.0",
     "vue-moment": "4.0.0",
     "vue-moment": "4.0.0",
     "vue-router": "3.0.1",
     "vue-router": "3.0.1",
     "vue-simple-breakpoints": "1.0.3",
     "vue-simple-breakpoints": "1.0.3",
     "vue-status-indicator": "1.1.1",
     "vue-status-indicator": "1.1.1",
     "vue-template-compiler": "2.5.17",
     "vue-template-compiler": "2.5.17",
     "vue-tour": "1.1.0",
     "vue-tour": "1.1.0",
-    "vue-tree-navigation": "3.0.1",
     "vue2-animate": "2.1.0",
     "vue2-animate": "2.1.0",
     "vuedraggable": "2.16.0",
     "vuedraggable": "2.16.0",
-    "vuetify": "1.3.3",
+    "vuetify": "1.3.8",
     "vuex": "3.0.1",
     "vuex": "3.0.1",
     "vuex-pathify": "1.1.3",
     "vuex-pathify": "1.1.3",
     "vuex-persistedstate": "2.5.4",
     "vuex-persistedstate": "2.5.4",
-    "webpack": "4.23.1",
+    "webpack": "4.25.1",
     "webpack-bundle-analyzer": "3.0.3",
     "webpack-bundle-analyzer": "3.0.3",
     "webpack-cli": "3.1.2",
     "webpack-cli": "3.1.2",
     "webpack-dev-middleware": "3.4.0",
     "webpack-dev-middleware": "3.4.0",
     "webpack-hot-middleware": "2.24.3",
     "webpack-hot-middleware": "2.24.3",
     "webpack-merge": "4.1.4",
     "webpack-merge": "4.1.4",
-    "webpack-subresource-integrity": "1.2.0",
+    "webpack-subresource-integrity": "1.3.0",
     "whatwg-fetch": "3.0.0",
     "whatwg-fetch": "3.0.0",
     "write-file-webpack-plugin": "4.4.1",
     "write-file-webpack-plugin": "4.4.1",
     "xterm": "3.8.0"
     "xterm": "3.8.0"

+ 1 - 0
server/db/migrations/2.0.0.js

@@ -116,6 +116,7 @@ exports.up = knex => {
       table.string('publishEndDate')
       table.string('publishEndDate')
       table.text('content')
       table.text('content')
       table.text('render')
       table.text('render')
+      table.json('toc')
       table.string('contentType').notNullable()
       table.string('contentType').notNullable()
       table.string('createdAt').notNullable()
       table.string('createdAt').notNullable()
       table.string('updatedAt').notNullable()
       table.string('updatedAt').notNullable()

+ 31 - 2
server/jobs/render-page.js

@@ -1,6 +1,7 @@
 require('../core/worker')
 require('../core/worker')
 
 
 const _ = require('lodash')
 const _ = require('lodash')
+const cheerio = require('cheerio')
 
 
 /* global WIKI */
 /* global WIKI */
 
 
@@ -21,15 +22,43 @@ module.exports = async (job) => {
       })
       })
     }
     }
 
 
+    // Parse TOC
+    const $ = cheerio.load(output)
+    let isStrict = $('h1').length > 0 // <- Allows for documents using H2 as top level
+    let toc = { root: [] }
+
+    $('h1,h2,h3,h4,h5,h6').each((idx, el) => {
+      const depth = _.toSafeInteger(el.name.substring(1)) - (isStrict ? 1 : 2)
+      const leafPath = _.reduce(_.times(depth), (curPath, curIdx) => {
+        if (_.has(toc, curPath)) {
+          const lastLeafIdx = _.get(toc, curPath).length - 1
+          curPath = `${curPath}[${lastLeafIdx}].children`
+        }
+        return curPath
+      }, 'root')
+
+      const leafSlug = $('.toc-anchor', el).first().attr('href')
+      $('.toc-anchor', el).remove()
+      _.get(toc, leafPath).push({
+        title: _.trim($(el).text()),
+        anchor: leafSlug,
+        children: []
+      })
+    })
+
     // Save to DB
     // Save to DB
     await WIKI.models.pages.query()
     await WIKI.models.pages.query()
-      .patch({ render: output })
+      .patch({
+        render: output,
+        toc: JSON.stringify(toc.root)
+      })
       .where('id', job.data.page.id)
       .where('id', job.data.page.id)
 
 
     // Save to cache
     // Save to cache
     await WIKI.models.pages.savePageToCache({
     await WIKI.models.pages.savePageToCache({
       ...job.data.page,
       ...job.data.page,
-      render: output
+      render: output,
+      toc: JSON.stringify(toc.root)
     })
     })
 
 
     WIKI.logger.info(`Rendering page ${job.data.page.path}: [ COMPLETED ]`)
     WIKI.logger.info(`Rendering page ${job.data.page.path}: [ COMPLETED ]`)

+ 2 - 2
server/models/pageHistory.js

@@ -96,8 +96,8 @@ module.exports = class PageHistory extends Model {
       isPublished: opts.isPublished,
       isPublished: opts.isPublished,
       localeCode: opts.localeCode,
       localeCode: opts.localeCode,
       path: opts.path,
       path: opts.path,
-      publishEndDate: opts.publishEndDate,
-      publishStartDate: opts.publishStartDate,
+      publishEndDate: opts.publishEndDate || '',
+      publishStartDate: opts.publishStartDate || '',
       title: opts.title
       title: opts.title
     })
     })
   }
   }

+ 8 - 5
server/models/pages.js

@@ -108,6 +108,7 @@ module.exports = class Page extends Model {
       publishStartDate: 'string',
       publishStartDate: 'string',
       render: 'string',
       render: 'string',
       title: 'string',
       title: 'string',
+      toc: 'string',
       updatedAt: 'string'
       updatedAt: 'string'
     })
     })
   }
   }
@@ -125,9 +126,10 @@ module.exports = class Page extends Model {
       isPublished: opts.isPublished,
       isPublished: opts.isPublished,
       localeCode: opts.locale,
       localeCode: opts.locale,
       path: opts.path,
       path: opts.path,
-      publishEndDate: opts.publishEndDate,
-      publishStartDate: opts.publishStartDate,
-      title: opts.title
+      publishEndDate: opts.publishEndDate || '',
+      publishStartDate: opts.publishStartDate || '',
+      title: opts.title,
+      toc: '[]'
     })
     })
     const page = await WIKI.models.pages.getPageFromDb({
     const page = await WIKI.models.pages.getPageFromDb({
       path: opts.path,
       path: opts.path,
@@ -154,8 +156,8 @@ module.exports = class Page extends Model {
       content: opts.content,
       content: opts.content,
       description: opts.description,
       description: opts.description,
       isPublished: opts.isPublished,
       isPublished: opts.isPublished,
-      publishEndDate: opts.publishEndDate,
-      publishStartDate: opts.publishStartDate,
+      publishEndDate: opts.publishEndDate || '',
+      publishStartDate: opts.publishStartDate || '',
       title: opts.title
       title: opts.title
     }).where('id', ogPage.id)
     }).where('id', ogPage.id)
     const page = await WIKI.models.pages.getPageFromDb({
     const page = await WIKI.models.pages.getPageFromDb({
@@ -243,6 +245,7 @@ module.exports = class Page extends Model {
       publishStartDate: page.publishStartDate,
       publishStartDate: page.publishStartDate,
       render: page.render,
       render: page.render,
       title: page.title,
       title: page.title,
+      toc: page.toc,
       updatedAt: page.updatedAt
       updatedAt: page.updatedAt
     }))
     }))
   }
   }

+ 1 - 0
server/views/page.pug

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

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 383 - 392
yarn.lock


Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov