diff options
author | Andy Scherzinger <info@andy-scherzinger.de> | 2025-06-23 15:12:00 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-06-23 15:12:00 +0200 |
commit | 75437f142c6793bc9695ea52ca803771cd2ef3e9 (patch) | |
tree | 8d332d5e8cb508419e328af907090efefcae3419 /apps | |
parent | bfbea3817591c9eec1d9d479ceee0576ecf876c9 (diff) | |
parent | 98485f1247b173073aca8504c6d3d99aee6d9f59 (diff) | |
download | nextcloud-server-stable29.tar.gz nextcloud-server-stable29.zip |
Merge pull request #53643 from nextcloud/backport/47555/stable29stable29
[stable29] feat(files): Allow more than 50 favorite views
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files/lib/Controller/ViewController.php | 27 | ||||
-rw-r--r-- | apps/files/src/init.ts | 2 | ||||
-rw-r--r-- | apps/files/src/views/favorites.spec.ts | 96 | ||||
-rw-r--r-- | apps/files/src/views/favorites.ts | 35 | ||||
-rw-r--r-- | apps/files/tests/Controller/ViewControllerTest.php | 15 |
5 files changed, 84 insertions, 91 deletions
diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php index a0eed8fb205..18fb8f1a780 100644 --- a/apps/files/lib/Controller/ViewController.php +++ b/apps/files/lib/Controller/ViewController.php @@ -35,7 +35,6 @@ */ namespace OCA\Files\Controller; -use OCA\Files\Activity\Helper; use OCA\Files\AppInfo\Application; use OCA\Files\Event\LoadAdditionalScriptsEvent; use OCA\Files\Event\LoadSearchPlugins; @@ -59,11 +58,9 @@ use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\Template\ITemplateManager; use OCP\IConfig; -use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUserSession; -use OCP\Share\IManager; /** * @package OCA\Files\Controller @@ -71,47 +68,38 @@ use OCP\Share\IManager; #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] class ViewController extends Controller { private IURLGenerator $urlGenerator; - private IL10N $l10n; private IConfig $config; private IEventDispatcher $eventDispatcher; private IUserSession $userSession; private IAppManager $appManager; private IRootFolder $rootFolder; - private Helper $activityHelper; private IInitialState $initialState; private ITemplateManager $templateManager; - private IManager $shareManager; private UserConfig $userConfig; private ViewConfig $viewConfig; public function __construct(string $appName, IRequest $request, IURLGenerator $urlGenerator, - IL10N $l10n, IConfig $config, IEventDispatcher $eventDispatcher, IUserSession $userSession, IAppManager $appManager, IRootFolder $rootFolder, - Helper $activityHelper, IInitialState $initialState, ITemplateManager $templateManager, - IManager $shareManager, UserConfig $userConfig, - ViewConfig $viewConfig + ViewConfig $viewConfig, ) { parent::__construct($appName, $request); $this->urlGenerator = $urlGenerator; - $this->l10n = $l10n; $this->config = $config; $this->eventDispatcher = $eventDispatcher; $this->userSession = $userSession; $this->appManager = $appManager; $this->rootFolder = $rootFolder; - $this->activityHelper = $activityHelper; $this->initialState = $initialState; $this->templateManager = $templateManager; - $this->shareManager = $shareManager; $this->userConfig = $userConfig; $this->viewConfig = $viewConfig; } @@ -201,18 +189,6 @@ class ViewController extends Controller { $userId = $this->userSession->getUser()->getUID(); - // Get all the user favorites to create a submenu - try { - $userFolder = $this->rootFolder->getUserFolder($userId); - $favElements = $this->activityHelper->getFavoriteNodes($userId, true); - $favElements = array_map(fn (Folder $node) => [ - 'fileid' => $node->getId(), - 'path' => $userFolder->getRelativePath($node->getPath()), - ], $favElements); - } catch (\RuntimeException $e) { - $favElements = []; - } - // If the file doesn't exists in the folder and // exists in only one occurrence, redirect to that file // in the correct folder @@ -240,7 +216,6 @@ class ViewController extends Controller { $this->initialState->provideInitialState('storageStats', $storageInfo); $this->initialState->provideInitialState('config', $this->userConfig->getConfigs()); $this->initialState->provideInitialState('viewConfigs', $this->viewConfig->getConfigs()); - $this->initialState->provideInitialState('favoriteFolders', $favElements); // File sorting user config $filesSortingConfig = json_decode($this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}'), true); diff --git a/apps/files/src/init.ts b/apps/files/src/init.ts index f3672e3f26f..06a5cc6c6a0 100644 --- a/apps/files/src/init.ts +++ b/apps/files/src/init.ts @@ -35,7 +35,7 @@ import { entry as newFolderEntry } from './newMenu/newFolder.ts' import { entry as newTemplatesFolder } from './newMenu/newTemplatesFolder.ts' import { registerTemplateEntries } from './newMenu/newFromTemplate.ts' -import registerFavoritesView from './views/favorites' +import { registerFavoritesView } from './views/favorites.ts' import registerRecentView from './views/recent' import registerPersonalFilesView from './views/personal-files' import registerFilesView from './views/files' diff --git a/apps/files/src/views/favorites.spec.ts b/apps/files/src/views/favorites.spec.ts index b14869a0589..68d829c8229 100644 --- a/apps/files/src/views/favorites.spec.ts +++ b/apps/files/src/views/favorites.spec.ts @@ -20,22 +20,42 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -import { basename } from 'path' + +import type { Folder as CFolder, Navigation } from '@nextcloud/files' + import { expect } from '@jest/globals' -import { Folder, Navigation, getNavigation } from '@nextcloud/files' +import * as filesUtils from '@nextcloud/files' import { CancelablePromise } from 'cancelable-promise' -import eventBus from '@nextcloud/event-bus' +import * as eventBus from '@nextcloud/event-bus' import * as initialState from '@nextcloud/initial-state' +import { basename } from 'path' import { action } from '../actions/favoriteAction' import * as favoritesService from '../services/Favorites' -import registerFavoritesView from './favorites' +import { registerFavoritesView } from './favorites' + +const { Folder, getNavigation } = filesUtils + +jest.mock('@nextcloud/axios', () => ({ + post: jest.fn(), +})) jest.mock('webdav/dist/node/request.js', () => ({ request: jest.fn(), })) -global.window.OC = { +jest.mock('@nextcloud/files', () => ({ + __esModule: true, + ...jest.requireActual('@nextcloud/files'), +})) + +jest.mock('@nextcloud/event-bus', () => ({ + __esModule: true, + ...jest.requireActual('@nextcloud/event-bus'), +})) + +window.OC = { + ...window.OC, TAG_FAVORITE: '_$!<Favorite>!$_', } @@ -56,11 +76,12 @@ describe('Favorites view definition', () => { delete window._nc_navigation }) - test('Default empty favorite view', () => { + test('Default empty favorite view', async () => { jest.spyOn(eventBus, 'subscribe') - jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] })) + jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([])) + jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] })) - registerFavoritesView() + await registerFavoritesView() const favoritesView = Navigation.views.find(view => view.id === 'favorites') const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites') @@ -83,16 +104,31 @@ describe('Favorites view definition', () => { expect(favoritesView?.getContents).toBeDefined() }) - test('Default with favorites', () => { + test('Default with favorites', async () => { const favoriteFolders = [ - { fileid: 1, path: '/foo' }, - { fileid: 2, path: '/bar' }, - { fileid: 3, path: '/foo/bar' }, + new Folder({ + id: 1, + root: '/files/admin', + source: 'http://nextcloud.local/remote.php/dav/files/admin/foo', + owner: 'admin', + }), + new Folder({ + id: 2, + root: '/files/admin', + source: 'http://nextcloud.local/remote.php/dav/files/admin/bar', + owner: 'admin', + }), + new Folder({ + id: 3, + root: '/files/admin', + source: 'http://nextcloud.local/remote.php/dav/files/admin/foo/bar', + owner: 'admin', + }), ] - jest.spyOn(initialState, 'loadState').mockReturnValue(favoriteFolders) - jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] })) + jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve(favoriteFolders)) + jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] })) - registerFavoritesView() + await registerFavoritesView() const favoritesView = Navigation.views.find(view => view.id === 'favorites') const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites') @@ -110,7 +146,7 @@ describe('Favorites view definition', () => { expect(favoriteView?.order).toBe(index) expect(favoriteView?.params).toStrictEqual({ dir: folder.path, - fileid: folder.fileid.toString(), + fileid: String(folder.fileid), view: 'favorites', }) expect(favoriteView?.parent).toBe('favorites') @@ -132,10 +168,10 @@ describe('Dynamic update of favourite folders', () => { test('Add a favorite folder creates a new entry in the navigation', async () => { jest.spyOn(eventBus, 'emit') - jest.spyOn(initialState, 'loadState').mockReturnValue([]) - jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] })) + jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([])) + jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] })) - registerFavoritesView() + await registerFavoritesView() const favoritesView = Navigation.views.find(view => view.id === 'favorites') const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites') @@ -161,10 +197,17 @@ describe('Dynamic update of favourite folders', () => { test('Remove a favorite folder remove the entry from the navigation column', async () => { jest.spyOn(eventBus, 'emit') jest.spyOn(eventBus, 'subscribe') - jest.spyOn(initialState, 'loadState').mockReturnValue([{ fileid: 42, path: '/Foo/Bar' }]) - jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] })) - - registerFavoritesView() + jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([ + new Folder({ + id: 42, + root: '/files/admin', + source: 'http://nextcloud.local/remote.php/dav/files/admin/Foo/Bar', + owner: 'admin', + }), + ])) + jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] })) + + await registerFavoritesView() let favoritesView = Navigation.views.find(view => view.id === 'favorites') let favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites') @@ -201,11 +244,10 @@ describe('Dynamic update of favourite folders', () => { test('Renaming a favorite folder updates the navigation', async () => { jest.spyOn(eventBus, 'emit') - jest.spyOn(initialState, 'loadState').mockReturnValue([]) - jest.spyOn(favoritesService, 'getContents') - .mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] })) + jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([])) + jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] })) - registerFavoritesView() + await registerFavoritesView() const favoritesView = Navigation.views.find(view => view.id === 'favorites') const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites') diff --git a/apps/files/src/views/favorites.ts b/apps/files/src/views/favorites.ts index 30fd26549fa..2c127d012a7 100644 --- a/apps/files/src/views/favorites.ts +++ b/apps/files/src/views/favorites.ts @@ -22,10 +22,9 @@ import type { Folder, Node } from '@nextcloud/files' import { subscribe } from '@nextcloud/event-bus' -import { FileType, View, getNavigation } from '@nextcloud/files' -import { loadState } from '@nextcloud/initial-state' +import { FileType, View, getFavoriteNodes, getNavigation } from '@nextcloud/files' import { getLanguage, translate as t } from '@nextcloud/l10n' -import { basename } from 'path' +import { client } from '../services/WebdavClient.ts' import FolderSvg from '@mdi/svg/svg/folder.svg?raw' import StarSvg from '@mdi/svg/svg/star.svg?raw' @@ -33,22 +32,17 @@ import { getContents } from '../services/Favorites' import { hashCode } from '../utils/hashUtils' import logger from '../logger' -// The return type of the initial state -interface IFavoriteFolder { - fileid: number - path: string -} - -export const generateFavoriteFolderView = function(folder: IFavoriteFolder, index = 0): View { +const generateFavoriteFolderView = function(folder: Folder, index = 0): View { return new View({ id: generateIdFromPath(folder.path), - name: basename(folder.path), + name: folder.displayname, icon: FolderSvg, order: index, + params: { dir: folder.path, - fileid: folder.fileid.toString(), + fileid: String(folder.fileid), view: 'favorites', }, @@ -60,16 +54,11 @@ export const generateFavoriteFolderView = function(folder: IFavoriteFolder, inde }) } -export const generateIdFromPath = function(path: string): string { +const generateIdFromPath = function(path: string): string { return `favorite-${hashCode(path)}` } -export default () => { - // Load state in function for mock testing purposes - const favoriteFolders = loadState<IFavoriteFolder[]>('files', 'favoriteFolders', []) - const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[] - logger.debug('Generating favorites view', { favoriteFolders }) - +export const registerFavoritesView = async () => { const Navigation = getNavigation() Navigation.register(new View({ id: 'favorites', @@ -87,6 +76,9 @@ export default () => { getContents, })) + const favoriteFolders = (await getFavoriteNodes(client)).filter(node => node.type === FileType.Folder) as Folder[] + const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[] + logger.debug('Generating favorites view', { favoriteFolders }) favoriteFoldersViews.forEach(view => Navigation.register(view)) /** @@ -154,8 +146,7 @@ export default () => { // Add a folder to the favorites paths array and update the views const addToFavorites = function(node: Folder) { - const newFavoriteFolder: IFavoriteFolder = { path: node.path, fileid: node.fileid! } - const view = generateFavoriteFolderView(newFavoriteFolder) + const view = generateFavoriteFolderView(node) // Skip if already exists if (favoriteFolders.find((folder) => folder.path === node.path)) { @@ -163,7 +154,7 @@ export default () => { } // Update arrays - favoriteFolders.push(newFavoriteFolder) + favoriteFolders.push(node) favoriteFoldersViews.push(view) // Update and sort views diff --git a/apps/files/tests/Controller/ViewControllerTest.php b/apps/files/tests/Controller/ViewControllerTest.php index 78eb3d33e5f..fd66fececd2 100644 --- a/apps/files/tests/Controller/ViewControllerTest.php +++ b/apps/files/tests/Controller/ViewControllerTest.php @@ -34,7 +34,6 @@ namespace OCA\Files\Tests\Controller; use OC\Route\Router; use OC\URLGenerator; -use OCA\Files\Activity\Helper; use OCA\Files\Controller\ViewController; use OCA\Files\Service\UserConfig; use OCA\Files\Service\ViewConfig; @@ -51,12 +50,10 @@ use OCP\Files\IRootFolder; use OCP\Files\Template\ITemplateManager; use OCP\ICacheFactory; use OCP\IConfig; -use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserSession; -use OCP\Share\IManager; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use Test\TestCase; @@ -73,8 +70,6 @@ class ViewControllerTest extends TestCase { private $request; /** @var IURLGenerator */ private $urlGenerator; - /** @var IL10N */ - private $l10n; /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ private $config; /** @var IEventDispatcher */ @@ -89,14 +84,10 @@ class ViewControllerTest extends TestCase { private $appManager; /** @var IRootFolder|\PHPUnit\Framework\MockObject\MockObject */ private $rootFolder; - /** @var Helper|\PHPUnit\Framework\MockObject\MockObject */ - private $activityHelper; /** @var IInitialState|\PHPUnit\Framework\MockObject\MockObject */ private $initialState; /** @var ITemplateManager|\PHPUnit\Framework\MockObject\MockObject */ private $templateManager; - /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ - private $shareManager; /** @var UserConfig|\PHPUnit\Framework\MockObject\MockObject */ private $userConfig; /** @var ViewConfig|\PHPUnit\Framework\MockObject\MockObject */ @@ -120,15 +111,12 @@ class ViewControllerTest extends TestCase { $this->config = $this->createMock(IConfig::class); $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->initialState = $this->createMock(IInitialState::class); - $this->l10n = $this->createMock(IL10N::class); $this->request = $this->createMock(IRequest::class); $this->rootFolder = $this->createMock(IRootFolder::class); $this->templateManager = $this->createMock(ITemplateManager::class); $this->userConfig = $this->createMock(UserConfig::class); $this->userSession = $this->createMock(IUserSession::class); $this->viewConfig = $this->createMock(ViewConfig::class); - $this->activityHelper = $this->createMock(Helper::class); - $this->shareManager = $this->createMock(IManager::class); $this->user = $this->getMockBuilder(IUser::class)->getMock(); $this->user->expects($this->any()) @@ -169,16 +157,13 @@ class ViewControllerTest extends TestCase { 'files', $this->request, $this->urlGenerator, - $this->l10n, $this->config, $this->eventDispatcher, $this->userSession, $this->appManager, $this->rootFolder, - $this->activityHelper, $this->initialState, $this->templateManager, - $this->shareManager, $this->userConfig, $this->viewConfig, ]) |