Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
44 changes: 44 additions & 0 deletions cypress/e2e/page-list.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,48 @@ describe('Page list', function() {
})
})
})

describe('Page trash', function() {
it('allows to trash and restore page with subpage and attachment', function() {
cy.visit('/apps/collectives/Our%20Garden/Day%201')

// Insert attachment
cy.intercept({ method: 'POST', url: '**/text/attachment/upload*' }).as('attachmentUpload')
cy.get('input[data-text-el="attachment-file-input"]')
.selectFile('cypress/fixtures/test.png', { force: true })
cy.wait('@attachmentUpload')
cy.switchPageMode(0)

// Trash page
cy.contains('.page-list .app-content-list-item', 'Day 1')
.find('.action-item__menutoggle')
.click({ force: true })
cy.get('button.action-button')
.contains('Delete page and subpages')
.click()
cy.get('.page-list .app-content-list-item')
.should('not.contain', 'Day 1')

// Restore page
cy.get('.page-trash')
.click()
cy.contains('table tr', 'Day 1')
.find('button')
.contains('Restore')
.click()
cy.get('table tr')
.should('not.exist')

cy.visit('/apps/collectives/Our%20Garden/Day%201')
if (Cypress.env('ncVersion') === 'stable25') {
cy.getEditor()
.find('img.image__main')
.should('be.visible')
} else {
cy.getReadOnlyEditor()
.find('img.image__main')
.should('be.visible')
}
})
})
})
4 changes: 2 additions & 2 deletions cypress/e2e/pages.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ describe('Page', function() {
})

