diff --git a/cypress/e2e/page-list.spec.js b/cypress/e2e/page-list.spec.js
index b51048713..5bff0b0e6 100644
--- a/cypress/e2e/page-list.spec.js
+++ b/cypress/e2e/page-list.spec.js
@@ -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')
+ }
+ })
+ })
})
diff --git a/cypress/e2e/pages.spec.js b/cypress/e2e/pages.spec.js
index 486c9ef07..a252377f7 100644
--- a/cypress/e2e/pages.spec.js
+++ b/cypress/e2e/pages.spec.js
@@ -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')
@@ -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')
diff --git a/lib/Fs/NodeHelper.php b/lib/Fs/NodeHelper.php
index ea3b0590e..64435aea8 100644
--- a/lib/Fs/NodeHelper.php
+++ b/lib/Fs/NodeHelper.php
@@ -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);
}
/**
diff --git a/lib/Service/PageService.php b/lib/Service/PageService.php
index 26468f1bd..077024528 100644
--- a/lib/Service/PageService.php
+++ b/lib/Service/PageService.php
@@ -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 {
diff --git a/lib/Trash/PageTrashBackend.php b/lib/Trash/PageTrashBackend.php
index a2cf6321e..bf8f2a6e0 100644
--- a/lib/Trash/PageTrashBackend.php
+++ b/lib/Trash/PageTrashBackend.php
@@ -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;
@@ -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) {
@@ -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) {
diff --git a/src/components/Collective.vue b/src/components/Collective.vue
index aad82f92f..ccde1857a 100644
--- a/src/components/Collective.vue
+++ b/src/components/Collective.vue
@@ -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'
@@ -50,6 +50,7 @@ export default {
computed: {
...mapGetters([
'currentCollective',
+ 'currentCollectiveCanEdit',
'currentFileIdPage',
'currentPage',
'collectivePage',
@@ -100,6 +101,7 @@ export default {
...mapActions({
dispatchGetPages: GET_PAGES,
+ dispatchGetTrashPages: GET_TRASH_PAGES,
}),
initCollective() {
@@ -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'))
+ }
},
/**
@@ -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() {
diff --git a/src/components/Page/PageActionMenu.vue b/src/components/Page/PageActionMenu.vue
index 247ac8fba..8afe179a3 100644
--- a/src/components/Page/PageActionMenu.vue
+++ b/src/components/Page/PageActionMenu.vue
@@ -62,11 +62,9 @@
-
-
+
{{ deletePageString }}
@@ -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'
@@ -107,7 +104,6 @@ export default {
NcActionLink,
NcActionSeparator,
DeleteIcon,
- DeleteOffIcon,
EmoticonOutlineIcon,
FormatListBulletedIcon,
PagesTemplateIcon,
@@ -173,6 +169,7 @@ export default {
...mapGetters([
'currentCollective',
'currentCollectiveCanEdit',
+ 'hasSubpages',
'loading',
'pagesTreeWalk',
'showing',
@@ -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')
@@ -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
diff --git a/src/components/PageList.vue b/src/components/PageList.vue
index 748a7282b..69e9ea62e 100644
--- a/src/components/PageList.vue
+++ b/src/components/PageList.vue
@@ -105,6 +105,7 @@
class="page-list-drag-item" />
+
@@ -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'
@@ -138,6 +140,7 @@ export default {
Draggable,
Item,
PagesTemplateIcon,
+ PageTrash,
SubpageList,
SortAlphabeticalAscendingIcon,
SortAscendingIcon,
@@ -200,6 +203,12 @@ export default {
disableSorting() {
return this.filterString !== ''
},
+
+ displayTrash() {
+ return this.currentCollectiveCanEdit
+ && ('files_trashbin' in this.OC.appswebroots)
+ && !this.loading('collectives')
+ },
},
methods: {
diff --git a/src/components/PageList/Item.vue b/src/components/PageList/Item.vue
index 5fe56e078..f3c72d6cf 100644
--- a/src/components/PageList/Item.vue
+++ b/src/components/PageList/Item.vue
@@ -9,6 +9,7 @@
highlight: isHighlighted,
'dragged-over-target': isDraggedOverTarget,
'highlight-target': isHighlightedTarget,
+ 'highlight-animation': isHighlightAnimation,
}"
draggable
@dragstart="onDragstart"
@@ -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,
}),
@@ -260,6 +262,10 @@ export default {
return this.isPotentialDropTarget
&& this.isDragoverTargetPage
},
+
+ isHighlightAnimation() {
+ return this.highlightAnimationPageId === this.pageId
+ },
},
mounted() {
@@ -340,6 +346,8 @@ export default {
diff --git a/src/components/PageSidebar/SidebarTabAttachments.vue b/src/components/PageSidebar/SidebarTabAttachments.vue
index 19005e0ae..e421aff8f 100644
--- a/src/components/PageSidebar/SidebarTabAttachments.vue
+++ b/src/components/PageSidebar/SidebarTabAttachments.vue
@@ -436,15 +436,11 @@ export default {
}
-
diff --git a/src/css/animation.scss b/src/css/animation.scss
new file mode 100644
index 000000000..b11c87493
--- /dev/null
+++ b/src/css/animation.scss
@@ -0,0 +1,5 @@
+@keyframes highlight-animation {
+ 0% { background-color: var(--color-background-hover); }
+ 50% { background-color: var(--color-background-hover); }
+ 100% { background-color: rgba(var(--color-background-hover), 0); }
+}
diff --git a/src/mixins/pageMixin.js b/src/mixins/pageMixin.js
index 47e572caa..d9e245a8e 100644
--- a/src/mixins/pageMixin.js
+++ b/src/mixins/pageMixin.js
@@ -1,7 +1,8 @@
import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'
import { showError, showSuccess } from '@nextcloud/dialogs'
+import { emit } from '@nextcloud/event-bus'
import {
- DELETE_PAGE,
+ TRASH_PAGE,
GET_PAGES,
MOVE_PAGE,
NEW_PAGE,
@@ -45,7 +46,7 @@ export default {
dispatchSetPageEmoji: SET_PAGE_EMOJI,
dispatchSetPageSubpageOrder: SET_PAGE_SUBPAGE_ORDER,
dispatchMovePage: MOVE_PAGE,
- dispatchDeletePage: DELETE_PAGE,
+ dispatchTrashPage: TRASH_PAGE,
}),
/**
@@ -170,7 +171,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'))
@@ -182,9 +183,7 @@ export default {
this.$router.push(`/${encodeURIComponent(this.currentCollective.name)}`)
}
- // Delete pageId from parent page subpageOrder
- this.subpageOrderDelete(parentId, pageId)
-
+ emit('collectives:page-list:page-trashed')
showSuccess(t('collectives', 'Page deleted'))
},
diff --git a/src/store/actions.js b/src/store/actions.js
index 8dba4c2b3..205742218 100644
--- a/src/store/actions.js
+++ b/src/store/actions.js
@@ -18,7 +18,9 @@ export const UPDATE_COLLECTIVE_PAGE_MODE = 'UPDATE_COLLECTIVE_PAGE_MODE'
export const SET_COLLECTIVE_USER_SETTING_PAGE_ORDER = 'SET_COLLECTIVE_USER_SETTING_PAGE_ORDER'
export const MARK_COLLECTIVE_DELETED = 'MARK_COLLECTIVE_DELETED'
export const UNMARK_COLLECTIVE_DELETED = 'UNMARK_COLLECTIVE_DELETED'
+export const EXPAND_PARENTS = 'EXPAND_PARENTS'
export const GET_PAGES = 'GET_PAGES'
+export const GET_TRASH_PAGES = 'GET_TRASH_PAGES'
export const GET_PAGE = 'GET_PAGE'
export const NEW_PAGE = 'NEW_PAGE'
export const NEW_TEMPLATE = 'NEW_TEMPLATE'
@@ -27,6 +29,8 @@ export const RENAME_PAGE = 'RENAME_PAGE'
export const MOVE_PAGE = 'MOVE_PAGE'
export const SET_PAGE_EMOJI = 'SET_PAGE_EMOJI'
export const SET_PAGE_SUBPAGE_ORDER = 'SET_PAGE_SUBPAGE_ORDER'
+export const TRASH_PAGE = 'TRASH_PAGE'
+export const RESTORE_PAGE = 'RESTORE_PAGE'
export const DELETE_PAGE = 'DELETE_PAGE'
export const GET_ATTACHMENTS = 'GET_ATTACHMENTS'
export const GET_BACKLINKS = 'GET_BACKLINKS'
diff --git a/src/store/circles.js b/src/store/circles.js
index d89c2c67b..541a65abf 100644
--- a/src/store/circles.js
+++ b/src/store/circles.js
@@ -1,6 +1,6 @@
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
-import { GET_CIRCLES, RENAME_CIRCLE, ADD_MEMBERS_TO_CIRCLE, LEAVE_CIRCLE, GET_PAGES } from './actions.js'
+import { GET_CIRCLES, RENAME_CIRCLE, ADD_MEMBERS_TO_CIRCLE, LEAVE_CIRCLE, GET_PAGES, GET_TRASH_PAGES } from './actions.js'
import {
SET_CIRCLES,
UPDATE_CIRCLE,
@@ -81,6 +81,7 @@ export default {
if (collective.id === getters.currentCollective?.id) {
// Update page list, properties like `collectivePath` might have changed
await dispatch(GET_PAGES)
+ await dispatch(GET_TRASH_PAGES)
}
commit(PATCH_COLLECTIVE_WITH_CIRCLE, response.data.ocs.data)
},
diff --git a/src/store/mutations.js b/src/store/mutations.js
index 41fc2880e..c5e150813 100644
--- a/src/store/mutations.js
+++ b/src/store/mutations.js
@@ -11,9 +11,12 @@ export const RESTORE_COLLECTIVE_FROM_TRASH = 'RESTORE_COLLECTIVE_FROM_TRASH'
export const DELETE_COLLECTIVE_FROM_TRASH = 'DELETE_COLLECTIVE_FROM_TRASH'
export const REMOVE_COLLECTIVE = 'REMOVE_COLLECTICE'
export const SET_PAGES = 'SET_PAGES'
+export const SET_TRASH_PAGES = 'SET_TRASH_PAGES'
export const UPDATE_PAGE = 'UPDATE_PAGE'
export const ADD_PAGE = 'ADD_PAGE'
-export const DELETE_PAGE_BY_ID = 'DELETE_PAGE_BY_ID'
+export const MOVE_PAGE_INTO_TRASH = 'MOVE_PAGE_INTO_TRASH'
+export const RESTORE_PAGE_FROM_TRASH = 'RESTORE_PAGE_FROM_TRASH'
+export const DELETE_PAGE_FROM_TRASH_BY_ID = 'DELETE_PAGE_FROM_TRASH_BY_ID'
export const SELECT_VERSION = 'SELECT_VERSION'
export const SET_VERSIONS = 'SET_VERSIONS'
export const SET_ATTACHMENTS = 'SET_ATTACHMENTS'
diff --git a/src/store/pages.js b/src/store/pages.js
index 3446d0d99..f37b36cea 100644
--- a/src/store/pages.js
+++ b/src/store/pages.js
@@ -7,9 +7,12 @@ import * as sortOrders from '../util/sortOrders.js'
import {
SET_PAGES,
+ SET_TRASH_PAGES,
ADD_PAGE,
UPDATE_PAGE,
- DELETE_PAGE_BY_ID,
+ MOVE_PAGE_INTO_TRASH,
+ RESTORE_PAGE_FROM_TRASH,
+ DELETE_PAGE_FROM_TRASH_BY_ID,
SET_ATTACHMENTS,
SET_ATTACHMENT_DELETED,
SET_ATTACHMENT_UNDELETED,
@@ -19,7 +22,9 @@ import {
} from './mutations.js'
import {
+ EXPAND_PARENTS,
GET_PAGES,
+ GET_TRASH_PAGES,
GET_PAGE,
NEW_PAGE,
NEW_TEMPLATE,
@@ -28,6 +33,8 @@ import {
MOVE_PAGE,
SET_PAGE_EMOJI,
SET_PAGE_SUBPAGE_ORDER,
+ TRASH_PAGE,
+ RESTORE_PAGE,
DELETE_PAGE,
GET_ATTACHMENTS,
GET_BACKLINKS,
@@ -38,6 +45,7 @@ export const TEMPLATE_PAGE = 'Template'
export default {
state: {
pages: [],
+ trashPages: [],
newPage: undefined,
sortBy: undefined,
collapsed: {},
@@ -46,6 +54,7 @@ export default {
deletedAttachments: [],
backlinks: [],
highlightPageId: null,
+ highlightAnimationPageId: null,
isDragoverTargetPage: false,
draggedPageId: null,
},
@@ -167,6 +176,12 @@ export default {
return state.pages.find(p => (p.id === fileId))
},
+ hasSubpages(state, _getters) {
+ return (pageId) => {
+ return state.pages.filter(p => p.parentId === pageId).length > 0
+ }
+ },
+
sortedSubpages(state, getters) {
return (parentId, sortOrder) => {
const parentPage = state.pages.find(p => p.id === parentId)
@@ -274,6 +289,14 @@ export default {
return (parentId, pageId) => `${getters.pageUrl(parentId, pageId)}/backlinks`
},
+ trashIndexUrl(_state, getters) {
+ return `${getters.pagesUrl}/trash`
+ },
+
+ trashActionUrl(_state, getters) {
+ return (pageId) => `${getters.pagesUrl}/trash/${pageId}`
+ },
+
pageTitle(state, getters) {
return pageId => {
const page = state.pages.find(p => p.id === pageId)
@@ -293,6 +316,10 @@ export default {
keptSortable(state) {
return (pageId) => state.pages.find(p => p.id === pageId)?.keepSortable
},
+
+ trashPages(state) {
+ return state.trashPages.sort((a, b) => b.trashTimestamp - a.trashTimestamp)
+ },
},
mutations: {
@@ -300,6 +327,10 @@ export default {
state.pages = pages
},
+ [SET_TRASH_PAGES](state, trashPages) {
+ state.trashPages = trashPages
+ },
+
[UPDATE_PAGE](state, page) {
state.pages.splice(
state.pages.findIndex(p => p.id === page.id),
@@ -313,8 +344,22 @@ export default {
state.newPage = page
},
- [DELETE_PAGE_BY_ID](state, id) {
- state.pages.splice(state.pages.findIndex(p => p.id === id), 1)
+ [MOVE_PAGE_INTO_TRASH](state, page) {
+ const trashPage = { ...page }
+ state.pages.splice(state.pages.findIndex(p => p.id === page.id), 1)
+ trashPage.trashTimestamp = Date.now() / 1000
+ state.trashPages.unshift(trashPage)
+ },
+
+ [RESTORE_PAGE_FROM_TRASH](state, trashPage) {
+ const page = { ...trashPage }
+ page.trashTimestamp = null
+ state.pages.unshift(page)
+ state.trashPages.splice(state.trashPages.findIndex(p => p.id === trashPage.id), 1)
+ },
+
+ [DELETE_PAGE_FROM_TRASH_BY_ID](state, id) {
+ state.trashPages.splice(state.trashPages.findIndex(p => p.id === id), 1)
},
[SET_ATTACHMENTS](state, { attachments }) {
@@ -394,6 +439,10 @@ export default {
state.highlightPageId = pageId
},
+ setHighlightAnimationPageId(state, pageId) {
+ state.highlightAnimationPageId = pageId
+ },
+
setDragoverTargetPage(state, bool) {
state.isDragoverTargetPage = bool
},
@@ -404,6 +453,21 @@ export default {
},
actions: {
+ /**
+ * Expand all parents of a page
+ * Needs to be an action to have access to the getter `pageParents`
+ *
+ * @param {object} store the vuex store
+ * @param {Function} store.commit commit changes
+ * @param {object} store.getters getters of the store
+ * @param {number} pageId Page ID
+ */
+ [EXPAND_PARENTS]({ commit, getters }, pageId) {
+ for (const page of getters.pageParents(pageId)) {
+ commit('expand', page.id)
+ }
+ },
+
/**
* Get list of all pages
*
@@ -421,6 +485,20 @@ export default {
commit('done', 'collective')
},
+ /**
+ * Get list of all pages in trash
+ *
+ * @param {object} store the vuex store
+ * @param {Function} store.commit commit changes
+ * @param {object} store.getters getters of the store
+ */
+ async [GET_TRASH_PAGES]({ commit, getters }) {
+ commit('load', 'pageTrash')
+ const response = await axios.get(getters.trashIndexUrl)
+ commit(SET_TRASH_PAGES, response.data.data)
+ commit('done', 'pageTrash')
+ },
+
/**
* Get a single page and update it in the store
*
@@ -605,8 +683,7 @@ export default {
},
/**
- *
- * Delete the current page
+ * Trash the page with the given id
*
* @param {object} store the vuex store
* @param {Function} store.commit commit changes
@@ -615,13 +692,41 @@ export default {
* @param {number} page.parentId ID of the parent page
* @param {number} page.pageId ID of the page
*/
- async [DELETE_PAGE]({ commit, getters }, { parentId, pageId }) {
+ async [TRASH_PAGE]({ commit, getters }, { parentId, pageId }) {
commit('load', 'page')
- await axios.delete(getters.pageUrl(parentId, pageId))
- commit(DELETE_PAGE_BY_ID, pageId)
+ const response = await axios.delete(getters.pageUrl(parentId, pageId))
+ commit(MOVE_PAGE_INTO_TRASH, response.data.data)
commit('done', 'page')
},
+ /**
+ * Restore the page with the given id from trash
+ *
+ * @param {object} store the vuex store
+ * @param {Function} store.commit commit changes
+ * @param {object} store.getters getters of the store
+ * @param {object} page the page
+ * @param {number} page.pageId ID of the page to restore
+ */
+ async [RESTORE_PAGE]({ commit, getters }, { pageId }) {
+ const response = await axios.patch(getters.trashActionUrl(pageId))
+ commit(RESTORE_PAGE_FROM_TRASH, response.data.data)
+ },
+
+ /**
+ * Delete the page with the given id from trash
+ *
+ * @param {object} store the vuex store
+ * @param {Function} store.commit commit changes
+ * @param {object} store.getters getters of the store
+ * @param {object} page the page
+ * @param {number} page.pageId ID of the page to delete
+ */
+ async [DELETE_PAGE]({ commit, getters }, { pageId }) {
+ axios.delete(getters.trashActionUrl(pageId))
+ commit(DELETE_PAGE_FROM_TRASH_BY_ID, pageId)
+ },
+
/**
* Get list of attachments for a page
*
diff --git a/tests/Integration/features/pages.feature b/tests/Integration/features/pages.feature
index 755341ad3..3f6fd64e6 100644
--- a/tests/Integration/features/pages.feature
+++ b/tests/Integration/features/pages.feature
@@ -46,10 +46,6 @@ Feature: pages
And user "jane" sets subpageOrder for page "firstpage" to "[1,2]" with parentPath "Readme.md" in "BehatPagesCollective"
And user "jane" fails to set subpageOrder for page "firstpage" to "[invalid]" with parentPath "Readme.md" in "BehatPagesCollective"
- Scenario: Fail to trash a page with subpages
- When user "jane" fails to trash page "firstpage" with parentPath "Readme.md" in "BehatPagesCollective"
- Then user "jane" sees pagePath "firstpage/Readme.md" in "BehatPagesCollective"
-
Scenario: Rename parent page
When user "jane" renames page "firstpage" to "parentpage" with parentPath "Readme.md" in "BehatPagesCollective"
Then user "jane" sees pagePath "parentpage/Readme.md" in "BehatPagesCollective"
@@ -78,6 +74,11 @@ Feature: pages
When user "jane" restores page "subpage" from trash in "BehatPagesCollective"
Then user "jane" sees pagePath "parentpage/subpage.md" in "BehatPagesCollective"
+ Scenario: Trash and restore a page with subpages
+ When user "jane" trashes page "parentpage" with parentPath "Readme.md" in "BehatPagesCollective"
+ And user "jane" doesn't see pagePath "parentpage/Readme.md" in "BehatPagesCollective"
+ Then user "jane" restores page "parentpage" from trash in "BehatPagesCollective"
+
Scenario: Trash and delete all subpages reverts subfolders
When user "jane" trashes page "subpage" with parentPath "parentpage/Readme.md" in "BehatPagesCollective"
And user "jane" trashes page "subpage2" with parentPath "parentpage/Readme.md" in "BehatPagesCollective"