diff --git a/CHANGELOG.md b/CHANGELOG.md index 55e5978488..251c668351 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ You can also check [on GitHub](https://github.com/nextcloud/news/releases), the ## [25.x.x] ### Changed - added alternative development environment (#2670) +- Implement `j` and `k` keyboards shortcuts for navigating through feed items (#2671) ### Fixed diff --git a/package-lock.json b/package-lock.json index b21cfe49c8..2251bb265b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "lodash": "^4.17.21", "vue": "^2.7.16", "vue-router": "^3.5.3", + "vue-shortkey": "^3.1.7", "vuex": "^3.6.2" }, "devDependencies": { @@ -7183,6 +7184,11 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "node_modules/custom-event-polyfill": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz", + "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==" + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -7623,6 +7629,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.615.tgz", "integrity": "sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng==" }, + "node_modules/element-matches": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/element-matches/-/element-matches-0.1.2.tgz", + "integrity": "sha512-yWh1otcs3OKUWDvu/IxyI36ZI3WNaRZlI0uG/DK6fu0pap0VYZ0J5pEGTk1zakme+hT0OKHwhlHc0N5TJhY6yQ==" + }, "node_modules/elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -19863,6 +19874,15 @@ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.6.5.tgz", "integrity": "sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ==" }, + "node_modules/vue-shortkey": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/vue-shortkey/-/vue-shortkey-3.1.7.tgz", + "integrity": "sha512-Wm/vPXXS+4Wl/LoYpD+cZc0J0HIoVlY8Ep0JLIqqswmAya3XUBtsqKbhzEf9sXo+3rZ5p1YsUyZfXas8XD7YjQ==", + "dependencies": { + "custom-event-polyfill": "^1.0.7", + "element-matches": "^0.1.2" + } + }, "node_modules/vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", @@ -25837,6 +25857,11 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "custom-event-polyfill": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz", + "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==" + }, "data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -26167,6 +26192,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.615.tgz", "integrity": "sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng==" }, + "element-matches": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/element-matches/-/element-matches-0.1.2.tgz", + "integrity": "sha512-yWh1otcs3OKUWDvu/IxyI36ZI3WNaRZlI0uG/DK6fu0pap0VYZ0J5pEGTk1zakme+hT0OKHwhlHc0N5TJhY6yQ==" + }, "elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -35072,6 +35102,15 @@ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.6.5.tgz", "integrity": "sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ==" }, + "vue-shortkey": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/vue-shortkey/-/vue-shortkey-3.1.7.tgz", + "integrity": "sha512-Wm/vPXXS+4Wl/LoYpD+cZc0J0HIoVlY8Ep0JLIqqswmAya3XUBtsqKbhzEf9sXo+3rZ5p1YsUyZfXas8XD7YjQ==", + "requires": { + "custom-event-polyfill": "^1.0.7", + "element-matches": "^0.1.2" + } + }, "vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", diff --git a/package.json b/package.json index 19478f06e6..f432f87111 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "lodash": "^4.17.21", "vue": "^2.7.16", "vue-router": "^3.5.3", + "vue-shortkey": "^3.1.7", "vuex": "^3.6.2" }, "browserslist": [ diff --git a/src/components/feed-display/FeedItemDisplayList.vue b/src/components/feed-display/FeedItemDisplayList.vue index 28134220c6..d62a4ee7e1 100644 --- a/src/components/feed-display/FeedItemDisplayList.vue +++ b/src/components/feed-display/FeedItemDisplayList.vue @@ -28,6 +28,12 @@ + +
@@ -124,6 +130,9 @@ export default Vue.extend({ cfg() { return _.defaults({ ...this.config }, DEFAULT_DISPLAY_LIST_CONFIG) }, + selectedItem() { + return this.$store.getters.selected + }, }, mounted() { this.mounted = true @@ -175,6 +184,55 @@ export default Vue.extend({ return response.sort(this.sort) }, + // Trigger the click event programmatically to benefit from the item handling inside the FeedItemRow component + clickItem(item: FeedItem) { + const refName = 'feedItemRow' + item.id + const ref = this.$refs[refName] + // Make linter happy + const componentInstance = Array.isArray(ref) && ref.length && ref.length > 0 ? ref[0] : undefined + const element = componentInstance ? componentInstance.$el : undefined + + if (element) { + element.click() + + // TODO: This doesn't seem to do a lot in the VirtualScroll component + element.scrollIntoView(true) + // this.$nextTick(() => element.scrollIntoView()) + } + }, + currentIndex(items: FeedItem[]): number { + return this.selectedItem ? items.findIndex((item: FeedItem) => item.id === this.selectedItem.id) || 0 : -1 + }, + // TODO: Make jumpToPreviousItem() highlight the current item + jumpToPreviousItem() { + console.log('Previous item') + const items = this.filterSortedItems() + let currentIndex = this.currentIndex(items) + console.log('currentIndex', currentIndex) + // Prepare to jump to the first item, if none was selected + if (currentIndex === -1) { + currentIndex = 1 + } + // Jump to the previous item + if (currentIndex > 0) { + const previousItem = items[currentIndex - 1] + console.log('previousItem', previousItem) + this.clickItem(previousItem) + } + }, + // TODO: Make jumpToNextItem() highlight the current item + jumpToNextItem() { + console.log('Next item') + const items = this.filterSortedItems() + const currentIndex = this.currentIndex(items) + console.log('currentIndex', currentIndex) + // Jump to the first item, if none was selected, otherwise jump to the next item + if (currentIndex === -1 || (currentIndex < items.length - 1)) { + const nextItem = items[currentIndex + 1] + console.log('nextItem', nextItem) + this.clickItem(nextItem) + } + }, }, }) diff --git a/src/main.js b/src/main.js index 02ccfc7a92..acfc921d09 100644 --- a/src/main.js +++ b/src/main.js @@ -16,6 +16,7 @@ Vue.prototype.OCA = OCA Vue.use(Vuex) Vue.use(VueRouter) +Vue.use(require('vue-shortkey')) Vue.directive('tooltip', Tooltip)