aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorskjnldsv <skjnldsv@protonmail.com>2024-12-13 12:10:47 +0100
committerskjnldsv <skjnldsv@protonmail.com>2024-12-17 10:18:57 +0100
commit76e728eb98bee41badc18399ce574be61c0a59c9 (patch)
tree93aec7e63320b6d7045957ac4aededf7848ea425
parentf94fbc7d09d4039538b1d508c2db2fb50a861b0a (diff)
downloadnextcloud-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.ts1
-rw-r--r--apps/files/src/store/userconfig.ts4
-rw-r--r--apps/files/src/views/FilesList.vue6
-rw-r--r--apps/files/src/views/Settings.vue82
-rw-r--r--apps/systemtags/src/components/SystemTagPicker.vue2
-rw-r--r--apps/systemtags/src/files_actions/inlineSystemTagsAction.ts2
-rw-r--r--apps/systemtags/src/init.ts8
-rw-r--r--apps/systemtags/src/logger.ts2
-rw-r--r--apps/systemtags/src/services/HotKeysService.spec.ts56
-rw-r--r--apps/systemtags/src/services/HotKeysService.ts24
-rw-r--r--apps/systemtags/src/services/api.ts2
-rw-r--r--apps/systemtags/src/services/files.ts5
-rw-r--r--apps/systemtags/src/services/logger.ts10
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()