diff --git a/apps/files/src/store/files.ts b/apps/files/src/store/files.ts index f18bee42550..08b5d0757d3 100644 --- a/apps/files/src/store/files.ts +++ b/apps/files/src/store/files.ts @@ -149,6 +149,21 @@ export const useFilesStore = function(...args) { // Otherwise, it means we receive an event for a node that is not in the store fetchNode(node).then(n => this.updateNodes([n])) }, + + // Handlers for legacy sidebar (no real nodes support) + onAddFavorite(node: Node) { + const ourNode = this.getNode(node.source) + if (ourNode) { + Vue.set(ourNode.attributes, 'favorite', 1) + } + }, + + onRemoveFavorite(node: Node) { + const ourNode = this.getNode(node.source) + if (ourNode) { + Vue.set(ourNode.attributes, 'favorite', 0) + } + }, }, }) @@ -159,6 +174,9 @@ export const useFilesStore = function(...args) { subscribe('files:node:deleted', fileStore.onDeletedNode) subscribe('files:node:updated', fileStore.onUpdatedNode) subscribe('files:node:moved', fileStore.onMovedNode) + // legacy sidebar + subscribe('files:favorites:added', fileStore.onAddFavorite) + subscribe('files:favorites:removed', fileStore.onRemoveFavorite) fileStore._initialized = true } diff --git a/apps/files/src/views/Sidebar.vue b/apps/files/src/views/Sidebar.vue index 196d64990a6..5418d36297b 100644 --- a/apps/files/src/views/Sidebar.vue +++ b/apps/files/src/views/Sidebar.vue @@ -404,10 +404,10 @@ export default { }, /** - * Toggle favourite state + * Toggle favorite state * TODO: better implementation * - * @param {boolean} state favourited or not + * @param {boolean} state is favorite or not */ async toggleStarred(state) { try { @@ -430,17 +430,21 @@ export default { */ const isDir = this.fileInfo.type === 'dir' const Node = isDir ? Folder : File - emit(state ? 'files:favorites:added' : 'files:favorites:removed', new Node({ + const node = new Node({ fileid: this.fileInfo.id, - source: this.davPath, - root: `/files/${getCurrentUser().uid}`, + source: `${davRemoteURL}${davRootPath}${this.file}`, + root: davRootPath, mime: isDir ? undefined : this.fileInfo.mimetype, - })) + attributes: { + favorite: 1, + }, + }) + emit(state ? 'files:favorites:added' : 'files:favorites:removed', node) this.fileInfo.isFavourited = state } catch (error) { - showError(t('files', 'Unable to change the favourite state of the file')) - logger.error('Unable to change favourite state', { error }) + showError(t('files', 'Unable to change the favorite state of the file')) + logger.error('Unable to change favorite state', { error }) } }, diff --git a/apps/files/src/views/favorites.ts b/apps/files/src/views/favorites.ts index 8d561b4f5b8..cadc7704e14 100644 --- a/apps/files/src/views/favorites.ts +++ b/apps/files/src/views/favorites.ts @@ -65,7 +65,7 @@ export const registerFavoritesView = async () => { favoriteFoldersViews.forEach(view => Navigation.register(view)) /** - * Update favourites navigation when a new folder is added + * Update favorites navigation when a new folder is added */ subscribe('files:favorites:added', (node: Node) => { if (node.type !== FileType.Folder) { @@ -99,7 +99,7 @@ export const registerFavoritesView = async () => { }) /** - * Update favourites navigation when a folder is renamed + * Update favorites navigation when a folder is renamed */ subscribe('files:node:renamed', (node: Node) => { if (node.type !== FileType.Folder) { diff --git a/cypress/e2e/files/favorites.cy.ts b/cypress/e2e/files/favorites.cy.ts new file mode 100644 index 00000000000..d873e6963e6 --- /dev/null +++ b/cypress/e2e/files/favorites.cy.ts @@ -0,0 +1,137 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { User } from '@nextcloud/cypress' +import { getActionButtonForFile, getRowForFile, triggerActionForFile } from './FilesUtils' + +describe('files: Favorites', { testIsolation: true }, () => { + let user: User + + beforeEach(() => { + cy.createRandomUser().then(($user) => { + user = $user + cy.uploadContent(user, new Blob([]), 'text/plain', '/file.txt') + cy.mkdir(user, '/new folder') + cy.login(user) + cy.visit('/apps/files') + }) + }) + + it('Mark file as favorite', () => { + // See file exists + getRowForFile('file.txt') + .should('exist') + + cy.intercept('POST', '**/apps/files/api/v1/files/file.txt').as('addToFavorites') + // Click actions + getActionButtonForFile('file.txt').click({ force: true }) + // See action is called 'Add to favorites' + cy.get('[data-cy-files-list-row-action="favorite"] > button').last() + .should('exist') + .and('have.text', 'Add to favorites') + .click({ force: true }) + cy.wait('@addToFavorites') + // See favorites star + getRowForFile('file.txt') + .findByRole('img', { name: 'Favorite' }) + .should('exist') + }) + + it('Un-mark file as favorite', () => { + // See file exists + getRowForFile('file.txt') + .should('exist') + + cy.intercept('POST', '**/apps/files/api/v1/files/file.txt').as('addToFavorites') + // toggle favorite + triggerActionForFile('file.txt', 'favorite') + cy.wait('@addToFavorites') + + // See favorites star + getRowForFile('file.txt') + .findByRole('img', { name: 'Favorite' }) + .should('be.visible') + + // Remove favorite + // click action button + getActionButtonForFile('file.txt').click({ force: true }) + // See action is called 'Remove from favorites' + cy.get('[data-cy-files-list-row-action="favorite"] > button').last() + .should('exist') + .and('have.text', 'Remove from favorites') + .click({ force: true }) + cy.wait('@addToFavorites') + // See no favorites star anymore + getRowForFile('file.txt') + .findByRole('img', { name: 'Favorite' }) + .should('not.exist') + }) + + it('See favorite folders in navigation', () => { + cy.intercept('POST', '**/apps/files/api/v1/files/new%20folder').as('addToFavorites') + + // see navigation has no entry + cy.get('[data-cy-files-navigation-item="favorites"]') + .should('be.visible') + .contains('new folder') + .should('not.exist') + + // toggle favorite + triggerActionForFile('new folder', 'favorite') + cy.wait('@addToFavorites') + + // See in navigation + cy.get('[data-cy-files-navigation-item="favorites"]') + .should('be.visible') + .contains('new folder') + .should('exist') + + // toggle favorite + triggerActionForFile('new folder', 'favorite') + cy.wait('@addToFavorites') + + // See no longer in navigation + cy.get('[data-cy-files-navigation-item="favorites"]') + .should('be.visible') + .contains('new folder') + .should('not.exist') + }) + + it('Mark file as favorite using the sidebar', () => { + // See file exists + getRowForFile('new folder') + .should('exist') + // see navigation has no entry + cy.get('[data-cy-files-navigation-item="favorites"]') + .should('be.visible') + .contains('new folder') + .should('not.exist') + + cy.intercept('PROPPATCH', '**/remote.php/dav/files/*/new%20folder').as('addToFavorites') + // open sidebar + triggerActionForFile('new folder', 'details') + // open actions + cy.get('[data-cy-sidebar]') + .findByRole('button', { name: 'Actions' }) + .click() + // trigger menu button + cy.findAllByRole('menu') + .findByRole('menuitem', { name: 'Add to favorites' }) + .should('be.visible') + .click() + cy.wait('@addToFavorites') + + // See favorites star + getRowForFile('new folder') + .findByRole('img', { name: 'Favorite' }) + .should('be.visible') + + // See folder in navigation + cy.get('[data-cy-files-navigation-item="favorites"]') + .should('be.visible') + .contains('new folder') + .should('exist') + }) +})