Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add page trash user interface
Fixes: #47

Signed-off-by: Jonas <[email protected]>
  • Loading branch information
mejo- committed Jun 27, 2023
commit c9f777598d548574f86610a32d27fb000036a325
7 changes: 6 additions & 1 deletion src/components/Collective.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { mapActions, mapGetters, mapMutations } from 'vuex'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { listen } from '@nextcloud/notify_push'
import { NcAppContentDetails, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
import { GET_PAGES } from '../store/actions.js'
import { GET_PAGES, GET_TRASH_PAGES } from '../store/actions.js'
import { SELECT_VERSION } from '../store/mutations.js'
import displayError from '../util/displayError.js'
import Page from './Page.vue'
Expand Down Expand Up @@ -100,6 +100,7 @@ export default {

...mapActions({
dispatchGetPages: GET_PAGES,
dispatchGetTrashPages: GET_TRASH_PAGES,
}),

initCollective() {
Expand Down Expand Up @@ -173,6 +174,8 @@ export default {
async getPages() {
await this.dispatchGetPages()
.catch(displayError('Could not fetch pages'))
await this.dispatchGetTrashPages()
.catch(displayError('Could not fetch page trash'))
},

/**
Expand All @@ -181,6 +184,8 @@ export default {
async getPagesBackground() {
await this.dispatchGetPages(false)
.catch(displayError('Could not fetch pages'))
await this.dispatchGetTrashPages()
.catch(displayError('Could not fetch page trash'))
},

closeNav() {
Expand Down
9 changes: 9 additions & 0 deletions src/components/PageList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
class="page-list-drag-item" />
</Draggable>
</div>
<PageTrash v-if="displayTrash" />
</NcAppContentList>
</template>

Expand All @@ -117,6 +118,7 @@ import CloseIcon from 'vue-material-design-icons/Close.vue'
import Draggable from './PageList/Draggable.vue'
import SubpageList from './PageList/SubpageList.vue'
import Item from './PageList/Item.vue'
import PageTrash from './PageList/PageTrash.vue'
import SortAlphabeticalAscendingIcon from 'vue-material-design-icons/SortAlphabeticalAscending.vue'
import SortAscendingIcon from 'vue-material-design-icons/SortAscending.vue'
import SortClockAscendingOutlineIcon from 'vue-material-design-icons/SortClockAscendingOutline.vue'
Expand All @@ -138,6 +140,7 @@ export default {
Draggable,
Item,
PagesTemplateIcon,
PageTrash,
SubpageList,
SortAlphabeticalAscendingIcon,
SortAscendingIcon,
Expand Down Expand Up @@ -200,6 +203,12 @@ export default {
disableSorting() {
return this.filterString !== ''
},

displayTrash() {
return this.currentCollectiveCanEdit
&& ('files_trashbin' in this.OC.appswebroots)
&& !this.loading('collectives')
},
},

methods: {
Expand Down
290 changes: 290 additions & 0 deletions src/components/PageList/PageTrash.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
<template>
<div id="page-trash"
v-click-outside="clickOutsideConfig">
<div id="page-trash__header">
<NcButton type="tertiary" class="page-trash-button" @click="toggleTrash">
<template #icon>
<DeleteIcon class="page-trash-button__icon" :size="20" />
</template>
{{ t('collectives', 'Deleted pages') }}
</NcButton>
</div>
<transition name="slide-up">
<div v-show="open" id="page-trash__content">
<div v-for="trashPage in trashPages"
:key="`page-trash-${trashPage.id}`"
class="app-content-list-item"
:class="{ mobile: isMobile }">
<div class="app-content-list-item-icon">
<PageTemplateIcon v-if="isTemplate(trashPage)" :size="22" fill-color="var(--color-background-darker)" />
<div v-else-if="trashPage.emoji" class="item-icon-emoji">
{{ trashPage.emoji }}
</div>
<PageIcon v-else :size="22" fill-color="var(--color-background-darker)" />
</div>
<a class="app-content-list-item-link">
<div :ref="`page-title-${trashPage.id}`"
v-tooltip="pageTitleIfTruncated(trashPage)"
class="app-content-list-item-line-one"
:class="{ 'template': isTemplate(trashPage) }">
{{ pageTitleString(trashPage) }}
</div>
</a>
<div class="page-list-item-actions">
<NcActions>
<NcActionButton :close-after-click="true" @click="restorePage(trashPage)">
<template #icon>
<RestoreIcon :size="20" />
</template>
{{ t('collectives', 'Restore') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="deletePage(trashPage)">
<template #icon>
<DeleteIcon :size="20" />
</template>
{{ t('collectives', 'Delete permanently') }}
</NcActionButton>
</NcActions>
</div>
</div>
</div>
</transition>
</div>
</template>

<script>
import { NcActions, NcActionButton, NcButton } from '@nextcloud/vue'
import isMobile from '@nextcloud/vue/dist/Mixins/isMobile.js'
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
import RestoreIcon from 'vue-material-design-icons/Restore.vue'
import PageIcon from '../Icon/PageIcon.vue'
import PageTemplateIcon from '../Icon/PageTemplateIcon.vue'
import { directive as ClickOutside } from 'v-click-outside'
import { mapActions, mapGetters } from 'vuex'
import { RESTORE_PAGE, DELETE_PAGE } from '../../store/actions.js'

export default {
name: 'PageTrash',

directives: {
ClickOutside,
},

components: {
NcActions,
NcActionButton,
NcButton,
DeleteIcon,
RestoreIcon,
PageIcon,
PageTemplateIcon,
},

mixins: [
isMobile,
],

data() {
return {
open: false,
clickOutsideConfig: {
handler: this.closeTrash,
},
}
},

computed: {
...mapGetters([
'trashPages',
]),

isTemplate() {
return (trashPage) => {
return trashPage.title === 'Template'
}
},

pageTitleIsTruncated() {
return (trashPage) => {
return false
// TODO: ref not working
// return this.$refs[`page-title-${trashPage.id}`].scrollWidth > this.$refs[`page-title-${trashPage.id}`].clientWidth
}
},

pageTitleIfTruncated() {
return (trashPage) => {
return this.pageTitleIsTruncated(trashPage) ? this.pageTitleString : null
}
},

pageTitleString() {
return (trashPage) => {
return this.isTemplate(trashPage) ? t('collectives', 'Template') : trashPage.title
}
},
},

methods: {
...mapActions({
dispatchRestorePage: RESTORE_PAGE,
dispatchDeletePage: DELETE_PAGE,
}),

toggleTrash() {
this.open = !this.open
},

closeTrash() {
this.open = false
},

restorePage(trashPage) {
this.dispatchRestorePage({ pageId: trashPage.id })
this.closeTrash()
},

deletePage(trashPage) {
this.dispatchDeletePage({ pageId: trashPage.id })
this.closeTrash()
},
},
}
</script>

<style scoped lang="scss">
#page-trash {
width: 100%;
position: absolute;
bottom: 0;
// margin-top: auto;

&__header {
box-sizing: border-box;
margin: 0 3px 3px 3px;
padding-top: calc(var(--default-grid-baseline, 4px) * 2);

.page-trash-button {
display: flex;
flex: 1 1 0;
height: 44px;
width: 100%;
padding: 0;
margin: 0;
// background-color: var(--color-main-background);
box-shadow: none;
border: 0;
border-radius: var(--border-radius-pill);
color: var(--color-main-text);
padding-right: 14px;
line-height: 44px;

:deep(.button-vue__wrapper) {
width: 100%;
justify-content: start;
}

&:hover,
&:focus {
background-color: var(--color-background-hover);
}

&__icon {
width: 44px;
height: 44px;
min-width: 44px;
}

:deep(.button-vue__text) {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: normal;
}
}
}

&__content {
display: block;
padding: 10px 4px;
/* Restrict height of trash and make scrollable */
max-height: 300px;
overflow-y: auto;
box-sizing: border-box;
}

.slide-up-leave-active,
.slide-up-enter-active {
transition-duration: var(--animation-slow);
transition-property: max-height, padding;
overflow-y: hidden !important;
}

.slide-up-enter,
.slide-up-leave-to {
max-height: 0 !important;
padding: 0 4px !important;
}

.app-content-list-item {
box-sizing: border-box;
height: 44px;
// border-bottom: 4px solid var(--color-main-background);
margin-bottom: 4px;

padding-left: 0;
border-radius: var(--border-radius-large);
cursor: default;

&:hover, &:focus, &:active {
background-color: var(--color-background-hover);
}

&.mobile, &:hover, &:focus, &:active {
// Shorter width to prevent collision with actions
.app-content-list-item-link {
width: calc(100% - 24px);
}

.page-list-item-actions {
visibility: visible;
}
}

.app-content-list-item-icon {
display: flex;
justify-content: center;
align-items: center;
// Emojis are too big with default 1.5em
font-size: 1.3em;
cursor: default;

.item-icon-emoji {
&.landing-page {
margin: -3px 0;
}
}
}

.app-content-list-item-line-one {
padding-left: 40px;
cursor: default;
}

.app-content-list-item-link {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
}

.page-list-item-actions {
visibility: hidden;
display: flex;
position: absolute;
top: 0;
right: 0;
margin: 0;
}
}
</style>
6 changes: 3 additions & 3 deletions src/mixins/pageMixin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'
import { showError, showSuccess } from '@nextcloud/dialogs'
import {
DELETE_PAGE,
TRASH_PAGE,
GET_PAGES,
MOVE_PAGE,
NEW_PAGE,
Expand Down Expand Up @@ -45,7 +45,7 @@ export default {
dispatchSetPageEmoji: SET_PAGE_EMOJI,
dispatchSetPageSubpageOrder: SET_PAGE_SUBPAGE_ORDER,
dispatchMovePage: MOVE_PAGE,
dispatchDeletePage: DELETE_PAGE,
dispatchTrashPage: TRASH_PAGE,
}),

/**
Expand Down Expand Up @@ -170,7 +170,7 @@ export default {
const currentPageId = this.currentPage?.id

try {
await this.dispatchDeletePage({ parentId, pageId })
await this.dispatchTrashPage({ parentId, pageId })
} catch (e) {
console.error(e)
showError(t('collectives', 'Could not delete the page'))
Expand Down
Loading