diff --git a/client/components/common/nav-header.vue b/client/components/common/nav-header.vue
index 9dd3addc..0e0a13e5 100644
--- a/client/components/common/nav-header.vue
+++ b/client/components/common/nav-header.vue
@@ -241,12 +241,6 @@
page-selector(mode='create', v-model='duplicateOpts.modal', :open-handler='pageDuplicateHandle', :path='duplicateOpts.path', :locale='duplicateOpts.locale')
page-delete(v-model='deletePageModal', v-if='path && path.length')
page-convert(v-model='convertPageModal', v-if='path && path.length')
-
- .nav-header-dev(v-if='isDevMode')
- v-icon mdi-alert
- div
- .overline DEVELOPMENT VERSION
- .overline This code base is NOT for production use!
diff --git a/ux/src/components/PageDeleteDialog.vue b/ux/src/components/PageDeleteDialog.vue
new file mode 100644
index 00000000..9e3e4100
--- /dev/null
+++ b/ux/src/components/PageDeleteDialog.vue
@@ -0,0 +1,109 @@
+
+q-dialog(ref='dialogRef', @hide='onDialogHide')
+ q-card(style='min-width: 550px; max-width: 850px;')
+ q-card-section.card-header
+ q-icon(name='img:/_assets/icons/fluent-delete-bin.svg', left, size='sm')
+ span {{t(`pageDeleteDialog.title`)}}
+ q-card-section
+ .text-body2
+ i18n-t(keypath='pageDeleteDialog.confirm')
+ template(v-slot:name)
+ strong {{pageName}}
+ .text-caption.text-grey.q-mt-sm {{t('pageDeleteDialog.pageId', { id: pageId })}}
+ q-card-actions.card-actions
+ q-space
+ q-btn.acrylic-btn(
+ flat
+ :label='t(`common.actions.cancel`)'
+ color='grey'
+ padding='xs md'
+ @click='onDialogCancel'
+ )
+ q-btn(
+ unelevated
+ :label='t(`common.actions.delete`)'
+ color='negative'
+ padding='xs md'
+ @click='confirm'
+ :loading='state.isLoading'
+ )
+
+
+
diff --git a/ux/src/components/PageSaveDialog.vue b/ux/src/components/PageSaveDialog.vue
index 103475e0..a929fdfc 100644
--- a/ux/src/components/PageSaveDialog.vue
+++ b/ux/src/components/PageSaveDialog.vue
@@ -1,57 +1,376 @@
-q-card.page-save-dialog(style='width: 860px; max-width: 90vw;')
- q-toolbar.bg-primary.text-white
- .text-subtitle2 {{$t('editor.pageSave.title')}}
- page-browser
- q-card-section
- q-input(
- v-model='reason'
- label='Reason for change'
- dense
- outlined
- )
- q-card-actions.card-actions
- q-space
- q-btn.acrylic-btn(
- icon='las la-times'
- :label='$t(`common.actions.cancel`)'
- color='grey-7'
- padding='xs md'
- v-close-popup
- flat
- )
- q-btn(
- icon='las la-check'
- :label='$t(`common.actions.save`)'
- unelevated
- color='primary'
- padding='xs md'
- @click=''
- v-close-popup
- )
+q-dialog(ref='dialogRef', @hide='onDialogHide')
+ q-card.page-save-dialog(style='width: 860px; max-width: 90vw;')
+ q-card-section.card-header
+ q-icon(name='img:/_assets/icons/fluent-save-as.svg', left, size='sm')
+ span {{t('pageSaveDialog.title')}}
+ .row.page-save-dialog-browser
+ .col-4.q-px-sm
+ tree(
+ :nodes='state.treeNodes'
+ :roots='state.treeRoots'
+ v-model:selected='state.currentFolderId'
+ @lazy-load='treeLazyLoad'
+ :use-lazy-load='true'
+ @context-action='treeContextAction'
+ :context-action-list='[`newFolder`]'
+ :display-mode='state.displayMode'
+ )
+ .col-8
+ q-list.page-save-dialog-filelist(dense)
+ q-item(
+ v-for='item of files'
+ :key='item.id'
+ clickable
+ active-class='active'
+ :active='item.id === state.currentFileId'
+ @click.native='state.currentFileId = item.id'
+ @dblclick.native='openItem(item)'
+ )
+ q-item-section(side)
+ q-icon(:name='item.icon', size='sm')
+ q-item-section
+ q-item-label {{item.title}}
+ q-list.q-py-sm
+ q-item
+ blueprint-icon(icon='new-document')
+ q-item-section
+ q-input(
+ v-model='state.title'
+ label='Page Title'
+ dense
+ outlined
+ )
+ q-item
+ blueprint-icon(icon='file-submodule')
+ q-item-section
+ q-input(
+ v-model='state.path'
+ label='Path Name'
+ dense
+ outlined
+ )
+ q-card-actions.card-actions.q-px-md
+ q-btn.acrylic-btn(
+ icon='las la-ellipsis-h'
+ color='blue-grey'
+ padding='xs sm'
+ flat
+ )
+ q-tooltip(anchor='center right' self='center left') Display Options
+ q-menu(
+ auto-close
+ transition-show='jump-down'
+ transition-hide='jump-up'
+ anchor='top left'
+ self='bottom left'
+ )
+ q-card.q-pa-sm
+ q-list(dense)
+ q-item(clickable, @click='state.displayMode = `path`')
+ q-item-section(side)
+ q-icon(
+ :name='state.displayMode === `path` ? `las la-check-circle` : `las la-circle`'
+ :color='state.displayMode === `path` ? `positive` : `grey`'
+ size='xs'
+ )
+ q-item-section.q-pr-sm Browse Using Paths
+ q-item(clickable, @click='state.displayMode = `title`')
+ q-item-section(side)
+ q-icon(
+ :name='state.displayMode === `title` ? `las la-check-circle` : `las la-circle`'
+ :color='state.displayMode === `title` ? `positive` : `grey`'
+ size='xs'
+ )
+ q-item-section.q-pr-sm Browse Using Titles
+ q-space
+ q-btn.acrylic-btn(
+ icon='las la-times'
+ :label='t(`common.actions.cancel`)'
+ color='grey-7'
+ padding='xs md'
+ @click='onDialogCancel'
+ flat
+ )
+ q-btn(
+ icon='las la-check'
+ :label='t(`common.actions.save`)'
+ unelevated
+ color='primary'
+ padding='xs md'
+ @click='save'
+ v-close-popup
+ )
-
diff --git a/ux/src/components/TreeLevel.vue b/ux/src/components/TreeLevel.vue
index dbf46b04..31efee46 100644
--- a/ux/src/components/TreeLevel.vue
+++ b/ux/src/components/TreeLevel.vue
@@ -6,6 +6,7 @@ ul.treeview-level
q-icon(name='img:/_assets/icons/fluent-ftp.svg', size='sm')
.treeview-label-text(:class='$q.dark.isActive ? `text-purple-4` : `text-purple`') root
q-menu(
+ v-if='rootContextActionList.length > 0'
touch-position
context-menu
auto-close
@@ -14,10 +15,15 @@ ul.treeview-level
)
q-card.q-pa-sm
q-list(dense, style='min-width: 150px;')
- q-item(clickable, @click='createRootFolder')
+ q-item(
+ v-for='action of rootContextActionList'
+ :key='action.key'
+ clickable
+ @click='action.handler(null)'
+ )
q-item-section(side)
- q-icon(name='las la-plus-circle', color='primary')
- q-item-section New Folder
+ q-icon(:name='action.icon', :color='action.iconColor')
+ q-item-section(:class='action.labelColor && (`text-` + action.labelColor)') {{action.label}}
q-icon(
v-if='!selection'
name='las la-angle-right'
@@ -62,9 +68,15 @@ const roots = inject('roots')
const nodes = inject('nodes')
const selection = inject('selection')
const emitContextAction = inject('emitContextAction')
+const contextActionList = inject('contextActionList')
// COMPUTED
+const rootContextActionList = computed(() => {
+ if (props.parentId) { return [] }
+ return contextActionList.filter(c => c.key === 'newFolder')
+})
+
const level = computed(() => {
const items = []
if (!props.parentId) {
diff --git a/ux/src/components/TreeNav.vue b/ux/src/components/TreeNav.vue
index ac25e5fd..4b42de0e 100644
--- a/ux/src/components/TreeNav.vue
+++ b/ux/src/components/TreeNav.vue
@@ -7,6 +7,7 @@