mirror of https://github.com/requarks/wiki
parent
2a051637a6
commit
4cdeba80ce
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.0 KiB |
@ -1,42 +0,0 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
.col-auto.bg-grey-1(style='width: 250px;')
|
||||
q-tree(
|
||||
:nodes='tree'
|
||||
default-expand-all
|
||||
node-key='label'
|
||||
@lazy-load='onLazyLoad'
|
||||
)
|
||||
.col Doude
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
tree: [
|
||||
{
|
||||
label: 'Item 1',
|
||||
icon: 'las la-folder',
|
||||
children: [
|
||||
{ label: 'Item 1.1' },
|
||||
{
|
||||
label: 'Item 1.2',
|
||||
icon: 'las la-folder',
|
||||
children: [
|
||||
{ label: 'Item 1.2.1' },
|
||||
{ label: 'Item 1.2.2' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onLazyLoad ({ node, key, done, fail }) {
|
||||
done([])
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,109 @@
|
||||
<template lang="pug">
|
||||
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'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import gql from 'graphql-tag'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDialogPluginComponent, useQuasar } from 'quasar'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
// PROPS
|
||||
|
||||
const props = defineProps({
|
||||
pageId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
pageName: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// EMITS
|
||||
|
||||
defineEmits([
|
||||
...useDialogPluginComponent.emits
|
||||
])
|
||||
|
||||
// QUASAR
|
||||
|
||||
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
|
||||
const $q = useQuasar()
|
||||
|
||||
// I18N
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// DATA
|
||||
|
||||
const state = reactive({
|
||||
isLoading: false
|
||||
})
|
||||
|
||||
// METHODS
|
||||
|
||||
async function confirm () {
|
||||
state.isLoading = true
|
||||
try {
|
||||
const resp = await APOLLO_CLIENT.mutate({
|
||||
mutation: gql`
|
||||
mutation deletePage ($id: UUID!) {
|
||||
deletePage(id: $id) {
|
||||
operation {
|
||||
succeeded
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: props.pageId
|
||||
}
|
||||
})
|
||||
if (resp?.data?.deletePage?.operation?.succeeded) {
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
message: t('pageDeleteDialog.deleteSuccess')
|
||||
})
|
||||
onDialogOK()
|
||||
} else {
|
||||
throw new Error(resp?.data?.deletePage?.operation?.message || 'An unexpected error occured.')
|
||||
}
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: err.message
|
||||
})
|
||||
}
|
||||
state.isLoading = false
|
||||
}
|
||||
</script>
|
@ -1,57 +1,376 @@
|
||||
<template lang="pug">
|
||||
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
|
||||
)
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageBrowser from './PageBrowser.vue'
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed, onMounted, reactive } from 'vue'
|
||||
import { useDialogPluginComponent, useQuasar } from 'quasar'
|
||||
import { cloneDeep, find } from 'lodash-es'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PageBrowser
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
reason: ''
|
||||
}
|
||||
import fileTypes from '../helpers/fileTypes'
|
||||
|
||||
import FolderCreateDialog from 'src/components/FolderCreateDialog.vue'
|
||||
import Tree from 'src/components/TreeNav.vue'
|
||||
|
||||
import { usePageStore } from 'src/stores/page'
|
||||
import { useSiteStore } from 'src/stores/site'
|
||||
|
||||
// PROPS
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'save'
|
||||
},
|
||||
computed: {
|
||||
pageId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mounted () {
|
||||
pageName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
methods: {
|
||||
pagePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
// EMITS
|
||||
|
||||
defineEmits([
|
||||
...useDialogPluginComponent.emits
|
||||
])
|
||||
|
||||
// QUASAR
|
||||
|
||||
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
|
||||
const $q = useQuasar()
|
||||
|
||||
// STORES
|
||||
|
||||
const pageStore = usePageStore()
|
||||
const siteStore = useSiteStore()
|
||||
|
||||
// I18N
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// DATA
|
||||
|
||||
const state = reactive({
|
||||
displayMode: 'title',
|
||||
currentFolderId: '',
|
||||
currentFileId: '',
|
||||
treeNodes: {},
|
||||
treeRoots: [],
|
||||
fileList: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'folder',
|
||||
title: 'Beep Boop'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'folder',
|
||||
title: 'Second Folder'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'page',
|
||||
title: 'Some Page',
|
||||
pageType: 'markdown'
|
||||
}
|
||||
],
|
||||
title: '',
|
||||
path: ''
|
||||
})
|
||||
|
||||
const displayModes = [
|
||||
{ value: 'title', label: t('pageSaveDialog.displayModeTitle') },
|
||||
{ value: 'path', label: t('pageSaveDialog.displayModePath') }
|
||||
]
|
||||
|
||||
// COMPUTED
|
||||
|
||||
const files = computed(() => {
|
||||
return state.fileList.map(f => {
|
||||
switch (f.type) {
|
||||
case 'folder': {
|
||||
f.icon = fileTypes.folder.icon
|
||||
break
|
||||
}
|
||||
case 'page': {
|
||||
f.icon = fileTypes.page.icon
|
||||
break
|
||||
}
|
||||
}
|
||||
return f
|
||||
})
|
||||
})
|
||||
|
||||
// METHODS
|
||||
|
||||
async function save () {
|
||||
onDialogOK()
|
||||
}
|
||||
|
||||
async function treeLazyLoad (nodeId, { done, fail }) {
|
||||
await loadTree(nodeId, ['folder', 'page'])
|
||||
done()
|
||||
}
|
||||
|
||||
async function loadTree (parentId, types) {
|
||||
try {
|
||||
const resp = await APOLLO_CLIENT.query({
|
||||
query: gql`
|
||||
query loadTree (
|
||||
$siteId: UUID!
|
||||
$parentId: UUID
|
||||
$types: [TreeItemType]
|
||||
) {
|
||||
tree (
|
||||
siteId: $siteId
|
||||
parentId: $parentId
|
||||
types: $types
|
||||
) {
|
||||
__typename
|
||||
... on TreeItemFolder {
|
||||
id
|
||||
folderPath
|
||||
fileName
|
||||
title
|
||||
childrenCount
|
||||
}
|
||||
... on TreeItemPage {
|
||||
id
|
||||
folderPath
|
||||
fileName
|
||||
title
|
||||
createdAt
|
||||
updatedAt
|
||||
pageEditor
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
siteId: siteStore.id,
|
||||
parentId,
|
||||
types
|
||||
},
|
||||
fetchPolicy: 'network-only'
|
||||
})
|
||||
const items = cloneDeep(resp?.data?.tree)
|
||||
if (items?.length > 0) {
|
||||
const newTreeRoots = []
|
||||
for (const item of items) {
|
||||
switch (item.__typename) {
|
||||
case 'TreeItemFolder': {
|
||||
state.treeNodes[item.id] = {
|
||||
text: item.title,
|
||||
fileName: item.fileName,
|
||||
children: []
|
||||
}
|
||||
if (!item.folderPath) {
|
||||
newTreeRoots.push(item.id)
|
||||
} else {
|
||||
state.treeNodes[parentId].children.push(item.id)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newTreeRoots.length > 0) {
|
||||
state.treeRoots = newTreeRoots
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: 'Failed to load folder tree.',
|
||||
caption: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function treeContextAction (nodeId, action) {
|
||||
switch (action) {
|
||||
case 'newFolder': {
|
||||
newFolder(nodeId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function newFolder (parentId) {
|
||||
$q.dialog({
|
||||
component: FolderCreateDialog,
|
||||
componentProps: {
|
||||
parentId
|
||||
}
|
||||
}).onOk(() => {
|
||||
loadTree(parentId)
|
||||
})
|
||||
}
|
||||
|
||||
// MOUNTED
|
||||
|
||||
onMounted(() => {
|
||||
loadTree()
|
||||
state.title = props.pageName || ''
|
||||
state.path = props.pagePath || ''
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page-save-dialog {
|
||||
|
||||
&-browser {
|
||||
height: 300px;
|
||||
max-height: 90vh;
|
||||
border-bottom: 1px solid $blue-grey-1;
|
||||
|
||||
> .col-4 {
|
||||
@at-root .body--light & {
|
||||
background-color: $blue-grey-1;
|
||||
border-bottom-color: $blue-grey-1;
|
||||
}
|
||||
@at-root .body--dark & {
|
||||
background-color: $dark-4;
|
||||
border-bottom-color: $dark-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-filelist {
|
||||
padding: 8px 12px;
|
||||
|
||||
> .q-item {
|
||||
padding: 4px 6px;
|
||||
border-radius: 4px;
|
||||
|
||||
&.active {
|
||||
background-color: var(--q-primary);
|
||||
color: #FFF;
|
||||
|
||||
.fileman-filelist-label .q-item__label--caption {
|
||||
color: rgba(255,255,255,.7);
|
||||
}
|
||||
|
||||
.fileman-filelist-side .text-caption {
|
||||
color: rgba(255,255,255,.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in new issue