浏览代码

misc: migrate to vuetify 2.0 (wip)

NGPixel 5 年之前
父节点
当前提交
eccf1a1b19
共有 100 个文件被更改,包括 1318 次插入1198 次删除
  1. 3 47
      .babelrc
  2. 2 2
      .gitattributes
  3. 7 14
      client/client-app.js
  4. 3 10
      client/client-setup.js
  5. 84 84
      client/components/admin.vue
  6. 22 25
      client/components/admin/admin-analytics.vue
  7. 15 18
      client/components/admin/admin-auth.vue
  8. 56 56
      client/components/admin/admin-contribute.vue
  9. 24 22
      client/components/admin/admin-dashboard.vue
  10. 24 26
      client/components/admin/admin-general.vue
  11. 25 25
      client/components/admin/admin-groups-edit-rules.vue
  12. 8 8
      client/components/admin/admin-groups-edit-users.vue
  13. 10 10
      client/components/admin/admin-groups.vue
  14. 46 52
      client/components/admin/admin-locale.vue
  15. 10 10
      client/components/admin/admin-mail.vue
  16. 46 49
      client/components/admin/admin-navigation.vue
  17. 40 40
      client/components/admin/admin-pages-edit.vue
  18. 9 12
      client/components/admin/admin-pages.vue
  19. 6 6
      client/components/admin/admin-rendering.vue
  20. 6 6
      client/components/admin/admin-search.vue
  21. 22 22
      client/components/admin/admin-storage.vue
  22. 51 51
      client/components/admin/admin-system.vue
  23. 22 31
      client/components/admin/admin-theme.vue
  24. 96 18
      client/components/admin/admin-users-create.vue
  25. 59 59
      client/components/admin/admin-users-edit.vue
  26. 8 8
      client/components/admin/admin-users.vue
  27. 2 2
      client/components/admin/admin-utilities-content.vue
  28. 29 29
      client/components/admin/admin-utilities-telemetry.vue
  29. 6 6
      client/components/admin/admin-utilities.vue
  30. 6 6
      client/components/admin/admin-webhooks.vue
  31. 89 83
      client/components/common/nav-header.vue
  32. 2 3
      client/components/common/notify.vue
  33. 13 13
      client/components/common/page-selector.vue
  34. 20 18
      client/components/common/search-results.vue
  35. 6 6
      client/components/common/user-search.vue
  36. 18 18
      client/components/editor/editor-markdown.vue
  37. 6 6
      client/components/editor/editor-modal-blocks.vue
  38. 21 21
      client/components/editor/editor-modal-media.vue
  39. 35 35
      client/components/editor/markdown/help.vue
  40. 18 18
      client/components/history.vue
  41. 12 12
      client/components/profile.vue
  42. 2 2
      client/components/setup.vue
  43. 12 0
      client/graph/admin/users/users-mutation-create.gql
  44. 2 1
      client/index-app.js
  45. 0 1
      client/index-setup.js
  46. 0 3
      client/scss/app.scss
  47. 30 30
      client/scss/base/icons.scss
  48. 0 17
      client/scss/layout/_md2.scss
  49. 二进制
      client/static/favicons/android-icon-144x144.png
  50. 二进制
      client/static/favicons/android-icon-192x192.png
  51. 二进制
      client/static/favicons/android-icon-36x36.png
  52. 二进制
      client/static/favicons/android-icon-48x48.png
  53. 二进制
      client/static/favicons/android-icon-72x72.png
  54. 二进制
      client/static/favicons/android-icon-96x96.png
  55. 二进制
      client/static/favicons/apple-icon-114x114.png
  56. 二进制
      client/static/favicons/apple-icon-120x120.png
  57. 二进制
      client/static/favicons/apple-icon-144x144.png
  58. 二进制
      client/static/favicons/apple-icon-152x152.png
  59. 二进制
      client/static/favicons/apple-icon-180x180.png
  60. 二进制
      client/static/favicons/apple-icon-57x57.png
  61. 二进制
      client/static/favicons/apple-icon-60x60.png
  62. 二进制
      client/static/favicons/apple-icon-72x72.png
  63. 二进制
      client/static/favicons/apple-icon-76x76.png
  64. 二进制
      client/static/favicons/apple-icon-precomposed.png
  65. 二进制
      client/static/favicons/apple-icon.png
  66. 二进制
      client/static/favicons/favicon-16x16.png
  67. 二进制
      client/static/favicons/favicon-32x32.png
  68. 二进制
      client/static/favicons/favicon-96x96.png
  69. 二进制
      client/static/favicons/ms-icon-144x144.png
  70. 二进制
      client/static/favicons/ms-icon-150x150.png
  71. 二进制
      client/static/favicons/ms-icon-310x310.png
  72. 二进制
      client/static/favicons/ms-icon-70x70.png
  73. 二进制
      client/static/fonts/MaterialIcons-Regular.woff
  74. 二进制
      client/static/fonts/MaterialIcons-Regular.woff2
  75. 二进制
      client/static/fonts/Roboto-Bold.woff
  76. 二进制
      client/static/fonts/Roboto-BoldItalic.woff2
  77. 二进制
      client/static/fonts/Roboto-Italic.woff2
  78. 二进制
      client/static/fonts/Roboto-MediumItalic.woff
  79. 二进制
      client/static/fonts/Roboto-MediumItalic.woff2
  80. 二进制
      client/static/fonts/Roboto-Regular.woff
  81. 二进制
      client/static/fonts/Roboto-Regular.woff2
  82. 二进制
      client/static/fonts/RobotoMono-Regular.woff2
  83. 二进制
      client/static/img/donate_eth_qr.png
  84. 二进制
      client/static/img/donate_opencollective.png
  85. 二进制
      client/static/img/donate_patreon.png
  86. 二进制
      client/static/img/donate_paypal.png
  87. 93 72
      client/themes/default/components/page.vue
  88. 12 7
      client/themes/default/scss/app.scss
  89. 二进制
      client/themes/default/thumbnail.png
  90. 1 7
      dev/templates/legacy.pug
  91. 1 7
      dev/templates/master.pug
  92. 28 9
      dev/webpack/webpack.dev.js
  93. 35 34
      package.json
  94. 13 0
      server/db/migrations-sqlite/2.0.0-beta.242.js
  95. 13 0
      server/db/migrations/2.0.0-beta.242.js
  96. 1 5
      server/graph/resolvers/user.js
  97. 4 2
      server/graph/schemas/user.graphql
  98. 82 0
      server/models/users.js
  99. 1 7
      server/views/legacy/master.pug
  100. 1 7
      server/views/master.pug

+ 3 - 47
.babelrc

@@ -19,53 +19,9 @@
     "@babel/plugin-proposal-throw-expressions",
     [
       "prismjs", {
-        "languages": [
-          "markup",
-          "css",
-          "clike",
-          "javascript",
-          "c",
-          "bash",
-          "basic",
-          "cpp",
-          "csharp",
-          "arduino",
-          "ruby",
-          "elixir",
-          "fsharp",
-          "go",
-          "graphql",
-          "handlebars",
-          "haskell",
-          "ini",
-          "java",
-          "json",
-          "kotlin",
-          "latex",
-          "less",
-          "makefile",
-          "markdown",
-          "matlab",
-          "nginx",
-          "objectivec",
-          "perl",
-          "php",
-          "powershell",
-          "pug",
-          "python",
-          "typescript",
-          "rust",
-          "scss",
-          "scala",
-          "smalltalk",
-          "sql",
-          "stylus",
-          "swift",
-          "vbnet",
-          "yaml"
-        ],
-        "plugins": ["line-numbers"],
-        "theme": "dark",
+        "languages": ["clike", "markup"],
+        "plugins": ["line-numbers", "autoloader", "normalize-whitespace"],
+        "theme": "twilight",
         "css": true
       }
     ]

+ 2 - 2
.gitattributes

@@ -1,5 +1,5 @@
 # Auto detect text files and perform LF normalization
-* text=auto
+* text eol=lf
 
 # Custom for Visual Studio
 *.cs     diff=csharp
@@ -14,4 +14,4 @@
 *.pdf  diff=astextplain
 *.PDF	 diff=astextplain
 *.rtf	 diff=astextplain
-*.RTF	 diff=astextplain
+*.RTF	 diff=astextplain

+ 7 - 14
client/client-app.js

@@ -12,7 +12,7 @@ import { ErrorLink } from 'apollo-link-error'
 import { InMemoryCache } from 'apollo-cache-inmemory'
 import { getMainDefinition } from 'apollo-utilities'
 import VueApollo from 'vue-apollo'
-import Vuetify from 'vuetify'
+import Vuetify from 'vuetify/lib'
 import Velocity from 'velocity-animate'
 import Vuescroll from 'vuescroll/dist/vuescroll-native'
 import Hammer from 'hammerjs'
@@ -138,10 +138,8 @@ Vue.use(VueApollo)
 Vue.use(VueClipboards)
 Vue.use(localization.VueI18Next)
 Vue.use(helpers)
-Vue.use(VeeValidate, { events: '' })
-Vue.use(Vuetify, {
-  rtl: siteConfig.rtl
-})
+Vue.use(VeeValidate, { mode: 'eager' })
+Vue.use(Vuetify)
 Vue.use(VueMoment, { moment })
 Vue.use(Vuescroll)
 Vue.use(VueTour)
@@ -194,7 +192,10 @@ let bootstrap = () => {
     mixins: [helpers],
     apolloProvider,
     store,
-    i18n
+    i18n,
+    vuetify: new Vuetify({
+      rtl: siteConfig.rtl
+    })
   })
 
   // ----------------------------------
@@ -208,14 +209,6 @@ let bootstrap = () => {
   // ====================================
 
   import(/* webpackChunkName: "theme-page"  */ './themes/' + process.env.CURRENT_THEME + '/js/app.js')
-
-  // ====================================
-  // Load Icons
-  // ====================================
-
-  // import(/* webpackChunkName: "icons" */ './svg/icons.svg').then(icons => {
-  //   document.body.insertAdjacentHTML('beforeend', icons.default)
-  // })
 }
 
 window.boot.onDOMReady(bootstrap)

+ 3 - 10
client/client-setup.js

@@ -1,6 +1,6 @@
 /* eslint-disable import/first */
 import Vue from 'vue'
-import Vuetify from 'vuetify'
+import Vuetify from 'vuetify/lib'
 import VeeValidate from 'vee-validate'
 import boot from './modules/boot'
 /* eslint-enable import/first */
