Merge pull request #45658 from nextcloud/backport/44897/stable28

[stable28] fix(files): Close sidebar and update fileid when current node is deleted
This commit is contained in:
Ferdinand Thiessen 2024-06-10 19:01:09 +02:00 committed by GitHub
commit 9286eb32e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 172 additions and 16 deletions

View file

@ -140,14 +140,15 @@ export const action = new FileAction({
.every(permission => (permission & Permission.DELETE) !== 0)
},
async exec(node: Node) {
async exec(node: Node, view: View, dir: string) {
try {
await axios.delete(node.encodedSource)
// Let's delete even if it's moved to the trashbin
// since it has been removed from the current view
// and changing the view will trigger a reload anyway.
// and changing the view will trigger a reload anyway.
emit('files:node:deleted', node)
return true
} catch (error) {
logger.error('Error while deleting a file', { error, source: node.source, node })

View file

@ -29,7 +29,7 @@ import logger from '../logger'
import { useFilesStore } from './files'
export const usePathsStore = function(...args) {
const files = useFilesStore()
const files = useFilesStore(...args)
const store = defineStore('paths', {
state: () => ({

View file

@ -240,6 +240,14 @@ export default defineComponent({
return (this.$route?.query?.dir?.toString() || '/').replace(/^(.+)\/$/, '$1')
},
/**
* The current file id
*/
fileId(): number | null {
const number = Number.parseInt(this.$route?.params.fileid ?? '')
return Number.isNaN(number) ? null : number
},
/**
* The current folder.
*/
@ -453,6 +461,8 @@ export default defineComponent({
mounted() {
this.fetchContent()
subscribe('files:node:deleted', this.onNodeDeleted)
subscribe('files:node:updated', this.onUpdatedNode)
subscribe('nextcloud:unified-search.search', this.onSearch)
subscribe('nextcloud:unified-search.reset', this.onSearch)
@ -462,6 +472,7 @@ export default defineComponent({
},
unmounted() {
unsubscribe('files:node:deleted', this.onNodeDeleted)
unsubscribe('files:node:updated', this.onUpdatedNode)
unsubscribe('nextcloud:unified-search.search', this.onSearch)
unsubscribe('nextcloud:unified-search.reset', this.onSearch)
@ -535,6 +546,31 @@ export default defineComponent({
return this.filesStore.getNode(fileId)
},
/**
* Handle the node deleted event to reset open file
* @param node The deleted node
*/
onNodeDeleted(node: Node) {
if (node.fileid && node.fileid === this.fileId) {
if (node.fileid === this.currentFolder?.fileid) {
// Handle the edge case that the current directory is deleted
// in this case we neeed to keept the current view but move to the parent directory
window.OCP.Files.Router.goToRoute(
null,
{ view: this.$route.params.view },
{ dir: this.currentFolder?.dirname ?? '/' },
)
} else {
// If the currently active file is deleted we need to remove the fileid and possible the `openfile` query
window.OCP.Files.Router.goToRoute(
null,
{ ...this.$route.params, fileid: undefined },
{ ...this.$route.query, openfile: undefined },
)
}
}
},
/**
* The upload manager have finished handling the queue
* @param {Upload} upload the uploaded data

View file

@ -23,6 +23,7 @@
<template>
<NcAppSidebar v-if="file"
ref="sidebar"
data-cy-sidebar
v-bind="appSidebar"
:force-menu="true"
@close="close"
@ -105,7 +106,7 @@
import { getCurrentUser } from '@nextcloud/auth'
import { getCapabilities } from '@nextcloud/capabilities'
import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { File, Folder, formatFileSize } from '@nextcloud/files'
import { encodePath } from '@nextcloud/paths'
import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
@ -304,10 +305,13 @@ export default {
},
},
created() {
subscribe('files:node:deleted', this.onNodeDeleted)
window.addEventListener('resize', this.handleWindowResize)
this.handleWindowResize()
},
beforeDestroy() {
unsubscribe('file:node:deleted', this.onNodeDeleted)
window.removeEventListener('resize', this.handleWindowResize)
},
@ -507,6 +511,16 @@ export default {
this.resetData()
},
/**
* Handle if the current node was deleted
* @param {import('@nextcloud/files').Node} node The deleted node
*/
onNodeDeleted(node) {
if (this.fileInfo && node && this.fileInfo.id === node.fileid) {
this.close()
}
},
/**
* Allow to set the Sidebar as fullscreen from OCA.Files.Sidebar
*

View file

@ -28,7 +28,9 @@ export const getActionButtonForFile = (filename: string) => getActionsForFile(fi
export const triggerActionForFile = (filename: string, actionId: string) => {
getActionButtonForFile(filename).click({ force: true })
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('be.visible').click({ force: true })
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist')
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).scrollIntoView()
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).click({ force: true })
}
export const moveFile = (fileName: string, dirPath: string) => {

View file

@ -0,0 +1,98 @@
/**
* @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de>
*
* @author Ferdinand Thiessen <opensource@fthiessen.de>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { User } from '@nextcloud/cypress'
import { getRowForFile, navigateToFolder, triggerActionForFile } from './FilesUtils'
describe('Files: Sidebar', { testIsolation: true }, () => {
let user: User
let fileId: number = 0
beforeEach(() => cy.createRandomUser().then(($user) => {
user = $user
cy.mkdir(user, '/folder')
cy.uploadContent(user, new Blob([]), 'text/plain', '/file').then((response) => {
fileId = Number.parseInt(response.headers['oc-fileid'] ?? '0')
})
cy.login(user)
}))
it('opens the sidebar', () => {
cy.visit('/apps/files')
getRowForFile('file').should('be.visible')
triggerActionForFile('file', 'details')
cy.get('[data-cy-sidebar]').should('be.visible')
})
it('changes the current fileid', () => {
cy.visit('/apps/files')
getRowForFile('file').should('be.visible')
triggerActionForFile('file', 'details')
cy.get('[data-cy-sidebar]').should('be.visible')
cy.url().should('contain', `apps/files/files/${fileId}`)
})
it('closes the sidebar on delete', () => {
cy.visit('/apps/files')
getRowForFile('file').should('be.visible')
// open the sidebar
triggerActionForFile('file', 'details')
// validate it is open
cy.get('[data-cy-sidebar]').should('be.visible')
// wait for the sidebar to be settled
cy.wait(500)
triggerActionForFile('file', 'delete')
cy.get('[data-cy-sidebar]').should('not.exist')
})
it('changes the fileid on delete', () => {
cy.uploadContent(user, new Blob([]), 'text/plain', '/folder/other').then((response) => {
const otherFileId = Number.parseInt(response.headers['oc-fileid'] ?? '0')
cy.login(user)
cy.visit('/apps/files')
getRowForFile('folder').should('be.visible')
navigateToFolder('folder')
getRowForFile('other').should('be.visible')
// open the sidebar
triggerActionForFile('other', 'details')
// validate it is open
cy.get('[data-cy-sidebar]').should('be.visible')
cy.url().should('contain', `apps/files/files/${otherFileId}`)
// wait for the sidebar to be settled
cy.wait(500)
triggerActionForFile('other', 'delete')
cy.get('[data-cy-sidebar]').should('not.exist')
// Ensure the URL is changed
cy.url().should('not.contain', `apps/files/files/${otherFileId}`)
})
})
})

View file

@ -19,7 +19,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* eslint-disable n/no-unpublished-import */
// eslint-disable-next-line n/no-extraneous-import
import type { AxiosResponse } from 'axios'
import axios from '@nextcloud/axios'
import { addCommands, User } from '@nextcloud/cypress'
import { basename } from 'path'
@ -33,10 +35,12 @@ addCommands()
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
interface Chainable<Subject = any> {
/**
* Enable or disable a given user
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
enableUser(user: User, enable?: boolean): Cypress.Chainable<Cypress.Response<any>>,
/**
@ -49,7 +53,7 @@ declare global {
* Upload a raw content to a given user storage.
* **Warning**: Using this function will reset the previous session
*/
uploadContent(user: User, content: Blob, mimeType: string, target: string, mtime?: number): Cypress.Chainable<void>,
uploadContent(user: User, content: Blob, mimeType: string, target: string, mtime?: number): Cypress.Chainable<AxiosResponse>,
/**
* Create a new directory
@ -223,6 +227,7 @@ Cypress.Commands.add('uploadContent', (user, blob, mimeType, target, mtime = und
},
})
cy.log(`Uploaded content as ${fileName}`, response)
return response
} catch (error) {
cy.log('error', error)
throw new Error('Unable to process fixture')

4
dist/files-init.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/files-main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long