소스 검색

feat: markdown editor UI fix + insert media (wip)

Nick 6 년 전
부모
커밋
342747c860

+ 65 - 63
client/components/common/nav-header.vue

@@ -57,72 +57,74 @@
                 v-list-tile-content Images & Files
                 v-list-tile-content Images & Files
           v-toolbar-title(:class='{ "ml-2": $vuetify.breakpoint.mdAndUp, "ml-0": $vuetify.breakpoint.smAndDown }')
           v-toolbar-title(:class='{ "ml-2": $vuetify.breakpoint.mdAndUp, "ml-0": $vuetify.breakpoint.smAndDown }')
             span.subheading {{title}}
             span.subheading {{title}}
-      v-flex(md4, v-if='searchIsShown && $vuetify.breakpoint.mdAndUp')
+      v-flex(md4, v-if='$vuetify.breakpoint.mdAndUp')
         v-toolbar.nav-header-inner(color='black', dark, flat)
         v-toolbar.nav-header-inner(color='black', dark, flat)
-          transition(name='navHeaderSearch')
-            v-text-field(
-              ref='searchField',
-              v-if='searchIsShown && $vuetify.breakpoint.mdAndUp',
-              v-model='search',
-              color='white',
-              label='Search...',
-              single-line,
-              solo
-              flat
-              hide-details,
-              prepend-inner-icon='search',
-              :loading='searchIsLoading',
-              @keyup.enter='searchEnter'
-              @keyup.esc='searchClose'
-              @focus='searchFocus'
-              @blur='searchBlur'
-              @keyup.down='searchMove(`down`)'
-              @keyup.up='searchMove(`up`)'
-            )
-              v-progress-linear(
-                indeterminate,
-                slot='progress',
-                height='2',
-                color='blue'
+          slot(name='mid')
+            transition(name='navHeaderSearch', v-if='searchIsShown')
+              v-text-field(
+                ref='searchField',
+                v-if='searchIsShown && $vuetify.breakpoint.mdAndUp',
+                v-model='search',
+                color='white',
+                label='Search...',
+                single-line,
+                solo
+                flat
+                hide-details,
+                prepend-inner-icon='search',
+                :loading='searchIsLoading',
+                @keyup.enter='searchEnter'
+                @keyup.esc='searchClose'
+                @focus='searchFocus'
+                @blur='searchBlur'
+                @keyup.down='searchMove(`down`)'
+                @keyup.up='searchMove(`up`)'
               )
               )
-          v-menu(
-            v-model='searchAdvMenuShown'
-            left
-            offset-y
-            min-width='450'
-            :close-on-content-click='false'
-            nudge-bottom='7'
-            nudge-right='5'
-            )
-            v-btn.nav-header-search-adv(icon, outline, color='grey darken-2', slot='activator')
-              v-icon(color='white') expand_more
-            v-card.radius-0(dark)
-              v-toolbar(flat, color='grey darken-4', dense)
-                v-icon.mr-2 search
-                v-subheader.pl-0 Advanced Search
-                v-spacer
-                v-chip(label, small, color='primary') Coming soon
-              v-card-text.pa-4
-                v-checkbox.mt-0(
-                  label='Restrict to current language'
-                  color='white'
-                  v-model='searchRestrictLocale'
-                  hide-details
+                v-progress-linear(
+                  indeterminate,
+                  slot='progress',
+                  height='2',
+                  color='blue'
                 )
                 )
