Skip to content
This repository was archived by the owner on Jul 14, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/unreleased/enhancement-make-keybindings-global
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Make keybindings global

We've made keybindings global and introduced a data-attribute to mark elements that need custom keybindings

https://github.com/owncloud/web/pull/7569
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"not OperaMobile > 0"
],
"dependencies": {
"keycode": "^2.2.1",
"vue": "^2.6.10"
},
"devDependencies": {
Expand Down
97 changes: 51 additions & 46 deletions packages/web-app-files/src/components/FilesList/KeyboardActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
</template>

<script lang="ts">
import keycode from 'keycode'
import { bus } from 'web-pkg/src/instance'
import { mapActions, mapState, mapMutations } from 'vuex'
import { defineComponent } from '@vue/composition-api'
Expand All @@ -16,10 +17,10 @@ export default defineComponent({
type: Array,
required: true
},
keybindOnElementIds: {
type: Array,
keybindOnElementId: {
type: String,
required: false,
default: () => ['files-view', 'web-nav-sidebar']
default: 'files-view'
}
},
setup() {
Expand All @@ -40,13 +41,11 @@ export default defineComponent({
},

mounted() {
for (const elementId of this.keybindOnElementIds) {
const element = document.getElementById(elementId)
if (element) {
element.addEventListener('keydown', this.handleSelectionShortcuts, false)
}
const element = document.getElementById(this.keybindOnElementId)
if (element) {
element.addEventListener('keydown', this.handelLocalShortcuts, false)
}
document.addEventListener('keydown', this.handleClipboardShortcuts)
document.addEventListener('keydown', this.handleGlobalShortcuts)

const fileListClickedEvent = bus.subscribe('app.files.list.clicked', this.resetSelectionCursor)
const fileListClickedMetaEvent = bus.subscribe(
Expand All @@ -62,13 +61,11 @@ export default defineComponent({
bus.unsubscribe('app.files.list.clicked', fileListClickedEvent)
bus.unsubscribe('app.files.list.clicked.meta', fileListClickedMetaEvent)
bus.unsubscribe('app.files.list.clicked.shift', fileListClickedShiftEvent)
for (const elementId of this.keybindOnElementIds) {
const element = document.getElementById(elementId)
if (element) {
element.removeEventListener('keydown', this.handleSelectionShortcuts)
}
const element = document.getElementById(this.keybindOnElementId)
if (element) {
element.removeEventListener('keydown', this.handelLocalShortcuts)
}
document.removeEventListener('keydown', this.handleClipboardShortcuts)
document.removeEventListener('keydown', this.handleGlobalShortcuts)
})
},

Expand All @@ -88,42 +85,45 @@ export default defineComponent({
addFileSelection: 'ADD_FILE_SELECTION'
}),

handleSelectionShortcuts(event) {
const key = event.keyCode || event.which
const ctrl = window.navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey
const shift = event.shiftKey
areCustomKeyBindingsEnabled() {
const closestSelectionEl = window.getSelection().focusNode as HTMLElement
if (!closestSelectionEl) return false
let customKeyBindings
try {
customKeyBindings = closestSelectionEl?.closest("[data-custom-key-bindings='true']")
} catch {
customKeyBindings = closestSelectionEl?.parentElement.closest(
"[data-custom-key-bindings='true']"
)
}
if (customKeyBindings) return true
return false
},

this.handleFileSelectionShortcuts(key, shift, ctrl, event)
handelLocalShortcuts(event) {
const key = event.keyCode || event.which
if (key === keycode('space')) return this.handleSpaceAction(event)
},

handleClipboardShortcuts(event) {
handleGlobalShortcuts(event) {
const key = event.keyCode || event.which
const shift = event.shiftKey
const ctrl = window.navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey
const isCopyAction = key === 67
const isPasteAction = key === 86
const isCutAction = key === 88
const isTextSelected = window.getSelection().type === 'Range'

if (this.areCustomKeyBindingsEnabled()) return
if (isTextSelected) return
if (isCopyAction && ctrl) return this.copySelectedFiles()
if (isPasteAction && ctrl) return this.handlePasteAction()
if (isCutAction && ctrl) return this.cutSelectedFiles()
},

handleFileSelectionShortcuts(key, shift, ctrl, event) {
const isUpPressed = key === 38
const isDownPressed = key === 40
const isEscapePressed = key === 27
const isSpacePressed = key === 32
const isAPressed = key === 65

if (isDownPressed && !shift) return this.handleNavigateAction(event)
if (isUpPressed && !shift) return this.handleNavigateAction(event, true)
if (isSpacePressed) return this.handleSpaceAction(event)
if (isEscapePressed) return this.handleEscapeAction()
if (isDownPressed && shift) return this.handleShiftDownAction(event)
if (isUpPressed && shift) return this.handleShiftUpAction(event)
if (isAPressed && ctrl) return this.handleSelectAllAction(event)
if (key === keycode('c') && ctrl) return this.copySelectedFiles()
if (key === keycode('v') && ctrl) return this.handlePasteAction()
if (key === keycode('x') && ctrl) return this.cutSelectedFiles()
if (key === keycode('down') && !shift) return this.handleNavigateAction(event)
if (key === keycode('up') && !shift) return this.handleNavigateAction(event, true)

if (key === keycode('esc')) return this.handleEscapeAction()
if (key === keycode('down') && shift) return this.handleShiftDownAction(event)
if (key === keycode('up') && shift) return this.handleShiftUpAction(event)
if (key === keycode('a') && ctrl) return this.handleSelectAllAction(event)
},

handleNavigateAction(event, up = false) {
Expand All @@ -139,7 +139,6 @@ export default defineComponent({
this.resetFileSelection()
this.addFileSelection({ id: nextId })
this.scrollToResource({ id: nextId })
document.getElementById(this.keybindOnElementId).focus()
},

handleShiftClickAction(resource) {
Expand Down Expand Up @@ -237,15 +236,21 @@ export default defineComponent({
const latestSelectedRow = document.querySelectorAll(
`[data-item-id='${this.latestSelectedId}']`
)[0]
const nextRow = (
previous ? latestSelectedRow.previousSibling : latestSelectedRow.nextSibling
) as HTMLElement
let nextRow
try {
nextRow = (
previous ? latestSelectedRow.previousSibling : latestSelectedRow.nextSibling
) as HTMLElement
} catch {
return -1
}
if (nextRow === null) return -1
return nextRow.getAttribute('data-item-id')
},

getFirstResourceId() {
const firstRow = document.getElementsByClassName('oc-tbody-tr')[0]
if (!firstRow) return -1
return firstRow.getAttribute('data-item-id')
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/web-app-files/src/components/SideBar/SideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
:loading="loading"
:is-header-compact="isSingleResource"
v-bind="$attrs"
data-custom-key-bindings="true"
@beforeDestroy="destroySideBar"
@mounted="focusSideBar"
@fileChanged="focusSideBar"
Expand Down
1 change: 0 additions & 1 deletion packages/web-app-files/src/views/Personal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ export default defineComponent({
'REMOVE_FILES_FROM_SEARCHED',
'REMOVE_FILE_SELECTION'
]),

async fileDropped(fileIdTarget) {
const selected = [...this.selectedResources]
const targetInfo = this.paginatedResources.find((e) => e.id === fileIdTarget)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

exports[`Spaces project view space image should show if given 1`] = `
<div class="space-overview oc-flex">
<keyboard-actions-stub paginatedresources="" keybindonelementids="files-view,web-nav-sidebar"></keyboard-actions-stub>
<keyboard-actions-stub paginatedresources="" keybindonelementid="files-view"></keyboard-actions-stub>
<files-view-wrapper-stub>
<app-bar-stub breadcrumbs="" breadcrumbscontextactionsitems="[object Object]" hasbulkactions="true" hassidebartoggle="true" hasviewoptions="true" showactionsonselection="true"></app-bar-stub>
<div>
Expand Down Expand Up @@ -41,7 +41,7 @@ exports[`Spaces project view space image should show if given 1`] = `

exports[`Spaces project view space readme should show if given 1`] = `
<div class="space-overview oc-flex">
<keyboard-actions-stub paginatedresources="" keybindonelementids="files-view,web-nav-sidebar"></keyboard-actions-stub>
<keyboard-actions-stub paginatedresources="" keybindonelementid="files-view"></keyboard-actions-stub>
<files-view-wrapper-stub>
<app-bar-stub breadcrumbs="" breadcrumbscontextactionsitems="[object Object]" hasbulkactions="true" hassidebartoggle="true" hasviewoptions="true" showactionsonselection="true"></app-bar-stub>
<div>
Expand Down
1 change: 1 addition & 0 deletions packages/web-app-search/src/portals/SearchBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
v-if="availableProviders.length"
id="files-global-search"
:class="{ 'options-visible': optionsVisible && term }"
data-custom-key-bindings="true"
>
<oc-search-bar
id="files-global-search-bar"
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8482,6 +8482,13 @@ __metadata:
languageName: node
linkType: hard

"keycode@npm:^2.2.1":
version: 2.2.1
resolution: "keycode@npm:2.2.1"
checksum: 7a5c775b2410a3b6d9c07a415ecb70639187a965be239d2c11c71079cb1ca2f9dede94b86426f3ece475dc8ae3debf2367531bbcafc0af8c82f157d9a4f7e6cb
languageName: node
linkType: hard

"kind-of@npm:^3.0.2":
version: 3.2.2
resolution: "kind-of@npm:3.2.2"
Expand Down Expand Up @@ -11844,6 +11851,7 @@ __metadata:
jest-mock-axios: ^4.5.0
jest-serializer-vue: ^2.0.2
join-path: ^1.1.1
keycode: ^2.2.1
license-checker-rseidelsohn: ^3.1.0
lodash: ^4.17.21
node-fetch: ^2.6.7
Expand Down