mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 00:32:29 -04:00
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:
commit
9286eb32e4
13 changed files with 172 additions and 16 deletions
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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: () => ({
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
98
cypress/e2e/files/files-sidebar.cy.ts
Normal file
98
cypress/e2e/files/files-sidebar.cy.ts
Normal 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}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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
4
dist/files-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-init.js.map
vendored
2
dist/files-init.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-main.js
vendored
4
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-sidebar.js
vendored
4
dist/files-sidebar.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-sidebar.js.map
vendored
2
dist/files-sidebar.js.map
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue