page-selector.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <template lang="pug">
  2. v-dialog(v-model='isShown', max-width='850px')
  3. v-card.page-selector
  4. .dialog-header.is-blue
  5. v-icon.mr-3(color='white') mdi-page-next-outline
  6. .body-1 Select Page Location
  7. v-spacer
  8. v-progress-circular(
  9. indeterminate
  10. color='white'
  11. :size='20'
  12. :width='2'
  13. v-show='searchLoading'
  14. )
  15. .d-flex(style='min-height:400px;')
  16. v-flex.grey(xs4, :class='darkMode ? `darken-4` : `lighten-3`')
  17. v-toolbar(color='grey darken-3', dark, dense, flat)
  18. .body-2 Virtual Folders
  19. v-spacer
  20. v-btn(icon, tile, href='https://docs.requarks.io/', target='_blank')
  21. v-icon mdi-help-box
  22. v-treeview(
  23. :active.sync='currentNode'
  24. :open.sync='openNodes'
  25. :items='tree'
  26. :load-children='fetchFolders'
  27. dense
  28. expand-icon='mdi-menu-down-outline'
  29. item-id='path'
  30. item-text='title'
  31. activatable
  32. hoverable
  33. )
  34. template(slot='prepend', slot-scope='{ item, open, leaf }')
  35. v-icon mdi-{{ open ? 'folder-open' : 'folder' }}
  36. v-flex(xs8)
  37. v-toolbar(color='blue darken-2', dark, dense, flat)
  38. .body-2 Pages
  39. v-spacer
  40. v-btn(icon, tile, disabled): v-icon mdi-content-save-move-outline
  41. v-btn(icon, tile, disabled): v-icon mdi-trash-can-outline
  42. v-list.py-0(dense, v-if='currentPages.length > 0')
  43. v-list-item-group(
  44. v-model='currentPage'
  45. color='primary'
  46. )
  47. template(v-for='(page, idx) of currentPages')
  48. v-list-item(:key='page.id', :value='page.path')
  49. v-list-item-icon: v-icon mdi-file-document-box
  50. v-list-item-title {{page.title}}
  51. v-divider(v-if='idx < pages.length - 1')
  52. v-alert.animated.fadeIn(
  53. v-else
  54. text
  55. color='orange'
  56. prominent
  57. icon='mdi-alert'
  58. )
  59. .body-2 This folder is empty.
  60. v-card-actions.grey.pa-2(:class='darkMode ? `darken-2` : `lighten-1`')
  61. v-select(
  62. solo
  63. dark
  64. flat
  65. background-color='grey darken-3-d2'
  66. hide-details
  67. single-line
  68. :items='namespaces'
  69. style='flex: 0 0 100px; border-radius: 4px 0 0 4px;'
  70. v-model='currentLocale'
  71. )
  72. v-text-field(
  73. ref='pathIpt'
  74. solo
  75. hide-details
  76. prefix='/'
  77. v-model='currentPath'
  78. flat
  79. clearable
  80. style='border-radius: 0 4px 4px 0;'
  81. )
  82. v-card-chin
  83. v-spacer
  84. v-btn(text, @click='close') Cancel
  85. v-btn.px-4(color='primary', @click='open', :disabled='!isValidPath')
  86. v-icon(left) mdi-check
  87. span Select
  88. </template>
  89. <script>
  90. import _ from 'lodash'
  91. import { get } from 'vuex-pathify'
  92. import pageTreeQuery from 'gql/common/common-pages-query-tree.gql'
  93. /* global siteLangs, siteConfig */
  94. export default {
  95. props: {
  96. value: {
  97. type: Boolean,
  98. default: false
  99. },
  100. path: {
  101. type: String,
  102. default: 'new-page'
  103. },
  104. locale: {
  105. type: String,
  106. default: 'en'
  107. },
  108. mode: {
  109. type: String,
  110. default: 'create'
  111. },
  112. openHandler: {
  113. type: Function,
  114. default: () => {}
  115. }
  116. },
  117. data() {
  118. return {
  119. searchLoading: false,
  120. currentLocale: siteConfig.lang,
  121. currentPath: 'new-page',
  122. currentPage: null,
  123. currentNode: [0],
  124. openNodes: [0],
  125. tree: [{
  126. id: 0,
  127. title: '/ (root',
  128. children: []
  129. }],
  130. pages: [],
  131. all: [],
  132. namespaces: siteLangs.length ? siteLangs.map(ns => ns.code) : [siteConfig.lang]
  133. }
  134. },
  135. computed: {
  136. darkMode: get('site/dark'),
  137. isShown: {
  138. get() { return this.value },
  139. set(val) { this.$emit('input', val) }
  140. },
  141. currentPages () {
  142. return _.filter(this.pages, ['parent', _.head(this.currentNode) || 0])
  143. },
  144. isValidPath () {
  145. return this.currentPath && this.currentPath.length > 2
  146. }
  147. },
  148. watch: {
  149. isShown (newValue, oldValue) {
  150. if (newValue && !oldValue) {
  151. this.currentPath = this.path
  152. this.currentLocale = this.locale
  153. _.delay(() => {
  154. this.$refs.pathIpt.focus()
  155. })
  156. }
  157. },
  158. currentNode (newValue, oldValue) {
  159. if (newValue.length < 1) { // force a selection
  160. this.$nextTick(() => {
  161. this.currentNode = oldValue
  162. })
  163. } else {
  164. if (this.openNodes.indexOf(newValue[0]) < 0) { // auto open and load children
  165. const current = _.find(this.all, ['id', newValue[0]])
  166. if (current) {
  167. if (this.openNodes.indexOf(current.parent) < 0) {
  168. this.$nextTick(() => {
  169. this.openNodes.push(current.parent)
  170. })
  171. }
  172. }
  173. this.$nextTick(() => {
  174. this.openNodes.push(newValue[0])
  175. })
  176. }
  177. }
  178. },
  179. currentPage (newValue, oldValue) {
  180. if (!_.isEmpty(newValue)) {
  181. this.currentPath = newValue
  182. }
  183. }
  184. },
  185. methods: {
  186. close() {
  187. this.isShown = false
  188. },
  189. open() {
  190. const exit = this.openHandler({
  191. locale: this.currentLocale,
  192. path: this.currentPath
  193. })
  194. if (exit !== false) {
  195. this.close()
  196. }
  197. },
  198. async fetchFolders (item) {
  199. this.searchLoading = true
  200. const resp = await this.$apollo.query({
  201. query: pageTreeQuery,
  202. fetchPolicy: 'network-only',
  203. variables: {
  204. parent: item.id,
  205. mode: 'ALL',
  206. locale: this.currentLocale
  207. }
  208. })
  209. const items = _.get(resp, 'data.pages.tree', [])
  210. const itemFolders = _.filter(items, ['isFolder', true]).map(f => ({...f, children: []}))
  211. const itemPages = _.filter(items, ['isFolder', false])
  212. if (itemFolders.length > 0) {
  213. item.children = itemFolders
  214. } else {
  215. item.children = undefined
  216. }
  217. this.pages.push(...itemPages)
  218. this.all.push(...items)
  219. this.searchLoading = false
  220. }
  221. }
  222. }
  223. </script>
  224. <style lang='scss'>
  225. .page-selector {
  226. .v-treeview-node__label {
  227. font-size: 13px;
  228. }
  229. }
  230. </style>