311 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template lang="pug">
 | |
|   v-dialog(
 | |
|     v-model='isShown'
 | |
|     max-width='850px'
 | |
|     overlay-color='blue darken-4'
 | |
|     overlay-opacity='.7'
 | |
|     )
 | |
|     v-card.page-selector
 | |
|       .dialog-header.is-blue
 | |
|         v-icon.mr-3(color='white') mdi-page-next-outline
 | |
|         .body-1(v-if='mode === `create`') Select New Page Location
 | |
|         .body-1(v-else-if='mode === `move`') Move / Rename Page Location
 | |
|         .body-1(v-else-if='mode === `select`') Select Page
 | |
|         v-spacer
 | |
|         v-progress-circular(
 | |
|           indeterminate
 | |
|           color='white'
 | |
|           :size='20'
 | |
|           :width='2'
 | |
|           v-show='searchLoading'
 | |
|           )
 | |
|       .d-flex
 | |
|         v-flex.grey(xs5, :class='darkMode ? `darken-4` : `lighten-3`')
 | |
|           v-toolbar(color='grey darken-3', dark, dense, flat)
 | |
|             .body-2 Virtual Folders
 | |
|             v-spacer
 | |
|             v-btn(icon, tile, href='https://docs.requarks.io/guide/pages#folders', target='_blank')
 | |
|               v-icon mdi-help-box
 | |
|           div(style='height:400px;')
 | |
|             vue-scroll(:ops='scrollStyle')
 | |
|               v-treeview(
 | |
|                 :key='`pageTree-` + treeViewCacheId'
 | |
|                 :active.sync='currentNode'
 | |
|                 :open.sync='openNodes'
 | |
|                 :items='tree'
 | |
|                 :load-children='fetchFolders'
 | |
|                 dense
 | |
|                 expand-icon='mdi-menu-down-outline'
 | |
|                 item-id='path'
 | |
|                 item-text='title'
 | |
|                 activatable
 | |
|                 hoverable
 | |
|                 )
 | |
|                 template(slot='prepend', slot-scope='{ item, open, leaf }')
 | |
|                   v-icon mdi-{{ open ? 'folder-open' : 'folder' }}
 | |
|         v-flex(xs7)
 | |
|           v-toolbar(color='blue darken-2', dark, dense, flat)
 | |
|             .body-2 Pages
 | |
|             //- v-spacer
 | |
|             //- v-btn(icon, tile, disabled): v-icon mdi-content-save-move-outline
 | |
|             //- v-btn(icon, tile, disabled): v-icon mdi-trash-can-outline
 | |
|           div(v-if='currentPages.length > 0', style='height:400px;')
 | |
|             vue-scroll(:ops='scrollStyle')
 | |
|               v-list.py-0(dense)
 | |
|                 v-list-item-group(
 | |
|                   v-model='currentPage'
 | |
|                   color='primary'
 | |
|                   )
 | |
|                   template(v-for='(page, idx) of currentPages')
 | |
|                     v-list-item(:key='`page-` + page.id', :value='page.path')
 | |
|                       v-list-item-icon: v-icon mdi-text-box
 | |
|                       v-list-item-title {{page.title}}
 | |
|                     v-divider(v-if='idx < pages.length - 1')
 | |
|           v-alert.animated.fadeIn(
 | |
|             v-else
 | |
|             text
 | |
|             color='orange'
 | |
|             prominent
 | |
|             icon='mdi-alert'
 | |
|             )
 | |
|             .body-2 This folder is empty.
 | |
|       v-card-actions.grey.pa-2(:class='darkMode ? `darken-2` : `lighten-1`')
 | |
|         v-select(
 | |
|           solo
 | |
|           dark
 | |
|           flat
 | |
|           background-color='grey darken-3-d2'
 | |
|           hide-details
 | |
|           single-line
 | |
|           :items='namespaces'
 | |
|           style='flex: 0 0 100px; border-radius: 4px 0 0 4px;'
 | |
|           v-model='currentLocale'
 | |
|           )
 | |