-                v-checkbox(
-                  label='Search below current path only'
-                  color='white'
-                  v-model='searchRestrictPath'
-                  hide-details
-                )
-              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-menu(
+              v-model='searchAdvMenuShown'
+              left
+              offset-y
+              min-width='450'
+              :close-on-content-click='false'
+              nudge-bottom='7'
+              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
+              v-card.radius-0(dark)
+                v-toolbar(flat, color='grey darken-4', dense)
+                  v-icon.mr-2 search
+                  v-subheader.pl-0 Advanced Search
+                  v-spacer
+                  v-chip(label, small, color='primary') Coming soon
+                v-card-text.pa-4
+                  v-checkbox.mt-0(
+                    label='Restrict to current language'
+                    color='white'
+                    v-model='searchRestrictLocale'
+                    hide-details
+                  )
+                  v-checkbox(
+                    label='Search below current path only'
+                    color='white'
+                    v-model='searchRestrictPath'
+                    hide-details
+                  )
+                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-flex(xs6, :md4='searchIsShown', :md6='!searchIsShown')
         v-toolbar.nav-header-inner(color='black', dark, flat)
         v-toolbar.nav-header-inner(color='black', dark, flat)
           v-spacer
           v-spacer

+ 10 - 2
client/components/editor.vue

@@ -1,6 +1,10 @@
 <template lang="pug">
 <template lang="pug">
   v-app.editor(:dark='darkMode')
   v-app.editor(:dark='darkMode')
     nav-header(dense)
     nav-header(dense)
+      template(slot='mid')
+        v-spacer
+        .subheading {{currentPageTitle}}
+        v-spacer
       template(slot='actions')
       template(slot='actions')
         v-btn(
         v-btn(
           outline
           outline
@@ -32,6 +36,7 @@
       editor-modal-properties(v-model='dialogProps')
       editor-modal-properties(v-model='dialogProps')
       editor-modal-editorselect(v-model='dialogEditorSelector')
       editor-modal-editorselect(v-model='dialogEditorSelector')
       editor-modal-unsaved(v-model='dialogUnsaved', @discard='exitGo')
       editor-modal-unsaved(v-model='dialogUnsaved', @discard='exitGo')
+      component(:is='activeModal')
 
 
     loader(v-model='dialogProgress', :title='$t(`editor:save.processing`)', :subtitle='$t(`editor:save.pleaseWait`)')
     loader(v-model='dialogProgress', :title='$t(`editor:save.processing`)', :subtitle='$t(`editor:save.pleaseWait`)')
     v-snackbar(
     v-snackbar(
@@ -70,7 +75,8 @@ export default {
     editorWysiwyg: () => import(/* webpackChunkName: "editor-wysiwyg", webpackMode: "lazy" */ './editor/editor-wysiwyg.vue'),
     editorWysiwyg: () => import(/* webpackChunkName: "editor-wysiwyg", webpackMode: "lazy" */ './editor/editor-wysiwyg.vue'),
     editorModalEditorselect: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-editorselect.vue'),
     editorModalEditorselect: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-editorselect.vue'),
     editorModalProperties: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-properties.vue'),
     editorModalProperties: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-properties.vue'),
-    editorModalUnsaved: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-unsaved.vue')
+    editorModalUnsaved: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-unsaved.vue'),
+    editorModalMedia: () => import(/* webpackChunkName: "editor", webpackMode: "eager" */ './editor/editor-modal-media.vue')
   },
   },
   props: {
   props: {
     locale: {
     locale: {
@@ -126,10 +132,12 @@ export default {
   computed: {
   computed: {
     currentEditor: sync('editor/editor'),
     currentEditor: sync('editor/editor'),
     darkMode: get('site/dark'),
     darkMode: get('site/dark'),
+    activeModal: sync('editor/activeModal'),
     mode: get('editor/mode'),
     mode: get('editor/mode'),
     notification: get('notification'),
     notification: get('notification'),
     notificationState: sync('notification@isActive'),
     notificationState: sync('notification@isActive'),
-    welcomeMode() { return this.mode === `create` && this.path === `home` }
+    welcomeMode() { return this.mode === `create` && this.path === `home` },
+    currentPageTitle: get('page/title')
   },
   },
   watch: {
   watch: {
     currentEditor(newValue, oldValue) {
     currentEditor(newValue, oldValue) {

+ 72 - 17
client/components/editor/editor-markdown.vue

@@ -1,6 +1,6 @@
 <template lang='pug'>
 <template lang='pug'>
   .editor-markdown
   .editor-markdown
-    v-toolbar.editor-markdown-toolbar(dense, color='primary', dark)
+    v-toolbar.editor-markdown-toolbar(dense, color='primary', dark, flat)
       v-tooltip(top)
       v-tooltip(top)
         v-btn(icon, slot='activator').mx-0
         v-btn(icon, slot='activator').mx-0
           v-icon format_bold
           v-icon format_bold
@@ -49,8 +49,33 @@
         v-btn(icon, slot='activator').mx-0
         v-btn(icon, slot='activator').mx-0
           v-icon remove
           v-icon remove
         span Horizontal Bar
         span Horizontal Bar
-
     .editor-markdown-main
     .editor-markdown-main
+      .editor-markdown-sidebar
+        v-tooltip(right)
+          v-btn(icon, slot='activator', dark, @click='toggleModal(`editorModalMedia`)').mx-0
+            v-icon(:color='activeModal === `editorModalMedia` ? `teal` : ``') image
+          span Insert Media
+        v-tooltip(right)
+          v-btn(icon, slot='activator', dark).mx-0
+            v-icon insert_drive_file
+          span Insert File
+        v-tooltip(right)
+          v-btn(icon, slot='activator', dark).mx-0
+            v-icon play_circle_outline
+          span Insert Video
+        v-tooltip(right)
+          v-btn(icon, slot='activator', dark).mx-0
+            v-icon multiline_chart
+          span Insert Diagram
+        v-tooltip(right)
+          v-btn(icon, slot='activator', dark).mx-0
+            v-icon functions
+          span Insert Math Expression
+        v-spacer
+        v-tooltip(right)
+          v-btn(icon, slot='activator', dark).mx-0
+            v-icon border_outer
+          span Table Helper
       .editor-markdown-editor
       .editor-markdown-editor
         .editor-markdown-editor-title(v-if='previewShown', @click='previewShown = false') Editor
         .editor-markdown-editor-title(v-if='previewShown', @click='previewShown = false') Editor
         .editor-markdown-editor-title(v-else='previewShown', @click='previewShown = true'): v-icon(dark) drag_indicator
         .editor-markdown-editor-title(v-else='previewShown', @click='previewShown = true'): v-icon(dark) drag_indicator
@@ -60,19 +85,16 @@
           .editor-markdown-preview-title(@click='previewShown = false') Preview
           .editor-markdown-preview-title(@click='previewShown = false') Preview
           .editor-markdown-preview-content.contents(ref='editorPreview', v-html='previewHTML')
           .editor-markdown-preview-content.contents(ref='editorPreview', v-html='previewHTML')
 
 
-      //- v-speed-dial.editor-markdown-insert(v-model='fabInsertMenu', :open-on-hover='true', direction='top', transition='slide-y-reverse-transition', fixed, bottom, :right='!previewShown || $vuetify.breakpoint.smAndDown')
-      //-   v-btn(color='blue', fab, dark, v-model='fabInsertMenu', slot='activator')
-      //-     v-icon add_circle
-      //-     v-icon close
-      //-   v-btn(color='teal', fab, dark): v-icon image
-      //-   v-btn(color='pink', fab, dark): v-icon insert_drive_file
-      //-   v-btn(color='red', fab, dark): v-icon play_circle_outline
-      //-   v-btn(color='purple', fab, dark): v-icon multiline_chart
-      //-   v-btn(color='indigo', fab, dark): v-icon functions
+    v-system-bar.editor-markdown-sysbar(dark, status, color='grey darken-3')
+      .caption.editor-markdown-sysbar-locale {{locale.toUpperCase()}}
+      .caption.px-3 /{{path}}
+      v-spacer
+      .caption Markdown
 </template>
 </template>
 
 
 <script>
 <script>
 import _ from 'lodash'
 import _ from 'lodash'
+import { get, sync } from 'vuex-pathify'
 
 
 // ========================================
 // ========================================
 // IMPORTS
 // IMPORTS
@@ -191,9 +213,15 @@ export default {
     },
     },
     isMobile() {
     isMobile() {
       return this.$vuetify.breakpoint.smAndDown
       return this.$vuetify.breakpoint.smAndDown
-    }
+    },
+    locale: get('page/locale'),
+    path: get('page/path'),
+    activeModal: sync('editor/activeModal')
   },
   },
   methods: {
   methods: {
+    toggleModal(key) {
+      this.activeModal = (this.activeModal === key) ? '' : key
+    },
     onCmReady(cm) {
     onCmReady(cm) {
       let self = this
       let self = this
       const keyBindings = {
       const keyBindings = {
@@ -208,7 +236,7 @@ export default {
         self.$parent.save()
         self.$parent.save()
       })
       })
 
 
-      cm.setSize(null, 'calc(100vh - 112px)')
+      cm.setSize(null, 'calc(100vh - 112px - 24px)')
       cm.setOption('extraKeys', keyBindings)
       cm.setOption('extraKeys', keyBindings)
       cm.on('cursorActivity', cm => {
       cm.on('cursorActivity', cm => {
         this.toolbarSync(cm)
         this.toolbarSync(cm)
@@ -262,6 +290,9 @@ export default {
 </script>
 </script>
 
 
 <style lang='scss'>
 <style lang='scss'>
+
+$editor-height: calc(100vh - 112px - 24px);
+
 .editor-markdown {
 .editor-markdown {
   &-main {
   &-main {
     display: flex;
     display: flex;
@@ -272,7 +303,7 @@ export default {
     background-color: darken(mc('grey', '900'), 4.5%);
     background-color: darken(mc('grey', '900'), 4.5%);
     flex: 1 1 50%;
     flex: 1 1 50%;
     display: block;
     display: block;
-    height: calc(100vh - 112px);
+    height: $editor-height;
     position: relative;
     position: relative;
 
 
     &-title {
     &-title {
@@ -302,7 +333,7 @@ export default {
     flex: 1 1 50%;
     flex: 1 1 50%;
     background-color: mc('grey', '100');
     background-color: mc('grey', '100');
     position: relative;
     position: relative;
-    height: calc(100vh - 112px);
+    height: $editor-height;
     overflow: hidden;
     overflow: hidden;
 
 
     @at-root .theme--dark & {
     @at-root .theme--dark & {
@@ -327,7 +358,7 @@ export default {
     }
     }
 
 
     &-content {
     &-content {
-      height: calc(100vh - 112px);
+      height: $editor-height;
       overflow-y: scroll;
       overflow-y: scroll;
       padding: 1rem 1rem 1rem 0;
       padding: 1rem 1rem 1rem 0;
       width: calc(100% + 1rem + 17px)
       width: calc(100% + 1rem + 17px)
@@ -364,7 +395,7 @@ export default {
     color: #FFF;
     color: #FFF;
 
 
     .v-toolbar__content {
     .v-toolbar__content {
-      padding-left: 16px;
+      padding-left: 78px;
 
 
       @include until($tablet) {
       @include until($tablet) {
         padding-left: 8px;
         padding-left: 8px;
@@ -379,6 +410,30 @@ export default {
     }
     }
   }
   }
 
 
+  &-sidebar {
+    background-color: mc('grey', '900');
+    width: 64px;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: center;
+    padding: 24px 0;
+  }
+
+  &-sysbar {
+    padding-left: 0;
+
+    &-locale {
+      background-color: rgba(255,255,255,.25);
+      display:inline-flex;
+      padding: 0 12px;
+      height: 24px;
+      width: 63px;
+      justify-content: center;
+      align-items: center;
+    }
+  }
+
   // ==========================================
   // ==========================================
   // Fix FAB revealing under codemirror
   // Fix FAB revealing under codemirror
   // ==========================================
   // ==========================================

+ 69 - 0
client/components/editor/editor-modal-media.vue

@@ -0,0 +1,69 @@
+<template lang='pug'>
+  v-card.editor-modal-media.animated.fadeInLeft(flat, tile)
+    v-container.pa-3(grid-list-lg, fluid)
+      v-layout(row, wrap)
+        v-flex(xs3)
+          v-card.radius-7.animated.fadeInLeft.wait-p1s(light)
+            v-card-text
+              file-pond(
+                name='mediaUpload'
+                ref='pond'
+                label-idle='Drop files here...'
+                allow-multiple='true'
+                accepted-file-types='image/jpeg, image/png'
+                :files='files'
+              )
+        v-flex(xs9)
+          v-card.radius-7.animated.fadeInLeft.wait-p3s(light)
+            v-card-text Beep boop
+</template>
+
+<script>
+// import _ from 'lodash'
+// import { sync } from 'vuex-pathify'
+import vueFilePond from 'vue-filepond'
+import 'filepond/dist/filepond.min.css'
+
+import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css'
+import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
+import FilePondPluginImagePreview from 'filepond-plugin-image-preview'
+
+const FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview)
+
+export default {
+  components: {
+    FilePond
+  },
+  props: {
+    value: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      files: []
+    }
+  },
+  computed: {
+    isShown: {
+      get() { return this.value },
+      set(val) { this.$emit('input', val) }
+    }
+  },
+  methods: {
+  }
+}
+</script>
+
+<style lang='scss'>
+.editor-modal-media {
+    position: fixed;
+    top: 112px;
+    left: 64px;
+    z-index: 10;
+    width: calc(100vw - 64px - 17px);
+    height: calc(100vh - 112px - 24px);
+    background-color: rgba(darken(mc('grey', '900'), 3%), .9) !important;
+}
+</style>

+ 2 - 1
client/store/editor.js

@@ -3,7 +3,8 @@ import { make } from 'vuex-pathify'
 const state = {
 const state = {
   editor: '',
   editor: '',
   content: '',
   content: '',
-  mode: 'create'
+  mode: 'create',
+  activeModal: ''
 }
 }
 
 
 export default {
 export default {

+ 4 - 0
package.json

@@ -216,6 +216,9 @@
     "eslint-plugin-standard": "4.0.0",
     "eslint-plugin-standard": "4.0.0",
     "eslint-plugin-vue": "5.1.0",
     "eslint-plugin-vue": "5.1.0",
     "file-loader": "3.0.1",
     "file-loader": "3.0.1",
+    "filepond": "4.2.0",
+    "filepond-plugin-file-validate-type": "1.2.2",
+    "filepond-plugin-image-preview": "4.0.3",
     "filesize.js": "1.0.2",
     "filesize.js": "1.0.2",
     "grapesjs": "0.14.52",
     "grapesjs": "0.14.52",
     "graphiql": "0.12.0",
     "graphiql": "0.12.0",
@@ -264,6 +267,7 @@
     "vue-chartjs": "3.4.0",
     "vue-chartjs": "3.4.0",
     "vue-clipboards": "1.2.4",
     "vue-clipboards": "1.2.4",
     "vue-codemirror": "4.0.6",
     "vue-codemirror": "4.0.6",
+    "vue-filepond": "5.0.0",
     "vue-hot-reload-api": "2.3.1",
     "vue-hot-reload-api": "2.3.1",
     "vue-loader": "15.6.2",
     "vue-loader": "15.6.2",
     "vue-material-design-icons": "3.0.0",
     "vue-material-design-icons": "3.0.0",

+ 20 - 0
yarn.lock

@@ -5110,6 +5110,21 @@ file-uri-to-path@1:
   resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
   resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
   integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
   integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
 
 
+filepond-plugin-file-validate-type@1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.2.tgz#8ac215b893b6beef28d82874cddcff6cd3069451"
+  integrity sha512-ZBVU9a18EWhfk3kkqULp7GX3KcSSAlZbLFqlquJ5VTerecL1BkUlQ/cpFJ5EucAdJ1Qdi1zEyPQ+e7JLxL+5Rg==
+
+filepond-plugin-image-preview@4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/filepond-plugin-image-preview/-/filepond-plugin-image-preview-4.0.3.tgz#9e8a6561455751498da29e4a6501ab828dc0d4a2"
+  integrity sha512-umWUAM9Jd2W66/bcT/KWxBh1mmcSJxYi/L0aSxJSRnEebOGtJlNZ3y9nnB6MMNmt+Ybi9ttto6mmc4OPyfbfuQ==
+
+filepond@4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/filepond/-/filepond-4.2.0.tgz#93852e933cc8acd9a60efeb5ef93fbbff15d1fef"
+  integrity sha512-JTSvxTQGbCXMZGoPOIjCKImv+Al3Y5z3f6gRoUGlQdqpnMHdnwOV0WG3hRCVBDN64ctAN3pgKtofkWfsnwwoTA==
+
 filesize.js@1.0.2:
 filesize.js@1.0.2:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/filesize.js/-/filesize.js-1.0.2.tgz#934c013395a71804875cf11e6f1ffe211c3f2192"
   resolved "https://registry.yarnpkg.com/filesize.js/-/filesize.js-1.0.2.tgz#934c013395a71804875cf11e6f1ffe211c3f2192"
@@ -13158,6 +13173,11 @@ vue-eslint-parser@^4.0.2:
     esquery "^1.0.1"
     esquery "^1.0.1"
     lodash "^4.17.11"
     lodash "^4.17.11"
 
 
+vue-filepond@5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/vue-filepond/-/vue-filepond-5.0.0.tgz#c3d9362fc41ee9dcb44f7962c1d41ff8e29e8cd2"
+  integrity sha512-8ka3ms8WU0vNQKSL1jgF24tCQUw+pA0CLgRRHe9AZ3l1yrehSXGEZZBQbC374PgzFW8CkJOr3cpM+7ZBNSVBvQ==
+
 vue-hot-reload-api@2.3.1, vue-hot-reload-api@^2.3.0:
 vue-hot-reload-api@2.3.1, vue-hot-reload-api@^2.3.0:
   version "2.3.1"
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
   resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"