You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

309 lines
8.3 KiB

5 years ago
5 years ago
  1. <template lang="pug">
  2. v-dialog(
  3. v-model='isShown'
  4. max-width='850px'
  5. overlay-color='blue darken-4'
  6. overlay-opacity='.7'
  7. )
  8. v-card.page-selector
  9. .dialog-header.is-blue
  10. v-icon.mr-3(color='white') mdi-page-next-outline
  11. .body-1(v-if='mode === `create`') Select New Page Location
  12. .body-1(v-else-if='mode === `move`') Move / Rename Page Location
  13. v-spacer
  14. v-progress-circular(
  15. indeterminate
  16. color='white'
  17. :size='20'
  18. :width='2'
  19. v-show='searchLoading'
  20. )
  21. .d-flex
  22. v-flex.grey(xs5, :class='darkMode ? `darken-4` : `lighten-3`')
  23. v-toolbar(color='grey darken-3', dark, dense, flat)
  24. .body-2 Virtual Folders
  25. v-spacer
  26. v-btn(icon, tile, href='https://docs.requarks.io/guide/pages#folders', target='_blank')
  27. v-icon mdi-help-box
  28. div(style='height:400px;')
  29. vue-scroll(:ops='scrollStyle')
  30. v-treeview(
  31. :key='`pageTree-` + treeViewCacheId'
  32. :active.sync='currentNode'
  33. :open.sync='openNodes'
  34. :items='tree'
  35. :load-children='fetchFolders'
  36. dense
  37. expand-icon='mdi-menu-down-outline'
  38. item-id='path'
  39. item-text='title'
  40. activatable
  41. hoverable
  42. )
  43. template(slot='prepend', slot-scope='{ item, open, leaf }')
  44. v-icon mdi-{{ open ? 'folder-open' : 'folder' }}
  45. v-flex(xs7)
  46. v-toolbar(color='blue darken-2', dark, dense, flat)
  47. .body-2 Pages
  48. //- v-spacer
  49. //- v-btn(icon, tile, disabled): v-icon mdi-content-save-move-outline
  50. //- v-btn(icon, tile, disabled): v-icon mdi-trash-can-outline
  51. div(v-if='currentPages.length > 0', style='height:400px;')
  52. vue-scroll(:ops='scrollStyle')
  53. v-list.py-0(dense)
  54. v-list-item-group(
  55. v-model='currentPage'
  56. color='primary'
  57. )
  58. template(v-for='(page, idx) of currentPages')
  59. v-list-item(:key='`page-` + page.id', :value='page.path')
  60. v-list-item-icon: v-icon mdi-text-box
  61. v-list-item-title {{page.title}}
  62. v-divider(v-if='idx < pages.length - 1')
  63. v-alert.animated.fadeIn(
  64. v-else
  65. text
  66. color='orange'
  67. prominent
  68. icon='mdi-alert'
  69. )
  70. .body-2 This folder is empty.
  71. v-card-actions.grey.pa-2(:class='darkMode ? `darken-2` : `lighten-1`')
  72. v-select(
  73. solo
  74. dark
  75. flat
  76. background-color='grey darken-3-d2'
  77. hide-details
  78. single-line
  79. :items='namespaces'
  80. style='flex: 0 0 100px; border-radius: 4px 0 0 4px;'
  81. v-model='currentLocale'
  82. )
  83. v-text-field(
  84. ref='pathIpt'
  85. solo
  86. hide-details
  87. prefix='/'
  88. v-model='currentPath'
  89. flat
  90. clearable
  91. style='border-radius: 0 4px 4px 0;'
  92. )
  93. v-card-chin
  94. v-spacer
  95. v-btn(text, @click='close') Cancel
  96. v-btn.px-4(color='primary', @click='open', :disabled='!isValidPath')
  97. v-icon(left) mdi-check
  98. span Select
  99. </template>
  100. <script>
  101. import _ from 'lodash'
  102. import { get } from 'vuex-pathify'
  103. import pageTreeQuery from 'gql/common/common-pages-query-tree.gql'
  104. const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
  105. /* global siteLangs, siteConfig */
  106. export default {
  107. props: {
  108. value: {
  109. type: Boolean,
  110. default: false
  111. },
  112. path: {
  113. type: String,
  114. default: 'new-page'
  115. },
  116. locale: {
  117. type: String,
  118. default: 'en'
  119. },
  120. mode: {
  121. type: String,
  122. default: 'create'
  123. },
  124. openHandler: {
  125. type: Function,
  126. default: () => {}
  127. }
  128. },
  129. data() {
  130. return {
  131. treeViewCacheId: 0,
  132. searchLoading: false,
  133. currentLocale: siteConfig.lang,
  134. currentFolderPath: '',
  135. currentPath: 'new-page',
  136. currentPage: null,
  137. currentNode: [0],
  138. openNodes: [0],
  139. tree: [
  140. {
  141. id: 0,
  142. title: '/ (root)',
  143. children: []
  144. }
  145. ],
  146. pages: [],
  147. all: [],
  148. namespaces: siteLangs.length ? siteLangs.map(ns => ns.code) : [siteConfig.lang],
  149. scrollStyle: {
  150. vuescroll: {},
  151. scrollPanel: {
  152. initialScrollX: 0.01, // fix scrollbar not disappearing on load
  153. scrollingX: false,
  154. speed: 50
  155. },
  156. rail: {
  157. gutterOfEnds: '2px'
  158. },
  159. bar: {
  160. onlyShowBarOnScroll: false,
  161. background: '#999',
  162. hoverStyle: {
  163. background: '#64B5F6'
  164. }
  165. }
  166. }
  167. }
  168. },
  169. computed: {
  170. darkMode: get('site/dark'),
  171. isShown: {
  172. get() { return this.value },
  173. set(val) { this.$emit('input', val) }
  174. },
  175. currentPages () {
  176. return _.sortBy(_.filter(this.pages, ['parent', _.head(this.currentNode) || 0]), ['title', 'path'])
  177. },
  178. isValidPath () {
  179. if (!this.currentPath) {
  180. return false
  181. }
  182. const firstSection = _.head(this.currentPath.split('/'))
  183. if (firstSection.length <= 1) {
  184. return false
  185. } else if (localeSegmentRegex.test(firstSection)) {
  186. return false
  187. } else if (
  188. _.some(['login', 'logout', 'register', 'verify', 'favicons', 'fonts', 'img', 'js', 'svg'], p => {
  189. return p === firstSection
  190. })) {
  191. return false
  192. } else {
  193. return true
  194. }
  195. }
  196. },
  197. watch: {
  198. isShown (newValue, oldValue) {
  199. if (newValue && !oldValue) {
  200. this.currentPath = this.path
  201. this.currentLocale = this.locale
  202. _.delay(() => {
  203. this.$refs.pathIpt.focus()
  204. })
  205. }
  206. },
  207. currentNode (newValue, oldValue) {
  208. if (newValue.length < 1) { // force a selection
  209. this.$nextTick(() => {
  210. this.currentNode = oldValue
  211. })
  212. } else {
  213. const current = _.find(this.all, ['id', newValue[0]])
  214. if (this.openNodes.indexOf(newValue[0]) < 0) { // auto open and load children
  215. if (current) {
  216. if (this.openNodes.indexOf(current.parent) < 0) {
  217. this.$nextTick(() => {
  218. this.openNodes.push(current.parent)
  219. })
  220. }
  221. }
  222. this.$nextTick(() => {
  223. this.openNodes.push(newValue[0])
  224. })
  225. }
  226. this.currentPath = _.compact([_.get(current, 'path', ''), _.last(this.currentPath.split('/'))]).join('/')
  227. }
  228. },
  229. currentPage (newValue, oldValue) {
  230. if (!_.isEmpty(newValue)) {
  231. this.currentPath = newValue
  232. }
  233. },
  234. currentLocale (newValue, oldValue) {
  235. this.$nextTick(() => {
  236. this.tree = [
  237. {
  238. id: 0,
  239. title: '/ (root)',
  240. children: []
  241. }
  242. ]
  243. this.currentNode = [0]
  244. this.openNodes = [0]
  245. this.pages = []
  246. this.all = []
  247. this.treeViewCacheId += 1
  248. })
  249. }
  250. },
  251. methods: {
  252. close() {
  253. this.isShown = false
  254. },
  255. open() {
  256. const exit = this.openHandler({
  257. locale: this.currentLocale,
  258. path: this.currentPath
  259. })
  260. if (exit !== false) {
  261. this.close()
  262. }
  263. },
  264. async fetchFolders (item) {
  265. this.searchLoading = true
  266. const resp = await this.$apollo.query({
  267. query: pageTreeQuery,
  268. fetchPolicy: 'network-only',
  269. variables: {
  270. parent: item.id,
  271. mode: 'ALL',
  272. locale: this.currentLocale
  273. }
  274. })
  275. const items = _.get(resp, 'data.pages.tree', [])
  276. const itemFolders = _.filter(items, ['isFolder', true]).map(f => ({...f, children: []}))
  277. const itemPages = _.filter(items, i => i.pageId > 0)
  278. if (itemFolders.length > 0) {
  279. item.children = itemFolders
  280. } else {
  281. item.children = undefined
  282. }
  283. this.pages = _.unionBy(this.pages, itemPages, 'id')
  284. this.all = _.unionBy(this.all, items, 'id')
  285. this.searchLoading = false
  286. }
  287. }
  288. }
  289. </script>
  290. <style lang='scss'>
  291. .page-selector {
  292. .v-treeview-node__label {
  293. font-size: 13px;
  294. }
  295. .v-treeview-node__content {
  296. cursor: pointer;
  297. }
  298. }
  299. </style>