|         v-text-field(
 | |
|           ref='pathIpt'
 | |
|           solo
 | |
|           hide-details
 | |
|           prefix='/'
 | |
|           v-model='currentPath'
 | |
|           flat
 | |
|           clearable
 | |
|           style='border-radius: 0 4px 4px 0;'
 | |
|         )
 | |
|       v-card-chin
 | |
|         v-spacer
 | |
|         v-btn(text, @click='close') Cancel
 | |
|         v-btn.px-4(color='primary', @click='open', :disabled='!isValidPath')
 | |
|           v-icon(left) mdi-check
 | |
|           span Select
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| import _ from 'lodash'
 | |
| import { get } from 'vuex-pathify'
 | |
| import pageTreeQuery from 'gql/common/common-pages-query-tree.gql'
 | |
| 
 | |
| const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
 | |
| 
 | |
| /* global siteLangs, siteConfig */
 | |
| 
 | |
| export default {
 | |
|   props: {
 | |
|     value: {
 | |
|       type: Boolean,
 | |
|       default: false
 | |
|     },
 | |
|     path: {
 | |
|       type: String,
 | |
|       default: 'new-page'
 | |
|     },
 | |
|     locale: {
 | |
|       type: String,
 | |
|       default: 'en'
 | |
|     },
 | |
|     mode: {
 | |
|       type: String,
 | |
|       default: 'create'
 | |
|     },
 | |
|     openHandler: {
 | |
|       type: Function,
 | |
|       default: () => {}
 | |
|     }
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       treeViewCacheId: 0,
 | |
|       searchLoading: false,
 | |
|       currentLocale: siteConfig.lang,
 | |
|       currentFolderPath: '',
 | |
|       currentPath: 'new-page',
 | |
|       currentPage: null,
 | |
|       currentNode: [0],
 | |
|       openNodes: [0],
 | |
|       tree: [
 | |
|         {
 | |
|           id: 0,
 | |
|           title: '/ (root)',
 | |
|           children: []
 | |
|         }
 | |
|       ],
 | |
|       pages: [],
 | |
|       all: [],
 | |
|       namespaces: siteLangs.length ? siteLangs.map(ns => ns.code) : [siteConfig.lang],
 | |
|       scrollStyle: {
 | |
|         vuescroll: {},
 | |
|         scrollPanel: {
 | |
|           initialScrollX: 0.01, // fix scrollbar not disappearing on load
 | |
|           scrollingX: false,
 | |
|           speed: 50
 | |
|         },
 | |
|         rail: {
 | |
|           gutterOfEnds: '2px'
 | |
|         },
 | |
|         bar: {
 | |
|           onlyShowBarOnScroll: false,
 | |
|           background: '#999',
 | |
|           hoverStyle: {
 | |
|             background: '#64B5F6'
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   },
 | |
|   computed: {
 | |
|     darkMode: get('site/dark'),
 | |
|     isShown: {
 | |
|       get() { return this.value },
 | |
|       set(val) { this.$emit('input', val) }
 | |
|     },
 | |
|     currentPages () {
 | |
|       return _.sortBy(_.filter(this.pages, ['parent', _.head(this.currentNode) || 0]), ['title', 'path'])
 | |
|     },
 | |
|     isValidPath () {
 | |
|       if (!this.currentPath) {
 | |
|         return false
 | |
|       }
 | |
|       const firstSection = _.head(this.currentPath.split('/'))
 | |
|       if (firstSection.length <= 1) {
 | |
|         return false
 | |
|       } else if (localeSegmentRegex.test(firstSection)) {
 | |
|         return false
 | |
|       } else if (
 | |
|         _.some(['login', 'logout', 'register', 'verify', 'favicons', 'fonts', 'img', 'js', 'svg'], p => {
 | |
|           return p === firstSection
 | |
|         })) {
 | |
|         return false
 | |
|       } else {
 | |
|         return true
 | |
|       }
 | |
|     }
 | |
|   },
 | |
|   watch: {
 | |
|     isShown (newValue, oldValue) {
 | |
|       if (newValue && !oldValue) {
 | |
|         this.currentPath = this.path
 | |
|         this.currentLocale = this.locale
 | |
|         _.delay(() => {
 | |
|           this.$refs.pathIpt.focus()
 | |
|         })
 | |
|       }
 | |
|     },
 | |
|     currentNode (newValue, oldValue) {
 | |
|       if (newValue.length < 1) { // force a selection
 | |
|         this.$nextTick(() => {
 | |
|           this.currentNode = oldValue
 | |
|         })
 | |
|       } else {
 | |
|         const current = _.find(this.all, ['id', newValue[0]])
 | |
| 
 | |
|         if (this.openNodes.indexOf(newValue[0]) < 0) { // auto open and load children
 | |
|           if (current) {
 | |
|             if (this.openNodes.indexOf(current.parent) < 0) {
 | |
|               this.$nextTick(() => {
 | |
|                 this.openNodes.push(current.parent)
 | |
|               })
 | |
|             }
 | |
|           }
 | |
|           this.$nextTick(() => {
 | |
|             this.openNodes.push(newValue[0])
 | |
|           })
 | |
|         }
 | |
| 
 | |
|         this.currentPath = _.compact([_.get(current, 'path', ''), _.last(this.currentPath.split('/'))]).join('/')
 | |
|       }
 | |
|     },
 | |
|     currentPage (newValue, oldValue) {
 | |
|       if (!_.isEmpty(newValue)) {
 | |
|         this.currentPath = newValue
 | |
|       }
 | |
|     },
 | |
|     currentLocale (newValue, oldValue) {
 | |
|       this.$nextTick(() => {
 | |
|         this.tree = [
 | |
|           {
 | |
|             id: 0,
 | |
|             title: '/ (root)',
 | |
|             children: []
 | |
|           }
 | |
|         ]
 | |
|         this.currentNode = [0]
 | |
|         this.openNodes = [0]
 | |
|         this.pages = []
 | |
|         this.all = []
 | |
|         this.treeViewCacheId += 1
 | |
|       })
 | |
|     }
 | |
|   },
 | |
|   methods: {
 | |
|     close() {
 | |
|       this.isShown = false
 | |
|     },
 | |
|     open() {
 | |
|       const exit = this.openHandler({
 | |
|         locale: this.currentLocale,
 | |
|         path: this.currentPath
 | |
|       })
 | |
|       if (exit !== false) {
 | |
|         this.close()
 | |
|       }
 | |
|     },
 | |
|     async fetchFolders (item) {
 | |
|       this.searchLoading = true
 | |
|       const resp = await this.$apollo.query({
 | |
|         query: pageTreeQuery,
 | |
|         fetchPolicy: 'network-only',
 | |
|         variables: {
 | |
|           parent: item.id,
 | |
|           mode: 'ALL',
 | |
|           locale: this.currentLocale
 | |
|         }
 | |
|       })
 | |
|       const items = _.get(resp, 'data.pages.tree', [])
 | |
|       const itemFolders = _.filter(items, ['isFolder', true]).map(f => ({...f, children: []}))
 | |
|       const itemPages = _.filter(items, i => i.pageId > 0)
 | |
|       if (itemFolders.length > 0) {
 | |
|         item.children = itemFolders
 | |
|       } else {
 | |
|         item.children = undefined
 | |
|       }
 | |
|       this.pages = _.unionBy(this.pages, itemPages, 'id')
 | |
|       this.all = _.unionBy(this.all, items, 'id')
 | |
| 
 | |
|       this.searchLoading = false
 | |
|     }
 | |
|   }
 | |
| }
 | |
| </script>
 | |
| 
 | |
| <style lang='scss'>
 | |
| 
 | |
| .page-selector {
 | |
|   .v-treeview-node__label {
 | |
|     font-size: 13px;
 | |
|   }
 | |
|   .v-treeview-node__content {
 | |
|     cursor: pointer;
 | |
|   }
 | |
| }
 | |
| 
 | |
| </style>
 |