diff options
author | skjnldsv <skjnldsv@protonmail.com> | 2024-12-13 12:10:47 +0100 |
---|---|---|
committer | skjnldsv <skjnldsv@protonmail.com> | 2024-12-17 10:18:57 +0100 |
commit | 76e728eb98bee41badc18399ce574be61c0a59c9 (patch) | |
tree | 93aec7e63320b6d7045957ac4aededf7848ea425 | |
parent | f94fbc7d09d4039538b1d508c2db2fb50a861b0a (diff) | |
download | nextcloud-server-76e728eb98bee41badc18399ce574be61c0a59c9.tar.gz nextcloud-server-76e728eb98bee41badc18399ce574be61c0a59c9.zip |
feat(systemtags): add systemtag manage keyboard shortcut
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
-rw-r--r-- | apps/files/src/store/active.ts | 1 | ||||
-rw-r--r-- | apps/files/src/store/userconfig.ts | 4 | ||||
-rw-r--r-- | apps/files/src/views/FilesList.vue | 6 | ||||
-rw-r--r-- | apps/files/src/views/Settings.vue | 82 | ||||
-rw-r--r-- | apps/systemtags/src/components/SystemTagPicker.vue | 2 | ||||
-rw-r--r-- | apps/systemtags/src/files_actions/inlineSystemTagsAction.ts | 2 | ||||
-rw-r--r-- | apps/systemtags/src/init.ts | 8 | ||||
-rw-r--r-- | apps/systemtags/src/logger.ts | 2 | ||||
-rw-r--r-- | apps/systemtags/src/services/HotKeysService.spec.ts | 56 | ||||
-rw-r--r-- | apps/systemtags/src/services/HotKeysService.ts | 24 | ||||
-rw-r--r-- | apps/systemtags/src/services/api.ts | 2 | ||||
-rw-r--r-- | apps/systemtags/src/services/files.ts | 5 | ||||
-rw-r--r-- | apps/systemtags/src/services/logger.ts | 10 |
13 files changed, 161 insertions, 43 deletions
diff --git a/apps/files/src/store/active.ts b/apps/files/src/store/active.ts index 2efb823b232..e261e817f3d 100644 --- a/apps/files/src/store/active.ts +++ b/apps/files/src/store/active.ts @@ -11,7 +11,6 @@ import { getNavigation } from '@nextcloud/files' import { subscribe } from '@nextcloud/event-bus' import logger from '../logger.ts' -import type { set } from 'lodash' export const useActiveStore = function(...args) { const store = defineStore('active', { diff --git a/apps/files/src/store/userconfig.ts b/apps/files/src/store/userconfig.ts index 6fd2ad886bc..ffe07a91bab 100644 --- a/apps/files/src/store/userconfig.ts +++ b/apps/files/src/store/userconfig.ts @@ -27,6 +27,8 @@ export const useUserConfigStore = function(...args) { actions: { /** * Update the user config local store + * @param key + * @param value */ onUpdate(key: string, value: boolean) { Vue.set(this.userConfig, key, value) @@ -34,6 +36,8 @@ export const useUserConfigStore = function(...args) { /** * Update the user config local store AND on server side + * @param key + * @param value */ async update(key: string, value: boolean) { await axios.put(generateUrl('/apps/files/api/v1/config/' + key), { diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 46a84faf188..af9cb45d60d 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -222,6 +222,9 @@ export default defineComponent({ }, setup() { + const { currentView } = useNavigation() + const { directory, fileId } = useRouteParameters() + const fileListWidth = useFileListWidth() const filesStore = useFilesStore() const filtersStore = useFiltersStore() const pathsStore = usePathsStore() @@ -229,9 +232,6 @@ export default defineComponent({ const uploaderStore = useUploaderStore() const userConfigStore = useUserConfigStore() const viewConfigStore = useViewConfigStore() - const { currentView } = useNavigation() - const fileListWidth = useFileListWidth() - const { directory, fileId } = useRouteParameters() const enableGridView = (loadState('core', 'config', [])['enable_non-accessible_features'] ?? true) const forbiddenCharacters = loadState<string[]>('files', 'forbiddenCharacters', []) diff --git a/apps/files/src/views/Settings.vue b/apps/files/src/views/Settings.vue index 0ff7a56e37d..ec012ed64ae 100644 --- a/apps/files/src/views/Settings.vue +++ b/apps/files/src/views/Settings.vue @@ -90,31 +90,41 @@ <h3>{{ t('files', 'Actions') }}</h3> <dl> <div> - <dt class="shortcut-key"><kbd>a</kbd></dt> + <dt class="shortcut-key"> + <kbd>a</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Open the actions menu for a file') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>F2</kbd></dt> + <dt class="shortcut-key"> + <kbd>F2</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Rename a file') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>Del</kbd></dt> + <dt class="shortcut-key"> + <kbd>Del</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Delete a file') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>s</kbd></dt> + <dt class="shortcut-key"> + <kbd>s</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Favorite or remove a file from favorites') }} </dd> </div> - <div> - <dt class="shortcut-key"><kbd>t</kbd></dt> + <div v-if="isSystemtagsEnabled"> + <dt class="shortcut-key"> + <kbd>t</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Manage tags for a file') }} </dd> @@ -124,25 +134,33 @@ <h3>{{ t('files', 'Selection') }}</h3> <dl> <div> - <dt class="shortcut-key"><kbd>Ctrl</kbd> + <kbd>A</kbd></dt> + <dt class="shortcut-key"> + <kbd>Ctrl</kbd> + <kbd>A</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Select all files') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>ESC</kbd></dt> + <dt class="shortcut-key"> + <kbd>ESC</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Deselect all files') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>Ctrl</kbd> + <kbd>Space</kbd></dt> + <dt class="shortcut-key"> + <kbd>Ctrl</kbd> + <kbd>Space</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Select or deselect a file') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>Ctrl</kbd> + <kbd>Shift</kbd> <span>+ <kbd>Space</kbd></span></dt> + <dt class="shortcut-key"> + <kbd>Ctrl</kbd> + <kbd>Shift</kbd> <span>+ <kbd>Space</kbd></span> + </dt> <dd class="shortcut-description"> {{ t('files', 'Select a range of files') }} </dd> @@ -152,31 +170,41 @@ <h3>{{ t('files', 'Navigation') }}</h3> <dl> <div> - <dt class="shortcut-key"><kbd>↑</kbd></dt> + <dt class="shortcut-key"> + <kbd>Alt</kbd> + <kbd>↑</kbd> + </dt> <dd class="shortcut-description"> - {{ t('files', 'Navigate to the file above') }} + {{ t('files', 'Navigate to the parent folder') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>Alt</kbd> + <kbd>↑</kbd></dt> + <dt class="shortcut-key"> + <kbd>↑</kbd> + </dt> <dd class="shortcut-description"> - {{ t('files', 'Navigate to the parent folder') }} + {{ t('files', 'Navigate to the file above') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>↓</kbd></dt> + <dt class="shortcut-key"> + <kbd>↓</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Navigate to the file below') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>←</kbd></dt> + <dt class="shortcut-key"> + <kbd>←</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Navigate to the file on the left (in grid mode)') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>→</kbd></dt> + <dt class="shortcut-key"> + <kbd>→</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Navigate to the file on the right (in grid mode)') }} </dd> @@ -186,19 +214,25 @@ <h3>{{ t('files', 'View') }}</h3> <dl> <div> - <dt class="shortcut-key"><kbd>V</kbd></dt> + <dt class="shortcut-key"> + <kbd>V</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Toggle the grid view') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>D</kbd></dt> + <dt class="shortcut-key"> + <kbd>D</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Open the sidebar for a file') }} </dd> </div> <div> - <dt class="shortcut-key"><kbd>?</kbd></dt> + <dt class="shortcut-key"> + <kbd>?</kbd> + </dt> <dd class="shortcut-description"> {{ t('files', 'Show those shortcuts') }} </dd> @@ -209,12 +243,12 @@ </template> <script> +import { getCapabilities } from '@nextcloud/capabilities' +import Clipboard from 'vue-material-design-icons/ContentCopy.vue' import NcAppSettingsDialog from '@nextcloud/vue/dist/Components/NcAppSettingsDialog.js' import NcAppSettingsSection from '@nextcloud/vue/dist/Components/NcAppSettingsSection.js' import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' -import Clipboard from 'vue-material-design-icons/ContentCopy.vue' import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js' -import Setting from '../components/Setting.vue' import { generateRemoteUrl, generateUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' @@ -222,7 +256,9 @@ import { loadState } from '@nextcloud/initial-state' import { showError, showSuccess } from '@nextcloud/dialogs' import { t } from '@nextcloud/l10n' import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js' + import { useUserConfigStore } from '../store/userconfig.ts' +import Setting from '../components/Setting.vue' export default { name: 'Settings', @@ -244,7 +280,9 @@ export default { setup() { const userConfigStore = useUserConfigStore() + const isSystemtagsEnabled = getCapabilities()?.systemtags?.enabled === true return { + isSystemtagsEnabled, userConfigStore, t, } diff --git a/apps/systemtags/src/components/SystemTagPicker.vue b/apps/systemtags/src/components/SystemTagPicker.vue index 652ea262409..e17fb1a7bc5 100644 --- a/apps/systemtags/src/components/SystemTagPicker.vue +++ b/apps/systemtags/src/components/SystemTagPicker.vue @@ -151,7 +151,7 @@ import TagIcon from 'vue-material-design-icons/Tag.vue' import { createTag, fetchTag, fetchTags, getTagObjects, setTagObjects, updateTag } from '../services/api' import { getNodeSystemTags, setNodeSystemTags } from '../utils' import { elementColor, invertTextColor, isDarkModeEnabled } from '../utils/colorUtils' -import logger from '../services/logger' +import logger from '../logger.ts' const debounceUpdateTag = debounce(updateTag, 500) const mainBackgroundColor = getComputedStyle(document.body) diff --git a/apps/systemtags/src/files_actions/inlineSystemTagsAction.ts b/apps/systemtags/src/files_actions/inlineSystemTagsAction.ts index cc6cb55c632..cab2f61906b 100644 --- a/apps/systemtags/src/files_actions/inlineSystemTagsAction.ts +++ b/apps/systemtags/src/files_actions/inlineSystemTagsAction.ts @@ -12,7 +12,7 @@ import '../css/fileEntryInlineSystemTags.scss' import { elementColor, isDarkModeEnabled } from '../utils/colorUtils' import { fetchTags } from '../services/api' import { getNodeSystemTags } from '../utils' -import logger from '../services/logger' +import logger from '../logger.ts' // Init tag cache const cache: TagWithId[] = [] diff --git a/apps/systemtags/src/init.ts b/apps/systemtags/src/init.ts index 54ba0d604ee..fdf645e6044 100644 --- a/apps/systemtags/src/init.ts +++ b/apps/systemtags/src/init.ts @@ -3,10 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import { registerDavProperty, registerFileAction } from '@nextcloud/files' +import { registerHotkeys } from './services/HotKeysService' +import { registerSystemTagsView } from './files_views/systemtagsView' + import { action as bulkSystemTagsAction } from './files_actions/bulkSystemTagsAction' import { action as inlineSystemTagsAction } from './files_actions/inlineSystemTagsAction' import { action as openInFilesAction } from './files_actions/openInFilesAction' -import { registerSystemTagsView } from './files_views/systemtagsView' registerDavProperty('nc:system-tags') registerFileAction(bulkSystemTagsAction) @@ -14,3 +16,7 @@ registerFileAction(inlineSystemTagsAction) registerFileAction(openInFilesAction) registerSystemTagsView() + +document.addEventListener('DOMContentLoaded', () => { + registerHotkeys() +}) diff --git a/apps/systemtags/src/logger.ts b/apps/systemtags/src/logger.ts index fa02888bdb8..5a37e3874ed 100644 --- a/apps/systemtags/src/logger.ts +++ b/apps/systemtags/src/logger.ts @@ -5,7 +5,7 @@ import { getLoggerBuilder } from '@nextcloud/logger' -export const logger = getLoggerBuilder() +export default getLoggerBuilder() .setApp('systemtags') .detectUser() .build() diff --git a/apps/systemtags/src/services/HotKeysService.spec.ts b/apps/systemtags/src/services/HotKeysService.spec.ts new file mode 100644 index 00000000000..2e329444f54 --- /dev/null +++ b/apps/systemtags/src/services/HotKeysService.spec.ts @@ -0,0 +1,56 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { describe, it, vi, expect, beforeEach, beforeAll } from 'vitest' +import { File, Permission, View } from '@nextcloud/files' + +import { getPinia } from '../../../files/src/store/index.ts' +import { useActiveStore } from '../../../files/src/store/active.ts' + +import { action as bulkSystemTagsAction } from '../files_actions/bulkSystemTagsAction.ts' +import { registerHotkeys } from './HotKeysService.ts' + +let file: File +const view = { + id: 'files', + name: 'Files', +} as View + +vi.mock('../files_actions/bulkSystemTagsAction.ts', { spy: true }) + +describe('HotKeysService testing', () => { + const activeStore = useActiveStore(getPinia()) + + beforeAll(() => { + registerHotkeys() + }) + + beforeEach(() => { + // Make sure the file is reset before each test + file = new File({ + id: 1, + source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', + owner: 'admin', + mime: 'text/plain', + permissions: Permission.ALL, + }) + + // Setting the view first as it reset the active node + activeStore.onChangedView(view) + activeStore.setActiveNode(file) + }) + + it('Pressing t should open the tag management dialog', () => { + window.dispatchEvent(new KeyboardEvent('keydown', { key: 't', code: 'KeyT' })) + + // Modifier keys should not trigger the action + window.dispatchEvent(new KeyboardEvent('keydown', { key: 't', code: 'KeyT', ctrlKey: true })) + window.dispatchEvent(new KeyboardEvent('keydown', { key: 't', code: 'KeyT', altKey: true })) + window.dispatchEvent(new KeyboardEvent('keydown', { key: 't', code: 'KeyT', shiftKey: true })) + window.dispatchEvent(new KeyboardEvent('keydown', { key: 't', code: 'KeyT', metaKey: true })) + + expect(bulkSystemTagsAction.enabled).toHaveReturnedWith(true) + expect(bulkSystemTagsAction.exec).toHaveBeenCalledOnce() + }) +}) diff --git a/apps/systemtags/src/services/HotKeysService.ts b/apps/systemtags/src/services/HotKeysService.ts new file mode 100644 index 00000000000..3661531fcb5 --- /dev/null +++ b/apps/systemtags/src/services/HotKeysService.ts @@ -0,0 +1,24 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js' + +import { action as manageTagAction } from '../files_actions/bulkSystemTagsAction.ts' +import { executeAction } from '../../../files/src/utils/actionUtils.ts' +import logger from '../logger.ts' + +/** + * This register the hotkeys for the Files app. + * 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() { + // t opens the tag management dialog + useHotKey('t', () => executeAction(manageTagAction), { + stop: true, + prevent: true, + }) + + logger.debug('Hotkeys registered') +} diff --git a/apps/systemtags/src/services/api.ts b/apps/systemtags/src/services/api.ts index c0b39fde6bd..ef44fa82dae 100644 --- a/apps/systemtags/src/services/api.ts +++ b/apps/systemtags/src/services/api.ts @@ -12,7 +12,7 @@ import { t } from '@nextcloud/l10n' import { davClient } from './davClient.js' import { formatTag, parseIdFromLocation, parseTags } from '../utils' -import { logger } from '../logger.js' +import logger from '../logger.ts' import { emit } from '@nextcloud/event-bus' export const fetchTagsPayload = `<?xml version="1.0"?> diff --git a/apps/systemtags/src/services/files.ts b/apps/systemtags/src/services/files.ts index 8759a99d560..d61b7ff4825 100644 --- a/apps/systemtags/src/services/files.ts +++ b/apps/systemtags/src/services/files.ts @@ -7,10 +7,11 @@ import type { FileStat, ResponseDataDetailed } from 'webdav' import type { ServerTagWithId, Tag, TagWithId } from '../types.js' import { t } from '@nextcloud/l10n' -import { davClient } from './davClient.js' + import { createTag, fetchTagsPayload } from './api.js' +import { davClient } from './davClient.js' import { formatTag, parseTags } from '../utils.js' -import { logger } from '../logger.js' +import logger from '../logger.ts' export const fetchTagsForFile = async (fileId: number): Promise<TagWithId[]> => { const path = '/systemtags-relations/files/' + fileId diff --git a/apps/systemtags/src/services/logger.ts b/apps/systemtags/src/services/logger.ts deleted file mode 100644 index 8cce9f5f92e..00000000000 --- a/apps/systemtags/src/services/logger.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import { getLoggerBuilder } from '@nextcloud/logger' - -export default getLoggerBuilder() - .setApp('systemtags') - .detectUser() - .build() |