describe('Using the search providers to search for a page', function() {
it('Search for page and page content', function() {
it('Search for page title', function() {
cy.get('.unified-search a').click()
cy.get('.unified-search__form input')
.type('Day')
Expand All @@ -271,7 +271,7 @@ describe('Page', function() {
})

describe('Using the search providers to search page content', function() {
it('Search for page and page content', function() {
it('Search for page content', function() {
cy.get('.unified-search a').click()
cy.get('.unified-search__form input')
.type('share your thoughts')
Expand Down
5 changes: 3 additions & 2 deletions lib/Fs/NodeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,9 @@ public static function isPage(File $file): bool {
* @return bool
*/
public static function isLandingPage(File $file): bool {
$internalPath = $file->getInternalPath();
return ($internalPath === PageInfo::INDEX_PAGE_TITLE . PageInfo::SUFFIX);
$internalPath = $file->getInternalPath() ?: '';
return ($internalPath === PageInfo::INDEX_PAGE_TITLE . PageInfo::SUFFIX)
|| preg_match('/^appdata_\w+\/collectives\/\d+\/' . PageInfo::INDEX_PAGE_TITLE . PageInfo::SUFFIX . '$/', $internalPath);
}

/**
Expand Down
5 changes: 0 additions & 5 deletions lib/Service/PageService.php
Original file line number Diff line number Diff line change
Expand Up @@ -843,11 +843,6 @@ public function trash(int $collectiveId, int $parentId, int $id, string $userId)

try {
if (NodeHelper::isIndexPage($file)) {
// Don't delete if still page has subpages
if (NodeHelper::indexPageHasOtherContent($file)) {
throw new NotPermittedException('Failed to delete page ' . $id . ' with subpages');
}

// Delete folder if it's an index page without subpages
$file->getParent()->delete();
} else {
Expand Down
22 changes: 11 additions & 11 deletions lib/Trash/PageTrashBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,14 @@ public function restoreItem(ITrashItem $item): void {
}

// Get pageId for restoring page in collective page database
$restorePageId = null;
$restorePageId = $node->getId();
if ($node instanceof Folder) {
// Try to use index page if folder is deleted
if (null !== $indexNode = $node->get(PageInfo::INDEX_PAGE_TITLE . PageInfo::SUFFIX)) {
try {
$indexNode = $node->get(PageInfo::INDEX_PAGE_TITLE . PageInfo::SUFFIX);
$restorePageId = $indexNode->getId();
} catch (NotFoundException $e) {
}
} else {
$restorePageId = $node->getId();
}

$targetLocation = $targetFolder->getInternalPath() . '/' . $originalLocation;
Expand Down Expand Up @@ -214,25 +214,25 @@ public function removeItem(ITrashItem $item): void {
}

// Get original parent folder of item to revert subfolders further down
$targetFolder = $this->collectiveFolderManager->getFolder($collectiveId);
$collectiveFolder = $this->collectiveFolderManager->getFolder($collectiveId);
$targetFolderPath = substr($item->getOriginalLocation(), 0, -strlen($item->getName()));
if ($targetFolderPath) {
try {
$targetFolder = $targetFolder->get($targetFolderPath);
$targetFolder = $collectiveFolder->get($targetFolderPath);
} catch (NotFoundException $e) {
$targetFolder = null;
}
}

// Get pageId for deleting page from collective page database
$deletePageId = null;
$deletePageId = $node->getId();
if ($node instanceof Folder) {
// Try to use index page if folder is deleted
if (null !== $indexNode = $node->get(PageInfo::INDEX_PAGE_TITLE . PageInfo::SUFFIX)) {
try {
$indexNode = $node->get(PageInfo::INDEX_PAGE_TITLE . PageInfo::SUFFIX);
$deletePageId = $indexNode->getId();
} catch (NotFoundException $e) {
}
} else {
$deletePageId = $node->getId();
}

if ($node->getStorage()->unlink($node->getInternalPath()) === false) {
Expand All @@ -259,7 +259,7 @@ public function removeItem(ITrashItem $item): void {
}

// Try to revert subfolders of target folder parent
if ($targetFolder) {
if ($targetFolder && $targetFolder->getId() !== $collectiveFolder->getId()) {
try {
NodeHelper::revertSubFolders($targetFolder->getParent());
} catch (\OCA\Collectives\Service\NotFoundException | \OCA\Collectives\Service\NotPermittedException $e) {
Expand Down
12 changes: 11 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 @@ -50,6 +50,7 @@ export default {
computed: {
...mapGetters([
'currentCollective',
'currentCollectiveCanEdit',
'currentFileIdPage',
'currentPage',
'collectivePage',
Expand Down Expand Up @@ -100,6 +101,7 @@ export default {

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

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

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

closeNav() {
Expand Down
15 changes: 4 additions & 11 deletions src/components/Page/PageActionMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,9 @@
</NcActionButton>
<NcActionButton v-if="currentCollectiveCanEdit && !isLandingPage"
:close-after-click="true"
:disabled="hasSubpages"
@click="deletePage(parentId, pageId)">
<template #icon>
<DeleteOffIcon v-if="hasSubpages" :size="20" />
<DeleteIcon v-else :size="20" />
<DeleteIcon :size="20" />
</template>
{{ deletePageString }}
</NcActionButton>
Expand All @@ -87,7 +85,6 @@ import { NcActions, NcActionButton, NcActionLink, NcActionSeparator } from '@nex
import isMobile from '@nextcloud/vue/dist/Mixins/isMobile.js'
import CollectiveActions from '../Collective/CollectiveActions.vue'
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
import DeleteOffIcon from 'vue-material-design-icons/DeleteOff.vue'
import EmoticonOutlineIcon from 'vue-material-design-icons/EmoticonOutline.vue'
import FormatListBulletedIcon from 'vue-material-design-icons/FormatListBulleted.vue'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
Expand All @@ -107,7 +104,6 @@ export default {
NcActionLink,
NcActionSeparator,
DeleteIcon,
DeleteOffIcon,
EmoticonOutlineIcon,
FormatListBulletedIcon,
PagesTemplateIcon,
Expand Down Expand Up @@ -173,6 +169,7 @@ export default {
...mapGetters([
'currentCollective',
'currentCollectiveCanEdit',
'hasSubpages',
'loading',
'pagesTreeWalk',
'showing',
Expand Down Expand Up @@ -201,8 +198,8 @@ export default {
},

deletePageString() {
return this.hasSubpages
? t('collectives', 'Cannot delete page with subpages')
return this.hasSubpages(this.pageId)
? t('collectives', 'Delete page and subpages')
: this.isTemplate
? t('collectives', 'Delete template')
: t('collectives', 'Delete page')
Expand All @@ -212,10 +209,6 @@ export default {
return !!this.templatePage(this.pageId)
},

hasSubpages() {
return !!this.visibleSubpages(this.pageId).length || !!this.hasTemplate
},

/**
* Other apps can register an extra collective action via
* OCA.Collectives.CollectiveExtraAction
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
16 changes: 16 additions & 0 deletions src/components/PageList/Item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
highlight: isHighlighted,
'dragged-over-target': isDraggedOverTarget,
'highlight-target': isHighlightedTarget,
'highlight-animation': isHighlightAnimation,
}"
draggable
@dragstart="onDragstart"
Expand Down Expand Up @@ -180,6 +181,7 @@ export default {
computed: {
...mapState({
highlightPageId: (state) => state.pages.highlightPageId,
highlightAnimationPageId: (state) => state.pages.highlightAnimationPageId,
isDragoverTargetPage: (state) => state.pages.isDragoverTargetPage,
draggedPageId: (state) => state.pages.draggedPageId,
}),
Expand Down Expand Up @@ -260,6 +262,10 @@ export default {
return this.isPotentialDropTarget
&& this.isDragoverTargetPage
},

isHighlightAnimation() {
return this.highlightAnimationPageId === this.pageId
},
},

mounted() {
Expand Down Expand Up @@ -340,6 +346,8 @@ export default {
</script>

<style lang="scss" scoped>
@import '../../css/animation.scss';

.app-content-list-item {
box-sizing: border-box;
height: 44px;
Expand Down Expand Up @@ -369,6 +377,14 @@ export default {
}
}

&.highlight-animation {
animation: highlight-animation 5s 1;

span.item-icon-badge {
animation: highlight-animation 5s 1;
}
}

&.highlight-target {
// background-color: var(--color-primary-element-light);
border: 1px solid var(--color-border-maxcontrast);
Expand Down
Loading