aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2025-06-11 13:43:26 +0200
committerFerdinand Thiessen <opensource@fthiessen.de>2025-06-11 14:38:56 +0200
commit0a8dbb402b932e3815822fe28e82d8f63f5cb218 (patch)
tree0a38e6a70894116339e48053ec19cb7764366aa3
parentc4f071a577843751647879dfa97a29372bfb9e18 (diff)
downloadnextcloud-server-refactor/files-hotkeys.tar.gz
nextcloud-server-refactor/files-hotkeys.zip
refactor(files): move hotkey handling to composablerefactor/files-hotkeys
This is a composable - not a service, because it is using the `useHotKey` composable. At this moment it works, but in general its only safe to put composables into `setup`-context. This makes it future prove. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r--apps/files/src/FilesApp.vue6
-rw-r--r--apps/files/src/composables/useHotKeys.spec.ts (renamed from apps/files/src/services/HotKeysService.spec.ts)70
-rw-r--r--apps/files/src/composables/useHotKeys.ts (renamed from apps/files/src/services/HotKeysService.ts)46
-rw-r--r--apps/files/src/main.ts4
4 files changed, 77 insertions, 49 deletions
diff --git a/apps/files/src/FilesApp.vue b/apps/files/src/FilesApp.vue
index 54821a03457..6fc02113162 100644
--- a/apps/files/src/FilesApp.vue
+++ b/apps/files/src/FilesApp.vue
@@ -12,11 +12,10 @@
<script lang="ts">
import { isPublicShare } from '@nextcloud/sharing/public'
import { defineComponent } from 'vue'
-
import NcContent from '@nextcloud/vue/components/NcContent'
-
import Navigation from './views/Navigation.vue'
import FilesList from './views/FilesList.vue'
+import { useHotKeys } from './composables/useHotKeys'
export default defineComponent({
name: 'FilesApp',
@@ -28,6 +27,9 @@ export default defineComponent({
},
setup() {
+ // Register global hotkeys
+ useHotKeys()
+
const isPublic = isPublicShare()
return {
diff --git a/apps/files/src/services/HotKeysService.spec.ts b/apps/files/src/composables/useHotKeys.spec.ts
index 7bbba77b222..d36dca3863a 100644
--- a/apps/files/src/services/HotKeysService.spec.ts
+++ b/apps/files/src/composables/useHotKeys.spec.ts
@@ -2,9 +2,12 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+import type { Location } from 'vue-router'
+
import { File, Permission, View } from '@nextcloud/files'
-import { describe, it, vi, expect, beforeEach, beforeAll, afterEach } from 'vitest'
-import { nextTick } from 'vue'
+import { enableAutoDestroy, mount } from '@vue/test-utils'
+import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest'
+import { defineComponent, nextTick } from 'vue'
import axios from '@nextcloud/axios'
import { getPinia } from '../store/index.ts'
@@ -14,38 +17,64 @@ import { action as deleteAction } from '../actions/deleteAction.ts'
import { action as favoriteAction } from '../actions/favoriteAction.ts'
import { action as renameAction } from '../actions/renameAction.ts'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
-import { registerHotkeys } from './HotKeysService.ts'
+import { useHotKeys } from './useHotKeys.ts'
import { useUserConfigStore } from '../store/userconfig.ts'
+// this is the mocked current route
+const route = vi.hoisted(() => ({
+ name: 'test',
+ params: {
+ fileId: 123,
+ },
+ query: {
+ openFile: 'false',
+ dir: '/parent/dir',
+ },
+}))
+
+// mocked router
+const router = vi.hoisted(() => ({
+ push: vi.fn<(route: Location) => void>(),
+}))
+
+vi.mock('../actions/sidebarAction.ts', { spy: true })
+vi.mock('../actions/deleteAction.ts', { spy: true })
+vi.mock('../actions/favoriteAction.ts', { spy: true })
+vi.mock('../actions/renameAction.ts', { spy: true })
+
+vi.mock('vue-router/composables', () => ({
+ useRoute: vi.fn(() => route),
+ useRouter: vi.fn(() => router),
+}))
+
let file: File
const view = {
id: 'files',
name: 'Files',
} as View
-vi.mock('../actions/sidebarAction.ts', { spy: true })
-vi.mock('../actions/deleteAction.ts', { spy: true })
-vi.mock('../actions/favoriteAction.ts', { spy: true })
-vi.mock('../actions/renameAction.ts', { spy: true })
+const TestComponent = defineComponent({
+ name: 'test',
+ setup() {
+ useHotKeys()
+ },
+ template: '<div />',
+})
describe('HotKeysService testing', () => {
const activeStore = useActiveStore(getPinia())
- const goToRouteMock = vi.fn()
-
let initialState: HTMLInputElement
+ enableAutoDestroy(afterEach)
+
afterEach(() => {
document.body.removeChild(initialState)
})
- beforeAll(() => {
- registerHotkeys()
- })
-
beforeEach(() => {
// Make sure the router is reset before each test
- goToRouteMock.mockClear()
+ router.push.mockClear()
// Make sure the file is reset before each test
file = new File({
@@ -61,9 +90,6 @@ describe('HotKeysService testing', () => {
activeStore.setActiveNode(file)
window.OCA = { Files: { Sidebar: { open: () => {}, setActiveTab: () => {} } } }
- // We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
- window.OCP = { Files: { Router: { goToRoute: goToRouteMock, params: {}, query: {} } } }
-
initialState = document.createElement('input')
initialState.setAttribute('type', 'hidden')
initialState.setAttribute('id', 'initial-state-files_trashbin-config')
@@ -71,6 +97,8 @@ describe('HotKeysService testing', () => {
allow_delete: true,
})))
document.body.appendChild(initialState)
+
+ mount(TestComponent)
})
it('Pressing d should open the sidebar once', () => {
@@ -130,13 +158,11 @@ describe('HotKeysService testing', () => {
})
it('Pressing alt+up should go to parent directory', () => {
- expect(goToRouteMock).toHaveBeenCalledTimes(0)
- window.OCP.Files.Router.query = { dir: '/foo/bar' }
-
+ expect(router.push).toHaveBeenCalledTimes(0)
dispatchEvent({ key: 'ArrowUp', code: 'ArrowUp', altKey: true })
- expect(goToRouteMock).toHaveBeenCalledOnce()
- expect(goToRouteMock.mock.calls[0][2].dir).toBe('/foo')
+ expect(router.push).toHaveBeenCalledOnce()
+ expect(router.push.mock.calls[0][0].query?.dir).toBe('/parent')
})
it('Pressing v should toggle grid view', async () => {
diff --git a/apps/files/src/services/HotKeysService.ts b/apps/files/src/composables/useHotKeys.ts
index 1ed369b061b..ff56627b2f9 100644
--- a/apps/files/src/services/HotKeysService.ts
+++ b/apps/files/src/composables/useHotKeys.ts
@@ -4,13 +4,15 @@
*/
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { dirname } from 'path'
+import { useRoute, useRouter } from 'vue-router/composables'
import { action as deleteAction } from '../actions/deleteAction.ts'
import { action as favoriteAction } from '../actions/favoriteAction.ts'
import { action as renameAction } from '../actions/renameAction.ts'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
-import { executeAction } from '../utils/actionUtils.ts'
import { useUserConfigStore } from '../store/userconfig.ts'
+import { useRouteParameters } from './useRouteParameters.ts'
+import { executeAction } from '../utils/actionUtils.ts'
import logger from '../logger.ts'
/**
@@ -18,7 +20,12 @@ import logger from '../logger.ts'
* As much as possible, we try to have all the hotkeys in one place.
* Please make sure to add tests for the hotkeys after adding a new one.
*/
-export const registerHotkeys = function() {
+export function useHotKeys(): void {
+ const userConfigStore = useUserConfigStore()
+ const { directory } = useRouteParameters()
+ const router = useRouter()
+ const route = useRoute()
+
// d opens the sidebar
useHotKey('d', () => executeAction(sidebarAction), {
stop: true,
@@ -57,26 +64,23 @@ export const registerHotkeys = function() {
})
logger.debug('Hotkeys registered')
-}
-
-const goToParentDir = function() {
- const params = window.OCP.Files.Router?.params || {}
- const query = window.OCP.Files.Router?.query || {}
- const currentDir = (query?.dir || '/') as string
- const parentDir = dirname(currentDir)
+ /**
+ * Use the router to go to the parent directory
+ */
+ function goToParentDir() {
+ const dir = dirname(directory.value)
- logger.debug('Navigating to parent directory', { parentDir })
- window.OCP.Files.Router.goToRoute(
- null,
- { ...params },
- { ...query, dir: parentDir },
- )
-}
+ logger.debug('Navigating to parent directory', { dir })
+ router.push({ params: { ...route.params }, query: { ...route.query, dir } })
+ }
-const toggleGridView = function() {
- const userConfigStore = useUserConfigStore()
- const value = userConfigStore?.userConfig?.grid_view
- logger.debug('Toggling grid view', { old: value, new: !value })
- userConfigStore.update('grid_view', !value)
+ /**
+ * Toggle the grid view
+ */
+ function toggleGridView() {
+ const value = userConfigStore.userConfig.grid_view
+ logger.debug('Toggling grid view', { old: value, new: !value })
+ userConfigStore.update('grid_view', !value)
+ }
}
diff --git a/apps/files/src/main.ts b/apps/files/src/main.ts
index 4b8aca9efd4..463ecaf6239 100644
--- a/apps/files/src/main.ts
+++ b/apps/files/src/main.ts
@@ -8,7 +8,6 @@ import { PiniaVuePlugin } from 'pinia'
import Vue from 'vue'
import { getPinia } from './store/index.ts'
-import { registerHotkeys } from './services/HotKeysService.ts'
import FilesApp from './FilesApp.vue'
import router from './router/router'
import RouterService from './services/RouterService'
@@ -40,9 +39,6 @@ if (!window.OCP.Files.Router) {
// Init Pinia store
Vue.use(PiniaVuePlugin)
-// Init HotKeys AFTER pinia is set up
-registerHotkeys()
-
// Init Files App Settings Service
const Settings = new SettingsService()
Object.assign(window.OCA.Files, { Settings })