Skip to content

Commit 88b04ba

Browse files
Merge pull request #47555 from nextcloud/backport/47400/stable30
[stable30] feat(files): Allow more than 50 favorite views
2 parents 8c77516 + 0eacea3 commit 88b04ba

7 files changed

Lines changed: 101 additions & 116 deletions

File tree

apps/files/lib/Controller/ViewController.php

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
namespace OCA\Files\Controller;
99

1010
use OC\Files\FilenameValidator;
11-
use OCA\Files\Activity\Helper;
1211
use OCA\Files\AppInfo\Application;
1312
use OCA\Files\Event\LoadAdditionalScriptsEvent;
1413
use OCA\Files\Event\LoadSearchPlugins;
@@ -54,7 +53,6 @@ public function __construct(
5453
private IUserSession $userSession,
5554
private IAppManager $appManager,
5655
private IRootFolder $rootFolder,
57-
private Helper $activityHelper,
5856
private IInitialState $initialState,
5957
private ITemplateManager $templateManager,
6058
private UserConfig $userConfig,
@@ -146,18 +144,6 @@ public function index($dir = '', $view = '', $fileid = null, $fileNotFound = fal
146144

147145
$userId = $this->userSession->getUser()->getUID();
148146

149-
// Get all the user favorites to create a submenu
150-
try {
151-
$userFolder = $this->rootFolder->getUserFolder($userId);
152-
$favElements = $this->activityHelper->getFavoriteNodes($userId, true);
153-
$favElements = array_map(fn (Folder $node) => [
154-
'fileid' => $node->getId(),
155-
'path' => $userFolder->getRelativePath($node->getPath()),
156-
], $favElements);
157-
} catch (\RuntimeException $e) {
158-
$favElements = [];
159-
}
160-
161147
// If the file doesn't exists in the folder and
162148
// exists in only one occurrence, redirect to that file
163149
// in the correct folder
@@ -187,7 +173,6 @@ public function index($dir = '', $view = '', $fileid = null, $fileNotFound = fal
187173
$this->initialState->provideInitialState('storageStats', $storageInfo);
188174
$this->initialState->provideInitialState('config', $this->userConfig->getConfigs());
189175
$this->initialState->provideInitialState('viewConfigs', $this->viewConfig->getConfigs());
190-
$this->initialState->provideInitialState('favoriteFolders', $favElements);
191176

192177
// File sorting user config
193178
$filesSortingConfig = json_decode($this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}'), true);

apps/files/src/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { entry as newFolderEntry } from './newMenu/newFolder.ts'
2323
import { entry as newTemplatesFolder } from './newMenu/newTemplatesFolder.ts'
2424
import { registerTemplateEntries } from './newMenu/newFromTemplate.ts'
2525

26-
import registerFavoritesView from './views/favorites'
26+
import { registerFavoritesView } from './views/favorites.ts'
2727
import registerRecentView from './views/recent'
2828
import registerPersonalFilesView from './views/personal-files'
2929
import registerFilesView from './views/files'

apps/files/src/views/favorites.spec.ts

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,39 @@
33
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
44
* SPDX-License-Identifier: AGPL-3.0-or-later
55
*/
6-
import { basename } from 'path'
6+
7+
import type { Folder as CFolder, Navigation } from '@nextcloud/files'
8+
79
import { expect } from '@jest/globals'
8-
import { Folder, Navigation, getNavigation } from '@nextcloud/files'
10+
import * as filesUtils from '@nextcloud/files'
911
import { CancelablePromise } from 'cancelable-promise'
10-
import eventBus, { emit } from '@nextcloud/event-bus'
11-
import * as initialState from '@nextcloud/initial-state'
12+
import * as eventBus from '@nextcloud/event-bus'
13+
import { basename } from 'path'
1214

1315
import { action } from '../actions/favoriteAction'
1416
import * as favoritesService from '../services/Favorites'
15-
import registerFavoritesView from './favorites'
17+
import { registerFavoritesView } from './favorites'
18+
19+
const { Folder, getNavigation } = filesUtils
20+
21+
jest.mock('@nextcloud/axios', () => ({
22+
post: jest.fn(),
23+
}))
1624

1725
jest.mock('webdav/dist/node/request.js', () => ({
1826
request: jest.fn(),
1927
}))
2028

29+
jest.mock('@nextcloud/files', () => ({
30+
__esModule: true,
31+
...jest.requireActual('@nextcloud/files'),
32+
}))
33+
34+
jest.mock('@nextcloud/event-bus', () => ({
35+
__esModule: true,
36+
...jest.requireActual('@nextcloud/event-bus'),
37+
}))
38+
2139
window.OC = {
2240
...window.OC,
2341
TAG_FAVORITE: '_$!<Favorite>!$_',
@@ -40,11 +58,12 @@ describe('Favorites view definition', () => {
4058
delete window._nc_navigation
4159
})
4260

43-
test('Default empty favorite view', () => {
61+
test('Default empty favorite view', async () => {
4462
jest.spyOn(eventBus, 'subscribe')
45-
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
63+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
64+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
4665

47-
registerFavoritesView()
66+
await registerFavoritesView()
4867
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
4968
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
5069

@@ -67,16 +86,31 @@ describe('Favorites view definition', () => {
6786
expect(favoritesView?.getContents).toBeDefined()
6887
})
6988

70-
test('Default with favorites', () => {
89+
test('Default with favorites', async () => {
7190
const favoriteFolders = [
72-
{ fileid: 1, path: '/foo' },
73-
{ fileid: 2, path: '/bar' },
74-
{ fileid: 3, path: '/foo/bar' },
91+
new Folder({
92+
id: 1,
93+
root: '/files/admin',
94+
source: 'http://nextcloud.local/remote.php/dav/files/admin/foo',
95+
owner: 'admin',
96+
}),
97+
new Folder({
98+
id: 2,
99+
root: '/files/admin',
100+
source: 'http://nextcloud.local/remote.php/dav/files/admin/bar',
101+
owner: 'admin',
102+
}),
103+
new Folder({
104+
id: 3,
105+
root: '/files/admin',
106+
source: 'http://nextcloud.local/remote.php/dav/files/admin/foo/bar',
107+
owner: 'admin',
108+
}),
75109
]
76-
jest.spyOn(initialState, 'loadState').mockReturnValue(favoriteFolders)
77-
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
110+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve(favoriteFolders))
111+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
78112

79-
registerFavoritesView()
113+
await registerFavoritesView()
80114
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
81115
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
82116

@@ -94,7 +128,7 @@ describe('Favorites view definition', () => {
94128
expect(favoriteView?.order).toBe(index)
95129
expect(favoriteView?.params).toStrictEqual({
96130
dir: folder.path,
97-
fileid: folder.fileid.toString(),
131+
fileid: String(folder.fileid),
98132
view: 'favorites',
99133
})
100134
expect(favoriteView?.parent).toBe('favorites')
@@ -116,10 +150,10 @@ describe('Dynamic update of favourite folders', () => {
116150

117151
test('Add a favorite folder creates a new entry in the navigation', async () => {
118152
jest.spyOn(eventBus, 'emit')
119-
jest.spyOn(initialState, 'loadState').mockReturnValue([])
120-
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
153+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
154+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
121155

122-
registerFavoritesView()
156+
await registerFavoritesView()
123157
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
124158
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
125159

@@ -145,10 +179,17 @@ describe('Dynamic update of favourite folders', () => {
145179
test('Remove a favorite folder remove the entry from the navigation column', async () => {
146180
jest.spyOn(eventBus, 'emit')
147181
jest.spyOn(eventBus, 'subscribe')
148-
jest.spyOn(initialState, 'loadState').mockReturnValue([{ fileid: 42, path: '/Foo/Bar' }])
149-
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
150-
151-
registerFavoritesView()
182+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([
183+
new Folder({
184+
id: 42,
185+
root: '/files/admin',
186+
source: 'http://nextcloud.local/remote.php/dav/files/admin/Foo/Bar',
187+
owner: 'admin',
188+
}),
189+
]))
190+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
191+
192+
await registerFavoritesView()
152193
let favoritesView = Navigation.views.find(view => view.id === 'favorites')
153194
let favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
154195

@@ -185,10 +226,10 @@ describe('Dynamic update of favourite folders', () => {
185226

186227
test('Renaming a favorite folder updates the navigation', async () => {
187228
jest.spyOn(eventBus, 'emit')
188-
jest.spyOn(initialState, 'loadState').mockReturnValue([])
189-
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
229+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
230+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
190231

191-
registerFavoritesView()
232+
await registerFavoritesView()
192233
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
193234
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
194235

@@ -218,7 +259,7 @@ describe('Dynamic update of favourite folders', () => {
218259
})
219260

220261
// Exec the rename action
221-
emit('files:node:renamed', renamedFolder)
262+
eventBus.emit('files:node:renamed', renamedFolder)
222263
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:renamed', renamedFolder)
223264
})
224265
})

apps/files/src/views/favorites.ts

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,27 @@
55
import type { Folder, Node } from '@nextcloud/files'
66

77
import { subscribe } from '@nextcloud/event-bus'
8-
import { FileType, View, getNavigation } from '@nextcloud/files'
9-
import { loadState } from '@nextcloud/initial-state'
8+
import { FileType, View, getFavoriteNodes, getNavigation } from '@nextcloud/files'
109
import { getLanguage, translate as t } from '@nextcloud/l10n'
11-
import { basename } from 'path'
10+
import { client } from '../services/WebdavClient.ts'
1211
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
1312
import StarSvg from '@mdi/svg/svg/star.svg?raw'
1413

1514
import { getContents } from '../services/Favorites'
1615
import { hashCode } from '../utils/hashUtils'
1716
import logger from '../logger'
1817

19-
// The return type of the initial state
20-
interface IFavoriteFolder {
21-
fileid: number
22-
path: string
23-
}
24-
25-
export const generateFavoriteFolderView = function(folder: IFavoriteFolder, index = 0): View {
18+
const generateFavoriteFolderView = function(folder: Folder, index = 0): View {
2619
return new View({
2720
id: generateIdFromPath(folder.path),
28-
name: basename(folder.path),
21+
name: folder.displayname,
2922

3023
icon: FolderSvg,
3124
order: index,
25+
3226
params: {
3327
dir: folder.path,
34-
fileid: folder.fileid.toString(),
28+
fileid: String(folder.fileid),
3529
view: 'favorites',
3630
},
3731

@@ -43,16 +37,11 @@ export const generateFavoriteFolderView = function(folder: IFavoriteFolder, inde
4337
})
4438
}
4539

46-
export const generateIdFromPath = function(path: string): string {
40+
const generateIdFromPath = function(path: string): string {
4741
return `favorite-${hashCode(path)}`
4842
}
4943

50-
export default () => {
51-
// Load state in function for mock testing purposes
52-
const favoriteFolders = loadState<IFavoriteFolder[]>('files', 'favoriteFolders', [])
53-
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
54-
logger.debug('Generating favorites view', { favoriteFolders })
55-
44+
export const registerFavoritesView = async () => {
5645
const Navigation = getNavigation()
5746
Navigation.register(new View({
5847
id: 'favorites',
@@ -70,6 +59,9 @@ export default () => {
7059
getContents,
7160
}))
7261

62+
const favoriteFolders = (await getFavoriteNodes(client)).filter(node => node.type === FileType.Folder) as Folder[]
63+
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
64+
logger.debug('Generating favorites view', { favoriteFolders })
7365
favoriteFoldersViews.forEach(view => Navigation.register(view))
7466

7567
/**
@@ -137,16 +129,15 @@ export default () => {
137129

138130
// Add a folder to the favorites paths array and update the views
139131
const addToFavorites = function(node: Folder) {
140-
const newFavoriteFolder: IFavoriteFolder = { path: node.path, fileid: node.fileid! }
141-
const view = generateFavoriteFolderView(newFavoriteFolder)
132+
const view = generateFavoriteFolderView(node)
142133

143134
// Skip if already exists
144135
if (favoriteFolders.find((folder) => folder.path === node.path)) {
145136
return
146137
}
147138

148139
// Update arrays
149-
favoriteFolders.push(newFavoriteFolder)
140+
favoriteFolders.push(node)
150141
favoriteFoldersViews.push(view)
151142

152143
// Update and sort views

0 commit comments

Comments
 (0)