@@ -15,15 +15,8 @@ Vue.component('setup', () => import(/* webpackMode: "eager" */ './components/set
 
 let bootstrap = () => {
   window.WIKI = new Vue({
-    el: '#root'
-  })
-
-  // ====================================
-  // Load Icons
-  // ====================================
-
-  import(/* webpackChunkName: "icons" */ './svg/icons.svg').then(icons => {
-    document.body.insertAdjacentHTML('beforeend', icons.default)
+    el: '#root',
+    vuetify: new Vuetify()
   })
 }
 

+ 84 - 84
client/components/admin.vue

@@ -3,114 +3,114 @@
     nav-header(hide-search)
       template(slot='mid')
         v-spacer
-        .subheading.grey--text {{$t('admin:adminArea')}}
+        .overline.grey--text {{$t('admin:adminArea')}}
         v-spacer
-    v-navigation-drawer.pb-0.admin-sidebar(v-model='adminDrawerShown', app, fixed, clipped, :right='$vuetify.rtl', permanent)
+    v-navigation-drawer.pb-0.admin-sidebar(v-model='adminDrawerShown', app, fixed, clipped, :right='$vuetify.rtl', permanent, width='300')
       vue-scroll(:ops='scrollStyle')
-        v-list(dense)
-          v-list-tile.pt-2(to='/dashboard')
-            v-list-tile-avatar: v-icon dashboard
-            v-list-tile-title {{ $t('admin:dashboard.title') }}
+        v-list(dense, nav)
+          v-list-item(to='/dashboard')
+            v-list-item-avatar(size='24'): v-icon mdi-view-dashboard-variant
+            v-list-item-title {{ $t('admin:dashboard.title') }}
           template(v-if='hasPermission([`manage:system`, `manage:navigation`, `write:pages`, `manage:pages`, `delete:pages`])')
             v-divider.my-2
             v-subheader.pl-4 {{ $t('admin:nav.site') }}
-            v-list-tile(to='/general', v-if='hasPermission(`manage:system`)')
-              v-list-tile-avatar: v-icon widgets
-              v-list-tile-title {{ $t('admin:general.title') }}
-            v-list-tile(to='/locale', v-if='hasPermission(`manage:system`)')
-              v-list-tile-avatar: v-icon language
-              v-list-tile-title {{ $t('admin:locale.title') }}
-            v-list-tile(to='/navigation', v-if='hasPermission([`manage:system`, `manage:navigation`])')
-              v-list-tile-avatar: v-icon near_me
-              v-list-tile-title {{ $t('admin:navigation.title') }}
-            v-list-tile(to='/pages', v-if='hasPermission([`manage:system`, `write:pages`, `manage:pages`, `delete:pages`])')
-              v-list-tile-avatar: v-icon insert_drive_file
-              v-list-tile-title {{ $t('admin:pages.title') }}
-              v-list-tile-action
-                v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
+            v-list-item(to='/general', v-if='hasPermission(`manage:system`)')
+              v-list-item-avatar(size='24'): v-icon mdi-widgets
+              v-list-item-title {{ $t('admin:general.title') }}
+            v-list-item(to='/locale', v-if='hasPermission(`manage:system`)')
+              v-list-item-avatar(size='24'): v-icon mdi-web
+              v-list-item-title {{ $t('admin:locale.title') }}
+            v-list-item(to='/navigation', v-if='hasPermission([`manage:system`, `manage:navigation`])')
+              v-list-item-avatar(size='24'): v-icon mdi-near-me
+              v-list-item-title {{ $t('admin:navigation.title') }}
+            v-list-item(to='/pages', v-if='hasPermission([`manage:system`, `write:pages`, `manage:pages`, `delete:pages`])')
+              v-list-item-avatar(size='24'): v-icon mdi-file-document-outline
+              v-list-item-title {{ $t('admin:pages.title') }}
+              v-list-item-action
+                v-chip(x-small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
                   .caption.grey--text {{ info.pagesTotal }}
-            v-list-tile(to='/theme', v-if='hasPermission([`manage:system`, `manage:theme`])')
-              v-list-tile-avatar: v-icon palette
-              v-list-tile-title {{ $t('admin:theme.title') }}
+            v-list-item(to='/theme', v-if='hasPermission([`manage:system`, `manage:theme`])')
+              v-list-item-avatar(size='24'): v-icon mdi-palette-outline
+              v-list-item-title {{ $t('admin:theme.title') }}
           template(v-if='hasPermission([`manage:system`, `manage:groups`, `write:groups`, `manage:users`, `write:users`])')
             v-divider.my-2
             v-subheader.pl-4 {{ $t('admin:nav.users') }}
-            v-list-tile(to='/groups', v-if='hasPermission([`manage:system`, `manage:groups`, `write:groups`])')
-              v-list-tile-avatar: v-icon people
-              v-list-tile-title {{ $t('admin:groups.title') }}
-              v-list-tile-action
-                v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
+            v-list-item(to='/groups', v-if='hasPermission([`manage:system`, `manage:groups`, `write:groups`])')
+              v-list-item-avatar(size='24'): v-icon mdi-account-group
+              v-list-item-title {{ $t('admin:groups.title') }}
+              v-list-item-action
+                v-chip(x-small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
                   .caption.grey--text {{ info.groupsTotal }}
-            v-list-tile(to='/users', v-if='hasPermission([`manage:system`, `manage:groups`, `write:groups`, `manage:users`, `write:users`])')
-              v-list-tile-avatar: v-icon perm_identity
-              v-list-tile-title {{ $t('admin:users.title') }}
-              v-list-tile-action
-                v-chip(small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
+            v-list-item(to='/users', v-if='hasPermission([`manage:system`, `manage:groups`, `write:groups`, `manage:users`, `write:users`])')
+              v-list-item-avatar(size='24'): v-icon mdi-account-box
+              v-list-item-title {{ $t('admin:users.title') }}
+              v-list-item-action
+                v-chip(x-small, disabled, :color='darkMode ? `grey darken-3-d4` : `grey lighten-4`')
                   .caption.grey--text {{ info.usersTotal }}
           template(v-if='hasPermission(`manage:system`)')
             v-divider.my-2
             v-subheader.pl-4 {{ $t('admin:nav.modules') }}
-            v-list-tile(to='/analytics')
-              v-list-tile-avatar: v-icon timeline
-              v-list-tile-title {{ $t('admin:analytics.title') }}
-            v-list-tile(to='/auth')
-              v-list-tile-avatar: v-icon lock_outline
-              v-list-tile-title {{ $t('admin:auth.title') }}
-            v-list-tile(to='/comments', disabled)
-              v-list-tile-avatar: v-icon(color='grey lighten-2') comment
-              v-list-tile-title {{ $t('admin:comments.title') }}
-            v-list-tile(to='/editor', disabled)
-              v-list-tile-avatar: v-icon(color='grey lighten-2') transform
-              v-list-tile-title {{ $t('admin:editor.title') }}
-            v-list-tile(to='/logging', disabled)
-              v-list-tile-avatar: v-icon(color='grey lighten-2') graphic_eq
-              v-list-tile-title {{ $t('admin:logging.title') }}
-            v-list-tile(to='/rendering')
-              v-list-tile-avatar: v-icon system_update_alt
-              v-list-tile-title {{ $t('admin:rendering.title') }}
-            v-list-tile(to='/search')
-              v-list-tile-avatar: v-icon search
-              v-list-tile-title {{ $t('admin:search.title') }}
-            v-list-tile(to='/storage')
-              v-list-tile-avatar: v-icon storage
-              v-list-tile-title {{ $t('admin:storage.title') }}
+            v-list-item(to='/analytics')
+              v-list-item-avatar(size='24'): v-icon mdi-chart-timeline-variant
+              v-list-item-title {{ $t('admin:analytics.title') }}
+            v-list-item(to='/auth')
+              v-list-item-avatar(size='24'): v-icon mdi-lock-outline
+              v-list-item-title {{ $t('admin:auth.title') }}
+            v-list-item(to='/comments', disabled)
+              v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-comment-text-outline
+              v-list-item-title {{ $t('admin:comments.title') }}
+            v-list-item(to='/editor', disabled)
+              v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-playlist-edit
+              v-list-item-title {{ $t('admin:editor.title') }}
+            v-list-item(to='/logging', disabled)
+              v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-script-text-outline
+              v-list-item-title {{ $t('admin:logging.title') }}
+            v-list-item(to='/rendering')
+              v-list-item-avatar(size='24'): v-icon mdi-cogs
+              v-list-item-title {{ $t('admin:rendering.title') }}
+            v-list-item(to='/search')
+              v-list-item-avatar(size='24'): v-icon mdi-cloud-search-outline
+              v-list-item-title {{ $t('admin:search.title') }}
+            v-list-item(to='/storage')
+              v-list-item-avatar(size='24'): v-icon mdi-harddisk
+              v-list-item-title {{ $t('admin:storage.title') }}
           template(v-if='hasPermission([`manage:system`, `manage:api`])')
             v-divider.my-2
             v-subheader.pl-4 {{ $t('admin:nav.system') }}
-            v-list-tile(to='/api', v-if='hasPermission([`manage:system`, `manage:api`])', disabled)
-              v-list-tile-avatar: v-icon(color='grey lighten-2') call_split
-              v-list-tile-title {{ $t('admin:api.title') }}
-            v-list-tile(to='/mail', v-if='hasPermission(`manage:system`)')
-              v-list-tile-avatar: v-icon email
-              v-list-tile-title {{ $t('admin:mail.title') }}
-            v-list-tile(to='/system', v-if='hasPermission(`manage:system`)')
-              v-list-tile-avatar: v-icon tune
-              v-list-tile-title {{ $t('admin:system.title') }}
-            v-list-tile(to='/utilities', v-if='hasPermission(`manage:system`)')
-              v-list-tile-avatar: v-icon build
-              v-list-tile-title {{ $t('admin:utilities.title') }}
-            v-list-tile(to='/webhooks', v-if='hasPermission(`manage:system`)', disabled)
-              v-list-tile-avatar: v-icon(color='grey lighten-2') ac_unit
-              v-list-tile-title {{ $t('admin:webhooks.title') }}
+            v-list-item(to='/api', v-if='hasPermission([`manage:system`, `manage:api`])', disabled)
+              v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-call-split
+              v-list-item-title {{ $t('admin:api.title') }}
+            v-list-item(to='/mail', v-if='hasPermission(`manage:system`)')
+              v-list-item-avatar(size='24'): v-icon mdi-email-multiple-outline
+              v-list-item-title {{ $t('admin:mail.title') }}
+            v-list-item(to='/system', v-if='hasPermission(`manage:system`)')
+              v-list-item-avatar(size='24'): v-icon mdi-tune
+              v-list-item-title {{ $t('admin:system.title') }}
+            v-list-item(to='/utilities', v-if='hasPermission(`manage:system`)')
+              v-list-item-avatar(size='24'): v-icon mdi-wrench-outline
+              v-list-item-title {{ $t('admin:utilities.title') }}
+            v-list-item(to='/webhooks', v-if='hasPermission(`manage:system`)', disabled)
+              v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-webhook
+              v-list-item-title {{ $t('admin:webhooks.title') }}
             v-list-group(
               to='/dev'
               no-action
               v-if='hasPermission([`manage:system`, `manage:api`])'
               )
-              v-list-tile(slot='activator')
-                v-list-tile-avatar: v-icon weekend
-                v-list-tile-title {{ $t('admin:dev.title') }}
+              v-list-item(slot='activator')
+                v-list-item-avatar(size='24'): v-icon mdi-dev-to
+                v-list-item-title {{ $t('admin:dev.title') }}
 
-              v-list-tile(to='/dev-flags')
-                v-list-tile-title {{ $t('admin:dev.flags.title') }}
-              v-list-tile(to='/dev-graphiql')
-                v-list-tile-title {{ $t('admin:dev.graphiql.title') }}
-              v-list-tile(to='/dev-voyager')
-                v-list-tile-title {{ $t('admin:dev.voyager.title') }}
+              v-list-item(to='/dev-flags')
+                v-list-item-title {{ $t('admin:dev.flags.title') }}
+              v-list-item(to='/dev-graphiql')
+                v-list-item-title {{ $t('admin:dev.graphiql.title') }}
+              v-list-item(to='/dev-voyager')
+                v-list-item-title {{ $t('admin:dev.voyager.title') }}
             v-divider.my-2
-          v-list-tile(to='/contribute')
-            v-list-tile-avatar: v-icon favorite
-            v-list-tile-title {{ $t('admin:contribute.title') }}
+          v-list-item(to='/contribute')
+            v-list-item-avatar(size='24'): v-icon mdi-heart-outline
+            v-list-item-title {{ $t('admin:contribute.title') }}
 
     v-content(:class='darkMode ? "grey darken-4" : ""')
       transition(name='admin-router')

+ 22 - 25
client/components/admin/admin-analytics.vue

@@ -8,10 +8,10 @@
             .headline.primary--text.animated.fadeInLeft {{ $t('admin:analytics.title') }}
             .subheading.grey--text.animated.fadeInLeft.wait-p4s {{ $t('admin:analytics.subtitle') }}
           v-spacer
-          v-btn.animated.fadeInDown.wait-p2s(outline, color='grey', @click='refresh', large)
-            v-icon refresh
+          v-btn.animated.fadeInDown.wait-p2s.mr-3(outlined, color='grey', @click='refresh', large)
+            v-icon mdi-refresh
           v-btn.animated.fadeInDown(color='success', @click='save', depressed, large)
-            v-icon(left) check
+            v-icon(left) mdi-check
             span {{$t('common:actions.apply')}}
 
       v-flex(lg3, xs12)
@@ -20,21 +20,21 @@
             .subheading {{$t('admin:analytics.providers')}}
           v-list(two-line, dense).py-0
             template(v-for='(str, idx) in providers')
-              v-list-tile(:key='str.key', @click='selectedProvider = str.key', :disabled='!str.isAvailable')
-                v-list-tile-avatar
-                  v-icon(color='grey', v-if='!str.isAvailable') indeterminate_check_box
-                  v-icon(color='primary', v-else-if='str.isEnabled', v-ripple, @click='str.isEnabled = false') check_box
-                  v-icon(color='grey', v-else, v-ripple, @click='str.isEnabled = true') check_box_outline_blank
-                v-list-tile-content
-                  v-list-tile-title.body-2(:class='!str.isAvailable ? `grey--text` : (selectedProvider === str.key ? `primary--text` : ``)') {{ str.title }}
-                  v-list-tile-sub-title.caption(:class='!str.isAvailable ? `grey--text text--lighten-1` : (selectedProvider === str.key ? `blue--text ` : ``)') {{ str.description }}
-                v-list-tile-avatar(v-if='selectedProvider === str.key')
-                  v-icon.animated.fadeInLeft(color='primary') arrow_forward_ios
+              v-list-item(:key='str.key', @click='selectedProvider = str.key', :disabled='!str.isAvailable')
+                v-list-item-avatar(size='24')
+                  v-icon(color='grey', v-if='!str.isAvailable') mdi-minus-box-outline
+                  v-icon(color='primary', v-else-if='str.isEnabled', v-ripple, @click='str.isEnabled = false') mdi-checkbox-marked-outline
+                  v-icon(color='grey', v-else, v-ripple, @click='str.isEnabled = true') mdi-checkbox-blank-outline
+                v-list-item-content
+                  v-list-item-title.body-2(:class='!str.isAvailable ? `grey--text` : (selectedProvider === str.key ? `primary--text` : ``)') {{ str.title }}
+                  v-list-item-subtitle: .caption(:class='!str.isAvailable ? `grey--text text--lighten-1` : (selectedProvider === str.key ? `blue--text ` : ``)') {{ str.description }}
+                v-list-item-avatar(v-if='selectedProvider === str.key', size='24')
+                  v-icon.animated.fadeInLeft(color='primary', large) mdi-chevron-right
               v-divider(v-if='idx < providers.length - 1')
 
       v-flex(xs12, lg9)
 
-        v-card.wiki-form.animated.fadeInUp.wait-p2s
+        v-card.animated.fadeInUp.wait-p2s
           v-toolbar(color='primary', dense, flat, dark)
             .subheading {{provider.title}}
           v-card-text
@@ -44,18 +44,17 @@
               .caption.pt-3 {{provider.description}}
               .caption.pb-3: a(:href='provider.website') {{provider.website}}
               v-divider.mt-3
-              v-subheader.pl-0 {{$t('admin:analytics.providerConfiguration')}}
+              .overline.py-4 {{$t('admin:analytics.providerConfiguration')}}
               .body-1.ml-3(v-if='!provider.config || provider.config.length < 1'): em {{$t('admin:analytics.providerNoConfiguration')}}
               template(v-else, v-for='cfg in provider.config')
                 v-select(
                   v-if='cfg.value.type === "string" && cfg.value.enum'
-                  outline
-                  background-color='grey lighten-2'
+                  outlined
                   :items='cfg.value.enum'
                   :key='cfg.key'
                   :label='cfg.value.title'
                   v-model='cfg.value.value'
-                  prepend-icon='settings_applications'
+                  prepend-icon='mdi-settings-box'
                   :hint='cfg.value.hint ? cfg.value.hint : ""'
                   persistent-hint
                   :class='cfg.value.hint ? "mb-2" : ""'
@@ -66,30 +65,28 @@
                   :label='cfg.value.title'
                   v-model='cfg.value.value'
                   color='primary'
-                  prepend-icon='settings_applications'
+                  prepend-icon='mdi-settings-box'
                   :hint='cfg.value.hint ? cfg.value.hint : ""'
                   persistent-hint
                   )
                 v-textarea(
                   v-else-if='cfg.value.type === "string" && cfg.value.multiline'
-                  outline
-                  background-color='grey lighten-2'
+                  outlined
                   :key='cfg.key'
                   :label='cfg.value.title'
                   v-model='cfg.value.value'
-                  prepend-icon='settings_applications'
+                  prepend-icon='mdi-settings-box'
                   :hint='cfg.value.hint ? cfg.value.hint : ""'
                   persistent-hint
                   :class='cfg.value.hint ? "mb-2" : ""'
                   )
                 v-text-field(
                   v-else
-                  outline
-                  background-color='grey lighten-2'
+                  outlined
                   :key='cfg.key'
                   :label='cfg.value.title'
                   v-model='cfg.value.value'
-                  prepend-icon='settings_applications'
+                  prepend-icon='mdi-settings-box'
                   :hint='cfg.value.hint ? cfg.value.hint : ""'
                   persistent-hint
                   :class='cfg.value.hint ? "mb-2" : ""'

+ 15 - 18
client/components/admin/admin-auth.vue

@@ -8,7 +8,7 @@
             .headline.primary--text.animated.fadeInLeft {{ $t('admin:auth.title') }}
             .subheading.grey--text.animated.fadeInLeft.wait-p4s {{ $t('admin:auth.subtitle') }}
           v-spacer
-          v-btn.animated.fadeInDown.wait-p2s(outline, color='grey', @click='refresh', large)
+          v-btn.animated.fadeInDown.wait-p2s.mr-3(outlined, color='grey', @click='refresh', large)
             v-icon refresh
           v-btn.animated.fadeInDown(color='success', @click='save', depressed, large)
             v-icon(left) check
@@ -20,16 +20,16 @@
             .subheading {{$t('admin:auth.strategies')}}
           v-list(two-line, dense).py-0
             template(v-for='(str, idx) in strategies')
-              v-list-tile(:key='str.key', @click='selectedStrategy = str.key', :disabled='!str.isAvailable')
-                v-list-tile-avatar
+              v-list-item(:key='str.key', @click='selectedStrategy = str.key', :disabled='!str.isAvailable')
+                v-list-item-avatar
                   v-icon(color='grey', v-if='!str.isAvailable') indeterminate_check_box
                   v-icon(color='primary', v-else-if='str.isEnabled && str.key !== `local`', v-ripple, @click='str.isEnabled = false') check_box
                   v-icon(color='primary', v-else-if='str.isEnabled && str.key === `local`') check_box
                   v-icon(color='grey', v-else, v-ripple, @click='str.isEnabled = true') check_box_outline_blank
-                v-list-tile-content
-                  v-list-tile-title.body-2(:class='!str.isAvailable ? `grey--text` : (selectedStrategy === str.key ? `primary--text` : ``)') {{ str.title }}
-                  v-list-tile-sub-title.caption(:class='!str.isAvailable ? `grey--text text--lighten-1` : (selectedStrategy === str.key ? `blue--text ` : ``)') {{ str.description }}
-                v-list-tile-avatar(v-if='selectedStrategy === str.key')
+                v-list-item-content
+                  v-list-item-title.body-2(:class='!str.isAvailable ? `grey--text` : (selectedStrategy === str.key ? `primary--text` : ``)') {{ str.title }}
+                  v-list-item-sub-title.caption(:class='!str.isAvailable ? `grey--text text--lighten-1` : (selectedStrategy === str.key ? `blue--text ` : ``)') {{ str.description }}
+                v-list-item-avatar(v-if='selectedStrategy === str.key')
                   v-icon.animated.fadeInLeft(color='primary') arrow_forward_ios
               v-divider(v-if='idx < strategies.length - 1')
 
@@ -39,7 +39,7 @@
           v-card-text
             v-text-field.md2(
               v-model='jwtAudience'
-              outline
+              outlined
               prepend-icon='account_balance'
               :label='$t(`admin:auth.jwtAudience`)'
               :hint='$t(`admin:auth.jwtAudienceHint`)'
@@ -47,7 +47,7 @@
             )
             v-text-field.mt-3.md2(
               v-model='jwtExpiration'
-              outline
+              outlined
               prepend-icon='schedule'
               :label='$t(`admin:auth.tokenExpiration`)'
               :hint='$t(`admin:auth.tokenExpirationHint`)'
@@ -55,7 +55,7 @@
             )
             v-text-field.mt-3.md2(
               v-model='jwtRenewablePeriod'
-              outline
+              outlined
               prepend-icon='update'
               :label='$t(`admin:auth.tokenRenewalPeriod`)'
               :hint='$t(`admin:auth.tokenRenewalPeriodHint`)'
@@ -85,8 +85,7 @@
               template(v-else, v-for='cfg in strategy.config')
                 v-select(
                   v-if='cfg.value.type === "string" && cfg.value.enum'
-                  outline
-                  background-color='grey lighten-2'
+                  outlined
                   :items='cfg.value.enum'
                   :key='cfg.key'
                   :label='cfg.value.title'
@@ -108,8 +107,7 @@
                   )
                 v-textarea(
                   v-else-if='cfg.value.type === "string" && cfg.value.multiline'
-                  outline
-                  background-color='grey lighten-2'
+                  outlined
                   :key='cfg.key'
                   :label='cfg.value.title'
                   v-model='cfg.value.value'
@@ -120,8 +118,7 @@
                   )
                 v-text-field(
                   v-else
-                  outline
-                  background-color='grey lighten-2'
+                  outlined
                   :key='cfg.key'
                   :label='cfg.value.title'
                   v-model='cfg.value.value'
@@ -153,7 +150,7 @@
                   :label='$t(`admin:auth.domainsWhitelist`)'
                   v-model='strategy.domainWhitelist'
                   prepend-icon='mail_outline'
-                  outline
+                  outlined
                   :disabled='!strategy.selfRegistration'
                   :hint='$t(`admin:auth.domainsWhitelistHint`)'
                   persistent-hint
@@ -164,7 +161,7 @@
                   chips
                   )
                 v-autocomplete.mt-3.ml-3(
-                  outline
+                  outlined
                   :disabled='!strategy.selfRegistration'
                   :items='groups'
                   item-text='name'

+ 56 - 56
client/components/admin/admin-contribute.vue

@@ -91,18 +91,18 @@
             v-btn(outline, small, href='https://opencollective.com/wikijs/order/1273') Become a Sponsor
           v-list(two-line)
             template(v-for='(sponsor, idx) in sponsors')
-              v-list-tile(:key='sponsor.id')
-                v-list-tile-avatar
+              v-list-item(:key='sponsor.id')
+                v-list-item-avatar
                   img(v-if='sponsor.image', :src='sponsor.image')
                   v-avatar(v-else, color='teal', size='40')
                     span.white--text.subheading {{sponsor.name[0].toUpperCase()}}
-                v-list-tile-content
-                  v-list-tile-title {{sponsor.name}}
-                  v-list-tile-sub-title {{sponsor.description}}
-                v-list-tile-action(v-if='sponsor.twitter')
+                v-list-item-content
+                  v-list-item-title {{sponsor.name}}
+                  v-list-item-sub-title {{sponsor.description}}
+                v-list-item-action(v-if='sponsor.twitter')
                   v-btn(icon, :href='sponsor.twitter', target='_blank')
                     icon-twitter(fillColor='#9e9e9e')
-                v-list-tile-action(v-if='sponsor.website')
+                v-list-item-action(v-if='sponsor.website')
                   v-btn(icon, :href='sponsor.website', target='_blank')
                     v-icon(color='grey') public
               v-divider(v-if='idx < sponsors.length - 1')
@@ -112,91 +112,91 @@
             v-btn(outline, small, href='https://opencollective.com/wikijs/order/1272') Become a Backer
           v-list(two-line, dense)
             template(v-for='(backer, idx) in backers')
-              v-list-tile(:key='backer.id')
-                v-list-tile-avatar
+              v-list-item(:key='backer.id')
+                v-list-item-avatar
                   img(v-if='backer.image', :src='backer.image')
                   v-avatar(v-else, color='blue-grey', size='40')
                     span.white--text.subheading {{backer.name[0].toUpperCase()}}
-                v-list-tile-content
-                  v-list-tile-title {{backer.name}}
-                  v-list-tile-sub-title {{backer.description}}
-                v-list-tile-action(v-if='backer.twitter')
+                v-list-item-content
+                  v-list-item-title {{backer.name}}
+                  v-list-item-sub-title {{backer.description}}
+                v-list-item-action(v-if='backer.twitter')
                   v-btn(icon, :href='backer.twitter', target='_blank')
                     icon-twitter(fillColor='#9e9e9e')
-                v-list-tile-action(v-if='backer.website')
+                v-list-item-action(v-if='backer.website')
                   v-btn(icon, :href='backer.website', target='_blank')
                     v-icon(color='grey') public
               v-divider(v-if='idx < backers.length - 1')
           v-toolbar(color='primary', dense, dark)
             .subheading Special Thanks
           v-list(two-line)
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 img(src='https://static.requarks.io/logo/algolia.svg', alt='Algolia')
-              v-list-tile-content
-                v-list-tile-title Algolia
-                v-list-tile-sub-title Algolia is a powerful search-as-a-service solution, made easy to use with API clients, UI libraries, and pre-built integrations.
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Algolia
+                v-list-item-sub-title Algolia is a powerful search-as-a-service solution, made easy to use with API clients, UI libraries, and pre-built integrations.
+              v-list-item-action
                 v-btn(icon, href='https://www.algolia.com/', target='_blank')
                   v-icon(color='grey') public
             v-divider
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 img(src='https://static.requarks.io/logo/browserstack.svg', alt='Browserstack')
-              v-list-tile-content
-                v-list-tile-title BrowserStack
-                v-list-tile-sub-title BrowserStack is a cloud web and mobile testing platform that enables developers to test their websites and mobile applications.
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title BrowserStack
+                v-list-item-sub-title BrowserStack is a cloud web and mobile testing platform that enables developers to test their websites and mobile applications.
+              v-list-item-action
                 v-btn(icon, href='https://www.browserstack.com/', target='_blank')
                   v-icon(color='grey') public
             v-divider
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 img(src='https://static.requarks.io/logo/cloudflare.svg', alt='Cloudflare')
-              v-list-tile-content
-                v-list-tile-title Cloudflare
-                v-list-tile-sub-title Providing content delivery network services, DDoS mitigation, Internet security and distributed domain name server services.
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Cloudflare
+                v-list-item-sub-title Providing content delivery network services, DDoS mitigation, Internet security and distributed domain name server services.
+              v-list-item-action
                 v-btn(icon, href='https://www.cloudflare.com/', target='_blank')
                   v-icon(color='grey') public
             v-divider
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 img(src='https://static.requarks.io/logo/digitalocean.svg', alt='DigitalOcean')
-              v-list-tile-content
-                v-list-tile-title DigitalOcean
-                v-list-tile-sub-title Providing developers and businesses a reliable, easy-to-use cloud computing platform of virtual servers (Droplets), object storage (Spaces), and more.
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title DigitalOcean
+                v-list-item-sub-title Providing developers and businesses a reliable, easy-to-use cloud computing platform of virtual servers (Droplets), object storage (Spaces), and more.
+              v-list-item-action
                 v-btn(icon, href='https://m.do.co/c/5f7445bfa4d0', target='_blank')
                   v-icon(color='grey') public
             v-divider
-            v-list-tile
-              v-list-tile-avatar(tile)
+            v-list-item
+              v-list-item-avatar(tile)
                 img(src='/svg/logo-icons8.svg', alt='Icons8')
-              v-list-tile-content
-                v-list-tile-title Icons8
-                v-list-tile-sub-title All the Icons You Need. Guaranteed.
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Icons8
+                v-list-item-sub-title All the Icons You Need. Guaranteed.
+              v-list-item-action
                 v-btn(icon, href='https://icons8.com', target='_blank')
                   v-icon(color='grey') public
             v-divider
-            v-list-tile
-              v-list-tile-avatar(tile)
+            v-list-item
+              v-list-item-avatar(tile)
                 img(src='https://static.requarks.io/logo/lokalise.png', alt='Lokalise')
-              v-list-tile-content
-                v-list-tile-title Lokalise
-                v-list-tile-sub-title Lokalise is a translation management system built for agile teams who want to automate their localization process.
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Lokalise
+                v-list-item-sub-title Lokalise is a translation management system built for agile teams who want to automate their localization process.
+              v-list-item-action
                 v-btn(icon, href='https://lokalise.co', target='_blank')
                   v-icon(color='grey') public
             v-divider
-            v-list-tile
-              v-list-tile-avatar(tile)
+            v-list-item
+              v-list-item-avatar(tile)
                 img(src='https://static.requarks.io/logo/netlify.svg', alt='Netlify')
-              v-list-tile-content
-                v-list-tile-title Netlify
-                v-list-tile-sub-title Deploy modern static websites with Netlify. Get CDN, Continuous deployment, 1-click HTTPS, and all the services you need.
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Netlify
+                v-list-item-sub-title Deploy modern static websites with Netlify. Get CDN, Continuous deployment, 1-click HTTPS, and all the services you need.
+              v-list-item-action
                 v-btn(icon, href='https://wwwnetlify.com', target='_blank')
                   v-icon(color='grey') public
 

+ 24 - 22
client/components/admin/admin-dashboard.vue

@@ -10,8 +10,8 @@
       v-flex(xs12 md6 lg4 xl3 d-flex)
         v-card.primary.dashboard-card.animated.fadeInUp(dark)
           v-card-text
-            v-icon.dashboard-icon insert_drive_file
-            .subheading {{$t('admin:dashboard.pages')}}
+            v-icon.dashboard-icon mdi-file-document-outline
+            .overline {{$t('admin:dashboard.pages')}}
             animated-number.display-1(
               :value='info.pagesTotal'
               :duration='2000'
@@ -21,8 +21,8 @@
       v-flex(xs12 md6 lg4 xl3 d-flex)
         v-card.indigo.lighten-1.dashboard-card.animated.fadeInUp.wait-p2s(dark)
           v-card-text
-            v-icon.dashboard-icon person
-            .subheading {{$t('admin:dashboard.users')}}
+            v-icon.dashboard-icon mdi-account
+            .overline {{$t('admin:dashboard.users')}}
             animated-number.display-1(
               :value='info.usersTotal'
               :duration='2000'
@@ -32,8 +32,8 @@
       v-flex(xs12 md6 lg4 xl3 d-flex)
         v-card.indigo.lighten-2.dashboard-card.animated.fadeInUp.wait-p4s(dark)
           v-card-text
-            v-icon.dashboard-icon people
-            .subheading {{$t('admin:dashboard.groups')}}
+            v-icon.dashboard-icon mdi-account-group
+            .overline {{$t('admin:dashboard.groups')}}
             animated-number.display-1(
               :value='info.groupsTotal'
               :duration='2000'
@@ -46,19 +46,19 @@
           dark
           )
           v-btn.btn-animate-wrench(fab, absolute, right, top, small, light, to='system', v-if='hasPermission(`manage:system`)')
-            v-icon(:color='isLatestVersion ? `teal` : `red darken-4`') build
+            v-icon(:color='isLatestVersion ? `teal` : `red darken-4`', small) mdi-wrench
           v-card-text
-            v-icon.dashboard-icon blur_on
+            v-icon.dashboard-icon mdi-blur
             .subheading Wiki.js {{info.currentVersion}}
             .body-2(v-if='isLatestVersion') {{$t('admin:dashboard.versionLatest')}}
             .body-2(v-else) {{$t('admin:dashboard.versionNew', { version: info.latestVersion })}}
       v-flex(xs12, xl6)
         v-card.radius-7.animated.fadeInUp.wait-p2s
-          v-card-title.subheading(:class='$vuetify.dark ? `grey darken-2` : `grey lighten-5`') Recent Pages
+          v-card-title.subtitle-1(:class='$vuetify.dark ? `grey darken-2` : `grey lighten-5`') Recent Pages
           v-data-table.pb-2(
             :items='recentPages'
-            hide-actions
-            hide-headers
+            hide-default-footer
+            hide-default-header
             )
             template(slot='items' slot-scope='props')
               td(width='20', style='padding-right: 0;'): v-icon insert_drive_file
@@ -71,11 +71,11 @@
                 .caption Created {{ props.item.createdAt | moment('calendar') }}
       v-flex(xs12, xl6)
         v-card.radius-7.animated.fadeInUp.wait-p4s
-          v-card-title.subheading(:class='$vuetify.dark ? `grey darken-2` : `grey lighten-5`') Most Popular Pages
+          v-card-title.subtitle-1(:class='$vuetify.dark ? `grey darken-2` : `grey lighten-5`') Most Popular Pages
           v-data-table.pb-2(
             :items='popularPages'
-            hide-actions
-            hide-headers
+            hide-default-footer
+            hide-default-header
             )
             template(slot='items' slot-scope='props')
               td(width='20', style='padding-right: 0;'): v-icon insert_drive_file
@@ -91,11 +91,12 @@
         v-card.dashboard-contribute.animated.fadeInUp.wait-p4s
           v-card-text
             img(src='/svg/icon-heart-health.svg', alt='Contribute', style='height: 80px;')
-            .pl-3
-              .subheading {{$t('admin:contribute.title')}}
-              .body-2.pt-2 {{$t('admin:dashboard.contributeSubtitle')}}
-              .body-1 {{$t('admin:dashboard.contributeHelp')}}
-              v-btn.mx-0.mt-2(:color='$vuetify.dark ? `indigo lighten-3` : `indigo`', outline, small, to='/contribute') {{$t('admin:dashboard.contributeLearnMore')}}
+            .pl-5
+              .subtitle-1 {{$t('admin:contribute.title')}}
+              .body-2.mt-3: strong {{$t('admin:dashboard.contributeSubtitle')}}
+              .body-2 {{$t('admin:dashboard.contributeHelp')}}
+              v-btn.mx-0.mt-4(:color='$vuetify.dark ? `indigo lighten-3` : `indigo`', outlined, small, to='/contribute')
+                .caption: strong {{$t('admin:dashboard.contributeLearnMore')}}
 
 </template>
 
@@ -140,6 +141,7 @@ export default {
 
 .dashboard-card {
   display: flex;
+  width: 100%;
   border-radius: 7px;
 
   .v-card__text {
@@ -161,10 +163,10 @@ export default {
   .v-card__text {
     display: flex;
     align-items: center;
-    color: mc('indigo', '500');
+    color: mc('indigo', '500') !important;
 
     @at-root .theme--dark & {
-      color: mc('grey', '300');
+      color: mc('grey', '300') !important;
     }
   }
 }
@@ -173,7 +175,7 @@ export default {
   position: absolute;
   right: 0;
   top: 12px;
-  font-size: 120px;
+  font-size: 120px !important;
   opacity: .25;
 }
 

+ 24 - 26
client/components/admin/admin-general.vue

@@ -9,61 +9,60 @@
             .subheading.grey--text.animated.fadeInLeft {{ $t('admin:general.subtitle') }}
           v-spacer
           v-btn.animated.fadeInDown(color='success', depressed, @click='save', large)
-            v-icon(left) check
+            v-icon(left) mdi-check
             span {{$t('common:actions.apply')}}
         v-form.pt-3
           v-layout(row wrap)
             v-flex(lg6 xs12)
               v-form
-                v-card.wiki-form.animated.fadeInUp
+                v-card.animated.fadeInUp
                   v-toolbar(color='primary', dark, dense, flat)
-                    v-toolbar-title
-                      .subheading {{ $t('admin:general.siteInfo') }}
+                    v-toolbar-title.subtitle-1 {{ $t('admin:general.siteInfo') }}
                   v-subheader {{$t('admin:general.general')}}
                   .px-3.pb-3
                     v-text-field(
-                      outline
+                      outlined
                       :label='$t(`admin:general.siteUrl`)'
                       required
                       :counter='255'
                       v-model='config.host'
-                      prepend-icon='label_important'
+                      prepend-icon='mdi-label-variant-outline'
                       :hint='$t(`admin:general.siteUrlHint`)'
                       persistent-hint
                       )
-                    v-text-field.mt-2(
-                      outline
+                    v-text-field.mt-3(
+                      outlined
                       :label='$t(`admin:general.siteTitle`)'
                       required
                       :counter='50'
                       v-model='config.title'
-                      prepend-icon='public'
+                      prepend-icon='mdi-earth'
                       :hint='$t(`admin:general.siteTitleHint`)'
                       persistent-hint
                       )
                   v-divider
-                  v-subheader {{$t('admin:general.logo')}} #[v-chip.ml-2(label, color='grey', small, outline) coming soon]
+                  v-subheader {{$t('admin:general.logo')}} #[v-chip.ml-2(label, color='grey', small, outlined) coming soon]
                   v-card-text.pb-4.pl-5
                     v-layout.px-3(row, align-center)
                       v-avatar(size='100', :color='$vuetify.dark ? `grey darken-2` : `grey lighten-3`', :tile='config.logoIsSquare')
                       .ml-4
-                        v-btn.mx-0(color='teal', depressed, disabled)
-                          v-icon(left) cloud_upload
+                        v-btn.mr-3(color='teal', depressed, disabled)
+                          v-icon(left) mdi-cloud-upload
                           span {{$t('admin:general.uploadLogo')}}
                         v-btn(color='teal', depressed, disabled)
-                          v-icon(left) clear
+                          v-icon(left) mdi-close
                           span {{$t('admin:general.uploadClear')}}
-                        .caption.grey--text {{$t('admin:general.uploadSizeHint', { size: '120x120' })}}
+                        .caption.mt-3.grey--text {{$t('admin:general.uploadSizeHint', { size: '120x120' })}}
                         .caption.grey--text {{$t('admin:general.uploadTypesHint', { typeList: 'SVG, PNG', lastType: 'JPG' })}}.
                   v-divider
                   v-subheader {{$t('admin:general.footerCopyright')}}
                   .px-3.pb-3
                     v-text-field(
-                      outline
+                      outlined
                       :label='$t(`admin:general.companyName`)'
                       v-model='config.company'
                       :counter='255'
-                      prepend-icon='business'
+                      prepend-icon='mdi-domain'
                       persistent-hint
                       :hint='$t(`admin:general.companyNameHint`)'
                       )
@@ -71,31 +70,30 @@
                   v-subheader SEO
                   .px-3.pb-3
                     v-text-field(
-                      outline
+                      outlined
                       :label='$t(`admin:general.siteDescription`)'
                       :counter='255'
                       v-model='config.description'
-                      prepend-icon='explore'
+                      prepend-icon='mdi-compass'
                       :hint='$t(`admin:general.siteDescriptionHint`)'
                       persistent-hint
                       )
-                    v-select.mt-2(
-                      outline
+                    v-select.mt-3(
+                      outlined
                       :label='$t(`admin:general.metaRobots`)'
                       multiple
                       :items='metaRobots'
                       v-model='config.robots'
-                      prepend-icon='explore'
+                      prepend-icon='mdi-compass'
                       :return-object='false'
                       :hint='$t(`admin:general.metaRobotsHint`)'
                       persistent-hint
                       )
 
             v-flex(lg6 xs12)
-              v-card.wiki-form.animated.fadeInUp.wait-p4s
+              v-card.animated.fadeInUp.wait-p4s
                 v-toolbar(color='primary', dark, dense, flat)
-                  v-toolbar-title
-                    .subheading Features
+                  v-toolbar-title.subtitle-1 Features
                   v-spacer
                   v-chip(label, color='white', small).primary--text coming soon
                 v-card-text
@@ -108,11 +106,11 @@
                     disabled
                     )
                   v-text-field.mt-3(
-                    outline
+                    outlined
                     label='TinyPNG API Key'
                     :counter='255'
                     v-model='config.description'
-                    prepend-icon='subdirectory_arrow_right'
+                    prepend-icon='mdi-subdirectory-arrow-right'
                     hint='Get your API key at https://tinypng.com/developers'
                     persistent-hint
                     disabled

+ 25 - 25
client/components/admin/admin-groups-edit-rules.vue

@@ -29,25 +29,25 @@
           v-btn.is-icon(slot='activator', flat, outline, color='primary')
             v-icon more_horiz
           v-list(dense)
-            v-list-tile(@click='comingSoon')
-              v-list-tile-avatar
+            v-list-item(@click='comingSoon')
+              v-list-item-avatar
                 v-icon keyboard_capslock
-              v-list-tile-title Load Preset
+              v-list-item-title Load Preset
             v-divider
-            v-list-tile(@click='comingSoon')
-              v-list-tile-avatar
+            v-list-item(@click='comingSoon')
+              v-list-item-avatar
                 v-icon publish
-              v-list-tile-title Save As Preset
+              v-list-item-title Save As Preset
             v-divider
-            v-list-tile(@click='comingSoon')
-              v-list-tile-avatar
+            v-list-item(@click='comingSoon')
+              v-list-item-avatar
                 v-icon cloud_upload
-              v-list-tile-title Import Rules
+              v-list-item-title Import Rules
             v-divider
-            v-list-tile(@click='comingSoon')
-              v-list-tile-avatar
+            v-list-item(@click='comingSoon')
+              v-list-item-avatar
                 v-icon cloud_download
-              v-list-tile-title Export Rules
+              v-list-item-title Export Rules
       v-card-text(:class='$vuetify.dark ? `grey darken-4-l5` : `white`')
         .rules
           .caption(v-if='group.pageRules.length === 0')
@@ -81,15 +81,15 @@
                 v-chip.white--text.ml-0(v-if='index <= 1', small, label, :color='rule.deny ? `red` : `green`').caption {{ item.value }}
                 v-chip.white--text.ml-0(v-if='index === 2', small, label, :color='rule.deny ? `red lighten-2` : `green lighten-2`').caption + {{ rule.roles.length - 2 }} more
               template(slot='item', slot-scope='props')
-                v-list-tile-action(style='min-width: 30px;')
+                v-list-item-action(style='min-width: 30px;')
                   v-checkbox(
                     v-model='props.tile.props.value'
                     hide-details
                     color='primary'
                   )
                 v-icon.mr-2(:color='rule.deny ? `red` : `green`') {{props.item.icon}}
-                v-list-tile-content
-                  v-list-tile-title.body-2 {{props.item.text}}
+                v-list-item-content
+                  v-list-item-title.body-2 {{props.item.text}}
                 v-chip.mr-2.grey--text(label, small, :color='$vuetify.dark ? `grey darken-4` : `grey lighten-4`').caption {{props.item.value}}
 
             //- Match
@@ -105,10 +105,10 @@
               template(slot='selection', slot-scope='{ item, index }')
                 .body-1 {{item.text}}
               template(slot='item', slot-scope='data')
-                v-list-tile-avatar
+                v-list-item-avatar
                   v-avatar.white--text.radius-4(color='blue', size='30', tile) {{ data.item.icon }}
-                v-list-tile-content
-                  v-list-tile-title(v-html='data.item.text')
+                v-list-item-content
+                  v-list-item-title(v-html='data.item.text')
             //- Locales
             v-select.mr-1(
               :background-color='$vuetify.dark ? `grey darken-3-d5` : `blue-grey lighten-5`'
@@ -125,8 +125,8 @@
               template(slot='selection', slot-scope='{ item, index }')
                 v-chip.white--text.ml-0(v-if='rule.locales.length === 1', small, label, :color='rule.deny ? `red` : `green`').caption {{ item.value.toUpperCase() }}
                 v-chip.white--text.ml-0(v-else-if='index === 0', small, label, :color='rule.deny ? `red` : `green`').caption {{ rule.locales.length }} locales
-              v-list-tile(slot='prepend-item', @click='rule.locales = []')
-                v-list-tile-action(style='min-width: 30px;')
+              v-list-item(slot='prepend-item', @click='rule.locales = []')
+                v-list-item-action(style='min-width: 30px;')
                   v-checkbox(
                     :input-value='rule.locales.length === 0'
                     hide-details
@@ -134,19 +134,19 @@
                     readonly
                   )
                 v-icon.mr-2(:color='rule.deny ? `red` : `green`') public
-                v-list-tile-content
-                  v-list-tile-title.body-2 Any Locale
+                v-list-item-content
+                  v-list-item-title.body-2 Any Locale
               v-divider(slot='prepend-item')
               template(slot='item', slot-scope='props')
-                v-list-tile-action(style='min-width: 30px;')
+                v-list-item-action(style='min-width: 30px;')
                   v-checkbox(
                     v-model='props.tile.props.value'
                     hide-details
                     color='primary'
                   )
                 v-icon.mr-2(:color='rule.deny ? `red` : `green`') language
-                v-list-tile-content
-                  v-list-tile-title.body-2 {{props.item.text}}
+                v-list-item-content
+                  v-list-item-title.body-2 {{props.item.text}}
                 v-chip.mr-2.grey--text(label, small, :color='$vuetify.dark ? `grey darken-4` : `grey lighten-4`').caption {{props.item.value.toUpperCase()}}
 
             //- Path

+ 8 - 8
client/components/admin/admin-groups-edit-users.vue

@@ -30,16 +30,16 @@
             v-menu(bottom, right, min-width='200')
               v-btn(icon, slot='activator'): v-icon.grey--text.text--darken-1 more_horiz
               v-list
-                v-list-tile(:to='`/users/` + props.item.id')
-                  v-list-tile-action: v-icon(color='primary') person
-                  v-list-tile-content
-                    v-list-tile-title View User Profile
+                v-list-item(:to='`/users/` + props.item.id')
+                  v-list-item-action: v-icon(color='primary') person
+                  v-list-item-content
+                    v-list-item-title View User Profile
                 template(v-if='props.item.id !== 2')
                   v-divider
-                  v-list-tile(@click='unassignUser(props.item.id)')
-                    v-list-tile-action: v-icon(color='orange') highlight_off
-                    v-list-tile-content
-                      v-list-tile-title Unassign
+                  v-list-item(@click='unassignUser(props.item.id)')
+                    v-list-item-action: v-icon(color='orange') highlight_off
+                    v-list-item-content
+                      v-list-item-title Unassign
       template(slot='no-data')
         v-alert.ma-3(icon='warning', :value='true', outline) No users to display.
     .text-xs-center.py-2(v-if='group.users.length > 15')

+ 10 - 10
client/components/admin/admin-groups.vue

@@ -8,19 +8,19 @@
             .headline.blue--text.text--darken-2.animated.fadeInLeft Groups
             .subheading.grey--text.animated.fadeInLeft.wait-p4s Manage groups and their permissions
           v-spacer
-          v-btn.animated.fadeInDown.wait-p2s(color='grey', outline, @click='refresh', large)
-            v-icon refresh
+          v-btn.animated.fadeInDown.wait-p2s.mr-3(color='grey', outlined, @click='refresh', large)
+            v-icon mdi-refresh
           v-dialog(v-model='newGroupDialog', max-width='500')
-            v-btn.animated.fadeInDown(color='primary', depressed, slot='activator', large)
-              v-icon(left) add
-              span New Group
-            v-card.wiki-form
+            template(v-slot:activator='{ on }')
+              v-btn.animated.fadeInDown(color='primary', depressed, v-on='on', large)
+                v-icon(left) mdi-plus
+                span New Group
+            v-card
               .dialog-header.is-short New Group
               v-card-text
                 v-text-field.md2(
-                  outline
-                  background-color='grey lighten-3'
-                  prepend-icon='people'
+                  outlined
+                  prepend-icon='mdi-account-group'
                   v-model='newGroupName'
                   label='Group Name'
                   counter='255'
@@ -30,7 +30,7 @@
                   )
               v-card-chin
                 v-spacer
-                v-btn(flat, @click='newGroupDialog = false') Cancel
+                v-btn(text, @click='newGroupDialog = false') Cancel
                 v-btn(color='primary', @click='createGroup') Create
         v-card.mt-3.animated.fadeInUp
           v-data-table(

+ 46 - 52
client/components/admin/admin-locale.vue

@@ -9,20 +9,19 @@
             .subheading.grey--text.animated.fadeInLeft.wait-p4s {{ $t('admin:locale.subtitle') }}
           v-spacer
           v-btn.animated.fadeInDown(color='success', depressed, @click='save', large, :loading='loading')
-            v-icon(left) check
+            v-icon(left) mdi-check
             span {{$t('common:actions.apply')}}
         v-form.pt-3
           v-layout(row wrap)
             v-flex(lg6 xs12)
               v-card.wiki-form.animated.fadeInUp
                 v-toolbar(color='primary', dark, dense, flat)
-                  v-toolbar-title
-                    .subheading {{ $t('admin:locale.settings') }}
+                  v-toolbar-title.subtitle-1 {{ $t('admin:locale.settings') }}
                 v-card-text
                   v-select(
-                    outline
+                    outlined
                     :items='installedLocales'
-                    prepend-icon='language'
+                    prepend-icon='mdi-web'
                     v-model='selectedLocale'
                     item-value='code'
                     item-text='nativeName'
@@ -32,13 +31,13 @@
                   )
                     template(slot='item', slot-scope='data')
                       template(v-if='typeof data.item !== "object"')
-                        v-list-tile-content(v-text='data.item')
+                        v-list-item-content(v-text='data.item')
                       template(v-else)
-                        v-list-tile-avatar
+                        v-list-item-avatar
                           v-avatar.blue.white--text(tile, size='40', v-html='data.item.code.toUpperCase()')
-                        v-list-tile-content
-                          v-list-tile-title(v-html='data.item.name')
-                          v-list-tile-sub-title(v-html='data.item.nativeName')
+                        v-list-item-content
+                          v-list-item-title(v-html='data.item.name')
+                          v-list-item-sub-title(v-html='data.item.nativeName')
                   v-divider.mt-3
                   v-switch(
                     v-model='autoUpdate'
@@ -50,8 +49,7 @@
 
               v-card.wiki-form.mt-3.animated.fadeInUp.wait-p2s
                 v-toolbar(color='primary', dark, dense, flat)
-                  v-toolbar-title
-                    .subheading {{ $t('admin:locale.namespacing') }}
+                  v-toolbar-title.subtitle-1 {{ $t('admin:locale.namespacing') }}
                 v-card-text
                   v-switch(
                     v-model='namespacing'
@@ -61,19 +59,19 @@
                     :hint='$t("admin:locale.namespaces.hint")'
                     )
                   v-alert.mt-3(
-                    outline
+                    outlined
                     color='orange'
                     :value='true'
-                    icon='warning'
+                    icon='mdi-alert'
                     )
                     span {{ $t('admin:locale.namespacingPrefixWarning.title', { langCode: selectedLocale }) }}
                     .caption.grey--text {{ $t('admin:locale.namespacingPrefixWarning.subtitle') }}
                   v-divider.mt-3.mb-4
                   v-select(
-                    outline
+                    outlined
                     :disabled='!namespacing'
                     :items='installedLocales'
-                    prepend-icon='language'
+                    prepend-icon='mdi-web'
                     multiple
                     chips
                     deletable-chips
@@ -87,52 +85,47 @@
                     )
                     template(slot='item', slot-scope='data')
                       template(v-if='typeof data.item !== "object"')
-                        v-list-tile-content(v-text='data.item')
+                        v-list-item-content(v-text='data.item')
                       template(v-else)
-                        v-list-tile-avatar
+                        v-list-item-avatar
                           v-avatar.blue.white--text(tile, size='40', v-html='data.item.code.toUpperCase()')
-                        v-list-tile-content
-                          v-list-tile-title(v-html='data.item.name')
-                          v-list-tile-sub-title(v-html='data.item.nativeName')
-                        v-list-tile-action
+                        v-list-item-content
+                          v-list-item-title(v-html='data.item.name')
+                          v-list-item-sub-title(v-html='data.item.nativeName')
+                        v-list-item-action
                           v-checkbox(:input-value='data.tile.props.value', color='primary', value)
             v-flex(lg6 xs12)
               v-card.animated.fadeInUp.wait-p4s
                 v-toolbar(color='teal', dark, dense, flat)
-                  v-toolbar-title
-                    .subheading {{ $t('admin:locale.downloadTitle') }}
+                  v-toolbar-title.subtitle-1 {{ $t('admin:locale.downloadTitle') }}
                 v-data-table(
                   :headers='headers',
                   :items='locales',
-                  hide-actions,
+                  hide-default-footer,
                   item-key='code',
-                  :rows-per-page-items='[-1]'
-                )
-                  template(v-slot:items='lc')
-                    td
-                      v-chip.white--text(label, color='teal', small) {{lc.item.code}}
-                    td
-                      strong {{lc.item.name}}
-                    td
-                      span {{ lc.item.nativeName }}
-                    td.text-xs-center
-                      v-icon(v-if='lc.item.isRTL') check
-                    td
-                      .d-flex.align-center.pl-4
-                        .caption.mr-2(:class='lc.item.availability <= 33 ? `red--text` : (lc.item.availability <= 66) ? `orange--text` : `green--text`') {{lc.item.availability}}%
-                        v-progress-circular(:value='lc.item.availability', width='2', size='20', :color='lc.item.availability <= 33 ? `red` : (lc.item.availability <= 66) ? `orange` : `green`')
-                    td.text-xs-center
-                      v-progress-circular(v-if='lc.item.isDownloading', indeterminate, color='blue', size='20', :width='2')
-                      v-btn(v-else-if='lc.item.isInstalled && lc.item.installDate < lc.item.updatedAt', icon, @click='download(lc.item)')
-                        v-icon.blue--text cached
-                      v-btn(v-else-if='lc.item.isInstalled', icon, @click='download(lc.item)')
-                        v-icon.green--text check
-                      v-btn(v-else, icon, @click='download(lc.item)')
-                        v-icon.grey--text cloud_download
+                  :items-per-page='1000'
+                  )
+                  template(v-slot:item.code='{ item }')
+                    v-chip.white--text(label, color='teal', small) {{item.code}}
+                  template(v-slot:item.name='{ item }')
+                    strong {{item.name}}
+                  template(v-slot:item.isRTL='{ item }')
+                    v-icon(v-if='item.isRTL') mdi-check
+                  template(v-slot:item.availability='{ item }')
+                    .d-flex.align-center.pl-4
+                      v-progress-circular(:value='item.availability', width='2', size='20', :color='item.availability <= 33 ? `red` : (item.availability <= 66) ? `orange` : `green`')
+                      .caption.ml-2(:class='item.availability <= 33 ? `red--text` : (item.availability <= 66) ? `orange--text` : `green--text`') {{item.availability}}%
+                  template(v-slot:item.isInstalled='{ item }')
+                    v-progress-circular(v-if='item.isDownloading', indeterminate, color='blue', size='20', :width='2')
+                    v-btn(v-else-if='item.isInstalled && item.installDate < item.updatedAt', icon, @click='download(item)')
+                      v-icon.blue--text mdi-cached
+                    v-btn(v-else-if='item.isInstalled', icon, @click='download(item)')
+                      v-icon.green--text mdi-check
+                    v-btn(v-else, icon, small, @click='download(item)')
+                      v-icon.grey--text mdi-cloud-download
               v-card.wiki-form.mt-3.animated.fadeInUp.wait-p5s
                 v-toolbar(color='teal', dark, dense, flat)
-                  v-toolbar-title
-                    .subheading {{ $t('admin:locale.sideload') }}
+                  v-toolbar-title.subtitle-1 {{ $t('admin:locale.sideload') }}
                   v-spacer
                   v-chip(label, color='white', small).teal--text coming soon
                 v-card-text
@@ -170,7 +163,7 @@ export default {
           text: this.$t('admin:locale.code'),
           align: 'left',
           value: 'code',
-          width: 10
+          width: 90
         },
         {
           text: this.$t('admin:locale.name'),
@@ -193,12 +186,13 @@ export default {
           text: this.$t('admin:locale.availability'),
           align: 'center',
           value: 'availability',
+          sortable: false,
           width: 100
         },
         {
           text: this.$t('admin:locale.download'),
           align: 'center',
-          value: 'code',
+          value: 'isInstalled',
           sortable: false,
           width: 100
         }

+ 10 - 10
client/components/admin/admin-mail.vue

@@ -22,7 +22,7 @@
                   v-subheader {{ $t('admin:mail.sender') }}
                   .px-3.pb-3
                     v-text-field(
-                      outline
+                      outlined
                       v-model='config.senderName'
                       :label='$t(`admin:mail.senderName`)'
                       required
@@ -30,7 +30,7 @@
                       prepend-icon='person'
                       )
                     v-text-field(
-                      outline
+                      outlined
                       v-model='config.senderEmail'
                       :label='$t(`admin:mail.senderEmail`)'
                       required
@@ -41,7 +41,7 @@
                   v-subheader {{ $t('admin:mail.smtp') }}
                   .px-3.pb-3
                     v-text-field(
-                      outline
+                      outlined
                       v-model='config.host'
                       :label='$t(`admin:mail.smtpHost`)'
                       required
@@ -49,7 +49,7 @@
                       prepend-icon='memory'
                       )
                     v-text-field(
-                      outline
+                      outlined
                       v-model='config.port'
                       :label='$t(`admin:mail.smtpPort`)'
                       required
@@ -67,7 +67,7 @@
                       prepend-icon='vpn_lock'
                       )
                     v-text-field.mt-3(
-                      outline
+                      outlined
                       v-model='config.user'
                       :label='$t(`admin:mail.smtpUser`)'
                       required
@@ -75,7 +75,7 @@
                       prepend-icon='lock_outline'
                       )
                     v-text-field(
-                      outline
+                      outlined
                       v-model='config.pass'
                       :label='$t(`admin:mail.smtpPwd`)'
                       required
@@ -98,7 +98,7 @@
                       prepend-icon='vpn_key'
                       )
                     v-text-field(
-                      outline
+                      outlined
                       v-model='config.dkimDomainName'
                       :label='$t(`admin:mail.dkimDomainName`)'
                       :counter='255'
@@ -106,7 +106,7 @@
                       :disabled='!config.useDKIM'
                       )
                     v-text-field(
-                      outline
+                      outlined
                       v-model='config.dkimKeySelector'
                       :label='$t(`admin:mail.dkimKeySelector`)'
                       :counter='255'
@@ -114,7 +114,7 @@
                       :disabled='!config.useDKIM'
                       )
                     v-text-field(
-                      outline
+                      outlined
                       v-model='config.dkimPrivateKey'
                       :label='$t(`admin:mail.dkimPrivateKey`)'
                       prepend-icon='vpn_key'
@@ -131,7 +131,7 @@
                   .pa-3
                     .body-2.grey--text.text--darken-2 {{ $t('admin:mail.testHint') }}
                     v-text-field.mt-3(
-                      outline
+                      outlined
                       v-model='testEmail'
                       :label='$t(`admin:mail.testRecipient`)'
                       :counter='255'

+ 46 - 49
client/components/admin/admin-navigation.vue

@@ -8,30 +8,30 @@
             .headline.primary--text.animated.fadeInLeft {{$t('navigation.title')}}
             .subheading.grey--text.animated.fadeInLeft.wait-p4s {{$t('navigation.subtitle')}}
           v-spacer
-          v-btn.animated.fadeInDown.wait-p2s(outline, color='grey', @click='refresh', large)
-            v-icon refresh
+          v-btn.animated.fadeInDown.wait-p2s.mr-3(outlined, color='grey', @click='refresh', large)
+            v-icon mdi-refresh
           v-btn.animated.fadeInDown(color='success', depressed, @click='save', large)
-            v-icon(left) check
+            v-icon(left) mdi-check
             span {{$t('common:actions.apply')}}
         v-container.pa-0.mt-3(fluid, grid-list-lg)
           v-layout(row)
             v-flex(style='flex: 0 0 350px;')
               v-card.animated.fadeInUp
-                v-list.py-2(dense, dark, :class='navTree.length < 1 ? "grey lighten-4" : "primary"')
-                  v-list-tile(v-if='navTree.length < 1')
-                    v-list-tile-avatar: v-icon(color='grey') explore_off
-                    v-list-tile-content
+                v-list.py-2(dense, nav, dark, :class='navTree.length < 1 ? "grey lighten-4" : "primary"')
+                  v-list-item(v-if='navTree.length < 1')
+                    v-list-item-avatar(size='24'): v-icon(color='grey') explore_off
+                    v-list-item-content
                       .caption.grey--text {{$t('navigation.emptyList')}}
                   draggable(v-model='navTree')
                     template(v-for='navItem in navTree')
-                      v-list-tile(
+                      v-list-item(
                         v-if='navItem.kind === "link"'
                         :key='navItem.id'
                         :class='(navItem === current) ? "blue" : ""'
                         @click='selectItem(navItem)'
                         )
-                        v-list-tile-avatar: v-icon {{navItem.icon}}
-                        v-list-tile-title {{navItem.label}}
+                        v-list-item-avatar(size='24'): v-icon {{navItem.icon}}
+                        v-list-item-title {{navItem.label}}
                       .py-2.clickable(
                         v-else-if='navItem.kind === "divider"'
                         :key='navItem.id'
@@ -47,58 +47,56 @@
                         ) {{navItem.label}}
                 v-card-chin
                   v-menu(offset-y, bottom, min-width='200px', style='flex: 1 1;')
-                    v-btn(slot='activator', color='primary', depressed, block)
-                      v-icon(left) add
-                      span {{$t('common:actions.add')}}
+                    template(v-slot:activator='{ on }')
+                      v-btn(v-on='on', color='primary', depressed, block)
+                        v-icon(left) mdi-plus
+                        span {{$t('common:actions.add')}}
                     v-list
-                      v-list-tile(@click='addItem("link")')
-                        v-list-tile-avatar: v-icon link
-                        v-list-tile-title {{$t('navigation.link')}}
-                      v-list-tile(@click='addItem("header")')
-                        v-list-tile-avatar: v-icon title
-                        v-list-tile-title {{$t('navigation.header')}}
-                      v-list-tile(@click='addItem("divider")')
-                        v-list-tile-avatar: v-icon power_input
-                        v-list-tile-title {{$t('navigation.divider')}}
+                      v-list-item(@click='addItem("link")')
+                        v-list-item-avatar(size='24'): v-icon mdi-link
+                        v-list-item-title {{$t('navigation.link')}}
+                      v-list-item(@click='addItem("header")')
+                        v-list-item-avatar(size='24'): v-icon mdi-format-title
+                        v-list-item-title {{$t('navigation.header')}}
+                      v-list-item(@click='addItem("divider")')
+                        v-list-item-avatar(size='24'): v-icon mdi-minus
+                        v-list-item-title {{$t('navigation.divider')}}
             v-flex.animated.fadeInUp.wait-p2s
               v-card.wiki-form(v-if='current.kind === "link"')
-                v-toolbar(dense, color='blue', flat, dark)
-                  .subheading {{$t('navigation.edit', { kind: $t('navigation.link') })}}
+                v-toolbar(dense, color='blue', flat, dark).subtitle-1 {{$t('navigation.edit', { kind: $t('navigation.link') })}}
                 v-card-text
                   v-text-field(
-                    outline
+                    outlined
                     :label='$t("navigation.label")'
-                    prepend-icon='title'
+                    prepend-icon='mdi-format-title'
                     v-model='current.label'
                   )
                   v-text-field(
-                    outline
+                    outlined
                     :label='$t("navigation.icon")'
-                    prepend-icon='casino'
+                    prepend-icon='mdi-dice-5'
                     v-model='current.icon'
                     hide-details
                   )
-                  .caption.pt-3.pl-5 The default icon set is #[strong Material Icons]. In order to use another icon set, you must first select it in the Theme administration section.
-                  .caption.pt-3.pl-5 #[strong Material Icons] #[em (default)]
-                  .caption.pl-5 Refer to the #[a(href='https://material.io/tools/icons/?style=baseline', target='_blank') Material Icons Reference] for the list of all possible values.
+                  .caption.pt-3.pl-5 The default icon set is #[strong Material Design Icons]. In order to use another icon set, you must first select it in the Theme administration section.
                   .caption.pt-3.pl-5: strong Material Design Icons
-                  .caption.pl-5 Refer to the #[a(href='https://cdn.materialdesignicons.com/3.7.95/', target='_blank') Material Design Icons Reference] for the list of all possible values. You must prefix all values with #[code mdi-], e.g. #[code mdi-home]
+                  .caption.pl-5 Refer to the #[a(href='https://materialdesignicons.com/', target='_blank') Material Design Icons Reference] for the list of all possible values. You must prefix all values with #[code mdi-], e.g. #[code mdi-home]
                   .caption.pt-3.pl-5: strong Font Awesome 5
                   .caption.pl-5 Refer to the #[a(href='https://fontawesome.com/icons?d=gallery&m=free', target='_blank') Font Awesome 5 Reference] for the list of all possible values. You must prefix all values with #[code fas fa-], e.g. #[code fas fa-home]
                   .caption.pt-3.pl-5: strong Font Awesome 4
                   .caption.pl-5 Refer to the #[a(href='https://fontawesome.com/v4.7.0/icons/', target='_blank') Font Awesome 4 Reference] for the list of all possible values. You must prefix all values with #[code fa fa-], e.g. #[code fa fa-home]
                   v-select.mt-4(
-                    outline
+                    outlined
                     :label='$t("navigation.targetType")'
-                    prepend-icon='near_me'
+                    prepend-icon='mdi-near-me'
                     :items='navTypes'
                     v-model='current.targetType'
                   )
                   v-text-field(
                     v-if='current.targetType === `external`'
-                    outline
+                    outlined
                     :label='$t("navigation.target")'
-                    prepend-icon='near_me'
+                    prepend-icon='mdi-near-me'
                     v-model='current.target'
                   )
                   v-btn(
@@ -108,11 +106,11 @@
                     disabled
                     @click='selectPage'
                     )
-                    v-icon(left) search
+                    v-icon(left) mdi-search
                     span Select Page...
                   v-text-field(
                     v-else-if='current.targetType === `search`'
-                    outline
+                    outlined
                     :label='$t("navigation.navType.searchQuery")'
                     prepend-icon='search'
                     v-model='current.target'
@@ -120,28 +118,27 @@
 
                 v-card-chin
                   v-spacer
-                  v-btn(color='red', outline, @click='deleteItem(current)')
-                    v-icon(left) delete
+                  v-btn.px-5(color='red', outlined, @click='deleteItem(current)')
+                    v-icon(left) mdi-delete
                     span {{$t('navigation.delete', { kind: $t('navigation.link') })}}
               v-card(v-else-if='current.kind === "header"')
                 v-toolbar(dense, color='blue', flat, dark)
                   .subheading {{$t('navigation.edit', { kind: $t('navigation.header') })}}
                 v-card-text
                   v-text-field(
-                    outline
-                    background-color='grey lighten-2'
+                    outlined
                     :label='$t("navigation.label")'
-                    prepend-icon='title'
+                    prepend-icon='mdi-format-title'
                     v-model='current.label'
                   )
                 v-card-chin
                   v-spacer
-                  v-btn(color='red', outline, @click='deleteItem(current)')
-                    v-icon(left) delete
+                  v-btn.px-5(color='red', outlined, @click='deleteItem(current)')
+                    v-icon(left) mdi-delete
                     span {{$t('navigation.delete', { kind: $t('navigation.header') })}}
               div(v-else-if='current.kind === "divider"')
-                v-btn.mt-0(color='red', outline, @click='deleteItem(current)')
-                  v-icon(left) delete
+                v-btn.mt-0.px-5(color='red', outlined, @click='deleteItem(current)')
+                  v-icon(left) mdi-delete
                   span {{$t('navigation.delete', { kind: $t('navigation.divider') })}}
               v-card(v-else)
                 v-card-text.grey--text(v-if='navTree.length > 0') {{$t('navigation.noSelectionText')}}
@@ -172,7 +169,7 @@ export default {
       return [
         // { text: this.$t('navigation.navType.external'), value: 'external' },
         { text: this.$t('navigation.navType.home'), value: 'home' },
-        { text: 'Internal Path / External Link', value: 'external' },
+        { text: 'Internal Path / External Link', value: 'external' }
         // { text: this.$t('navigation.navType.page'), value: 'page' }
         // { text: this.$t('navigation.navType.searchQuery'), value: 'search' }
       ]
@@ -189,7 +186,7 @@ export default {
           newItem = {
             ...newItem,
             label: this.$t('navigation.untitled', { kind: this.$t(`navigation.link`) }),
-            icon: 'chevron_right',
+            icon: 'mdi-chevron-right',
             targetType: 'home',
             target: '/'
           }

+ 40 - 40
client/components/admin/admin-pages-edit.vue

@@ -54,41 +54,41 @@
             v-icon.mr-2 subject
             span Properties
           v-list.py-0(two-line, dense)
-            v-list-tile
-              v-list-tile-content
-                v-list-tile-title.caption.grey--text Title
-                v-list-tile-sub-title.body-2.grey--text.text--darken-3 {{ page.title }}
+            v-list-item
+              v-list-item-content
+                v-list-item-title.caption.grey--text Title
+                v-list-item-sub-title.body-2.grey--text.text--darken-3 {{ page.title }}
             v-divider
-            v-list-tile
-              v-list-tile-content
-                v-list-tile-title.caption.grey--text Description
-                v-list-tile-sub-title.body-2.grey--text.text--darken-3 {{ page.description || '-' }}
+            v-list-item
+              v-list-item-content
+                v-list-item-title.caption.grey--text Description
+                v-list-item-sub-title.body-2.grey--text.text--darken-3 {{ page.description || '-' }}
             v-divider
-            v-list-tile
-              v-list-tile-content
-                v-list-tile-title.caption.grey--text Locale
-                v-list-tile-sub-title.body-2.grey--text.text--darken-3 {{ page.locale }}
-              v-list-tile-action
+            v-list-item
+              v-list-item-content
+                v-list-item-title.caption.grey--text Locale
+                v-list-item-sub-title.body-2.grey--text.text--darken-3 {{ page.locale }}
+              v-list-item-action
                 v-btn(icon)
                   v-icon(color='grey') edit
             v-divider
-            v-list-tile
-              v-list-tile-content
-                v-list-tile-title.caption.grey--text Path
-                v-list-tile-sub-title.body-2.grey--text.text--darken-3 {{ page.path }}
-              v-list-tile-action
+            v-list-item
+              v-list-item-content
+                v-list-item-title.caption.grey--text Path
+                v-list-item-sub-title.body-2.grey--text.text--darken-3 {{ page.path }}
+              v-list-item-action
                 v-btn(icon)
                   v-icon(color='grey') edit
             v-divider
-            v-list-tile
-              v-list-tile-content
-                v-list-tile-title.caption.grey--text Editor
-                v-list-tile-sub-title.body-2.grey--text.text--darken-3 {{ page.editor || '?' }}
+            v-list-item
+              v-list-item-content
+                v-list-item-title.caption.grey--text Editor
+                v-list-item-sub-title.body-2.grey--text.text--darken-3 {{ page.editor || '?' }}
             v-divider
-            v-list-tile
-              v-list-tile-content
-                v-list-tile-title.caption.grey--text Content Type
-                v-list-tile-sub-title.body-2.grey--text.text--darken-3 {{ page.contentType || '?' }}
+            v-list-item
+              v-list-item-content
+                v-list-item-title.caption.grey--text Content Type
+                v-list-item-sub-title.body-2.grey--text.text--darken-3 {{ page.contentType || '?' }}
 
         v-toolbar.elevation-2.mt-3.animated.fadeInUp.wait-p4s(color='white', dense)
           v-spacer
@@ -117,25 +117,25 @@
             v-icon.mr-2 people
             span Users
           v-list.py-0(two-line, dense)
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 v-btn(icon, :to='`/users/` + page.creatorId')
                   v-icon(color='grey') person
-              v-list-tile-content
-                v-list-tile-title.caption.grey--text Creator
-                v-list-tile-sub-title.body-2.grey--text.text--darken-3 {{ page.creatorName }} #[em.caption ({{ page.creatorEmail }})]
-              v-list-tile-action
-                v-list-tile-action-text {{ page.createdAt | moment('calendar') }}
+              v-list-item-content
+                v-list-item-title.caption.grey--text Creator
+                v-list-item-sub-title.body-2.grey--text.text--darken-3 {{ page.creatorName }} #[em.caption ({{ page.creatorEmail }})]
+              v-list-item-action
+                v-list-item-action-text {{ page.createdAt | moment('calendar') }}
             v-divider
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 v-btn(icon, :to='`/users/` + page.authorId')
                   v-icon(color='grey') person
-              v-list-tile-content
-                v-list-tile-title.caption.grey--text Last Editor
-                v-list-tile-sub-title.body-2.grey--text.text--darken-3 {{ page.authorName }} #[em.caption ({{ page.authorEmail }})]
-              v-list-tile-action
-                v-list-tile-action-text {{ page.updatedAt | moment('calendar') }}
+              v-list-item-content
+                v-list-item-title.caption.grey--text Last Editor
+                v-list-item-sub-title.body-2.grey--text.text--darken-3 {{ page.authorName }} #[em.caption ({{ page.authorEmail }})]
+              v-list-item-action
+                v-list-item-action-text {{ page.updatedAt | moment('calendar') }}
         v-card.mt-3.animated.fadeInUp.wait-p4s
           v-toolbar(color='primary', dense, dark, flat)
             v-icon.mr-2 history

+ 9 - 12
client/components/admin/admin-pages.vue

@@ -8,37 +8,34 @@
             .headline.blue--text.text--darken-2.animated.fadeInLeft Pages
             .subheading.grey--text.animated.fadeInLeft.wait-p2s Manage pages
           v-spacer
-          v-btn.animated.fadeInDown.wait-p1s(color='grey', outline, @click='refresh', large)
-            v-icon.grey--text refresh
-          v-btn.animated.fadeInDown(color='primary', outline, large, @click='recyclebin', disabled)
-            v-icon(left) delete_outline
+          v-btn.animated.fadeInDown.wait-p1s(color='grey', outlined, @click='refresh', large)
+            v-icon.grey--text mdi-refresh
+          v-btn.animated.fadeInDown.mx-3(color='primary', outlined, large, @click='recyclebin', disabled)
+            v-icon(left) mdi-delete-outline
             span Recycle Bin
           v-btn.animated.fadeInDown(color='primary', depressed, large, @click='newpage', disabled)
-            v-icon(left) add
+            v-icon(left) mdi-plus
             span New Page
         v-card.wiki-form.mt-3.animated.fadeInUp
           v-toolbar(flat, :color='$vuetify.dark ? `grey darken-3-d5` : `grey lighten-5`', height='80')
             v-spacer
             v-text-field(
-              outline
+              outlined
               v-model='search'
-              append-icon='search'
+              prepend-inner-icon='mdi-file-search-outline'
               label='Search Pages...'
-              single-line
               hide-details
               )
             v-select.ml-2(
-              outline
+              outlined
               hide-details
-              single-line
               label='Locale'
               :items='langs'
               v-model='selectedLang'
             )
             v-select.ml-2(
-              outline
+              outlined
               hide-details
-              single-line
               label='Publish State'
               :items='states'
               v-model='selectedState'

+ 6 - 6
client/components/admin/admin-rendering.vue

@@ -43,18 +43,18 @@
               v-spacer
             v-list.py-0(two-line, dense)
               template(v-for='(rdr, n) in core.children')
-                v-list-tile(
+                v-list-item(
                   avatar
                   :key='rdr.key'
                   @click='selectRenderer(rdr.key)'
                   :class='currentRenderer.key === rdr.key ? (darkMode ? `grey darken-4-l4` : `blue lighten-5`) : ``'
                   )
-                  v-list-tile-avatar
+                  v-list-item-avatar
                     v-icon(:color='currentRenderer.key === rdr.key ? "primary" : "grey"') {{rdr.icon}}
-                  v-list-tile-content
-                    v-list-tile-title {{rdr.title}}
-                    v-list-tile-sub-title {{rdr.description}}
-                  v-list-tile-avatar
+                  v-list-item-content
+                    v-list-item-title {{rdr.title}}
+                    v-list-item-sub-title {{rdr.description}}
+                  v-list-item-avatar
                     status-indicator(v-if='rdr.isEnabled', positive, pulse)
                     status-indicator(v-else, negative, pulse)
                 v-divider.my-0(v-if='n < core.children.length - 1')

+ 6 - 6
client/components/admin/admin-search.vue

@@ -23,15 +23,15 @@
             .subheading {{$t('admin:search.searchEngine')}}
           v-list.py-0(two-line, dense)
             template(v-for='(eng, idx) in engines')
-              v-list-tile(:key='eng.key', @click='selectedEngine = eng.key', :disabled='!eng.isAvailable')
-                v-list-tile-avatar
+              v-list-item(:key='eng.key', @click='selectedEngine = eng.key', :disabled='!eng.isAvailable')
+                v-list-item-avatar
                   v-icon(color='grey', v-if='!eng.isAvailable') cancel
                   v-icon(color='primary', v-else-if='eng.key === selectedEngine') radio_button_checked
                   v-icon(color='grey', v-else) radio_button_unchecked
-                v-list-tile-content
-                  v-list-tile-title.body-2(:class='!eng.isAvailable ? `grey--text` : (selectedEngine === eng.key ? `primary--text` : ``)') {{ eng.title }}
-                  v-list-tile-sub-title.caption(:class='!eng.isAvailable ? `grey--text text--lighten-1` : (selectedEngine === eng.key ? `blue--text ` : ``)') {{ eng.description }}
-                v-list-tile-avatar(v-if='selectedEngine === eng.key')
+                v-list-item-content
+                  v-list-item-title.body-2(:class='!eng.isAvailable ? `grey--text` : (selectedEngine === eng.key ? `primary--text` : ``)') {{ eng.title }}
+                  v-list-item-sub-title.caption(:class='!eng.isAvailable ? `grey--text text--lighten-1` : (selectedEngine === eng.key ? `blue--text ` : ``)') {{ eng.description }}
+                v-list-item-avatar(v-if='selectedEngine === eng.key')
                   v-icon.animated.fadeInLeft(color='primary') arrow_forward_ios
               v-divider(v-if='idx < engines.length - 1')
 

+ 22 - 22
client/components/admin/admin-storage.vue

@@ -20,15 +20,15 @@
             .subheading {{$t('admin:storage.targets')}}
           v-list(two-line, dense).py-0
             template(v-for='(tgt, idx) in targets')
-              v-list-tile(:key='tgt.key', @click='selectedTarget = tgt.key', :disabled='!tgt.isAvailable')
-                v-list-tile-avatar
+              v-list-item(:key='tgt.key', @click='selectedTarget = tgt.key', :disabled='!tgt.isAvailable')
+                v-list-item-avatar
                   v-icon(color='grey', v-if='!tgt.isAvailable') indeterminate_check_box
                   v-icon(color='primary', v-else-if='tgt.isEnabled', v-ripple, @click='tgt.key !== `local` && (tgt.isEnabled = false)') check_box
                   v-icon(color='grey', v-else, v-ripple, @click='tgt.isEnabled = true') check_box_outline_blank
-                v-list-tile-content
-                  v-list-tile-title.body-2(:class='!tgt.isAvailable ? `grey--text` : (selectedTarget === tgt.key ? `primary--text` : ``)') {{ tgt.title }}
-                  v-list-tile-sub-title.caption(:class='!tgt.isAvailable ? `grey--text text--lighten-1` : (selectedTarget === tgt.key ? `blue--text ` : ``)') {{ tgt.description }}
-                v-list-tile-avatar(v-if='selectedTarget === tgt.key')
+                v-list-item-content
+                  v-list-item-title.body-2(:class='!tgt.isAvailable ? `grey--text` : (selectedTarget === tgt.key ? `primary--text` : ``)') {{ tgt.title }}
+                  v-list-item-sub-title.caption(:class='!tgt.isAvailable ? `grey--text text--lighten-1` : (selectedTarget === tgt.key ? `blue--text ` : ``)') {{ tgt.description }}
+                v-list-item-avatar(v-if='selectedTarget === tgt.key')
                   v-icon.animated.fadeInLeft(color='primary') arrow_forward_ios
               v-divider(v-if='idx < targets.length - 1')
 
@@ -43,28 +43,28 @@
             )
           v-list.py-0(two-line, dense)
             template(v-for='(tgt, n) in status')
-              v-list-tile(:key='tgt.key')
+              v-list-item(:key='tgt.key')
                 template(v-if='tgt.status === `pending`')
-                  v-list-tile-avatar(color='purple')
+                  v-list-item-avatar(color='purple')
                     v-icon(color='white') schedule
-                  v-list-tile-content
-                    v-list-tile-title.body-2 {{tgt.title}}
-                    v-list-tile-sub-title.purple--text.caption {{tgt.status}}
-                  v-list-tile-action
+                  v-list-item-content
+                    v-list-item-title.body-2 {{tgt.title}}
+                    v-list-item-sub-title.purple--text.caption {{tgt.status}}
+                  v-list-item-action
                     v-progress-circular(indeterminate, :size='20', :width='2', color='purple')
                 template(v-else-if='tgt.status === `operational`')
-                  v-list-tile-avatar(color='green')
+                  v-list-item-avatar(color='green')
                     v-icon(color='white') check_circle
-                  v-list-tile-content
-                    v-list-tile-title.body-2 {{tgt.title}}
-                    v-list-tile-sub-title.green--text.caption {{$t('admin:storage.lastSync', { time: $options.filters.moment(tgt.lastAttempt, 'from') })}}
+                  v-list-item-content
+                    v-list-item-title.body-2 {{tgt.title}}
+                    v-list-item-sub-title.green--text.caption {{$t('admin:storage.lastSync', { time: $options.filters.moment(tgt.lastAttempt, 'from') })}}
                 template(v-else)
-                  v-list-tile-avatar(color='red')
+                  v-list-item-avatar(color='red')
                     v-icon(color='white') highlight_off
-                  v-list-tile-content
-                    v-list-tile-title.body-2 {{tgt.title}}
-                    v-list-tile-sub-title.red--text.caption {{$t('admin:storage.lastSyncAttempt', { time: $options.filters.moment(tgt.lastAttempt, 'from') })}}
-                  v-list-tile-action
+                  v-list-item-content
+                    v-list-item-title.body-2 {{tgt.title}}
+                    v-list-item-sub-title.red--text.caption {{$t('admin:storage.lastSyncAttempt', { time: $options.filters.moment(tgt.lastAttempt, 'from') })}}
+                  v-list-item-action
                     v-menu
                       v-btn(slot='activator', icon)
                         v-icon(color='red') info
@@ -73,7 +73,7 @@
                         v-card-text {{tgt.message}}
 
               v-divider(v-if='n < status.length - 1')
-            v-list-tile(v-if='status.length < 1')
+            v-list-item(v-if='status.length < 1')
               em {{$t('admin:storage.noTarget')}}
 
       v-flex(xs12, lg9)

+ 51 - 51
client/components/admin/admin-system.vue

@@ -13,83 +13,83 @@
               v-btn.animated.fadeInLeft.wait-p2s.btn-animate-rotate(fab, absolute, right, top, small, light, @click='refresh'): v-icon refresh
               v-list(two-line, dense)
                 v-subheader Wiki.js
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-icon.blue.white--text system_update_alt
-                  v-list-tile-content
-                    v-list-tile-title {{ $t('admin:system.currentVersion') }}
-                    v-list-tile-sub-title {{ info.currentVersion }}
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                  v-list-item-content
+                    v-list-item-title {{ $t('admin:system.currentVersion') }}
+                    v-list-item-sub-title {{ info.currentVersion }}
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-icon.blue.white--text open_in_browser
-                  v-list-tile-content
-                    v-list-tile-title {{ $t('admin:system.latestVersion') }}
-                    v-list-tile-sub-title {{ info.latestVersion }}
-                  v-list-tile-action
-                    v-list-tile-action-text {{ $t('admin:system.published') }} {{ info.latestVersionReleaseDate | moment('from') }}
+                  v-list-item-content
+                    v-list-item-title {{ $t('admin:system.latestVersion') }}
+                    v-list-item-sub-title {{ info.latestVersion }}
+                  v-list-item-action
+                    v-list-item-action-text {{ $t('admin:system.published') }} {{ info.latestVersionReleaseDate | moment('from') }}
 
                 v-divider.mt-3
 
                 v-subheader {{ $t('admin:system.hostInfo') }}
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-avatar.blue-grey(size='40')
                       img(:src='`/svg/icon-` + platformLogo + `-logo.svg`', alt='Platform', style='width: 24px;')
-                  v-list-tile-content
-                    v-list-tile-title {{ $t('admin:system.os') }}
-                    v-list-tile-sub-title {{ (info.platform === 'docker') ? 'Docker Container (Linux)' : info.operatingSystem }}
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                  v-list-item-content
+                    v-list-item-title {{ $t('admin:system.os') }}
+                    v-list-item-sub-title {{ (info.platform === 'docker') ? 'Docker Container (Linux)' : info.operatingSystem }}
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-icon.blue-grey.white--text computer
-                  v-list-tile-content
-                    v-list-tile-title {{ $t('admin:system.hostname') }}
-                    v-list-tile-sub-title {{ info.hostname }}
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                  v-list-item-content
+                    v-list-item-title {{ $t('admin:system.hostname') }}
+                    v-list-item-sub-title {{ info.hostname }}
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-icon.blue-grey.white--text nfc
-                  v-list-tile-content
-                    v-list-tile-title {{ $t('admin:system.cpuCores') }}
-                    v-list-tile-sub-title {{ info.cpuCores }}
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                  v-list-item-content
+                    v-list-item-title {{ $t('admin:system.cpuCores') }}
+                    v-list-item-sub-title {{ info.cpuCores }}
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-icon.blue-grey.white--text memory
-                  v-list-tile-content
-                    v-list-tile-title {{ $t('admin:system.totalRAM') }}
-                    v-list-tile-sub-title {{ info.ramTotal }}
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                  v-list-item-content
+                    v-list-item-title {{ $t('admin:system.totalRAM') }}
+                    v-list-item-sub-title {{ info.ramTotal }}
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-icon.blue-grey.white--text last_page
-                  v-list-tile-content
-                    v-list-tile-title {{ $t('admin:system.workingDirectory') }}
-                    v-list-tile-sub-title {{ info.workingDirectory }}
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                  v-list-item-content
+                    v-list-item-title {{ $t('admin:system.workingDirectory') }}
+                    v-list-item-sub-title {{ info.workingDirectory }}
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-icon.blue-grey.white--text settings
-                  v-list-tile-content
-                    v-list-tile-title {{ $t('admin:system.configFile') }}
-                    v-list-tile-sub-title {{ info.configFile }}
+                  v-list-item-content
+                    v-list-item-title {{ $t('admin:system.configFile') }}
+                    v-list-item-sub-title {{ info.configFile }}
 
           v-flex(lg6 xs12)
             v-card.pb-3.animated.fadeInUp.wait-p4s
               v-list(dense)
                 v-subheader Node.js
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-avatar.light-green(size='40')
                       icon-node-js(fillColor='#FFFFFF')
-                  v-list-tile-content
-                    v-list-tile-title {{ info.nodeVersion }}
+                  v-list-item-content
+                    v-list-item-title {{ info.nodeVersion }}
 
                 v-divider.mt-3
 
                 v-subheader {{ info.dbType }}
-                v-list-tile(avatar)
-                  v-list-tile-avatar
+                v-list-item(avatar)
+                  v-list-item-avatar
                     v-avatar.indigo.darken-1(size='40')
                       icon-database(fillColor='#FFFFFF')
-                  v-list-tile-content
-                    v-list-tile-title(v-html='dbVersion')
-                    v-list-tile-sub-title {{ info.dbHost }}
+                  v-list-item-content
+                    v-list-item-title(v-html='dbVersion')
+                    v-list-item-sub-title {{ info.dbHost }}
 
                 v-alert.mt-3(:value='isDbLimited', color='deep-orange', icon='warning') {{ $t('admin:system.dbPartialSupport') }}
 </template>

+ 22 - 31
client/components/admin/admin-theme.vue

@@ -9,37 +9,34 @@
             .subheading.grey--text.animated.fadeInLeft.wait-p2s {{$t('admin:theme.subtitle')}}
           v-spacer
           v-btn.animated.fadeInRight(color='success', depressed, @click='save', large, :loading='loading')
-            v-icon(left) check
+            v-icon(left) mdi-check
             span {{$t('common:actions.apply')}}
         v-form.pt-3
           v-layout(row wrap)
             v-flex(lg6 xs12)
               v-card.wiki-form.animated.fadeInUp
                 v-toolbar(color='primary', dark, dense, flat)
-                  v-toolbar-title
-                    .subheading {{$t('admin:theme.title')}}
+                  v-toolbar-title.subtitle-1 {{$t('admin:theme.title')}}
                 v-card-text
                   v-select(
                     :items='themes'
-                    outline
-                    background-color='grey lighten-2'
-                    prepend-icon='palette'
+                    outlined
+                    prepend-icon='mdi-palette'
                     v-model='config.theme'
                     :label='$t(`admin:theme.siteTheme`)'
                     persistent-hint
                     :hint='$t(`admin:theme.siteThemeHint`)'
                     )
                     template(slot='item', slot-scope='data')
-                      v-list-tile-avatar
+                      v-list-item-avatar
                         v-icon.blue--text(dark) filter_frames
-                      v-list-tile-content
-                        v-list-tile-title(v-html='data.item.text')
-                        v-list-tile-sub-title(v-html='data.item.author')
+                      v-list-item-content
+                        v-list-item-title(v-html='data.item.text')
+                        v-list-item-sub-title(v-html='data.item.author')
                   v-select.mt-3(
                     :items='iconsets'
-                    outline
-                    background-color='grey lighten-2'
-                    prepend-icon='pets'
+                    outlined
+                    prepend-icon='mdi-paw'
                     v-model='config.iconset'
                     :label='$t(`admin:theme.iconset`)'
                     persistent-hint
@@ -56,14 +53,12 @@
 
               v-card.wiki-form.mt-3.animated.fadeInUp.wait-p2s
                 v-toolbar(color='primary', dark, dense, flat)
-                  v-toolbar-title
-                    .subheading {{$t(`admin:theme.codeInjection`)}}
+                  v-toolbar-title.subtitle-1 {{$t(`admin:theme.codeInjection`)}}
                 v-card-text
                   v-textarea(
                     v-model='config.injectCSS'
                     :label='$t(`admin:theme.cssOverride`)'
-                    outline
-                    background-color='grey lighten-1'
+                    outlined
                     color='primary'
                     persistent-hint
                     :hint='$t(`admin:theme.cssOverrideHint`)'
@@ -75,8 +70,7 @@
                   v-textarea.mt-3(
                     v-model='config.injectHead'
                     :label='$t(`admin:theme.headHtmlInjection`)'
-                    outline
-                    background-color='grey lighten-1'
+                    outlined
                     color='primary'
                     persistent-hint
                     :hint='$t(`admin:theme.headHtmlInjectionHint`)'
@@ -85,8 +79,7 @@
                   v-textarea.mt-2(
                     v-model='config.injectBody'
                     :label='$t(`admin:theme.bodyHtmlInjection`)'
-                    outline
-                    background-color='grey lighten-1'
+                    outlined
                     color='primary'
                     persistent-hint
                     :hint='$t(`admin:theme.bodyHtmlInjectionHint`)'
@@ -95,16 +88,15 @@
             v-flex(lg6 xs12)
               v-card.animated.fadeInUp.wait-p2s
                 v-toolbar(color='teal', dark, dense, flat)
-                  v-toolbar-title
-                    .subheading {{$t('admin:theme.downloadThemes')}}
+                  v-toolbar-title.subtitle-1 {{$t('admin:theme.downloadThemes')}}
                   v-spacer
                   v-chip(label, color='white', small).teal--text coming soon
                 v-data-table(
                   :headers='headers',
                   :items='themes',
-                  hide-actions,
+                  hide-default-footer,
                   item-key='value',
-                  :rows-per-page-items='[-1]'
+                  :items-per-page='1000'
                 )
                   template(v-slot:items='thm')
                     td
@@ -114,11 +106,11 @@
                     td.text-xs-center
                       v-progress-circular(v-if='thm.item.isDownloading', indeterminate, color='blue', size='20', :width='2')
                       v-btn(v-else-if='thm.item.isInstalled && thm.item.installDate < thm.item.updatedAt', icon)
-                        v-icon.blue--text cached
+                        v-icon.blue--text mdi-cached
                       v-btn(v-else-if='thm.item.isInstalled', icon)
-                        v-icon.green--text check
+                        v-icon.green--text mdi-check
                       v-btn(v-else, icon)
-                        v-icon.grey--text cloud_download
+                        v-icon.grey--text mdi-cloud-download
 </template>
 
 <script>
@@ -136,10 +128,9 @@ export default {
         { text: 'Default', author: 'requarks.io', value: 'default', isInstalled: true, installDate: '', updatedAt: '' }
       ],
       iconsets: [
-        { text: 'Material Icons (default)', value: 'md' },
-        { text: 'Material Design Icons', value: 'mdi' },
+        { text: 'Material Design Icons (default)', value: 'mdi' },
         { text: 'Font Awesome 5', value: 'fa' },
-        { text: 'Font Awesome 4', value: 'fa4' },
+        { text: 'Font Awesome 4', value: 'fa4' }
       ],
       config: {
         theme: 'default',

+ 96 - 18
client/components/admin/admin-users-create.vue

@@ -2,46 +2,72 @@
   v-dialog(v-model='isShown', max-width='650', persistent)
     v-card.wiki-form
       .dialog-header.is-short
+        v-icon.mr-3(color='white') mdi-plus
         span New User
+        v-spacer
+        v-btn.mx-0(color='white', outlined, disabled, dark)
+          v-icon(left) mdi-database-import
+          span Bulk Import
       v-card-text
         v-select(
           :items='providers'
           item-text='title'
           item-value='key'
-          outline
-          prepend-icon='business'
+          outlined
+          prepend-icon='mdi-domain'
           v-model='provider'
           label='Provider'
           )
         v-text-field(
-          outline
-          prepend-icon='email'
+          outlined
+          prepend-icon='mdi-at'
           v-model='email'
           label='Email Address'
+          v-validate='{ required: true, email: true, min: 2, max: 255 }',
+          data-vv-name='email',
+          data-vv-as='Email Address',
+          data-vv-scope='newUser',
+          :error-messages='errors.collect(`newUser.email`)'
+          key='newUserEmail'
+          persistent-hint
           ref='emailInput'
           )
         v-text-field(
           v-if='provider === `local`'
-          outline
-          prepend-icon='lock'
-          append-icon='casino'
+          outlined
+          prepend-icon='mdi-lock-outline'
+          append-icon='mdi-dice-5'
           v-model='password'
           :label='mustChangePwd ? `Temporary Password` : `Password`'
           counter='255'
           @click:append='generatePwd'
+          v-validate='{ required: true, min: 6, max: 255 }',
+          data-vv-name='password',
+          data-vv-as='Password',
+          data-vv-scope='newUser',
+          :error-messages='errors.collect(`newUser.password`)'
+          key='newUserPassword'
+          persistent-hint
           )
         v-text-field(
-          outline
-          prepend-icon='person'
+          outlined
+          prepend-icon='mdi-account-outline'
           v-model='name'
           label='Name'
+          v-validate='{ required: true, min: 2, max: 255 }',
+          data-vv-name='name',
+          data-vv-as='Name',
+          data-vv-scope='newUser',
+          :error-messages='errors.collect(`newUser.name`)'
+          key='newUserName'
+          persistent-hint
           )
         v-select(
           :items='groups'
           item-text='name'
           item-value='key'
-          outline
-          prepend-icon='people'
+          outlined
+          prepend-icon='mdi-account-group'
           v-model='group'
           label='Assign to Group(s)...'
           clearable
@@ -63,14 +89,15 @@
         )
       v-card-chin
         v-spacer
-        v-btn(flat, @click='isShown = false') Cancel
-        v-btn(color='primary', @click='newUser(true)') Create
-        v-btn(color='primary', @click='newUser(false)') Create and Close
+        v-btn(text, @click='isShown = false') Cancel
+        v-btn(depressed, color='primary', @click='newUser(false)', :disabled='errors.any(`newUser`)') Create
+        v-btn(depressed, color='primary', @click='newUser(true)', :disabled='errors.any(`newUser`)') Create and Close
 </template>
 
 <script>
-import uuidv4 from 'uuid/v4'
+import _ from 'lodash'
 
+import createUserMutation from 'gql/admin/users/users-mutation-create.gql'
 import providersQuery from 'gql/admin/users/users-query-strategies.gql'
 import groupsQuery from 'gql/admin/auth/auth-query-groups.gql'
 
@@ -103,18 +130,69 @@ export default {
   watch: {
     value(newValue, oldValue) {
       if (newValue) {
+        this.$validator.reset()
         this.$nextTick(() => {
           this.$refs.emailInput.focus()
         })
       }
+    },
+    provider(newValue, oldValue) {
+      this.$validator.reset()
     }
   },
   methods: {
-    async newUser() {
-      this.isShown = false
+    async newUser(close = false) {
+      const validationSuccess = await this.$validator.validateAll('newUser')
+      if (!validationSuccess) {
+        return
+      }
+
+      try {
+        const resp = await this.$apollo.mutate({
+          mutation: createUserMutation,
+          variables: {
+            providerKey: this.provider,
+            email: this.email,
+            passwordRaw: this.password,
+            name: this.name,
+            groups: this.groups,
+            mustChangePassword: this.mustChangePwd,
+            sendWelcomeEmail: this.sendWelcomeEmail
+          },
+          watchLoading (isLoading) {
+            this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-users-create')
+          }
+        })
+        if (_.get(resp, 'data.users.create.responseResult.succeeded', false)) {
+          this.$store.commit('showNotification', {
+            style: 'success',
+            message: 'New user created successfully.',
+            icon: 'check'
+          })
+
+          this.email = ''
+          this.password = ''
+          this.name = ''
+
+          if (close) {
+            this.isShown = false
+          } else {
+            this.$refs.emailInput.focus()
+          }
+        } else {
+          this.$store.commit('showNotification', {
+            style: 'red',
+            message: _.get(resp, 'data.users.create.responseResult.message', 'An unexpected error occured.'),
+            icon: 'warning'
+          })
+        }
+      } catch (err) {
+        this.$store.commit('pushGraphError', err)
+      }
     },
     generatePwd() {
-      this.password = uuidv4().slice(-12)
+      const pwdChars = 'abcdefghkmnpqrstuvwxyzABCDEFHJKLMNPQRSTUVWXYZ23456789_*=?#!()+'
+      this.password = _.sampleSize(pwdChars, 12).join('')
     }
   },
   apollo: {

+ 59 - 59
client/components/admin/admin-users-edit.vue

@@ -31,23 +31,23 @@
             v-icon.mr-2 directions_run
             span Basic Info
           v-list.py-0(two-line, dense)
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 v-icon alternate_email
-              v-list-tile-content
-                v-list-tile-title Email
-                v-list-tile-sub-title {{ user.email }}
-              v-list-tile-action(v-if='!user.isSystem')
+              v-list-item-content
+                v-list-item-title Email
+                v-list-item-sub-title {{ user.email }}
+              v-list-item-action(v-if='!user.isSystem')
                   v-btn(icon, color='grey', flat)
                     v-icon edit
             v-divider
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 v-icon person
-              v-list-tile-content
-                v-list-tile-title Display Name
-                v-list-tile-sub-title {{ user.name }}
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Display Name
+                v-list-item-sub-title {{ user.name }}
+              v-list-item-action
                   v-btn(icon, color='grey', flat)
                     v-icon edit
         v-card.mt-3.animated.fadeInUp.wait-p2s(v-if='!user.isSystem')
@@ -55,58 +55,58 @@
             v-icon.mr-2 lock_outline
             span Authentication
           v-list.py-0(two-line, dense)
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 v-icon business
-              v-list-tile-content
-                v-list-tile-title Provider
-                v-list-tile-sub-title {{ user.providerKey }}
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Provider
+                v-list-item-sub-title {{ user.providerKey }}
+              v-list-item-action
                 v-img(src='https://static.requarks.io/logo/wikijs.svg', alt='')
             template(v-if='user.providerKey === `local`')
               v-divider
-              v-list-tile
-                v-list-tile-avatar
+              v-list-item
+                v-list-item-avatar
                   v-icon security
-                v-list-tile-content
-                  v-list-tile-title Password
-                  v-list-tile-sub-title ********
-                v-list-tile-action
+                v-list-item-content
+                  v-list-item-title Password
+                  v-list-item-sub-title ********
+                v-list-item-action
                     v-btn(icon, color='grey', flat)
                       v-icon cached
-                v-list-tile-action
+                v-list-item-action
                     v-btn(icon, color='grey', flat)
                       v-icon email
               v-divider
-              v-list-tile
-                v-list-tile-avatar
+              v-list-item
+                v-list-item-avatar
                   v-icon screen_lock_portrait
-                v-list-tile-content
-                  v-list-tile-title Two Factor Authentication (2FA)
-                  v-list-tile-sub-title.red--text Inactive
-                v-list-tile-action
+                v-list-item-content
+                  v-list-item-title Two Factor Authentication (2FA)
+                  v-list-item-sub-title.red--text Inactive
+                v-list-item-action
                     v-btn(icon, color='grey', flat)
                       v-icon power_settings_new
               template(v-if='user.providerId')
                 v-divider
-                v-list-tile
-                  v-list-tile-avatar
+                v-list-item
+                  v-list-item-avatar
                     v-icon person
-                  v-list-tile-content
-                    v-list-tile-title Provider Id
-                    v-list-tile-sub-title {{ user.providerId }}
+                  v-list-item-content
+                    v-list-item-title Provider Id
+                    v-list-item-sub-title {{ user.providerId }}
         v-card.mt-3.animated.fadeInUp.wait-p4s
           v-toolbar(color='primary', dense, dark, flat)
             v-icon.mr-2 people
             span User Groups
           v-list(dense)
             template(v-for='(group, idx) in user.groups')
-              v-list-tile
-                v-list-tile-avatar
+              v-list-item
+                v-list-item-avatar
                   v-icon people_outline
-                v-list-tile-content
-                  v-list-tile-title {{group.name}}
-                v-list-tile-action
+                v-list-item-content
+                  v-list-item-title {{group.name}}
+                v-list-item-action
                   v-btn(icon, color='red', flat)
                     v-icon clear
               v-divider(v-if='idx < user.groups.length - 1')
@@ -121,33 +121,33 @@
             v-icon.mr-2 directions_walk
             span Extended Metadata
           v-list.py-0(two-line, dense)
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 v-icon public
-              v-list-tile-content
-                v-list-tile-title Location
-                v-list-tile-sub-title {{ user.location }}
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Location
+                v-list-item-sub-title {{ user.location }}
+              v-list-item-action
                   v-btn(icon, color='grey', flat)
                     v-icon edit
             v-divider
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 v-icon local_library
-              v-list-tile-content
-                v-list-tile-title Job Title
-                v-list-tile-sub-title {{ user.jobTitle }}
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Job Title
+                v-list-item-sub-title {{ user.jobTitle }}
+              v-list-item-action
                   v-btn(icon, color='grey', flat)
                     v-icon edit
             v-divider
-            v-list-tile
-              v-list-tile-avatar
+            v-list-item
+              v-list-item-avatar
                 v-icon map
-              v-list-tile-content
-                v-list-tile-title Timezone
-                v-list-tile-sub-title {{ user.timezone }}
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title Timezone
+                v-list-item-sub-title {{ user.timezone }}
+              v-list-item-action
                   v-btn(icon, color='grey', flat)
                     v-icon edit
         v-card.mt-3.animated.fadeInUp.wait-p4s

+ 8 - 8
client/components/admin/admin-users.vue

@@ -6,25 +6,25 @@
           img.animated.fadeInUp(src='/svg/icon-customer.svg', alt='Users', style='width: 80px;')
           .admin-header-title
             .headline.blue--text.text--darken-2.animated.fadeInLeft Users
-            .subheading.grey--text.animated.fadeInLeft.wait-p2s Manage users #[v-chip(label, color='primary', small).white--text coming soon]
+            .subheading.grey--text.animated.fadeInLeft.wait-p2s Manage users
           v-spacer
-          v-btn.animated.fadeInDown.wait-p2s(outline, color='grey', large, @click='refresh')
-            v-icon refresh
-          v-btn.animated.fadeInDown(color='primary', large, depressed, @click='createUser', disabled)
-            v-icon(left) add
+          v-btn.animated.fadeInDown.wait-p2s.mr-3(outlined, color='grey', large, @click='refresh')
+            v-icon mdi-refresh
+          v-btn.animated.fadeInDown(color='primary', large, depressed, @click='createUser')
+            v-icon(left) mdi-plus
             span New User
         v-card.wiki-form.mt-3.animated.fadeInUp
           v-toolbar(flat, :color='$vuetify.dark ? `grey darken-3-d5` : `grey lighten-5`', height='80')
             v-spacer
             v-text-field(
-              outline
+              outlined
               v-model='search'
-              prepend-inner-icon='search'
+              prepend-inner-icon='mdi-account-search-outline'
               label='Search Users...'
               hide-details
               )
             v-select.ml-2(
-              outline
+              outlined
               hide-details
               label='Identity Provider'
               :items='strategies'

+ 2 - 2
client/components/admin/admin-utilities-content.vue

@@ -9,7 +9,7 @@
       v-toolbar.radius-7.mt-3.wiki-form(flat, color='grey lighten-4', height='80')
         v-select(
           label='Source Locale'
-          outline
+          outlined
           hide-details
           :items='locales'
           item-text='name'
@@ -19,7 +19,7 @@
         v-icon.mx-3(large) arrow_forward
         v-select(
           label='Target Locale'
-          outline
+          outlined
           hide-details
           :items='locales'
           item-text='name'

+ 29 - 29
client/components/admin/admin-utilities-telemetry.vue

@@ -11,40 +11,40 @@
         v-subheader What is collected?
         .body-1.pl-3 When telemetry is enabled, only the following data is transmitted:
         v-list
-          v-list-tile
-            v-list-tile-avatar: v-icon info_outline
-            v-list-tile-content
-              v-list-tile-title.body-1 Version of Wiki.js installed
-              v-list-tile-sub-title.caption: em e.g. v2.0.123
-          v-list-tile
-            v-list-tile-avatar: v-icon info_outline
-            v-list-tile-content
-              v-list-tile-title.body-1 Basic OS information
-              v-list-tile-sub-title.caption: em Platform (Linux, macOS or Windows), Total CPU cores and DB type (PostgreSQL, MySQL, MariaDB, SQLite or SQL Server)
-          v-list-tile
-            v-list-tile-avatar: v-icon info_outline
-            v-list-tile-content
-              v-list-tile-title.body-1 Crash debug data
-              v-list-tile-sub-title.caption: em Stack trace of the error
-          v-list-tile
-            v-list-tile-avatar: v-icon info_outline
-            v-list-tile-content
-              v-list-tile-title.body-1 Setup analytics
-              v-list-tile-sub-title.caption: em Installation checkpoint reached
+          v-list-item
+            v-list-item-avatar: v-icon info_outline
+            v-list-item-content
+              v-list-item-title.body-1 Version of Wiki.js installed
+              v-list-item-sub-title.caption: em e.g. v2.0.123
+          v-list-item
+            v-list-item-avatar: v-icon info_outline
+            v-list-item-content
+              v-list-item-title.body-1 Basic OS information
+              v-list-item-sub-title.caption: em Platform (Linux, macOS or Windows), Total CPU cores and DB type (PostgreSQL, MySQL, MariaDB, SQLite or SQL Server)
+          v-list-item
+            v-list-item-avatar: v-icon info_outline
+            v-list-item-content
+              v-list-item-title.body-1 Crash debug data
+              v-list-item-sub-title.caption: em Stack trace of the error
+          v-list-item
+            v-list-item-avatar: v-icon info_outline
+            v-list-item-content
+              v-list-item-title.body-1 Setup analytics
+              v-list-item-sub-title.caption: em Installation checkpoint reached
         .body-1.pl-3 Note that crash debug data is stored for a maximum of 30 days while analytics are stored for a maximum of 16 months, after which it is permanently deleted.
         v-divider.my-3
         v-subheader What is it used for?
         .body-1.pl-3 Telemetry is used by developers to improve Wiki.js, mostly for the following reasons:
         v-list(dense)
-          v-list-tile
-            v-list-tile-avatar: v-icon chevron_right
-            v-list-tile-content: v-list-tile-title.body-1 Identify critical bugs more easily and fix them in a timely manner.
-          v-list-tile
-            v-list-tile-avatar: v-icon chevron_right
-            v-list-tile-content: v-list-tile-title.body-1 Understand the upgrade rate of current installations.
-          v-list-tile
-            v-list-tile-avatar: v-icon chevron_right
-            v-list-tile-content: v-list-tile-title.body-1  Optimize performance and testing scenarios based on most popular environments.
+          v-list-item
+            v-list-item-avatar: v-icon chevron_right
+            v-list-item-content: v-list-item-title.body-1 Identify critical bugs more easily and fix them in a timely manner.
+          v-list-item
+            v-list-item-avatar: v-icon chevron_right
+            v-list-item-content: v-list-item-title.body-1 Understand the upgrade rate of current installations.
+          v-list-item
+            v-list-item-avatar: v-icon chevron_right
+            v-list-item-content: v-list-item-title.body-1  Optimize performance and testing scenarios based on most popular environments.
         .body-1.pl-3 Only authorized developers have access to the data. It is not shared to any 3rd party nor is it used for any other application than improving Wiki.js.
         v-divider.my-3
         v-subheader Settings

+ 6 - 6
client/components/admin/admin-utilities.vue

@@ -14,13 +14,13 @@
             .subheading {{$t('admin:utilities.tools')}}
           v-list(two-line, dense).py-0
             template(v-for='(tool, idx) in tools')
-              v-list-tile(:key='tool.key', @click='selectedTool = tool.key', :disabled='!tool.isAvailable')
-                v-list-tile-avatar
+              v-list-item(:key='tool.key', @click='selectedTool = tool.key', :disabled='!tool.isAvailable')
+                v-list-item-avatar
                   v-icon(:color='!tool.isAvailable ? `grey lighten-1` : (selectedTool === tool.key ? `blue ` : `grey darken-1`)') {{ tool.icon }}
-                v-list-tile-content
-                  v-list-tile-title.body-2(:class='!tool.isAvailable ? `grey--text` : (selectedTool === tool.key ? `primary--text` : ``)') {{ $t('admin:utilities.' + tool.i18nKey + 'Title') }}
-                  v-list-tile-sub-title.caption(:class='!tool.isAvailable ? `grey--text text--lighten-1` : (selectedTool === tool.key ? `blue--text ` : ``)') {{ $t('admin:utilities.' + tool.i18nKey + 'Subtitle') }}
-                v-list-tile-avatar(v-if='selectedTool === tool.key')
+                v-list-item-content
+                  v-list-item-title.body-2(:class='!tool.isAvailable ? `grey--text` : (selectedTool === tool.key ? `primary--text` : ``)') {{ $t('admin:utilities.' + tool.i18nKey + 'Title') }}
+                  v-list-item-sub-title.caption(:class='!tool.isAvailable ? `grey--text text--lighten-1` : (selectedTool === tool.key ? `blue--text ` : ``)') {{ $t('admin:utilities.' + tool.i18nKey + 'Subtitle') }}
+                v-list-item-avatar(v-if='selectedTool === tool.key')
                   v-icon.animated.fadeInLeft(color='primary') arrow_forward_ios
               v-divider(v-if='idx < tools.length - 1')
 

+ 6 - 6
client/components/admin/admin-webhooks.vue

@@ -22,14 +22,14 @@
               span New
           v-list(two-line, dense).py-0
             template(v-for='(str, idx) in hooks')
-              v-list-tile(:key='str.key', @click='selectedHook = str.key')
-                v-list-tile-avatar
+              v-list-item(:key='str.key', @click='selectedHook = str.key')
+                v-list-item-avatar
                   v-icon(color='primary', v-if='str.isEnabled', v-ripple, @click='str.isEnabled = false') check_box
                   v-icon(color='grey', v-else, v-ripple, @click='str.isEnabled = true') check_box_outline_blank
-                v-list-tile-content
-                  v-list-tile-title.body-2(:class='!str.isAvailable ? `grey--text` : (selectedHook === str.key ? `primary--text` : ``)') {{ str.title }}
-                  v-list-tile-sub-title.caption(:class='!str.isAvailable ? `grey--text text--lighten-1` : (selectedHook === str.key ? `blue--text ` : ``)') {{ str.description }}
-                v-list-tile-avatar(v-if='selectedHook === str.key')
+                v-list-item-content
+                  v-list-item-title.body-2(:class='!str.isAvailable ? `grey--text` : (selectedHook === str.key ? `primary--text` : ``)') {{ str.title }}
+                  v-list-item-sub-title.caption(:class='!str.isAvailable ? `grey--text text--lighten-1` : (selectedHook === str.key ? `blue--text ` : ``)') {{ str.description }}
+                v-list-item-avatar(v-if='selectedHook === str.key')
                   v-icon.animated.fadeInLeft(color='primary') arrow_forward_ios
               v-divider(v-if='idx < hooks.length - 1')
 

+ 89 - 83
client/components/common/nav-header.vue

@@ -1,5 +1,5 @@
 <template lang='pug'>
-  v-toolbar.nav-header(color='black', dark, app, clipped-left, fixed, flat, :extended='searchIsShown && $vuetify.breakpoint.smAndDown')
+  v-app-bar.nav-header(color='black', dark, app, clipped-left, fixed, flat, :extended='searchIsShown && $vuetify.breakpoint.smAndDown')
     v-toolbar(color='deep-purple', flat, slot='extension', v-if='searchIsShown && $vuetify.breakpoint.smAndDown')
       v-text-field(
         ref='searchFieldMobile'
@@ -12,49 +12,50 @@
         solo
         flat
         hide-details
-        prepend-inner-icon='search'
+        prepend-inner-icon='mdi-search'
         :loading='searchIsLoading'
         @keyup.enter='searchEnter'
       )
     v-layout(row)
-      v-flex(xs6, :md4='searchIsShown', :md6='!searchIsShown')
-        v-toolbar.nav-header-inner(color='black', dark, flat)
+      v-flex(xs6, md4)
+        v-toolbar.nav-header-inner.pl-3(color='black', dark, flat)
           v-menu(open-on-hover, offset-y, bottom, left, min-width='250', transition='slide-y-transition')
-            v-toolbar-side-icon.btn-animate-app(slot='activator')
-              v-icon view_module
-            v-list(dense, :light='!$vuetify.dark', :dark='$vuetify.dark', :class='$vuetify.dark ? `grey darken-4` : ``').py-0
-              v-list-tile(avatar, href='/')
-                v-list-tile-avatar: v-icon(color='blue') home
-                v-list-tile-content {{$t('common:header.home')}}
-              v-list-tile(avatar, @click='pageNew', v-if='isAuthenticated')
-                v-list-tile-avatar: v-icon(color='green') add_box
-                v-list-tile-content {{$t('common:header.newPage')}}
+            template(v-slot:activator='{ on }')
+              v-app-bar-nav-icon.btn-animate-app(v-on='on')
+                v-icon mdi-menu-open
+            v-list(nav, :light='!$vuetify.dark', :dark='$vuetify.dark', :class='$vuetify.dark ? `grey darken-4` : ``')
+              v-list-item.pl-4(href='/')
+                v-list-item-avatar(size='24'): v-icon(color='blue') mdi-home
+                v-list-item-title.body-2 {{$t('common:header.home')}}
+              v-list-item.pl-4(@click='pageNew', v-if='isAuthenticated')
+                v-list-item-avatar(size='24'): v-icon(color='green') mdi-file-document-box-plus-outline
+                v-list-item-title.body-2 {{$t('common:header.newPage')}}
               template(v-if='path && path.length')
                 v-divider.my-0
                 v-subheader {{$t('common:header.currentPage')}}
-                v-list-tile(avatar, @click='pageView', v-if='mode !== `view`')
-                  v-list-tile-avatar: v-icon(color='indigo') subject
-                  v-list-tile-content {{$t('common:header.view')}}
-                v-list-tile(avatar, @click='pageEdit', v-if='mode !== `edit` && isAuthenticated')
-                  v-list-tile-avatar: v-icon(color='indigo') edit
-                  v-list-tile-content {{$t('common:header.edit')}}
-                v-list-tile(avatar, @click='pageHistory', v-if='mode !== `history`')
-                  v-list-tile-avatar: v-icon(color='indigo') history
-                  v-list-tile-content {{$t('common:header.history')}}
-                v-list-tile(avatar, @click='pageSource', v-if='mode !== `source`')
-                  v-list-tile-avatar: v-icon(color='indigo') code
-                  v-list-tile-content {{$t('common:header.viewSource')}}
-                v-list-tile(avatar, @click='pageMove', v-if='isAuthenticated')
-                  v-list-tile-avatar: v-icon(color='grey lighten-2') forward
-                  v-list-tile-content.grey--text.text--ligten-2 {{$t('common:header.move')}}
-                v-list-tile(avatar, @click='pageDelete', v-if='isAuthenticated')
-                  v-list-tile-avatar: v-icon(color='red darken-2') delete
-                  v-list-tile-content {{$t('common:header.delete')}}
+                v-list-item.pl-4(@click='pageView', v-if='mode !== `view`')
+                  v-list-item-avatar(size='24'): v-icon(color='indigo') subject
+                  v-list-item-title.body-2 {{$t('common:header.view')}}
+                v-list-item.pl-4(@click='pageEdit', v-if='mode !== `edit` && isAuthenticated')
+                  v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-file-document-edit-outline
+                  v-list-item-title.body-2 {{$t('common:header.edit')}}
+                v-list-item.pl-4(@click='pageHistory', v-if='mode !== `history`')
+                  v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-history
+                  v-list-item-title.body-2 {{$t('common:header.history')}}
+                v-list-item.pl-4(@click='pageSource', v-if='mode !== `source`')
+                  v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-code-braces
+                  v-list-item-title.body-2 {{$t('common:header.viewSource')}}
+                v-list-item.pl-4(@click='pageMove', v-if='isAuthenticated')
+                  v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-content-save-move-outline
+                  v-list-item-title.body-2.grey--text.text--ligten-2 {{$t('common:header.move')}}
+                v-list-item.pl-4(@click='pageDelete', v-if='isAuthenticated')
+                  v-list-item-avatar(size='24'): v-icon(color='red darken-2') mdi-trash-can-outline
+                  v-list-item-title.body-2 {{$t('common:header.delete')}}
               v-divider.my-0
               v-subheader {{$t('common:header.assets')}}
-              v-list-tile(avatar, @click='assets')
-                v-list-tile-avatar: v-icon(color='grey lighten-2') burst_mode
-                v-list-tile-content.grey--text.text--ligten-2 {{$t('common:header.imagesFiles')}}
+              v-list-item.pl-4(@click='assets')
+                v-list-item-avatar(size='24'): v-icon(color='grey lighten-2') mdi-folder-multiple-image
+                v-list-item-title.body-2.grey--text.text--ligten-2 {{$t('common:header.imagesFiles')}}
           v-toolbar-title(:class='{ "ml-2": $vuetify.breakpoint.mdAndUp, "ml-0": $vuetify.breakpoint.smAndDown }')
             span.subheading {{title}}
       v-flex(md4, v-if='$vuetify.breakpoint.mdAndUp')
@@ -71,7 +72,7 @@
                 solo
                 flat
                 hide-details,
-                prepend-inner-icon='search',
+                prepend-inner-icon='mdi-magnify',
                 :loading='searchIsLoading',
                 @keyup.enter='searchEnter'
                 @keyup.esc='searchClose'
@@ -80,12 +81,6 @@
                 @keyup.down='searchMove(`down`)'
                 @keyup.up='searchMove(`up`)'
               )
-                v-progress-linear(
-                  indeterminate,
-                  slot='progress',
-                  height='2',
-                  color='blue'
-                )
             v-menu(
               v-model='searchAdvMenuShown'
               left
@@ -96,11 +91,12 @@
               nudge-right='5'
               v-if='searchIsShown'
               )
-              v-btn.nav-header-search-adv(icon, outline, color='grey darken-2', slot='activator')
-                v-icon(color='white') expand_more
+              template(v-slot:activator='{ on }')
+                v-btn.nav-header-search-adv(icon, color='grey darken-2', v-on='on')
+                  v-icon(color='white') mdi-chevron-down
               v-card.radius-0(dark)
                 v-toolbar(flat, color='grey darken-4', dense)
-                  v-icon.mr-2 search
+                  v-icon.mr-2 mdi-feature-search-outline
                   v-subheader.pl-0 Advanced Search
                   v-spacer
                   v-chip(label, small, color='primary') Coming soon
@@ -119,14 +115,18 @@
                   )
                 v-divider
                 v-card-actions.grey.darken-3-d4
-                  v-btn(depressed, color='grey darken-3', block)
-                    v-icon(left) chevron_right
-                    span Save as defaults
-                  v-btn(depressed, color='grey darken-3', block)
-                    v-icon(left) cached
-                    span Reset
-      v-flex(xs6, :md4='searchIsShown', :md6='!searchIsShown')
-        v-toolbar.nav-header-inner(color='black', dark, flat)
+                  v-container.pa-0(grid-list-md)
+                    v-layout(row)
+                      v-flex(xs6)
+                        v-btn(depressed, color='grey darken-3', block)
+                          v-icon(left) mdi-chevron-right
+                          span Save as defaults
+                      v-flex(xs6)
+                        v-btn(depressed, color='grey darken-3', block)
+                          v-icon(left) mdi-cached
+                          span Reset
+      v-flex(xs6, md4)
+        v-toolbar.nav-header-inner.pr-4(color='black', dark, flat)
           v-spacer
           .navHeaderLoading.mr-3
             v-progress-circular(indeterminate, color='blue', :size='22', :width='2' v-show='isLoading')
@@ -138,53 +138,59 @@
             )
             v-icon(color='grey') search
           v-menu(offset-y, left, transition='slide-y-transition', v-if='mode === `view` && locales.length > 0')
-            v-tooltip(bottom, slot='activator')
-              v-btn(icon, slot='activator')
-                v-icon(color='grey') language
-              span {{$t('common:header.language')}}
+            template(v-slot:activator='{ on: menu }')
+              v-tooltip(bottom)
+                template(v-slot:activator='{ on: tooltip }')
+                  v-btn(icon, v-on='{ ...menu, ...tooltip }')
+                    v-icon(color='grey') mdi-web
+                span {{$t('common:header.language')}}
             v-list.py-0
               template(v-for='(lc, idx) of locales')
-                v-list-tile(@click='changeLocale(lc)')
-                  v-list-tile-action: v-chip(:color='lc.code === locale ? `blue` : `grey`', small, label, dark) {{lc.code.toUpperCase()}}
-                  v-list-tile-title {{lc.name}}
+                v-list-item(@click='changeLocale(lc)')
+                  v-list-item-action: v-chip(:color='lc.code === locale ? `blue` : `grey`', small, label, dark) {{lc.code.toUpperCase()}}
+                  v-list-item-title {{lc.name}}
                 v-divider.my-0(v-if='idx < locales.length - 1')
           v-tooltip(bottom, v-if='isAuthenticated && isAdmin')
-            v-btn.btn-animate-rotate(icon, href='/a', slot='activator')
-              v-icon(color='grey') settings
+            template(v-slot:activator='{ on }')
+              v-btn.btn-animate-rotate(icon, href='/a', v-on='on')
+                v-icon(color='grey') mdi-settings
             span {{$t('common:header.admin')}}
           v-menu(v-if='isAuthenticated', offset-y, min-width='300', left, transition='slide-y-transition')
-            v-tooltip(bottom, slot='activator')
-              v-btn(icon, slot='activator', outline, color='blue')
-                v-icon(v-if='picture.kind === `initials`', color='grey') account_circle
-                v-avatar(v-else-if='picture.kind === `image`', :size='29')
-                  v-img(:src='picture.url')
-              span {{$t('common:header.account')}}
+            template(v-slot:activator='{ on: menu }')
+              v-tooltip(bottom)
+                template(v-slot:activator='{ on: tooltip }')
+                  v-btn(icon, v-on='{ ...menu, ...tooltip }', outlined, color='blue')
+                    v-icon(v-if='picture.kind === `initials`', color='grey') mdi-account-circle
+                    v-avatar(v-else-if='picture.kind === `image`', :size='29')
+                      v-img(:src='picture.url')
+                span {{$t('common:header.account')}}
             v-list.py-0
-              v-list-tile.py-3.grey(avatar, :class='$vuetify.dark ? `darken-4-l5` : `lighten-5`')
-                v-list-tile-avatar
+              v-list-item.py-3.grey(avatar, :class='$vuetify.dark ? `darken-4-l5` : `lighten-5`')
+                v-list-item-avatar
                   v-avatar.blue(v-if='picture.kind === `initials`', :size='40')
                     span.white--text.subheading {{picture.initials}}
                   v-avatar(v-else-if='picture.kind === `image`', :size='40')
                     v-img(:src='picture.url')
-                v-list-tile-content
-                  v-list-tile-title {{name}}
-                  v-list-tile-sub-title {{email}}
+                v-list-item-content
+                  v-list-item-title {{name}}
+                  v-list-item-sub-title {{email}}
               v-divider.my-0
-              v-list-tile(href='/w', disabled)
-                v-list-tile-action: v-icon(color='blue') web
-                v-list-tile-title {{$t('common:header.myWiki')}}
+              v-list-item(href='/w', disabled)
+                v-list-item-action: v-icon(color='blue') mdi-view-compact-outline
+                v-list-item-title {{$t('common:header.myWiki')}}
               v-divider.my-0
-              v-list-tile(href='/p', disabled)
-                v-list-tile-action: v-icon(color='blue') person
-                v-list-tile-title {{$t('common:header.profile')}}
+              v-list-item(href='/p', disabled)
+                v-list-item-action: v-icon(color='blue') mdi-face-profile
+                v-list-item-title {{$t('common:header.profile')}}
               v-divider.my-0
-              v-list-tile(@click='logout')
-                v-list-tile-action: v-icon(color='red') exit_to_app
-                v-list-tile-title {{$t('common:header.logout')}}
+              v-list-item(@click='logout')
+                v-list-item-action: v-icon(color='red') mdi-logout
+                v-list-item-title {{$t('common:header.logout')}}
 
           v-tooltip(v-else, left)
-            v-btn(icon, slot='activator', outline, color='grey darken-3', href='/login')
-              v-icon(color='grey') account_circle
+            template(v-slot:activator='{ on }')
+              v-btn(icon, v-on='on', outline, color='grey darken-3', href='/login')
+                v-icon(color='grey') mdi-account-circle
             span {{$t('common:header.login')}}
 
     page-selector(mode='create', v-model='newPageModal', :open-handler='pageNewCreate')

+ 2 - 3
client/components/common/notify.vue

@@ -3,11 +3,10 @@
     :color='notification.style'
     top
     multi-line
-    auto-height
     v-model='notificationState'
     )
-    .text-xs-left
-      v-icon.mr-3(dark) {{ notification.icon }}
+    .text-left
+      v-icon.mr-3(dark) mdi-{{ notification.icon }}
       span {{ notification.message }}
 </template>
 

+ 13 - 13
client/components/common/page-selector.vue

@@ -1,5 +1,5 @@
 <template lang="pug">
-  v-dialog(v-model='isShown', lazy, max-width='850px')
+  v-dialog(v-model='isShown', max-width='850px')
     v-card.page-selector
       .dialog-header.is-dark
         v-icon.mr-2(color='white') find_in_page
@@ -35,21 +35,21 @@
       //-       v-btn(icon): v-icon forward
       //-       v-btn(icon): v-icon delete
       //-     v-list(dense)
-      //-       v-list-tile
-      //-         v-list-tile-avatar: v-icon insert_drive_file
-      //-         v-list-tile-title File A
+      //-       v-list-item
+      //-         v-list-item-avatar: v-icon insert_drive_file
+      //-         v-list-item-title File A
       //-       v-divider
-      //-       v-list-tile
-      //-         v-list-tile-avatar: v-icon insert_drive_file
-      //-         v-list-tile-title File B
+      //-       v-list-item
+      //-         v-list-item-avatar: v-icon insert_drive_file
+      //-         v-list-item-title File B
       //-       v-divider
-      //-       v-list-tile
-      //-         v-list-tile-avatar: v-icon insert_drive_file
-      //-         v-list-tile-title File C
+      //-       v-list-item
+      //-         v-list-item-avatar: v-icon insert_drive_file
+      //-         v-list-item-title File C
       //-       v-divider
-      //-       v-list-tile
-      //-         v-list-tile-avatar: v-icon insert_drive_file
-      //-         v-list-tile-title File D
+      //-       v-list-item
+      //-         v-list-item-avatar: v-icon insert_drive_file
+      //-         v-list-item-title File D
       v-card-actions.grey.pa-2(:class='darkMode ? `darken-3-d5` : `lighten-1`')
         v-select(
           solo

+ 20 - 18
client/components/common/search-results.vue

@@ -16,17 +16,17 @@
         .subheading {{$t('common:header.searchNoResult')}}
       template(v-if='results.length > 0')
         v-subheader.white--text {{$t('common:header.searchResultsCount', { total: response.totalHits })}}
-        v-list.search-results-items.radius-7(two-line)
+        v-list.search-results-items.radius-7.py-0(two-line)
           template(v-for='(item, idx) of results')
-            v-list-tile(@click='goToPage(item)', :key='item.id', :class='idx === cursor ? `highlighted` : ``')
-              v-list-tile-avatar(tile)
+            v-list-item(@click='goToPage(item)', :key='item.id', :class='idx === cursor ? `highlighted` : ``')
+              v-list-item-avatar(tile)
                 img(src='/svg/icon-selective-highlighting.svg')
-              v-list-tile-content
-                v-list-tile-title(v-html='item.title')
-                v-list-tile-sub-title(v-html='item.description')
-                .caption.grey--text.mt-1(v-html='item.path')
-              v-list-tile-action
-                v-chip(label) {{item.locale.toUpperCase()}}
+              v-list-item-content
+                v-list-item-title(v-html='item.title')
+                v-list-item-subtitle(v-html='item.description')
+                .caption.grey--text(v-html='item.path')
+              v-list-item-action
+                v-chip(label, outlined) {{item.locale.toUpperCase()}}
             v-divider(v-if='idx < results.length - 1')
         v-pagination.mt-3(
           v-if='paginationLength > 1'
@@ -38,18 +38,18 @@
         v-subheader.white--text.mt-3 {{$t('common:header.searchDidYouMean')}}
         v-list.search-results-suggestions.radius-7(dense, dark)
           template(v-for='(term, idx) of suggestions')
-            v-list-tile(:key='term', @click='setSearchTerm(term)', :class='idx + results.length === cursor ? `highlighted` : ``')
-              v-list-tile-avatar
-                v-icon search
-              v-list-tile-content
-                v-list-tile-title(v-html='term')
+            v-list-item(:key='term', @click='setSearchTerm(term)', :class='idx + results.length === cursor ? `highlighted` : ``')
+              v-list-item-avatar
+                v-icon mdi-magnify
+              v-list-item-content
+                v-list-item-title(v-html='term')
             v-divider(v-if='idx < suggestions.length - 1')
       .text-xs-center.pt-5(v-if='search.length > 1')
-        v-btn(outline, color='orange', @click='search = ``', v-if='results.length > 0')
-          v-icon(left) save
+        v-btn.mx-2(outlined, color='orange', @click='search = ``', v-if='results.length > 0')
+          v-icon(left) mdi-content-save
           span {{$t('common:header.searchCopyLink')}}
-        v-btn(outline, color='pink', @click='search = ``')
-          v-icon(left) clear
+        v-btn.mx-2(outlined, color='pink', @click='search = ``')
+          v-icon(left) mdi-close
           span {{$t('common:header.searchClose')}}
 </template>
 
@@ -205,6 +205,8 @@ export default {
   }
 
   &-items {
+    text-align: left;
+
     .highlighted {
       background: #FFF linear-gradient(to bottom, #FFF, mc('orange', '100'));
     }

+ 6 - 6
client/components/common/user-search.vue

@@ -30,13 +30,13 @@
           dense
           )
           template(v-for='(usr, idx) in items')
-            v-list-tile(:key='usr.id', @click='setUser(usr.id)')
-              v-list-tile-avatar(size='40', color='primary')
+            v-list-item(:key='usr.id', @click='setUser(usr.id)')
+              v-list-item-avatar(size='40', color='primary')
                 span.body-1.white--text {{usr.name | initials}}
-              v-list-tile-content
-                v-list-tile-title.body-2 {{usr.name}}
-                v-list-tile-sub-title {{usr.email}}
-              v-list-tile-action
+              v-list-item-content
+                v-list-item-title.body-2 {{usr.name}}
+                v-list-item-sub-title {{usr.email}}
+              v-list-item-action
                 v-icon(color='primary') arrow_forward
             v-divider.my-0(v-if='idx < items.length - 1')
       v-card-chin

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

@@ -24,10 +24,10 @@
             v-icon text_fields
           v-list.py-0
             template(v-for='(n, idx) in 6')
-              v-list-tile(@click='setHeaderLine(n)', :key='idx')
-                v-list-tile-action
+              v-list-item(@click='setHeaderLine(n)', :key='idx')
+                v-list-item-action
                   v-icon(:size='24 - (idx - 1) * 2') title
-                v-list-tile-title {{$t('editor:markup.heading', { level: n })}}
+                v-list-item-title {{$t('editor:markup.heading', { level: n })}}
               v-divider(v-if='idx < 5')
         v-tooltip(bottom, color='primary')
           v-btn.animated.fadeIn.wait-p4s(icon, slot='activator', @click='toggleMarkup({ start: `~` })').mx-0
@@ -41,30 +41,30 @@
           v-btn.animated.fadeIn.wait-p6s(icon, slot='activator').mx-0
             v-icon format_quote
           v-list.py-0
-            v-list-tile(@click='insertBeforeEachLine({ content: `> `})')
-              v-list-tile-action
+            v-list-item(@click='insertBeforeEachLine({ content: `> `})')
+              v-list-item-action
                 v-icon format_quote
-              v-list-tile-title {{$t('editor:markup.blockquote')}}
+              v-list-item-title {{$t('editor:markup.blockquote')}}
             v-divider
-            v-list-tile(@click='insertBeforeEachLine({ content: `> `, after: `{.is-info}`})')
-              v-list-tile-action
+            v-list-item(@click='insertBeforeEachLine({ content: `> `, after: `{.is-info}`})')
+              v-list-item-action
                 v-icon(color='blue') format_quote
-              v-list-tile-title {{$t('editor:markup.blockquoteInfo')}}
+              v-list-item-title {{$t('editor:markup.blockquoteInfo')}}
             v-divider
-            v-list-tile(@click='insertBeforeEachLine({ content: `> `, after: `{.is-success}`})')
-              v-list-tile-action
+            v-list-item(@click='insertBeforeEachLine({ content: `> `, after: `{.is-success}`})')
+              v-list-item-action
                 v-icon(color='success') format_quote
-              v-list-tile-title {{$t('editor:markup.blockquoteSuccess')}}
+              v-list-item-title {{$t('editor:markup.blockquoteSuccess')}}
             v-divider
-            v-list-tile(@click='insertBeforeEachLine({ content: `> `, after: `{.is-warning}`})')
-              v-list-tile-action
+            v-list-item(@click='insertBeforeEachLine({ content: `> `, after: `{.is-warning}`})')
+              v-list-item-action
                 v-icon(color='warning') format_quote
-              v-list-tile-title {{$t('editor:markup.blockquoteWarning')}}
+              v-list-item-title {{$t('editor:markup.blockquoteWarning')}}
             v-divider
-            v-list-tile(@click='insertBeforeEachLine({ content: `> `, after: `{.is-danger}`})')
-              v-list-tile-action
+            v-list-item(@click='insertBeforeEachLine({ content: `> `, after: `{.is-danger}`})')
+              v-list-item-action
                 v-icon(color='error') format_quote
-              v-list-tile-title {{$t('editor:markup.blockquoteError')}}
+              v-list-item-title {{$t('editor:markup.blockquoteError')}}
             v-divider
         v-tooltip(bottom, color='primary')
           v-btn.animated.fadeIn.wait-p7s(icon, slot='activator', @click='insertBeforeEachLine({ content: `- `})').mx-0

+ 6 - 6
client/components/editor/editor-modal-blocks.vue

@@ -10,14 +10,14 @@
                   .body-2.teal--text Blocks
               v-list(two-line)
                 template(v-for='(item, idx) of blocks')
-                  v-list-tile(@click='selectBlock(item)')
-                    v-list-tile-avatar
+                  v-list-item(@click='selectBlock(item)')
+                    v-list-item-avatar
                       v-avatar.radius-7(color='teal')
                         v-icon(dark) dashboard
-                    v-list-tile-content
-                      v-list-tile-title.body-2 {{item.title}}
-                      v-list-tile-sub-title {{item.description}}
-                    v-list-tile-avatar(v-if='block.key === item.key')
+                    v-list-item-content
+                      v-list-item-title.body-2 {{item.title}}
+                      v-list-item-sub-title {{item.description}}
+                    v-list-item-avatar(v-if='block.key === item.key')
                       v-icon.animated.fadeInLeft(color='teal') arrow_forward_ios
                   v-divider(v-if='idx < blocks.length - 1')
 

+ 21 - 21
client/components/editor/editor-modal-media.vue

@@ -79,41 +79,41 @@
                         v-btn.ma-0(icon, slot='activator')
                           v-icon(color='grey darken-2') more_horiz
                         v-list.py-0(style='border-top: 5px solid #444;')
-                          v-list-tile(@click='', disabled)
-                            v-list-tile-avatar
+                          v-list-item(@click='', disabled)
+                            v-list-item-avatar
                               v-icon(color='teal') short_text
-                            v-list-tile-content {{$t('common:actions.properties')}}
+                            v-list-item-content {{$t('common:actions.properties')}}
                           v-divider
                           template(v-if='props.item.kind === `IMAGE`')
-                            v-list-tile(@click='previewDialog = true', disabled)
-                              v-list-tile-avatar
+                            v-list-item(@click='previewDialog = true', disabled)
+                              v-list-item-avatar
                                 v-icon(color='green') image_search
-                              v-list-tile-content {{$t('common:actions.preview')}}
+                              v-list-item-content {{$t('common:actions.preview')}}
                             v-divider
-                            v-list-tile(@click='', disabled)
-                              v-list-tile-avatar
+                            v-list-item(@click='', disabled)
+                              v-list-item-avatar
                                 v-icon(color='indigo') crop_rotate
-                              v-list-tile-content {{$t('common:actions.edit')}}
+                              v-list-item-content {{$t('common:actions.edit')}}
                             v-divider
-                            v-list-tile(@click='', disabled)
-                              v-list-tile-avatar
+                            v-list-item(@click='', disabled)
+                              v-list-item-avatar
                                 v-icon(color='purple') offline_bolt
-                              v-list-tile-content {{$t('common:actions.optimize')}}
+                              v-list-item-content {{$t('common:actions.optimize')}}
                             v-divider
-                          v-list-tile(@click='openRenameDialog')
-                            v-list-tile-avatar
+                          v-list-item(@click='openRenameDialog')
+                            v-list-item-avatar
                               v-icon(color='orange') keyboard
-                            v-list-tile-content {{$t('common:actions.rename')}}
+                            v-list-item-content {{$t('common:actions.rename')}}
                           v-divider
-                          v-list-tile(@click='', disabled)
-                            v-list-tile-avatar
+                          v-list-item(@click='', disabled)
+                            v-list-item-avatar
                               v-icon(color='blue') forward
-                            v-list-tile-content {{$t('common:actions.move')}}
+                            v-list-item-content {{$t('common:actions.move')}}
                           v-divider
-                          v-list-tile(@click='deleteDialog = true')
-                            v-list-tile-avatar
+                          v-list-item(@click='deleteDialog = true')
+                            v-list-item-avatar
                               v-icon(color='red') delete
-                            v-list-tile-content {{$t('common:actions.delete')}}
+                            v-list-item-content {{$t('common:actions.delete')}}
                 template(slot='no-data')
                   v-alert.mt-3.radius-7(icon='folder_open', :value='true', outline, color='teal') {{$t('editor:assets.folderEmpty')}}
               .text-xs-center.py-2(v-if='this.pageTotal > 1')

+ 35 - 35
client/components/editor/markdown/help.vue

@@ -224,39 +224,39 @@
                 v-icon.mr-3(color='teal') keyboard
                 .body-2.teal--text Keyboard Shortcuts
               v-list.editor-markdown-help-kbd(two-line, dense)
-                v-list-tile
-                  v-list-tile-content.body-2 Bold
-                  v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd B]
+                v-list-item
+                  v-list-item-content.body-2 Bold
+                  v-list-item-action #[kbd {{ctrlKey}}] + #[kbd B]
                 v-divider
-                v-list-tile
-                  v-list-tile-content.body-2 Italic
-                  v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd I]
+                v-list-item
+                  v-list-item-content.body-2 Italic
+                  v-list-item-action #[kbd {{ctrlKey}}] + #[kbd I]
                 v-divider
-                v-list-tile
-                  v-list-tile-content.body-2 Increase Header Level
-                  v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + #[kbd Right]
+                v-list-item
+                  v-list-item-content.body-2 Increase Header Level
+                  v-list-item-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + #[kbd Right]
                 v-divider
-                v-list-tile
-                  v-list-tile-content.body-2 Decrease Header Level
-                  v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + #[kbd Left]
+                v-list-item
+                  v-list-item-content.body-2 Decrease Header Level
+                  v-list-item-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + #[kbd Left]
                 v-divider
-                v-list-tile
-                  v-list-tile-content.body-2 Save
-                  v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd S]
+                v-list-item
+                  v-list-item-content.body-2 Save
+                  v-list-item-action #[kbd {{ctrlKey}}] + #[kbd S]
                 v-divider
-                v-list-tile
-                  v-list-tile-content.body-2 Undo
-                  v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd Z]
+                v-list-item
+                  v-list-item-content.body-2 Undo
+                  v-list-item-action #[kbd {{ctrlKey}}] + #[kbd Z]
                 v-divider
-                v-list-tile
-                  v-list-tile-content.body-2 Redo
-                  v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd Y]
+                v-list-item
+                  v-list-item-content.body-2 Redo
+                  v-list-item-action #[kbd {{ctrlKey}}] + #[kbd Y]
                 v-divider
-                v-list-tile
-                  v-list-tile-content
-                    v-list-tile-title.body-2 Distraction Free Mode
-                    v-list-tile-sub-title Press <kbd>Esc</kbd> to exit.
-                  v-list-tile-action #[kbd F11]
+                v-list-item
+                  v-list-item-content
+                    v-list-item-title.body-2 Distraction Free Mode
+                    v-list-item-sub-title Press <kbd>Esc</kbd> to exit.
+                  v-list-item-action #[kbd F11]
 
           v-card.radius-7.animated.fadeInUp.wait-p3s.mt-3(light)
             v-card-text
@@ -264,17 +264,17 @@
                 v-icon.mr-3(color='teal') mouse
                 .body-2.teal--text Multi-Selection
               v-list.editor-markdown-help-kbd(two-line, dense)
-                v-list-tile
-                  v-list-tile-content.body-2 Multiple Cursors
-                  v-list-tile-action #[kbd {{ctrlKey}}] + Left Click
+                v-list-item
+                  v-list-item-content.body-2 Multiple Cursors
+                  v-list-item-action #[kbd {{ctrlKey}}] + Left Click
                 v-divider
-                v-list-tile
-                  v-list-tile-content.body-2 Select Region
-                  v-list-tile-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + Left Click
+                v-list-item
+                  v-list-item-content.body-2 Select Region
+                  v-list-item-action #[kbd {{ctrlKey}}] + #[kbd {{altKey}}] + Left Click
                 v-divider
-                v-list-tile
-                  v-list-tile-content.body-2 Deselect
-                  v-list-tile-action #[kbd Esc]
+                v-list-item
+                  v-list-item-content.body-2 Deselect
+                  v-list-item-action #[kbd Esc]
 </template>
 
 <script>

+ 18 - 18
client/components/history.vue

@@ -53,29 +53,29 @@
                     v-menu(offset-x, left)
                       v-btn(icon, slot='activator'): v-icon more_horiz
                       v-list(dense).history-promptmenu
-                        v-list-tile(@click='setDiffTarget(ph.versionId)')
-                          v-list-tile-avatar: v-icon call_made
-                          v-list-tile-title Set as Differencing Target
+                        v-list-item(@click='setDiffTarget(ph.versionId)')
+                          v-list-item-avatar: v-icon call_made
+                          v-list-item-title Set as Differencing Target
                         v-divider
-                        v-list-tile(@click='setDiffSource(ph.versionId)')
-                          v-list-tile-avatar: v-icon call_received
-                          v-list-tile-title Set as Differencing Source
+                        v-list-item(@click='setDiffSource(ph.versionId)')
+                          v-list-item-avatar: v-icon call_received
+                          v-list-item-title Set as Differencing Source
                         v-divider
-                        v-list-tile
-                          v-list-tile-avatar: v-icon code
-                          v-list-tile-title View Source
+                        v-list-item
+                          v-list-item-avatar: v-icon code
+                          v-list-item-title View Source
                         v-divider
-                        v-list-tile
-                          v-list-tile-avatar: v-icon cloud_download
-                          v-list-tile-title Download Version
+                        v-list-item
+                          v-list-item-avatar: v-icon cloud_download
+                          v-list-item-title Download Version
                         v-divider
-                        v-list-tile
-                          v-list-tile-avatar: v-icon restore
-                          v-list-tile-title Restore
+                        v-list-item
+                          v-list-item-avatar: v-icon restore
+                          v-list-item-title Restore
                         v-divider
-                        v-list-tile
-                          v-list-tile-avatar: v-icon call_split
-                          v-list-tile-title Branch off from here
+                        v-list-item
+                          v-list-item-avatar: v-icon call_split
+                          v-list-item-title Branch off from here
 
             v-btn.ma-0.radius-7(
               v-if='total > trail.length'

+ 12 - 12
client/components/profile.vue

@@ -3,20 +3,20 @@
     nav-header
     v-navigation-drawer.pb-0(v-model='profileDrawerShown', app, fixed, clipped, left, permanent)
       v-list(dense)
-        v-list-tile.pt-2(to='/profile')
-          v-list-tile-action: v-icon account_circle
-          v-list-tile-title Profile
-        v-list-tile(to='/preferences')
-          v-list-tile-action: v-icon settings
-          v-list-tile-title Preferences
+        v-list-item.pt-2(to='/profile')
+          v-list-item-action: v-icon account_circle
+          v-list-item-title Profile
+        v-list-item(to='/preferences')
+          v-list-item-action: v-icon settings
+          v-list-item-title Preferences
         v-divider.my-2
         v-subheader My Content
-        v-list-tile(to='/pages')
-          v-list-tile-action: v-icon pages
-          v-list-tile-title Pages
-        v-list-tile(to='/comments')
-          v-list-tile-action: v-icon question_answer
-          v-list-tile-title Comments
+        v-list-item(to='/pages')
+          v-list-item-action: v-icon pages
+          v-list-item-title Pages
+        v-list-item(to='/comments')
+          v-list-item-action: v-icon question_answer
+          v-list-item-title Comments
 
     v-content
       transition(name='profile-router')

+ 2 - 2
client/components/setup.vue

@@ -148,7 +148,7 @@ export default {
         try {
           const resp = await axios.post('/finalize', this.conf)
           if (resp.data.ok === true) {
-              this.success = true
+            this.success = true
             _.delay(() => {
               window.location.assign('/login')
             }, 3000)
@@ -157,7 +157,7 @@ export default {
             this.errorMessage = resp.data.error
             this.loading = false
           }
-        } catch(err) {
+        } catch (err) {
           window.alert(err.message)
         }
       }, 1000)

+ 12 - 0
client/graph/admin/users/users-mutation-create.gql

@@ -0,0 +1,12 @@
+mutation ($providerKey: String!, $email: String!, $name: String!, $passwordRaw: String, $groups: [Int]!, $mustChangePassword: Boolean, $sendWelcomeEmail: Boolean) {
+  users {
+    create(providerKey: $providerKey, email: $email, name: $name, passwordRaw: $passwordRaw, groups: $groups, mustChangePassword: $mustChangePassword, sendWelcomeEmail: $sendWelcomeEmail) {
+      responseResult {
+        succeeded
+        errorCode
+        slug
+        message
+      }
+    }
+  }
+}

+ 2 - 1
client/index-app.js

@@ -1,10 +1,11 @@
 require('core-js/stable')
 require('regenerator-runtime/runtime')
 
-require('vuetify/src/stylus/main.styl')
 require('./scss/app.scss')
 require('./themes/' + process.env.CURRENT_THEME + '/scss/app.scss')
 
+require('@mdi/font/css/materialdesignicons.css')
+
 require('./helpers/compatibility.js')
 require('./client-app.js')
 require('./themes/' + process.env.CURRENT_THEME + '/js/app.js')

+ 0 - 1
client/index-setup.js

@@ -1,7 +1,6 @@
 require('core-js/stable')
 require('regenerator-runtime/runtime')
 
-require('vuetify/src/stylus/main.styl')
 require('./scss/app.scss')
 require('./helpers/compatibility.js')
 

+ 0 - 3
client/scss/app.scss

@@ -15,12 +15,9 @@
 @import 'components/v-dialog';
 @import 'components/v-form';
 
-@import 'layout/md2';
-
 // @import '../libs/twemoji/twemoji-awesome';
 // @import '../libs/prism/prism.css';
 @import '~vue-tour/dist/vue-tour.css';
-@import '~vue-status-indicator/styles.css';
 @import '~xterm/dist/xterm.css';
 // @import 'node_modules/diff2html/dist/diff2html.min';
 

+ 30 - 30
client/scss/base/icons.scss

@@ -1,37 +1,37 @@
-@font-face {
-  font-family: 'Material Icons';
-  font-style: normal;
-  font-weight: 400;
-  src: local('Material Icons'),
-    local('MaterialIcons-Regular'),
-    url(/fonts/MaterialIcons-Regular.woff2) format('woff2'),
-    url(/fonts/MaterialIcons-Regular.woff) format('woff');
-}
+// @font-face {
+//   font-family: 'Material Icons';
+//   font-style: normal;
+//   font-weight: 400;
+//   src: local('Material Icons'),
+//     local('MaterialIcons-Regular'),
+//     url(/fonts/MaterialIcons-Regular.woff2) format('woff2'),
+//     url(/fonts/MaterialIcons-Regular.woff) format('woff');
+// }
 
-.material-icons {
-  font-family: 'Material Icons', sans-serif;
-  font-weight: normal;
-  font-style: normal;
-  font-size: 24px;  /* Preferred icon size */
-  display: inline-flex;
-  line-height: 1;
-  text-transform: none;
-  letter-spacing: normal;
-  word-wrap: normal;
-  white-space: nowrap;
-  direction: ltr;
+// .material-icons {
+//   font-family: 'Material Icons', sans-serif;
+//   font-weight: normal;
+//   font-style: normal;
+//   font-size: 24px;  /* Preferred icon size */
+//   display: inline-flex;
+//   line-height: 1;
+//   text-transform: none;
+//   letter-spacing: normal;
+//   word-wrap: normal;
+//   white-space: nowrap;
+//   direction: ltr;
 
-  /* Support for all WebKit browsers. */
-  -webkit-font-smoothing: antialiased;
-  /* Support for Safari and Chrome. */
-  text-rendering: optimizeLegibility;
+//   /* Support for all WebKit browsers. */
+//   -webkit-font-smoothing: antialiased;
+//   /* Support for Safari and Chrome. */
+//   text-rendering: optimizeLegibility;
 
-  /* Support for Firefox. */
-  -moz-osx-font-smoothing: grayscale;
+//   /* Support for Firefox. */
+//   -moz-osx-font-smoothing: grayscale;
 
-  /* Support for IE. */
-  font-feature-settings: 'liga';
-}
+//   /* Support for IE. */
+//   font-feature-settings: 'liga';
+// }
 
 .icons {
   display: inline-block;

+ 0 - 17
client/scss/layout/_md2.scss

@@ -1,17 +0,0 @@
-.md2 {
-
-  &.v-text-field {
-    .v-input__slot {
-      border-radius: 7px;
-    }
-  }
-
-  &.v-btn {
-    border-radius: 7px;
-  }
-
-  &.v-card {
-    border-radius: 7px;
-  }
-
-}

二进制
client/static/favicons/android-icon-144x144.png


二进制
client/static/favicons/android-icon-192x192.png


二进制
client/static/favicons/android-icon-36x36.png


二进制
client/static/favicons/android-icon-48x48.png


二进制
client/static/favicons/android-icon-72x72.png


二进制
client/static/favicons/android-icon-96x96.png


二进制
client/static/favicons/apple-icon-114x114.png


二进制
client/static/favicons/apple-icon-120x120.png


二进制
client/static/favicons/apple-icon-144x144.png


二进制
client/static/favicons/apple-icon-152x152.png


二进制
client/static/favicons/apple-icon-180x180.png


二进制
client/static/favicons/apple-icon-57x57.png


二进制
client/static/favicons/apple-icon-60x60.png


二进制
client/static/favicons/apple-icon-72x72.png


二进制
client/static/favicons/apple-icon-76x76.png


二进制
client/static/favicons/apple-icon-precomposed.png


二进制
client/static/favicons/apple-icon.png


二进制
client/static/favicons/favicon-16x16.png


二进制
client/static/favicons/favicon-32x32.png


二进制
client/static/favicons/favicon-96x96.png


二进制
client/static/favicons/ms-icon-144x144.png


二进制
client/static/favicons/ms-icon-150x150.png


二进制
client/static/favicons/ms-icon-310x310.png


二进制
client/static/favicons/ms-icon-70x70.png


二进制
client/static/fonts/MaterialIcons-Regular.woff


二进制
client/static/fonts/MaterialIcons-Regular.woff2


二进制
client/static/fonts/Roboto-Bold.woff


二进制
client/static/fonts/Roboto-BoldItalic.woff2


二进制
client/static/fonts/Roboto-Italic.woff2


二进制
client/static/fonts/Roboto-MediumItalic.woff


二进制
client/static/fonts/Roboto-MediumItalic.woff2


二进制
client/static/fonts/Roboto-Regular.woff


二进制
client/static/fonts/Roboto-Regular.woff2


二进制
client/static/fonts/RobotoMono-Regular.woff2


二进制
client/static/img/donate_eth_qr.png


二进制
client/static/img/donate_opencollective.png


二进制
client/static/img/donate_patreon.png


二进制
client/static/img/donate_paypal.png


+ 93 - 72
client/themes/default/components/page.vue

@@ -41,83 +41,93 @@
             divider='/'
             )
             template(slot='item', slot-scope='props')
-              v-icon(v-if='props.item.path === "/"', small, @click='goHome') home
-              v-btn.ma-0(v-else, :href='props.item.path', small, flat) {{props.item.name}}
+              v-icon(v-if='props.item.path === "/"', small, @click='goHome') mdi-home
+              v-btn.ma-0(v-else, :href='props.item.path', small, text) {{props.item.name}}
           template(v-if='!isPublished')
             v-spacer
             .caption.red--text {{$t('common:page.unpublished')}}
             status-indicator.ml-3(negative, pulse)
         v-divider
-      v-layout(row)
-        v-flex(xs12, lg9, xl10)
-          v-toolbar(:color='darkMode ? `grey darken-4-l3` : `grey lighten-4`', flat, :height='90')
-            div
-              .headline.grey--text(:class='darkMode ? `text--lighten-2` : `text--darken-3`') {{title}}
-              .caption.grey--text.text--darken-1 {{description}}
-          v-divider
-          .contents(ref='container')
-            slot(name='contents')
+      v-toolbar.px-2(:color='darkMode ? `grey darken-4-l3` : `grey lighten-4`', flat, :height='90')
+        div
+          .headline.grey--text(:class='darkMode ? `text--lighten-2` : `text--darken-3`') {{title}}
+          .caption.grey--text.text--darken-1 {{description}}
+        v-spacer
+      v-divider
+      v-container.pl-5.pt-2(fill-height, fluid, grid-list-xl)
+        v-layout(row)
+          v-flex.page-col-sd(lg3, xl2, fill-height, v-if='$vuetify.breakpoint.lgAndUp')
+            v-card(v-if='toc.length')
+              .overline.pa-5.pb-0(:class='darkMode ? `indigo--text text--lighten-3` : `primary--text`') {{$t('common:page.toc')}}
+              v-list.pb-3(dense, nav, :class='darkMode ? `darken-3-d3` : ``')
+                template(v-for='(tocItem, tocIdx) in toc')
+                  v-list-item(@click='$vuetify.goTo(tocItem.anchor, scrollOpts)')
+                    v-icon(color='grey', small) mdi-chevron-right
+                    v-list-item-title.pl-3 {{tocItem.title}}
+                  // v-divider(v-if='tocIdx < toc.length - 1 || tocItem.children.length')
+                  template(v-for='tocSubItem in tocItem.children')
+                    v-list-item(@click='$vuetify.goTo(tocSubItem.anchor, scrollOpts)')
+                      v-icon.pl-3(color='grey lighten-1', small) mdi-chevron-right
+                      v-list-item-title.pl-3.caption.grey--text.text--darken-1 {{tocSubItem.title}}
+                    // v-divider(inset, v-if='tocIdx < toc.length - 1')
 
-        v-flex(lg3, xl2, fill-height, v-if='$vuetify.breakpoint.lgAndUp')
-          v-toolbar(:color='darkMode ? `grey darken-4-l3` : `grey lighten-4`', flat, :height='90')
-            div
-              .caption.grey--text.text--lighten-1 {{$t('common:page.lastEditedBy')}}
-              .body-2.grey--text(:class='darkMode ? `` : `text--darken-3`') {{ authorName }}
-              .caption.grey--text.text--darken-1 {{ updatedAt | moment('calendar') }}
-            template(v-if='isAuthenticated')
-              v-spacer
-              v-tooltip(left)
-                v-btn.btn-animate-edit(icon, slot='activator', :href='"/e/" + locale + "/" + path')
-                  v-icon(color='grey') edit
-                span {{$t('common:page.editPage')}}
-          v-divider
-          template(v-if='toc.length')
-            v-list.grey.pb-3(dense, :class='darkMode ? `darken-3-d3` : `lighten-3`')
-              v-subheader.pl-4(:class='darkMode ? `indigo--text text--lighten-3` : `primary--text`') {{$t('common:page.toc')}}
-              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(dense, :class='darkMode ? `darken-3` : `lighten-4`')
-          //-   v-subheader.pl-4.yellow--text.text--darken-4 Rating
-          //-   .text-xs-center
-          //-     v-rating(
-          //-       v-model='rating'
-          //-       color='yellow darken-3'
-          //-       background-color='grey lighten-1'
-          //-       half-increments
-          //-       hover
-          //-     )
-          //-     .pb-2.caption.grey--text 5 votes
-          //- v-divider
-          template(v-if='tags.length')
-            v-list.grey(dense, :class='darkMode ? `darken-3-d3` : `lighten-3`')
-              v-subheader.pl-4.teal--text Tags
-              template(v-for='(tag, idx) in tags')
-                v-list-tile(:href='`/t/` + tag.slug')
-                  v-list-tile-avatar: v-icon(color='teal') label
-                  v-list-tile-title {{tag.title}}
-                v-divider(inset, v-if='idx < tags.length - 1')
-            v-divider
-          v-toolbar(:color='darkMode ? `grey darken-3` : `grey lighten-4`', flat, dense)
-            v-spacer
-            v-tooltip(bottom)
-              v-btn(icon, slot='activator'): v-icon(color='grey') bookmark
-              span {{$t('common:page.bookmark')}}
-            v-tooltip(bottom)
-              v-btn(icon, slot='activator'): v-icon(color='grey') share
-              span {{$t('common:page.share')}}
-            v-tooltip(bottom)
-              v-btn(icon, slot='activator'): v-icon(color='grey') print
-              span {{$t('common:page.printFormat')}}
-            v-spacer
+            v-card.mt-5
+              .pa-5.pt-3
+                .overline.indigo--text.d-flex.align-center
+                  span {{$t('common:page.lastEditedBy')}}
+                  v-spacer
+                  v-tooltip(left, v-if='isAuthenticated')
+                    template(v-slot:activator='{ on }')
+                      v-btn.btn-animate-edit(icon, :href='"/e/" + locale + "/" + path', v-on='on', x-small)
+                        v-icon(color='grey', dense) mdi-pencil
+                    span {{$t('common:page.editPage')}}
+                .body-2.grey--text(:class='darkMode ? `` : `text--darken-3`') {{ authorName }}
+                .caption.grey--text.text--darken-1 {{ updatedAt | moment('calendar') }}
+
+            v-card.mt-5
+              .pa-5
+                .overline.teal--text.pb-2 Tags
+                v-chip.mr-1(
+                  label
+                  color='teal lighten-5'
+                  v-for='(tag, idx) in tags'
+                  :href='`/t/` + tag.slug'
+                  :key='tag.slug'
+                  )
+                  v-icon(color='teal', left, small) mdi-label
+                  span.teal--text.text--darken-2 {{tag.text}}
+              v-divider
+              .pa-5
+                .overline.pb-2.yellow--text.text--darken-4 Rating
+                .text-center
+                  v-rating(
+                    v-model='rating'
+                    color='yellow darken-3'
+                    background-color='grey lighten-1'
+                    half-increments
+                    hover
+                  )
+                  .caption.grey--text 5 votes
+              v-divider
+              v-toolbar(:color='darkMode ? `grey darken-3` : `grey lighten-4`', flat, dense)
+                v-spacer
+                v-tooltip(bottom)
+                  template(v-slot:activator='{ on }')
+                    v-btn(icon, small, v-on='on'): v-icon(color='grey') mdi-bookmark
+                  span {{$t('common:page.bookmark')}}
+                v-tooltip(bottom)
+                  template(v-slot:activator='{ on }')
+                    v-btn(icon, small, v-on='on'): v-icon(color='grey') mdi-share-variant
+                  span {{$t('common:page.share')}}
+                v-tooltip(bottom)
+                  template(v-slot:activator='{ on }')
+                    v-btn(icon, small, v-on='on'): v-icon(color='grey') mdi-printer
+                  span {{$t('common:page.printFormat')}}
+                v-spacer
+
+          v-flex.page-col-content(xs12, lg9, xl10)
+            .contents(ref='container')
+              slot(name='contents')
     nav-footer
     notify
     search-results
@@ -133,7 +143,7 @@
         @click='$vuetify.goTo(0, scrollOpts)'
         color='primary'
         )
-        v-icon arrow_upward
+        v-icon mdi-arrow-up
 </template>
 
 <script>
@@ -142,6 +152,17 @@ import Prism from 'prismjs'
 import { get } from 'vuex-pathify'
 import _ from 'lodash'
 
+Prism.plugins.autoloader.languages_path = '/js/prism/'
+Prism.plugins.NormalizeWhitespace.setDefaults({
+  'remove-trailing': true,
+  'remove-indent': true,
+  'left-trim': true,
+  'right-trim': true,
+  'break-lines': 160,
+  'remove-initial-line-feed': true,
+  'tabs-to-spaces': 2
+})
+
 export default {
   components: {
     StatusIndicator

+ 12 - 7
client/themes/default/scss/app.scss

@@ -2,7 +2,7 @@
 
 .contents {
   color: mc('grey', '800');
-  padding: 24px 0 50px;
+  padding: .5rem 0 50px;
   position: relative;
 
   > div > *:first-child {
@@ -51,7 +51,7 @@
   }
 
   h1 {
-    padding: 0 24px;
+    padding: 0;
     color: mc('blue', '800');
     margin-top: 2rem;
     position: relative;
@@ -68,6 +68,7 @@
       width: 100%;
       height: 2px;
       background: linear-gradient(to right, mc('theme', 'primary'), rgba(mc('theme', 'primary'), 0));
+      border-radius: 3px;
 
       @at-root .theme--dark & {
         background: linear-gradient(to right, mc('teal', '300') 0%, mc('teal', '500') 10%, rgba(mc('teal', '900'), 0) 100%);
@@ -82,7 +83,7 @@
     }
   }
   h2 {
-    margin: 1rem 24px 0 24px;
+    margin: 1rem 0 0 0;
     padding: 8px 0 0 0;
     color: mc('grey', '800');
     position: relative;
@@ -113,7 +114,7 @@
     }
   }
   h3 {
-    margin: 0 24px;
+    margin: 0;
     padding: 8px 0 0 0;
     color: mc('grey', '700');
     position: relative;
@@ -134,7 +135,7 @@
   }
   h4, h5, h6 {
     font-size: 1rem;
-    margin: 0 24px;
+    margin: 0;
     padding: 8px 0 0 0;
     color: mc('grey', '700');
     position: relative;
@@ -176,10 +177,14 @@
     @at-root .contents > div > p:first-child {
       padding-top: 0;
     }
+
+    @at-root .v-application & {
+      margin-bottom: 0;
+    }
   }
 
   hr {
-    margin: 1rem;
+    margin: 1rem 0;
     height: 1px;
     border: none;
     background-color: mc('grey', '400');
@@ -198,7 +203,7 @@
     padding: 0 0 1rem 0;
     border-left: 5px solid mc('blue', '500');
     border-radius: .5rem;
-    margin: 1rem 2rem;
+    margin: 1rem 0;
 
     > p:first-child .emoji {
       margin-right: .5rem;

二进制
client/themes/default/thumbnail.png


+ 1 - 7
dev/templates/legacy.pug

@@ -28,13 +28,7 @@ html
     link(rel='manifest', href='/manifest.json')
 
     //- Icon Set
-    if config.theming.iconset === 'mdi'
-      link(
-        type='text/css'
-        rel='stylesheet'
-        href='https://cdn.materialdesignicons.com/3.7.95/css/materialdesignicons.min.css'
-        )
-    else if config.theming.iconset === 'fa'
+    if config.theming.iconset === 'fa'
       link(
         type='text/css'
         rel='stylesheet'

+ 1 - 7
dev/templates/master.pug

@@ -32,13 +32,7 @@ html
       var siteConfig = !{JSON.stringify(siteConfig)}; var siteLangs = !{JSON.stringify(langs)}
 
     //- Icon Set
-    if config.theming.iconset === 'mdi'
-      link(
-        type='text/css'
-        rel='stylesheet'
-        href='https://cdn.materialdesignicons.com/3.7.95/css/materialdesignicons.min.css'
-        )
-    else if config.theming.iconset === 'fa'
+    if config.theming.iconset === 'fa'
       link(
         type='text/css'
         rel='stylesheet'

+ 28 - 9
dev/webpack/webpack.dev.js

@@ -3,6 +3,7 @@ const path = require('path')
 const fs = require('fs-extra')
 const yargs = require('yargs').argv
 const _ = require('lodash')
+const Fiber = require('fibers')
 
 const { VueLoaderPlugin } = require('vue-loader')
 const CopyWebpackPlugin = require('copy-webpack-plugin')
@@ -10,6 +11,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin')
 const HtmlWebpackPugPlugin = require('html-webpack-pug-plugin')
 const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin')
 const SriWebpackPlugin = require('webpack-subresource-integrity')
+const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin')
 const WriteFilePlugin = require('write-file-webpack-plugin')
 
 const babelConfig = fs.readJsonSync(path.join(process.cwd(), '.babelrc'))
@@ -66,7 +68,7 @@ module.exports = {
         ]
       },
       {
-        test: /\.scss$/,
+        test: /\.sass$/,
         use: [
           {
             loader: 'cache-loader',
@@ -80,24 +82,39 @@ module.exports = {
           {
             loader: 'sass-loader',
             options: {
+              implementation: require('sass'),
+              fiber: Fiber,
               sourceMap: false
             }
-          },
-          {
-            loader: 'sass-resources-loader',
-            options: {
-              resources: path.join(process.cwd(), '/client/scss/global.scss')
-            }
           }
         ]
       },
       {
-        test: /\.styl$/,
+        test: /\.scss$/,
         use: [
+          {
+            loader: 'cache-loader',
+            options: {
+              cacheDirectory: cacheDir
+            }
+          },
           'style-loader',
           'css-loader',
           'postcss-loader',
-          'stylus-loader'
+          {
+            loader: 'sass-loader',
+            options: {
+              implementation: require('sass'),
+              fiber: Fiber,
+              sourceMap: false
+            }
+          },
+          {
+            loader: 'sass-resources-loader',
+            options: {
+              resources: path.join(process.cwd(), '/client/scss/global.scss')
+            }
+          }
         ]
       },
       {
@@ -186,8 +203,10 @@ module.exports = {
   },
   plugins: [
     new VueLoaderPlugin(),
+    new VuetifyLoaderPlugin(),
     new CopyWebpackPlugin([
       { from: 'client/static' },
+      { from: './node_modules/prismjs/components', to: 'js/prism' },
       { from: './node_modules/graphql-voyager/dist/voyager.worker.js', to: 'js/' }
     ], {}),
     new HtmlWebpackPlugin({

+ 35 - 34
package.json

@@ -38,10 +38,10 @@
     "@bugsnag/js": "6.3.2",
     "algoliasearch": "3.33.0",
     "apollo-fetch": "0.7.0",
-    "apollo-server": "2.6.9",
-    "apollo-server-express": "2.6.9",
+    "apollo-server": "2.7.2",
+    "apollo-server-express": "2.7.2",
     "auto-load": "3.0.4",
-    "aws-sdk": "2.493.0",
+    "aws-sdk": "2.501.0",
     "axios": "0.19.0",
     "azure-search-client": "3.1.5",
     "bcryptjs-then": "1.0.1",
@@ -67,7 +67,7 @@
     "express": "4.17.1",
     "express-brute": "1.0.1",
     "express-session": "1.16.2",
-    "file-type": "12.0.1",
+    "file-type": "12.1.0",
     "filesize": "4.1.2",
     "fs-extra": "8.1.0",
     "getos": "3.1.1",
@@ -86,8 +86,8 @@
     "js-yaml": "3.13.1",
     "jsonwebtoken": "8.5.1",
     "klaw": "3.0.0",
-    "knex": "0.19.0",
-    "lodash": "4.17.14",
+    "knex": "0.19.1",
+    "lodash": "4.17.15",
     "markdown-it": "9.0.1",
     "markdown-it-abbr": "1.0.4",
     "markdown-it-anchor": "5.2.4",
@@ -108,11 +108,11 @@
     "moment-timezone": "0.5.26",
     "mongodb": "3.2.7",
     "mssql": "5.1.0",
-    "multer": "1.4.1",
+    "multer": "1.4.2",
     "mysql2": "1.6.5",
     "nanoid": "2.0.3",
     "node-2fa": "1.1.2",
-    "node-cache": "4.2.0",
+    "node-cache": "4.2.1",
     "nodemailer": "6.3.0",
     "objection": "1.6.9",
     "passport": "0.4.0",
@@ -135,7 +135,7 @@
     "passport-saml": "1.1.0",
     "passport-twitch": "1.0.3",
     "pem-jwk": "2.0.0",
-    "pg": "7.11.0",
+    "pg": "7.12.0",
     "pg-hstore": "2.3.3",
     "pg-query-stream": "2.0.0",
     "pg-tsquery": "8.0.5",
@@ -148,9 +148,9 @@
     "safe-regex": "2.0.2",
     "sanitize-filename": "1.6.1",
     "scim-query-filter-parser": "1.1.0",
-    "semver": "6.2.0",
+    "semver": "6.3.0",
     "serve-favicon": "2.5.0",
-    "simple-git": "1.121.0",
+    "simple-git": "1.123.0",
     "solr-node": "1.2.1",
     "sqlite3": "4.0.9",
     "striptags": "3.1.1",
@@ -163,7 +163,7 @@
     "validator": "11.1.0",
     "validator-as-promised": "1.0.2",
     "winston": "3.2.1",
-    "yargs": "13.2.4"
+    "yargs": "13.3.0"
   },
   "devDependencies": {
     "@babel/cli": "^7.5.0",
@@ -179,6 +179,7 @@
     "@babel/plugin-syntax-import-meta": "^7.2.0",
     "@babel/polyfill": "^7.4.4",
     "@babel/preset-env": "^7.5.4",
+    "@mdi/font": "3.8.95",
     "@panter/vue-i18next": "0.15.1",
     "@vue/babel-preset-app": "3.9.2",
     "animate-sass": "0.8.2",
@@ -198,27 +199,28 @@
     "babel-loader": "^8.0.6",
     "babel-plugin-graphql-tag": "2.4.0",
     "babel-plugin-lodash": "3.3.4",
-    "babel-plugin-prismjs": "1.0.2",
+    "babel-plugin-prismjs": "1.1.1",
     "babel-plugin-transform-imports": "2.0.0",
     "brace": "0.11.1",
-    "cache-loader": "4.0.1",
+    "cache-loader": "4.1.0",
     "chart.js": "2.8.0",
     "clean-webpack-plugin": "3.0.0",
-    "copy-webpack-plugin": "5.0.3",
+    "copy-webpack-plugin": "5.0.4",
     "core-js": "3.1.4",
-    "css-loader": "3.0.0",
+    "css-loader": "3.1.0",
     "cssnano": "4.1.10",
     "duplicate-package-checker-webpack-plugin": "3.0.0",
     "epic-spinners": "1.1.0",
-    "eslint": "6.0.1",
+    "eslint": "6.1.0",
     "eslint-config-requarks": "1.0.7",
     "eslint-config-standard": "13.0.1",
-    "eslint-plugin-import": "2.18.0",
+    "eslint-plugin-import": "2.18.2",
     "eslint-plugin-node": "9.1.0",
     "eslint-plugin-promise": "4.2.1",
     "eslint-plugin-standard": "4.0.0",
     "eslint-plugin-vue": "5.2.3",
-    "file-loader": "4.0.0",
+    "fibers": "4.0.1",
+    "file-loader": "4.1.0",
     "filepond": "4.4.11",
     "filepond-plugin-file-validate-type": "1.2.4",
     "filesize.js": "1.0.2",
@@ -228,16 +230,15 @@
     "graphql-tag": "^2.10.1",
     "graphql-voyager": "1.0.0-rc.27",
     "hammerjs": "2.0.8",
-    "html-webpack-plugin": "3.2.0",
+    "html-webpack-plugin": "4.0.0-beta.8",
     "html-webpack-pug-plugin": "2.0.0",
     "i18next-chained-backend": "2.0.0",
     "i18next-localstorage-backend": "3.0.0",
     "i18next-xhr-backend": "3.0.0",
     "ignore-loader": "0.1.2",
     "js-cookie": "2.2.0",
-    "mini-css-extract-plugin": "0.7.0",
+    "mini-css-extract-plugin": "0.8.0",
     "moment-duration-format": "2.3.2",
-    "node-sass": "4.12.0",
     "offline-plugin": "5.0.7",
     "optimize-css-assets-webpack-plugin": "5.0.3",
     "postcss-cssnext": "3.1.0",
@@ -247,50 +248,50 @@
     "postcss-loader": "3.0.0",
     "postcss-preset-env": "6.7.0",
     "postcss-selector-parser": "6.0.2",
-    "prismjs": "1.16.0",
+    "prismjs": "1.17.1",
     "pug-lint": "2.6.0",
     "pug-loader": "2.4.0",
     "pug-plain-loader": "1.0.0",
-    "raw-loader": "3.0.0",
+    "raw-loader": "3.1.0",
     "react": "16.8.6",
     "react-dom": "16.8.6",
     "resolve-url-loader": "3.1.0",
+    "sass": "1.22.7",
     "sass-loader": "7.1.0",
     "sass-resources-loader": "2.0.1",
     "script-ext-html-webpack-plugin": "2.1.4",
     "simple-progress-webpack-plugin": "1.1.2",
     "style-loader": "0.23.1",
-    "stylus": "0.54.5",
-    "stylus-loader": "3.0.2",
     "terser": "4.1.2",
     "twemoji-awesome": "1.0.6",
-    "url-loader": "2.0.1",
-    "vee-validate": "2.2.12",
+    "url-loader": "2.1.0",
+    "vee-validate": "2.2.13",
     "velocity-animate": "1.5.2",
     "viz.js": "2.1.2",
     "vue": "2.6.10",
-    "vue-apollo": "3.0.0-rc.1",
+    "vue-apollo": "3.0.0-rc.2",
     "vue-chartjs": "3.4.2",
     "vue-clipboards": "1.3.0",
     "vue-codemirror": "4.0.6",
-    "vue-filepond": "5.1.1",
+    "vue-filepond": "5.1.2",
     "vue-hot-reload-api": "2.3.3",
-    "vue-loader": "15.7.0",
+    "vue-loader": "15.7.1",
     "vue-material-design-icons": "3.3.1",
     "vue-moment": "4.0.0",
     "vue-router": "3.0.7",
     "vue-simple-breakpoints": "1.0.3",
-    "vue-status-indicator": "1.1.1",
+    "vue-status-indicator": "1.2.1",
     "vue-template-compiler": "2.6.10",
     "vue-tour": "1.1.0",
     "vue2-animate": "2.1.0",
     "vuedraggable": "2.23.0",
     "vuescroll": "4.13.1",
-    "vuetify": "1.5.16",
+    "vuetify": "2.0.1",
+    "vuetify-loader": "1.3.0",
     "vuex": "3.1.1",
     "vuex-pathify": "1.2.4",
     "vuex-persistedstate": "2.5.4",
-    "webpack": "4.35.3",
+    "webpack": "4.38.0",
     "webpack-bundle-analyzer": "3.3.2",
     "webpack-cli": "3.3.6",
     "webpack-dev-middleware": "3.7.0",

+ 13 - 0
server/db/migrations-sqlite/2.0.0-beta.242.js

@@ -0,0 +1,13 @@
+exports.up = knex => {
+  return knex.schema
+    .table('users', table => {
+      table.boolean('mustChangePwd').notNullable().defaultTo(false)
+    })
+}
+
+exports.down = knex => {
+  return knex.schema
+    .table('users', table => {
+      table.dropColumn('mustChangePwd')
+    })
+}

+ 13 - 0
server/db/migrations/2.0.0-beta.242.js

@@ -0,0 +1,13 @@
+exports.up = knex => {
+  return knex.schema
+    .table('users', table => {
+      table.boolean('mustChangePwd').notNullable().defaultTo(false)
+    })
+}
+
+exports.down = knex => {
+  return knex.schema
+    .table('users', table => {
+      table.dropColumn('mustChangePwd')
+    })
+}

+ 1 - 5
server/graph/resolvers/user.js

@@ -29,11 +29,7 @@ module.exports = {
   },
   UserMutation: {
     create(obj, args) {
-      return WIKI.models.users.register({
-        ...args,
-        verify: false,
-        bypassChecks: true
-      })
+      return WIKI.models.users.createNewUser(args)
     },
     delete(obj, args) {
       return WIKI.models.users.query().deleteById(args.id)

+ 4 - 2
server/graph/schemas/user.graphql

@@ -36,10 +36,12 @@ type UserQuery {
 type UserMutation {
   create(
     email: String!
-    name: String
+    name: String!
     passwordRaw: String
     providerKey: String!
-    providerId: String
+    groups: [Int]!
+    mustChangePassword: Boolean
+    sendWelcomeEmail: Boolean
   ): UserResponse @auth(requires: ["write:users", "manage:users", "manage:system"])
 
   update(

+ 82 - 0
server/models/users.js

@@ -360,6 +360,88 @@ module.exports = class User extends Model {
     throw new WIKI.Error.AuthTFAInvalid()
   }
 
+  static async createNewUser ({ providerKey, email, passwordRaw, name, groups, mustChangePassword, sendWelcomeEmail }) {
+    // Input sanitization
+    email = _.toLower(email)
+
+    // Input validation
+    const validation = validate({
+      email,
+      passwordRaw,
+      name
+    }, {
+      email: {
+        email: true,
+        length: {
+          maximum: 255
+        }
+      },
+      passwordRaw: {
+        presence: {
+          allowEmpty: false
+        },
+        length: {
+          minimum: 6
+        }
+      },
+      name: {
+        presence: {
+          allowEmpty: false
+        },
+        length: {
+          minimum: 2,
+          maximum: 255
+        }
+      }
+    }, { format: 'flat' })
+    if (validation && validation.length > 0) {
+      throw new WIKI.Error.InputInvalid(validation[0])
+    }
+
+    // Check if email already exists
+    const usr = await WIKI.models.users.query().findOne({ email, providerKey })
+    if (!usr) {
+      // Create the account
+      const newUsr = await WIKI.models.users.query().insert({
+        provider: providerKey,
+        email,
+        name,
+        password: passwordRaw,
+        locale: 'en',
+        defaultEditor: 'markdown',
+        tfaIsActive: false,
+        isSystem: false,
+        isActive: true,
+        isVerified: true,
+        mustChangePwd: (mustChangePassword === true)
+      })
+
+      // Assign to group(s)
+      if (groups.length > 0) {
+        await newUsr.$relatedQuery('groups').relate(groups)
+      }
+
+      if (sendWelcomeEmail) {
+        // Send welcome email
+        await WIKI.mail.send({
+          template: 'accountWelcome',
+          to: email,
+          subject: `Welcome to the wiki ${WIKI.config.title}`,
+          data: {
+            preheadertext: `You've been invited to the wiki ${WIKI.config.title}`,
+            title: `You've been invited to the wiki ${WIKI.config.title}`,
+            content: `Click the button below to access the wiki.`,
+            buttonLink: `${WIKI.config.host}/login`,
+            buttonText: 'Login'
+          },
+          text: `You've been invited to the wiki ${WIKI.config.title}: ${WIKI.config.host}/login`
+        })
+      }
+    } else {
+      throw new WIKI.Error.AuthAccountAlreadyExists()
+    }
+  }
+
   static async register ({ email, password, name, verify = false, bypassChecks = false }, context) {
     const localStrg = await WIKI.models.authentication.getStrategy('local')
     // Check if self-registration is enabled

+ 1 - 7
server/views/legacy/master.pug

@@ -28,13 +28,7 @@ html
     link(rel='manifest', href='/manifest.json')
 
     //- Icon Set
-    if config.theming.iconset === 'mdi'
-      link(
-        type='text/css'
-        rel='stylesheet'
-        href='https://cdn.materialdesignicons.com/3.7.95/css/materialdesignicons.min.css'
-        )
-    else if config.theming.iconset === 'fa'
+    if config.theming.iconset === 'fa'
       link(
         type='text/css'
         rel='stylesheet'

+ 1 - 7
server/views/master.pug

@@ -32,13 +32,7 @@ html
       var siteConfig = !{JSON.stringify(siteConfig)}; var siteLangs = !{JSON.stringify(langs)}
 
     //- Icon Set
-    if config.theming.iconset === 'mdi'
-      link(
-        type='text/css'
-        rel='stylesheet'
-        href='https://cdn.materialdesignicons.com/3.7.95/css/materialdesignicons.min.css'
-        )
-    else if config.theming.iconset === 'fa'
+    if config.theming.iconset === 'fa'
       link(
         type='text/css'
         rel='stylesheet'

部分文件因为文件数量过多而无法显示