Skip to content

Commit 75437f1

Browse files
Merge pull request #53643 from nextcloud/backport/47555/stable29
[stable29] feat(files): Allow more than 50 favorite views
2 parents bfbea38 + 98485f1 commit 75437f1

File tree

7 files changed

+87
-94
lines changed

7 files changed

+87
-94
lines changed

apps/files/lib/Controller/ViewController.php

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
*/
3636
namespace OCA\Files\Controller;
3737

38-
use OCA\Files\Activity\Helper;
3938
use OCA\Files\AppInfo\Application;
4039
use OCA\Files\Event\LoadAdditionalScriptsEvent;
4140
use OCA\Files\Event\LoadSearchPlugins;
@@ -59,59 +58,48 @@
5958
use OCP\Files\NotFoundException;
6059
use OCP\Files\Template\ITemplateManager;
6160
use OCP\IConfig;
62-
use OCP\IL10N;
6361
use OCP\IRequest;
6462
use OCP\IURLGenerator;
6563
use OCP\IUserSession;
66-
use OCP\Share\IManager;
6764

6865
/**
6966
* @package OCA\Files\Controller
7067
*/
7168
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
7269
class ViewController extends Controller {
7370
private IURLGenerator $urlGenerator;
74-
private IL10N $l10n;
7571
private IConfig $config;
7672
private IEventDispatcher $eventDispatcher;
7773
private IUserSession $userSession;
7874
private IAppManager $appManager;
7975
private IRootFolder $rootFolder;
80-
private Helper $activityHelper;
8176
private IInitialState $initialState;
8277
private ITemplateManager $templateManager;
83-
private IManager $shareManager;
8478
private UserConfig $userConfig;
8579
private ViewConfig $viewConfig;
8680

8781
public function __construct(string $appName,
8882
IRequest $request,
8983
IURLGenerator $urlGenerator,
90-
IL10N $l10n,
9184
IConfig $config,
9285
IEventDispatcher $eventDispatcher,
9386
IUserSession $userSession,
9487
IAppManager $appManager,
9588
IRootFolder $rootFolder,
96-
Helper $activityHelper,
9789
IInitialState $initialState,
9890
ITemplateManager $templateManager,
99-
IManager $shareManager,
10091
UserConfig $userConfig,
101-
ViewConfig $viewConfig
92+
ViewConfig $viewConfig,
10293
) {
10394
parent::__construct($appName, $request);
10495
$this->urlGenerator = $urlGenerator;
105-
$this->l10n = $l10n;
10696
$this->config = $config;
10797
$this->eventDispatcher = $eventDispatcher;
10898
$this->userSession = $userSession;
10999
$this->appManager = $appManager;
110100
$this->rootFolder = $rootFolder;
111-
$this->activityHelper = $activityHelper;
112101
$this->initialState = $initialState;
113102
$this->templateManager = $templateManager;
114-
$this->shareManager = $shareManager;
115103
$this->userConfig = $userConfig;
116104
$this->viewConfig = $viewConfig;
117105
}
@@ -201,18 +189,6 @@ public function index($dir = '', $view = '', $fileid = null) {
201189

202190
$userId = $this->userSession->getUser()->getUID();
203191

204-
// Get all the user favorites to create a submenu
205-
try {
206-
$userFolder = $this->rootFolder->getUserFolder($userId);
207-
$favElements = $this->activityHelper->getFavoriteNodes($userId, true);
208-
$favElements = array_map(fn (Folder $node) => [
209-
'fileid' => $node->getId(),
210-
'path' => $userFolder->getRelativePath($node->getPath()),
211-
], $favElements);
212-
} catch (\RuntimeException $e) {
213-
$favElements = [];
214-
}
215-
216192
// If the file doesn't exists in the folder and
217193
// exists in only one occurrence, redirect to that file
218194
// in the correct folder
@@ -240,7 +216,6 @@ public function index($dir = '', $view = '', $fileid = null) {
240216
$this->initialState->provideInitialState('storageStats', $storageInfo);
241217
$this->initialState->provideInitialState('config', $this->userConfig->getConfigs());
242218
$this->initialState->provideInitialState('viewConfigs', $this->viewConfig->getConfigs());
243-
$this->initialState->provideInitialState('favoriteFolders', $favElements);
244219

245220
// File sorting user config
246221
$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
@@ -35,7 +35,7 @@ import { entry as newFolderEntry } from './newMenu/newFolder.ts'
3535
import { entry as newTemplatesFolder } from './newMenu/newTemplatesFolder.ts'
3636
import { registerTemplateEntries } from './newMenu/newFromTemplate.ts'
3737

38-
import registerFavoritesView from './views/favorites'
38+
import { registerFavoritesView } from './views/favorites.ts'
3939
import registerRecentView from './views/recent'
4040
import registerPersonalFilesView from './views/personal-files'
4141
import registerFilesView from './views/files'

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

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,42 @@
2020
* along with this program. If not, see <http://www.gnu.org/licenses/>.
2121
*
2222
*/
23-
import { basename } from 'path'
23+
24+
import type { Folder as CFolder, Navigation } from '@nextcloud/files'
25+
2426
import { expect } from '@jest/globals'
25-
import { Folder, Navigation, getNavigation } from '@nextcloud/files'
27+
import * as filesUtils from '@nextcloud/files'
2628
import { CancelablePromise } from 'cancelable-promise'
27-
import eventBus from '@nextcloud/event-bus'
29+
import * as eventBus from '@nextcloud/event-bus'
2830
import * as initialState from '@nextcloud/initial-state'
31+
import { basename } from 'path'
2932

3033
import { action } from '../actions/favoriteAction'
3134
import * as favoritesService from '../services/Favorites'
32-
import registerFavoritesView from './favorites'
35+
import { registerFavoritesView } from './favorites'
36+
37+
const { Folder, getNavigation } = filesUtils
38+
39+
jest.mock('@nextcloud/axios', () => ({
40+
post: jest.fn(),
41+
}))
3342

3443
jest.mock('webdav/dist/node/request.js', () => ({
3544
request: jest.fn(),
3645
}))
3746

38-
global.window.OC = {
47+
jest.mock('@nextcloud/files', () => ({
48+
__esModule: true,
49+
...jest.requireActual('@nextcloud/files'),
50+
}))
51+
52+
jest.mock('@nextcloud/event-bus', () => ({
53+
__esModule: true,
54+
...jest.requireActual('@nextcloud/event-bus'),
55+
}))
56+
57+
window.OC = {
58+
...window.OC,
3959
TAG_FAVORITE: '_$!<Favorite>!$_',
4060
}
4161

@@ -56,11 +76,12 @@ describe('Favorites view definition', () => {
5676
delete window._nc_navigation
5777
})
5878

59-
test('Default empty favorite view', () => {
79+
test('Default empty favorite view', async () => {
6080
jest.spyOn(eventBus, 'subscribe')
61-
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
81+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
82+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
6283

63-
registerFavoritesView()
84+
await registerFavoritesView()
6485
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
6586
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
6687

@@ -83,16 +104,31 @@ describe('Favorites view definition', () => {
83104
expect(favoritesView?.getContents).toBeDefined()
84105
})
85106

86-
test('Default with favorites', () => {
107+
test('Default with favorites', async () => {
87108
const favoriteFolders = [
88-
{ fileid: 1, path: '/foo' },
89-
{ fileid: 2, path: '/bar' },
90-
{ fileid: 3, path: '/foo/bar' },
109+
new Folder({
110+
id: 1,
111+
root: '/files/admin',
112+
source: 'http://nextcloud.local/remote.php/dav/files/admin/foo',
113+
owner: 'admin',
114+
}),
115+
new Folder({
116+
id: 2,
117+
root: '/files/admin',
118+
source: 'http://nextcloud.local/remote.php/dav/files/admin/bar',
119+
owner: 'admin',
120+
}),
121+
new Folder({
122+
id: 3,
123+
root: '/files/admin',
124+
source: 'http://nextcloud.local/remote.php/dav/files/admin/foo/bar',
125+
owner: 'admin',
126+
}),
91127
]
92-
jest.spyOn(initialState, 'loadState').mockReturnValue(favoriteFolders)
93-
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
128+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve(favoriteFolders))
129+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
94130

95-
registerFavoritesView()
131+
await registerFavoritesView()
96132
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
97133
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
98134

@@ -110,7 +146,7 @@ describe('Favorites view definition', () => {
110146
expect(favoriteView?.order).toBe(index)
111147
expect(favoriteView?.params).toStrictEqual({
112148
dir: folder.path,
113-
fileid: folder.fileid.toString(),
149+
fileid: String(folder.fileid),
114150
view: 'favorites',
115151
})
116152
expect(favoriteView?.parent).toBe('favorites')
@@ -132,10 +168,10 @@ describe('Dynamic update of favourite folders', () => {
132168

133169
test('Add a favorite folder creates a new entry in the navigation', async () => {
134170
jest.spyOn(eventBus, 'emit')
135-
jest.spyOn(initialState, 'loadState').mockReturnValue([])
136-
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
171+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
172+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
137173

138-
registerFavoritesView()
174+
await registerFavoritesView()
139175
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
140176
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
141177

@@ -161,10 +197,17 @@ describe('Dynamic update of favourite folders', () => {
161197
test('Remove a favorite folder remove the entry from the navigation column', async () => {
162198
jest.spyOn(eventBus, 'emit')
163199
jest.spyOn(eventBus, 'subscribe')
164-
jest.spyOn(initialState, 'loadState').mockReturnValue([{ fileid: 42, path: '/Foo/Bar' }])
165-
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
166-
167-
registerFavoritesView()
200+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([
201+
new Folder({
202+
id: 42,
203+
root: '/files/admin',
204+
source: 'http://nextcloud.local/remote.php/dav/files/admin/Foo/Bar',
205+
owner: 'admin',
206+
}),
207+
]))
208+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
209+
210+
await registerFavoritesView()
168211
let favoritesView = Navigation.views.find(view => view.id === 'favorites')
169212
let favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
170213

@@ -201,11 +244,10 @@ describe('Dynamic update of favourite folders', () => {
201244

202245
test('Renaming a favorite folder updates the navigation', async () => {
203246
jest.spyOn(eventBus, 'emit')
204-
jest.spyOn(initialState, 'loadState').mockReturnValue([])
205-
jest.spyOn(favoritesService, 'getContents')
206-
.mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
247+
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
248+
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
207249

208-
registerFavoritesView()
250+
await registerFavoritesView()
209251
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
210252
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')
211253

apps/files/src/views/favorites.ts

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

2424
import { subscribe } from '@nextcloud/event-bus'
25-
import { FileType, View, getNavigation } from '@nextcloud/files'
26-
import { loadState } from '@nextcloud/initial-state'
25+
import { FileType, View, getFavoriteNodes, getNavigation } from '@nextcloud/files'
2726
import { getLanguage, translate as t } from '@nextcloud/l10n'
28-
import { basename } from 'path'
27+
import { client } from '../services/WebdavClient.ts'
2928
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
3029
import StarSvg from '@mdi/svg/svg/star.svg?raw'
3130

3231
import { getContents } from '../services/Favorites'
3332
import { hashCode } from '../utils/hashUtils'
3433
import logger from '../logger'
3534

36-
// The return type of the initial state
37-
interface IFavoriteFolder {
38-
fileid: number
39-
path: string
40-
}
41-
42-
export const generateFavoriteFolderView = function(folder: IFavoriteFolder, index = 0): View {
35+
const generateFavoriteFolderView = function(folder: Folder, index = 0): View {
4336
return new View({
4437
id: generateIdFromPath(folder.path),
45-
name: basename(folder.path),
38+
name: folder.displayname,
4639

4740
icon: FolderSvg,
4841
order: index,
42+
4943
params: {
5044
dir: folder.path,
51-
fileid: folder.fileid.toString(),
45+
fileid: String(folder.fileid),
5246
view: 'favorites',
5347
},
5448

@@ -60,16 +54,11 @@ export const generateFavoriteFolderView = function(folder: IFavoriteFolder, inde
6054
})
6155
}
6256

63-
export const generateIdFromPath = function(path: string): string {
57+
const generateIdFromPath = function(path: string): string {
6458
return `favorite-${hashCode(path)}`
6559
}
6660

67-
export default () => {
68-
// Load state in function for mock testing purposes
69-
const favoriteFolders = loadState<IFavoriteFolder[]>('files', 'favoriteFolders', [])
70-
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
71-
logger.debug('Generating favorites view', { favoriteFolders })
72-
61+
export const registerFavoritesView = async () => {
7362
const Navigation = getNavigation()
7463
Navigation.register(new View({
7564
id: 'favorites',
@@ -87,6 +76,9 @@ export default () => {
8776
getContents,
8877
}))
8978

79+
const favoriteFolders = (await getFavoriteNodes(client)).filter(node => node.type === FileType.Folder) as Folder[]
80+
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
81+
logger.debug('Generating favorites view', { favoriteFolders })
9082
favoriteFoldersViews.forEach(view => Navigation.register(view))
9183

9284
/**
@@ -154,16 +146,15 @@ export default () => {
154146

155147
// Add a folder to the favorites paths array and update the views
156148
const addToFavorites = function(node: Folder) {
157-
const newFavoriteFolder: IFavoriteFolder = { path: node.path, fileid: node.fileid! }
158-
const view = generateFavoriteFolderView(newFavoriteFolder)
149+
const view = generateFavoriteFolderView(node)
159150

160151
// Skip if already exists
161152
if (favoriteFolders.find((folder) => folder.path === node.path)) {
162153
return
163154
}
164155

165156
// Update arrays
166-
favoriteFolders.push(newFavoriteFolder)
157+
favoriteFolders.push(node)
167158
favoriteFoldersViews.push(view)
168159

169160
// Update and sort views

0 commit comments

Comments
 (0)