NavEditOverlay.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. <template lang="pug">
  2. q-layout(view='hHh lpR fFf', container)
  3. q-header.card-header.q-px-md.q-py-sm
  4. q-icon(name='img:/_assets/icons/fluent-sidebar-menu.svg', left, size='md')
  5. span {{t(`navEdit.editMenuItems`)}}
  6. q-space
  7. transition(name='syncing')
  8. q-spinner-tail.q-mr-sm(
  9. v-show='state.loading > 0'
  10. color='accent'
  11. size='24px'
  12. )
  13. q-btn.q-mr-sm(
  14. flat
  15. rounded
  16. color='white'
  17. :aria-label='t(`common.actions.viewDocs`)'
  18. icon='las la-question-circle'
  19. :href='siteStore.docsBase + `/admin/editors/markdown`'
  20. target='_blank'
  21. type='a'
  22. )
  23. q-btn-group(push)
  24. q-btn(
  25. push
  26. color='white'
  27. text-color='grey-7'
  28. :label='t(`common.actions.cancel`)'
  29. :aria-label='t(`common.actions.cancel`)'
  30. icon='las la-times'
  31. @click='close'
  32. )
  33. q-btn(
  34. push
  35. color='positive'
  36. text-color='white'
  37. :label='t(`common.actions.save`)'
  38. :aria-label='t(`common.actions.save`)'
  39. icon='las la-check'
  40. :disabled='state.loading > 0'
  41. @click='save'
  42. )
  43. q-drawer.bg-dark-6(:model-value='true', :width='295', dark)
  44. q-scroll-area.nav-edit(
  45. :thumb-style='thumbStyle'
  46. :bar-style='barStyle'
  47. )
  48. sortable(
  49. class='q-list q-list--dense q-list--dark nav-edit-list'
  50. :list='state.items'
  51. item-key='id'
  52. :options='sortableOptions'
  53. @end='updateItemPosition'
  54. )
  55. template(#item='{element}')
  56. .nav-edit-item.nav-edit-item-header(
  57. v-if='element.type === `header`'
  58. :class='state.selected === element.id ? `is-active` : ``'
  59. @click='setItem(element)'
  60. )
  61. q-item-label.text-caption(
  62. header
  63. ) {{ element.label }}
  64. q-space
  65. q-item-section(side)
  66. q-icon.handle(name='mdi-drag-horizontal', size='sm')
  67. q-item.nav-edit-item.nav-edit-item-link(
  68. v-else-if='element.type === `link`'
  69. :class='{ "is-active": state.selected === element.id, "is-nested": element.isNested }'
  70. @click='setItem(element)'
  71. clickable
  72. )
  73. q-item-section(side)
  74. q-icon(:name='element.icon', color='white')
  75. q-item-section.text-wordbreak-all {{ element.label }}
  76. q-item-section(side)
  77. q-icon.handle(name='mdi-drag-horizontal', size='sm')
  78. .nav-edit-item.nav-edit-item-separator(
  79. v-else
  80. :class='state.selected === element.id ? `is-active` : ``'
  81. @click='setItem(element)'
  82. )
  83. q-separator(
  84. dark
  85. inset
  86. style='flex: 1; margin-top: 11px;'
  87. )
  88. q-item-section(side)
  89. q-icon.handle(name='mdi-drag-horizontal', size='sm')
  90. .q-pa-md.flex
  91. q-btn.acrylic-btn(
  92. style='flex: 1;'
  93. flat
  94. color='positive'
  95. :label='t(`common.actions.add`)'
  96. :aria-label='t(`common.actions.add`)'
  97. icon='las la-plus-circle'
  98. )
  99. q-menu(fit, :offset='[0, 10]', auto-close)
  100. q-list(separator)
  101. q-item(clickable, @click='addItem(`header`)')
  102. q-item-section(side)
  103. q-icon(name='las la-heading')
  104. q-item-section
  105. q-item-label {{t('navEdit.header')}}
  106. q-item(clickable, @click='addItem(`link`)')
  107. q-item-section(side)
  108. q-icon(name='las la-link')
  109. q-item-section
  110. q-item-label {{t('navEdit.link')}}
  111. q-item(clickable, @click='addItem(`separator`)')
  112. q-item-section(side)
  113. q-icon(name='las la-minus')
  114. q-item-section
  115. q-item-label {{t('navEdit.separator')}}
  116. q-btn.q-ml-sm.acrylic-btn(
  117. flat
  118. color='grey'
  119. :aria-label='t(`common.actions.add`)'
  120. icon='las la-ellipsis-v'
  121. padding='xs sm'
  122. )
  123. q-menu(:offset='[0, 10]' anchor='bottom right' self='top right' auto-close)
  124. q-list(separator)
  125. q-item(clickable, @click='clearItems', :disable='state.items.length < 1')
  126. q-item-section(side)
  127. q-icon(name='las la-trash-alt', color='negative')
  128. q-item-section
  129. q-item-label {{t('navEdit.clearItems')}}
  130. //- q-item(clickable)
  131. //- q-item-section(side)
  132. //- q-icon(name='mdi-import')
  133. //- q-item-section
  134. //- q-item-label Copy from...
  135. q-page-container
  136. q-page.q-pa-md
  137. template(v-if='state.items.length < 1')
  138. q-card
  139. q-card-section
  140. q-icon.q-mr-sm(name='las la-arrow-left', size='xs')
  141. span {{ t('navEdit.emptyMenuText') }}
  142. template(v-else-if='!state.selected')
  143. q-card
  144. q-card-section
  145. q-icon.q-mr-sm(name='las la-arrow-left', size='xs')
  146. span {{ t('navEdit.noSelection') }}
  147. template(v-if='state.current.type === `header`')
  148. q-card.q-pb-sm
  149. q-card-section
  150. .text-subtitle1 {{t('navEdit.header')}}
  151. q-item
  152. blueprint-icon(icon='typography')
  153. q-item-section
  154. q-item-label {{t(`navEdit.label`)}}
  155. q-item-label(caption) {{t(`navEdit.labelHint`)}}
  156. q-item-section
  157. q-input(
  158. outlined
  159. v-model='state.current.label'
  160. dense
  161. hide-bottom-space
  162. :aria-label='t(`navEdit.label`)'
  163. )
  164. q-item
  165. blueprint-icon(icon='user-groups')
  166. q-item-section
  167. q-item-label {{t(`navEdit.visibility`)}}
  168. q-item-label(caption) {{t(`navEdit.visibilityHint`)}}
  169. q-item-section(avatar)
  170. q-btn-toggle(
  171. v-model='state.current.visibilityLimited'
  172. push
  173. glossy
  174. no-caps
  175. toggle-color='primary'
  176. :options='visibilityOptions'
  177. )
  178. q-item.items-center(v-if='state.current.visibilityLimited')
  179. q-space
  180. .text-caption.q-mr-md {{ t('navEdit.selectGroups') }}
  181. q-select(
  182. style='width: 100%; max-width: calc(50% - 34px);'
  183. outlined
  184. v-model='state.current.visibilityGroups'
  185. :options='state.groups'
  186. option-value='id'
  187. option-label='name'
  188. emit-value
  189. map-options
  190. dense
  191. multiple
  192. :aria-label='t(`navEdit.selectGroups`)'
  193. )
  194. q-card.q-pa-md.q-mt-md.flex
  195. q-space
  196. q-btn.acrylic-btn(
  197. flat
  198. icon='las la-trash-alt'
  199. :label='t(`common.actions.delete`)'
  200. color='negative'
  201. padding='xs md'
  202. @click='removeItem(state.current.id)'
  203. )
  204. template(v-if='state.current.type === `link`')
  205. q-card.q-pb-sm
  206. q-card-section
  207. .text-subtitle1 {{t('navEdit.link')}}
  208. q-item
  209. blueprint-icon(icon='typography')
  210. q-item-section
  211. q-item-label {{t(`navEdit.label`)}}
  212. q-item-label(caption) {{t(`navEdit.labelHint`)}}
  213. q-item-section
  214. q-input(
  215. outlined
  216. v-model='state.current.label'
  217. dense
  218. hide-bottom-space
  219. :aria-label='t(`navEdit.label`)'
  220. )
  221. q-separator.q-my-sm(inset)
  222. q-item
  223. blueprint-icon(icon='spring')
  224. q-item-section
  225. q-item-label {{t(`navEdit.icon`)}}
  226. q-item-label(caption) {{t(`navEdit.iconHint`)}}
  227. q-item-section
  228. q-input(
  229. outlined
  230. v-model='state.current.icon'
  231. dense
  232. :aria-label='t(`navEdit.icon`)'
  233. )
  234. template(#append)
  235. q-icon.cursor-pointer(
  236. name='las la-icons'
  237. color='primary'
  238. )
  239. q-menu(content-class='shadow-7')
  240. .q-pa-lg: em [ TODO: Icon Picker Dialog ]
  241. // icon-picker-dialog(v-model='pageStore.icon')
  242. q-separator.q-my-sm(inset)
  243. q-item
  244. blueprint-icon(icon='link')
  245. q-item-section
  246. q-item-label {{t(`navEdit.target`)}}
  247. q-item-label(caption) {{t(`navEdit.targetHint`)}}
  248. q-item-section
  249. q-input(
  250. outlined
  251. v-model='state.current.target'
  252. dense
  253. hide-bottom-space
  254. :aria-label='t(`navEdit.target`)'
  255. )
  256. q-separator.q-my-sm(inset)
  257. q-item(tag='label')
  258. blueprint-icon(icon='external-link')
  259. q-item-section
  260. q-item-label {{t(`navEdit.openInNewWindow`)}}
  261. q-item-label(caption) {{t(`navEdit.openInNewWindowHint`)}}
  262. q-item-section(avatar)
  263. q-toggle(
  264. v-model='state.current.openInNewWindow'
  265. color='primary'
  266. checked-icon='las la-check'
  267. unchecked-icon='las la-times'
  268. :aria-label='t(`navEdit.openInNewWindow`)'
  269. )
  270. q-separator.q-my-sm(inset)
  271. q-item
  272. blueprint-icon(icon='user-groups')
  273. q-item-section
  274. q-item-label {{t(`navEdit.visibility`)}}
  275. q-item-label(caption) {{t(`navEdit.visibilityHint`)}}
  276. q-item-section(avatar)
  277. q-btn-toggle(
  278. v-model='state.current.visibilityLimited'
  279. push
  280. glossy
  281. no-caps
  282. toggle-color='primary'
  283. :options='visibilityOptions'
  284. )
  285. q-item.items-center(v-if='state.current.visibilityLimited')
  286. q-space
  287. .text-caption.q-mr-md {{ t('navEdit.selectGroups') }}
  288. q-select(
  289. style='width: 100%; max-width: calc(50% - 34px);'
  290. outlined
  291. v-model='state.current.visibilityGroups'
  292. :options='state.groups'
  293. option-value='id'
  294. option-label='name'
  295. emit-value
  296. map-options
  297. dense
  298. multiple
  299. :aria-label='t(`navEdit.selectGroups`)'
  300. )
  301. q-card.q-pa-md.q-mt-md.flex.items-start
  302. div
  303. q-btn.acrylic-btn(
  304. v-if='state.current.isNested'
  305. flat
  306. :label='t(`navEdit.unnestItem`)'
  307. icon='mdi-format-indent-decrease'
  308. color='teal'
  309. padding='xs md'
  310. @click='state.current.isNested = false'
  311. )
  312. q-btn.acrylic-btn(
  313. v-else
  314. flat
  315. :label='t(`navEdit.nestItem`)'
  316. icon='mdi-format-indent-increase'
  317. color='teal'
  318. padding='xs md'
  319. @click='state.current.isNested = true'
  320. )
  321. .text-caption.q-mt-md.text-grey-7 {{ t('navEdit.nestingWarn') }}
  322. q-space
  323. q-btn.acrylic-btn(
  324. flat
  325. icon='las la-trash-alt'
  326. :label='t(`common.actions.delete`)'
  327. color='negative'
  328. padding='xs md'
  329. @click='removeItem(state.current.id)'
  330. )
  331. template(v-if='state.current.type === `separator`')
  332. q-card.q-pb-sm
  333. q-card-section
  334. .text-subtitle1 {{t('navEdit.separator')}}
  335. q-item
  336. blueprint-icon(icon='user-groups')
  337. q-item-section
  338. q-item-label {{t(`navEdit.visibility`)}}
  339. q-item-label(caption) {{t(`navEdit.visibilityHint`)}}
  340. q-item-section(avatar)
  341. q-btn-toggle(
  342. v-model='state.current.visibilityLimited'
  343. push
  344. glossy
  345. no-caps
  346. toggle-color='primary'
  347. :options='visibilityOptions'
  348. )
  349. q-item.items-center(v-if='state.current.visibilityLimited')
  350. q-space
  351. .text-caption.q-mr-md {{ t('navEdit.selectGroups') }}
  352. q-select(
  353. style='width: 100%; max-width: calc(50% - 34px);'
  354. outlined
  355. v-model='state.current.visibilityGroups'
  356. :options='state.groups'
  357. option-value='id'
  358. option-label='name'
  359. emit-value
  360. map-options
  361. dense
  362. multiple
  363. :aria-label='t(`navEdit.selectGroups`)'
  364. )
  365. q-card.q-pa-md.q-mt-md.flex
  366. q-space
  367. q-btn.acrylic-btn(
  368. flat
  369. icon='las la-trash-alt'
  370. :label='t(`common.actions.delete`)'
  371. color='negative'
  372. padding='xs md'
  373. @click='removeItem(state.current.id)'
  374. )
  375. </template>
  376. <script setup>
  377. import { useI18n } from 'vue-i18n'
  378. import { useQuasar } from 'quasar'
  379. import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
  380. import { v4 as uuid } from 'uuid'
  381. import gql from 'graphql-tag'
  382. import { cloneDeep, last, pick } from 'lodash-es'
  383. import { usePageStore } from 'src/stores/page'
  384. import { useSiteStore } from 'src/stores/site'
  385. import { Sortable } from 'sortablejs-vue3'
  386. import IconPickerDialog from 'src/components/IconPickerDialog.vue'
  387. // QUASAR
  388. const $q = useQuasar()
  389. // STORES
  390. const pageStore = usePageStore()
  391. const siteStore = useSiteStore()
  392. // I18N
  393. const { t } = useI18n()
  394. // DATA
  395. const state = reactive({
  396. loading: 0,
  397. selected: null,
  398. items: [],
  399. current: {
  400. label: '',
  401. icon: '',
  402. target: '/',
  403. openInNewWindow: false,
  404. visibilityGroups: [],
  405. visibilityLimited: false,
  406. isNested: false
  407. },
  408. groups: []
  409. })
  410. const sortableOptions = {
  411. handle: '.handle',
  412. animation: 150
  413. }
  414. const visibilityOptions = [
  415. { value: false, label: t('navEdit.visibilityAll') },
  416. { value: true, label: t('navEdit.visibilityLimited') }
  417. ]
  418. const thumbStyle = {
  419. right: '2px',
  420. borderRadius: '5px',
  421. backgroundColor: '#FFF',
  422. width: '5px',
  423. opacity: 0.5
  424. }
  425. const barStyle = {
  426. backgroundColor: '#000',
  427. width: '9px',
  428. opacity: 0.1
  429. }
  430. // METHODS
  431. function setItem (item) {
  432. state.selected = item.id
  433. state.current = item
  434. }
  435. function addItem (type) {
  436. const newItem = {
  437. id: uuid(),
  438. type,
  439. visibilityGroups: [],
  440. visibilityLimited: false
  441. }
  442. switch (type) {
  443. case 'header': {
  444. newItem.label = t('navEdit.header')
  445. break
  446. }
  447. case 'link': {
  448. newItem.label = t('navEdit.link')
  449. newItem.icon = 'mdi-text-box-outline'
  450. newItem.target = '/'
  451. newItem.openInNewWindow = false
  452. newItem.isNested = false
  453. break
  454. }
  455. }
  456. state.items.push(newItem)
  457. state.selected = newItem.id
  458. state.current = newItem
  459. }
  460. function removeItem (id) {
  461. state.items = state.items.filter(item => item.id !== id)
  462. state.selected = null
  463. state.current = {}
  464. }
  465. function clearItems () {
  466. state.items = []
  467. state.selected = null
  468. state.current = {}
  469. }
  470. function updateItemPosition (ev) {
  471. const item = state.items.splice(ev.oldIndex, 1)[0]
  472. state.items.splice(ev.newIndex, 0, item)
  473. }
  474. function close () {
  475. siteStore.$patch({ overlay: '' })
  476. }
  477. async function loadGroups () {
  478. state.loading++
  479. const resp = await APOLLO_CLIENT.query({
  480. query: gql`
  481. query getGroupsForEditNavMenu {
  482. groups {
  483. id
  484. name
  485. }
  486. }
  487. `,
  488. fetchPolicy: 'network-only'
  489. })
  490. state.groups = cloneDeep(resp?.data?.groups ?? [])
  491. state.loading--
  492. }
  493. async function loadMenuItems () {
  494. state.loading++
  495. $q.loading.show()
  496. try {
  497. const resp = await APOLLO_CLIENT.query({
  498. query: gql`
  499. query getItemsForEditNavMenu (
  500. $id: UUID!
  501. ) {
  502. navigationById (
  503. id: $id
  504. ) {
  505. id
  506. type
  507. label
  508. icon
  509. target
  510. openInNewWindow
  511. visibilityGroups
  512. children {
  513. id
  514. type
  515. label
  516. icon
  517. target
  518. openInNewWindow
  519. visibilityGroups
  520. }
  521. }
  522. }
  523. `,
  524. variables: {
  525. id: pageStore.isHome ? pageStore.navigationId : pageStore.id
  526. },
  527. fetchPolicy: 'network-only'
  528. })
  529. for (const item of cloneDeep(resp?.data?.navigationById ?? [])) {
  530. state.items.push({
  531. ...pick(item, ['id', 'type', 'label', 'icon', 'target', 'openInNewWindow', 'visibilityGroups']),
  532. visibilityLimited: item.visibilityGroups?.length > 0
  533. })
  534. for (const child of (item?.children ?? [])) {
  535. state.items.push({
  536. ...pick(child, ['id', 'type', 'label', 'icon', 'target', 'openInNewWindow', 'visibilityGroups']),
  537. visibilityLimited: item.visibilityGroups?.length > 0,
  538. isNested: true
  539. })
  540. }
  541. }
  542. } catch (err) {
  543. console.error(err)
  544. $q.notify({
  545. type: 'negative',
  546. message: err.message
  547. })
  548. close()
  549. }
  550. $q.loading.hide()
  551. state.loading--
  552. }
  553. function cleanMenuItem (item, isNested = false) {
  554. switch (item.type) {
  555. case 'header': {
  556. return {
  557. ...pick(item, ['id', 'type', 'label']),
  558. visibilityGroups: item.visibilityLimited ? item.visibilityGroups : []
  559. }
  560. }
  561. case 'link': {
  562. return {
  563. ...pick(item, ['id', 'type', 'label', 'icon', 'target', 'openInNewWindow']),
  564. visibilityGroups: item.visibilityLimited ? item.visibilityGroups : [],
  565. ...!isNested && { children: [] }
  566. }
  567. }
  568. case 'separator': {
  569. return {
  570. ...pick(item, ['id', 'type', 'label', 'icon', 'target', 'openInNewWindow']),
  571. visibilityGroups: item.visibilityLimited ? item.visibilityGroups : []
  572. }
  573. }
  574. }
  575. }
  576. async function save () {
  577. state.loading++
  578. $q.loading.show()
  579. try {
  580. const items = []
  581. for (const item of state.items) {
  582. if (item.isNested) {
  583. if (items.length < 1 || last(items)?.type !== 'link') {
  584. throw new Error('One or more nested link items are not under a parent link!')
  585. }
  586. items[items.length - 1].children.push(cleanMenuItem(item, true))
  587. } else {
  588. items.push(cleanMenuItem(item))
  589. }
  590. }
  591. const resp = await APOLLO_CLIENT.mutate({
  592. mutation: gql`
  593. mutation updateMenuItems (
  594. $pageId: UUID!
  595. $mode: NavigationMode!
  596. $items: [NavigationItemInput!]
  597. ) {
  598. updateNavigation (
  599. pageId: $pageId
  600. mode: $mode
  601. items: $items
  602. ) {
  603. operation {
  604. succeeded
  605. message
  606. }
  607. }
  608. }
  609. `,
  610. variables: {
  611. pageId: pageStore.id,
  612. mode: siteStore.overlayOpts.mode,
  613. items
  614. }
  615. })
  616. if (resp?.data?.updateNavigation?.operation?.succeeded) {
  617. $q.notify({
  618. type: 'positive',
  619. message: t('navEdit.saveSuccess')
  620. })
  621. siteStore.nav.items = items
  622. // -> Clear GraphQL Cache
  623. APOLLO_CLIENT.cache.evict('ROOT_QUERY')
  624. APOLLO_CLIENT.cache.gc()
  625. close()
  626. } else {
  627. throw new Error(resp?.data?.updateNavigation?.operation?.message || 'Unexpected error occured.')
  628. }
  629. } catch (err) {
  630. $q.notify({
  631. type: 'negative',
  632. message: err.message
  633. })
  634. }
  635. $q.loading.hide()
  636. state.loading--
  637. }
  638. onMounted(() => {
  639. loadMenuItems()
  640. loadGroups()
  641. })
  642. onBeforeUnmount(() => {
  643. siteStore.overlayOpts = {}
  644. })
  645. </script>
  646. <style lang="scss" scoped>
  647. .nav-edit {
  648. height: 100%;
  649. .handle {
  650. cursor: grab;
  651. }
  652. }
  653. .nav-edit-item {
  654. position: relative;
  655. &.is-active {
  656. background-color: $blue-8;
  657. }
  658. &.sortable-chosen {
  659. background-color: $blue-5;
  660. }
  661. }
  662. .nav-edit-item-header {
  663. display: flex;
  664. cursor: pointer;
  665. }
  666. .nav-edit-item-link {
  667. &.is-nested {
  668. border-left: 10px solid $dark-1;
  669. background-color: $dark-4;
  670. &.is-active {
  671. background-color: $primary;
  672. }
  673. & + div:not(.is-nested) {
  674. &::before {
  675. content: '';
  676. display: 'block';
  677. position: absolute;
  678. top: 0;
  679. left: 0;
  680. width: 10px;
  681. height: 10px;
  682. border-style: solid;
  683. border-color: $dark-1 transparent transparent $dark-1;
  684. border-width: 10px 10px 10px 0;
  685. }
  686. }
  687. }
  688. &:not(.is-nested) + &.is-nested {
  689. &::before {
  690. content: '';
  691. display: 'block';
  692. position: absolute;
  693. top: -10px;
  694. left: -10px;
  695. width: 10px;
  696. height: 10px;
  697. border-style: solid;
  698. border-color: transparent transparent $dark-1 $dark-1;
  699. border-width: 0 10px 10px 0;
  700. }
  701. }
  702. }
  703. .nav-edit-item-separator {
  704. display: flex;
  705. cursor: pointer;
  706. }
  707. .nav-edit-item-header, .nav-edit-item-separator {
  708. & + .nav-edit-item-link.is-nested {
  709. background-color: $negative !important;
  710. border-left-color: darken($negative, 10%) !important;
  711. & + div:not(.is-nested) {
  712. &::before {
  713. display: none !important;
  714. }
  715. }
  716. }
  717. }
  718. .nav-edit-list {
  719. .nav-edit-item-separator + .nav-edit-item-header > .q-item__label {
  720. padding-top: 8px;
  721. }
  722. .is-nested:first-child {
  723. background-color: $negative !important;
  724. border-left-color: darken($negative, 10%) !important;
  725. & + div:not(.is-nested) {
  726. &::before {
  727. display: none !important;
  728. }
  729. }
  730. }
  731. }
  732. </style>