From ca82ef2951ca3d0e0bae717ee3deddf7c7111aae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 9 Jun 2023 02:22:14 +0000 Subject: [PATCH 01/13] Update dependency @nextcloud/vue to ^7.12.0 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63ab482fb..69e020b50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@nextcloud/paths": "^2.1.0", "@nextcloud/router": "^2.1.2", "@nextcloud/text": "^27.0.0-alpha.1", - "@nextcloud/vue": "^7.11.6", + "@nextcloud/vue": "^7.12.0", "debounce": "^1.2.1", "escape-html": "^1.0.3", "sortablejs": "^1.15.0", @@ -3542,9 +3542,9 @@ } }, "node_modules/@nextcloud/vue": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-7.11.6.tgz", - "integrity": "sha512-HqstdUdQYHMFx/xD36OElbE0DvXmGSnPI9/stvRDlTYV+aG2XNQPn57k5cXjsQe5LAFv0SXwXVTzKA6q5+wuoA==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-7.12.0.tgz", + "integrity": "sha512-f7x3YFBzc/mt27F7AU+ITLmGCwRpVM0aVTF+DxjaOdelQNTYZBuFJCCOk6nC+x+gg/KWLIxeWm/NWDxToCstbQ==", "dependencies": { "@floating-ui/dom": "^1.1.0", "@nextcloud/auth": "^2.0.0", diff --git a/package.json b/package.json index cb63a41f2..b6d1ae20c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@nextcloud/paths": "^2.1.0", "@nextcloud/router": "^2.1.2", "@nextcloud/text": "^27.0.0-alpha.1", - "@nextcloud/vue": "^7.11.6", + "@nextcloud/vue": "^7.12.0", "debounce": "^1.2.1", "escape-html": "^1.0.3", "sortablejs": "^1.15.0", From c9f777598d548574f86610a32d27fb000036a325 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 27 May 2023 17:30:08 +0100 Subject: [PATCH 02/13] Add page trash user interface Fixes: #47 Signed-off-by: Jonas --- src/components/Collective.vue | 7 +- src/components/PageList.vue | 9 + src/components/PageList/PageTrash.vue | 290 ++++++++++++++++++++++++++ src/mixins/pageMixin.js | 6 +- src/store/actions.js | 3 + src/store/circles.js | 3 +- src/store/mutations.js | 5 +- src/store/pages.js | 94 ++++++++- 8 files changed, 403 insertions(+), 14 deletions(-) create mode 100644 src/components/PageList/PageTrash.vue diff --git a/src/components/Collective.vue b/src/components/Collective.vue index aad82f92f..9c1a1c0cd 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' @@ -100,6 +100,7 @@ export default { ...mapActions({ dispatchGetPages: GET_PAGES, + dispatchGetTrashPages: GET_TRASH_PAGES, }), initCollective() { @@ -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')) }, /** @@ -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() { 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/PageTrash.vue b/src/components/PageList/PageTrash.vue new file mode 100644 index 000000000..467884c67 --- /dev/null +++ b/src/components/PageList/PageTrash.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/src/mixins/pageMixin.js b/src/mixins/pageMixin.js index 47e572caa..8c4908a96 100644 --- a/src/mixins/pageMixin.js +++ b/src/mixins/pageMixin.js @@ -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, @@ -45,7 +45,7 @@ export default { dispatchSetPageEmoji: SET_PAGE_EMOJI, dispatchSetPageSubpageOrder: SET_PAGE_SUBPAGE_ORDER, dispatchMovePage: MOVE_PAGE, - dispatchDeletePage: DELETE_PAGE, + dispatchTrashPage: TRASH_PAGE, }), /** @@ -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')) diff --git a/src/store/actions.js b/src/store/actions.js index 8dba4c2b3..f5fc5f0ac 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -19,6 +19,7 @@ export const SET_COLLECTIVE_USER_SETTING_PAGE_ORDER = 'SET_COLLECTIVE_USER_SETTI export const MARK_COLLECTIVE_DELETED = 'MARK_COLLECTIVE_DELETED' export const UNMARK_COLLECTIVE_DELETED = 'UNMARK_COLLECTIVE_DELETED' 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 +28,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..b75a244e7 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, @@ -20,6 +23,7 @@ import { import { GET_PAGES, + GET_TRASH_PAGES, GET_PAGE, NEW_PAGE, NEW_TEMPLATE, @@ -28,6 +32,8 @@ import { MOVE_PAGE, SET_PAGE_EMOJI, SET_PAGE_SUBPAGE_ORDER, + TRASH_PAGE, + RESTORE_PAGE, DELETE_PAGE, GET_ATTACHMENTS, GET_BACKLINKS, @@ -38,6 +44,7 @@ export const TEMPLATE_PAGE = 'Template' export default { state: { pages: [], + trashPages: [], newPage: undefined, sortBy: undefined, collapsed: {}, @@ -274,6 +281,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 +308,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 +319,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 +336,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() + 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 }) { @@ -421,6 +458,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 +656,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 +665,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 * From cdea1717d56474f94ea1662c6e23fb6c7c59e181 Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 30 May 2023 11:52:06 +0100 Subject: [PATCH 03/13] Allow to trash pages with subpages Signed-off-by: Jonas --- cypress/e2e/page-list.spec.js | 47 ++++++++++++++++++++++++ lib/Service/PageService.php | 5 --- src/components/Page/PageActionMenu.vue | 18 ++------- tests/Integration/features/pages.feature | 9 +++-- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/cypress/e2e/page-list.spec.js b/cypress/e2e/page-list.spec.js index b51048713..0557e802e 100644 --- a/cypress/e2e/page-list.spec.js +++ b/cypress/e2e/page-list.spec.js @@ -146,4 +146,51 @@ 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') + + // 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') + .click() + cy.get('.page-list .app-content-list-item') + .should('not.contain', 'Day 1') + + // Restore page + cy.get('#page-trash') + .click() + cy.contains('#page-trash__content .app-content-list-item', 'Day 1') + .find('.action-item__menutoggle') + .click({ force: true }) + cy.get('button.action-button') + .contains('Restore') + .click() + cy.get('#page-trash') + .click() + cy.get('#page-trash__content .app-content-list-item') + .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/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/src/components/Page/PageActionMenu.vue b/src/components/Page/PageActionMenu.vue index 247ac8fba..f52f95038 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, @@ -201,21 +197,15 @@ export default { }, deletePageString() { - return this.hasSubpages - ? t('collectives', 'Cannot delete page with subpages') - : this.isTemplate - ? t('collectives', 'Delete template') - : t('collectives', 'Delete page') + return this.isTemplate + ? t('collectives', 'Delete template') + : t('collectives', 'Delete page') }, hasTemplate() { 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/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" From 8f6f8959360f56ce13106db02a2a2a840235f46b Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 31 May 2023 09:15:12 +0100 Subject: [PATCH 04/13] Don't remove page from subpage order of parent when trashed We want the subpage order to stay consistent after restore Signed-off-by: Jonas --- src/mixins/pageMixin.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mixins/pageMixin.js b/src/mixins/pageMixin.js index 8c4908a96..706f48fe5 100644 --- a/src/mixins/pageMixin.js +++ b/src/mixins/pageMixin.js @@ -182,9 +182,6 @@ export default { this.$router.push(`/${encodeURIComponent(this.currentCollective.name)}`) } - // Delete pageId from parent page subpageOrder - this.subpageOrderDelete(parentId, pageId) - showSuccess(t('collectives', 'Page deleted')) }, From 2b700e557cd50b2a56dbc8b4c74c35f966ecc1f7 Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 31 May 2023 10:57:54 +0100 Subject: [PATCH 05/13] Several visual UX improvements for the page trashbin * Highlight trash button when trashing a page * Expand path to restored page in page list * Highlight and scroll into view restored page in page list * Don't remove subpage order of parent when trashing a page Signed-off-by: Jonas --- src/components/PageList/Item.vue | 20 +++++++++ src/components/PageList/PageTrash.vue | 60 ++++++++++++++++++++++++--- src/mixins/pageMixin.js | 3 +- src/store/actions.js | 1 + src/store/pages.js | 21 ++++++++++ 5 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/components/PageList/Item.vue b/src/components/PageList/Item.vue index 5fe56e078..8d73ce51e 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() { @@ -369,6 +375,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); @@ -449,4 +463,10 @@ export default { right: 0; margin: 0; } + +@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/components/PageList/PageTrash.vue b/src/components/PageList/PageTrash.vue index 467884c67..40b09e97d 100644 --- a/src/components/PageList/PageTrash.vue +++ b/src/components/PageList/PageTrash.vue @@ -2,7 +2,10 @@
- + @@ -54,14 +57,17 @@ diff --git a/src/components/PageList/PageTrash.vue b/src/components/PageList/PageTrash.vue index d1b36d25b..addf0b72b 100644 --- a/src/components/PageList/PageTrash.vue +++ b/src/components/PageList/PageTrash.vue @@ -197,6 +197,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); } +} From 2cfa45ed3e0597e53f694794cd16d9c993d1e9fd Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 27 Jun 2023 17:54:01 +0200 Subject: [PATCH 13/13] Improve highlight logic for page trash button * Clear timeout before setting a new one * Remove highlight class and add it again for re-highlighting * Clear timeout on component unmount to prevent memory leaks Signed-off-by: Jonas --- src/components/PageList/PageTrash.vue | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/PageList/PageTrash.vue b/src/components/PageList/PageTrash.vue index addf0b72b..fe42ae5b0 100644 --- a/src/components/PageList/PageTrash.vue +++ b/src/components/PageList/PageTrash.vue @@ -1,6 +1,6 @@