X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/b4cb375a02d2c81ac72ce9002d9f9ee2ab7f3922..refs/pull/3918/head:/resources/js/components/shortcuts.js diff --git a/resources/js/components/shortcuts.js b/resources/js/components/shortcuts.js index 799f0e629..a87213b2e 100644 --- a/resources/js/components/shortcuts.js +++ b/resources/js/components/shortcuts.js @@ -1,11 +1,4 @@ -/** - * The default mapping of unique id to shortcut key. - * @type {Object} - */ -const defaultMap = { - "edit": "e", - "global_search": "/", -}; +import {Component} from "./component"; function reverseMap(map) { const reversed = {}; @@ -15,21 +8,17 @@ function reverseMap(map) { return reversed; } -/** - * @extends {Component} - */ -class Shortcuts { + +export class Shortcuts extends Component { setup() { this.container = this.$el; - this.mapById = defaultMap; + this.mapById = JSON.parse(this.$opts.keyMap); this.mapByShortcut = reverseMap(this.mapById); this.hintsShowing = false; - // TODO - Allow custom key maps - // TODO - Allow turning off shortcuts - // TODO - Roll out to interface elements - // TODO - Hide hints on focus, scroll, click + + this.hideHints = this.hideHints.bind(this); this.setupListeners(); } @@ -41,23 +30,38 @@ class Shortcuts { return; } - const shortcutId = this.mapByShortcut[event.key]; - if (shortcutId) { - const wasHandled = this.runShortcut(shortcutId); - if (wasHandled) { - event.preventDefault(); - } - } + this.handleShortcutPress(event); }); window.addEventListener('keydown', event => { if (event.key === '?') { this.hintsShowing ? this.hideHints() : this.showHints(); - this.hintsShowing = !this.hintsShowing; } }); } + /** + * @param {KeyboardEvent} event + */ + handleShortcutPress(event) { + + const keys = [ + event.ctrlKey ? 'Ctrl' : '', + event.metaKey ? 'Cmd' : '', + event.key, + ]; + + const combo = keys.filter(s => Boolean(s)).join(' + '); + + const shortcutId = this.mapByShortcut[combo]; + if (shortcutId) { + const wasHandled = this.runShortcut(shortcutId); + if (wasHandled) { + event.preventDefault(); + } + } + } + /** * Run the given shortcut, and return a boolean to indicate if the event * was successfully handled by a shortcut action. @@ -66,7 +70,6 @@ class Shortcuts { */ runShortcut(id) { const el = this.container.querySelector(`[data-shortcut="${id}"]`); - console.info('Shortcut run', el); if (!el) { return false; } @@ -81,39 +84,79 @@ class Shortcuts { return true; } + if (el.matches('div[tabindex]')) { + el.click(); + el.focus(); + return true; + } + console.error(`Shortcut attempted to be ran for element type that does not have handling setup`, el); return false; } showHints() { + const wrapper = document.createElement('div'); + wrapper.classList.add('shortcut-container'); + this.container.append(wrapper); + const shortcutEls = this.container.querySelectorAll('[data-shortcut]'); + const displayedIds = new Set(); for (const shortcutEl of shortcutEls) { const id = shortcutEl.getAttribute('data-shortcut'); + if (displayedIds.has(id)) { + continue; + } + const key = this.mapById[id]; - this.showHintLabel(shortcutEl, key); + this.showHintLabel(shortcutEl, key, wrapper); + displayedIds.add(id); } + + window.addEventListener('scroll', this.hideHints); + window.addEventListener('focus', this.hideHints); + window.addEventListener('blur', this.hideHints); + window.addEventListener('click', this.hideHints); + + this.hintsShowing = true; } - showHintLabel(targetEl, key) { + /** + * @param {Element} targetEl + * @param {String} key + * @param {Element} wrapper + */ + showHintLabel(targetEl, key, wrapper) { const targetBounds = targetEl.getBoundingClientRect(); + const label = document.createElement('div'); label.classList.add('shortcut-hint'); label.textContent = key; - this.container.append(label); + + const linkage = document.createElement('div'); + linkage.classList.add('shortcut-linkage'); + linkage.style.left = targetBounds.x + 'px'; + linkage.style.top = targetBounds.y + 'px'; + linkage.style.width = targetBounds.width + 'px'; + linkage.style.height = targetBounds.height + 'px'; + + wrapper.append(label, linkage); const labelBounds = label.getBoundingClientRect(); - label.style.insetInlineStart = `${((targetBounds.x + targetBounds.width) - (labelBounds.width + 12))}px`; + label.style.insetInlineStart = `${((targetBounds.x + targetBounds.width) - (labelBounds.width + 6))}px`; label.style.insetBlockStart = `${(targetBounds.y + (targetBounds.height - labelBounds.height) / 2)}px`; } hideHints() { - const hints = this.container.querySelectorAll('.shortcut-hint'); - for (const hint of hints) { - hint.remove(); - } - } -} + const wrapper = this.container.querySelector('.shortcut-container'); + wrapper.remove(); + + window.removeEventListener('scroll', this.hideHints); + window.removeEventListener('focus', this.hideHints); + window.removeEventListener('blur', this.hideHints); + window.removeEventListener('click', this.hideHints); -export default Shortcuts; \ No newline at end of file + this.hintsShowing = false; + } +} \ No newline at end of file