function scrollto(target, scroll) { var padding = 10 var targetRect = target.getBoundingClientRect() var scrollRect = scroll.getBoundingClientRect() // target var relativeOffset = targetRect.y - scrollRect.y var absoluteOffset = relativeOffset + scroll.scrollTop if (padding <= relativeOffset && relativeOffset + targetRect.height <= scrollRect.height - padding) return var newPos = scroll.scrollTop if (relativeOffset < padding) { newPos = absoluteOffset - padding } else { newPos = absoluteOffset - scrollRect.height + targetRect.height + padding } scroll.scrollTop = Math.round(newPos) } var helperFunctions = { // navigation helper, navigate relative to selected item navigateToItem: function(relativePosition) { if (vm.itemSelected == null) { // if no item is selected, select first if (vm.items.length !== 0) vm.itemSelected = vm.items[0].id return } var itemPosition = vm.items.findIndex(function(x) { return x.id === vm.itemSelected }) if (itemPosition === -1) { if (vm.items.length !== 0) vm.itemSelected = vm.items[0].id return } var newPosition = itemPosition + relativePosition if (newPosition < 0 || newPosition >= vm.items.length) return vm.itemSelected = vm.items[newPosition].id vm.$nextTick(function() { var scroll = document.querySelector('#item-list-scroll') var handle = scroll.querySelector('input[type=radio]:checked') var target = handle && handle.parentElement if (target && scroll) scrollto(target, scroll) }) }, // navigation helper, navigate relative to selected feed navigateToFeed: function(relativePosition) { var navigationList = Array.from(document.querySelectorAll('#col-feed-list input[name=feed]')) .filter(function(r) { return r.offsetParent !== null && r.value !== 'folder:null' }) .map(function(r) { return r.value }) var currentFeedPosition = navigationList.indexOf(vm.feedSelected) if (currentFeedPosition == -1) { vm.feedSelected = '' return } var newPosition = currentFeedPosition+relativePosition if (newPosition < 0 || newPosition >= navigationList.length) return vm.feedSelected = navigationList[newPosition] vm.$nextTick(function() { var scroll = document.querySelector('#feed-list-scroll') var handle = scroll.querySelector('input[type=radio]:checked') var target = handle && handle.parentElement if (target && scroll) scrollto(target, scroll) }) }, scrollContent: function(direction) { var padding = 40 var scroll = document.querySelector('.content') if (!scroll) return var height = scroll.getBoundingClientRect().height var newpos = scroll.scrollTop + (height - padding) * direction if (typeof scroll.scrollTo == 'function') { scroll.scrollTo({top: newpos, left: 0, behavior: 'smooth'}) } else { scroll.scrollTop = newpos } } } var shortcutFunctions = { openItemLink: function() { if (vm.itemSelectedDetails && vm.itemSelectedDetails.link) { window.open(vm.itemSelectedDetails.link, '_blank') } }, toggleReadability: function() { vm.toggleReadability() }, toggleItemRead: function() { if (vm.itemSelected != null) { vm.toggleItemRead(vm.itemSelectedDetails) } }, markAllRead: function() { // same condition as 'Mark all read button' if (vm.filterSelected == 'unread'){ vm.markItemsRead() } }, toggleItemStarred: function() { if (vm.itemSelected != null) { vm.toggleItemStarred(vm.itemSelectedDetails) } }, focusSearch: function() { document.getElementById("searchbar").focus() }, nextItem(){ helperFunctions.navigateToItem(+1) }, previousItem() { helperFunctions.navigateToItem(-1) }, nextFeed(){ helperFunctions.navigateToFeed(+1) }, previousFeed() { helperFunctions.navigateToFeed(-1) }, scrollForward: function() { helperFunctions.scrollContent(+1) }, scrollBackward: function() { helperFunctions.scrollContent(-1) }, showAll() { vm.filterSelected = '' }, showUnread() { vm.filterSelected = 'unread' }, showStarred() { vm.filterSelected = 'starred' }, } // If you edit, make sure you update the help modal var keybindings = { "o": shortcutFunctions.openItemLink, "i": shortcutFunctions.toggleReadability, "r": shortcutFunctions.toggleItemRead, "R": shortcutFunctions.markAllRead, "s": shortcutFunctions.toggleItemStarred, "/": shortcutFunctions.focusSearch, "j": shortcutFunctions.nextItem, "k": shortcutFunctions.previousItem, "l": shortcutFunctions.nextFeed, "h": shortcutFunctions.previousFeed, "f": shortcutFunctions.scrollForward, "b": shortcutFunctions.scrollBackward, "1": shortcutFunctions.showUnread, "2": shortcutFunctions.showStarred, "3": shortcutFunctions.showAll, } var codebindings = { "KeyO": shortcutFunctions.openItemLink, "KeyI": shortcutFunctions.toggleReadability, //"r": shortcutFunctions.toggleItemRead, //"KeyR": shortcutFunctions.markAllRead, "KeyS": shortcutFunctions.toggleItemStarred, "Slash": shortcutFunctions.focusSearch, "KeyJ": shortcutFunctions.nextItem, "KeyK": shortcutFunctions.previousItem, "KeyL": shortcutFunctions.nextFeed, "KeyH": shortcutFunctions.previousFeed, "KeyF": shortcutFunctions.scrollForward, "KeyB": shortcutFunctions.scrollBackward, "Digit1": shortcutFunctions.showUnread, "Digit2": shortcutFunctions.showStarred, "Digit3": shortcutFunctions.showAll, } function isTextBox(element) { var tagName = element.tagName.toLowerCase() // Input elements that aren't text var inputBlocklist = ['button','checkbox','color','file','hidden','image','radio','range','reset','search','submit'] return tagName === 'textarea' || ( tagName === 'input' && inputBlocklist.indexOf(element.getAttribute('type').toLowerCase()) == -1 ) } document.addEventListener('keydown',function(event) { // Ignore while focused on text or // when using modifier keys (to not clash with browser behaviour) if (isTextBox(event.target) || event.metaKey || event.ctrlKey || event.altKey) { return } var keybindFunction = keybindings[event.key] || codebindings[event.code] if (keybindFunction) { event.preventDefault() keybindFunction() } })