]> BookStack Code Mirror - bookstack/blobdiff - resources/js/components/shortcuts.js
Opensearch: Fixed XML declaration when php short tags enabled
[bookstack] / resources / js / components / shortcuts.js
index a3cca5ddc8b5fc3ef74111c87dec23efa4a6f980..8bf26fbb51c1a680587eaf89f348f90853ea7ea6 100644 (file)
@@ -1,34 +1,4 @@
-/**
- * The default mapping of unique id to shortcut key.
- * @type {Object<string, string>}
- */
-const defaultMap = {
-    // Header actions
-    "home": "1",
-    "shelves_view": "2",
-    "books_view": "3",
-    "settings_view": "4",
-    "favorites_view": "5",
-    "profile_view": "6",
-    "global_search": "/",
-    "logout": "0",
-
-    // Generic actions
-    "edit": "e",
-    "new": "n",
-    "copy": "c",
-    "delete": "d",
-    "favorite": "f",
-    "export": "x",
-    "sort": "s",
-    "permissions": "p",
-    "move": "m",
-    "revisions": "r",
-
-    // Navigation
-    "next": "ArrowRight",
-    "prev": "ArrowLeft",
-};
+import {Component} from './component';
 
 function reverseMap(map) {
     const reversed = {};
@@ -38,46 +8,59 @@ 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;
 
         this.hideHints = this.hideHints.bind(this);
-        // TODO - Allow custom key maps
-        // TODO - Allow turning off shortcuts
+        this.hintAbortController = null;
 
         this.setupListeners();
     }
 
     setupListeners() {
         window.addEventListener('keydown', event => {
-
-            if (event.target.closest('input, select, textarea')) {
+            if (event.target.closest('input, select, textarea, .cm-editor, .editor-container')) {
                 return;
             }
 
-            const shortcutId = this.mapByShortcut[event.key];
-            if (shortcutId) {
-                const wasHandled = this.runShortcut(shortcutId);
-                if (wasHandled) {
-                    event.preventDefault();
+            if (event.key === '?') {
+                if (this.hintsShowing) {
+                    this.hideHints();
+                } else {
+                    this.showHints();
                 }
+                return;
             }
+
+            this.handleShortcutPress(event);
         });
+    }
 
-        window.addEventListener('keydown', event => {
-            if (event.key === '?') {
-                this.hintsShowing ? this.hideHints() : this.showHints();
+    /**
+     * @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();
             }
-        });
+        }
     }
 
     /**
@@ -88,7 +71,6 @@ class Shortcuts {
      */
     runShortcut(id) {
         const el = this.container.querySelector(`[data-shortcut="${id}"]`);
-        console.info('Shortcut run', el);
         if (!el) {
             return false;
         }
@@ -109,7 +91,7 @@ class Shortcuts {
             return true;
         }
 
-        console.error(`Shortcut attempted to be ran for element type that does not have handling setup`, el);
+        console.error('Shortcut attempted to be ran for element type that does not have handling setup', el);
 
         return false;
     }
@@ -132,10 +114,12 @@ class Shortcuts {
             displayedIds.add(id);
         }
 
-        window.addEventListener('scroll', this.hideHints);
-        window.addEventListener('focus', this.hideHints);
-        window.addEventListener('blur', this.hideHints);
-        window.addEventListener('click', this.hideHints);
+        this.hintAbortController = new AbortController();
+        const signal = this.hintAbortController.signal;
+        window.addEventListener('scroll', this.hideHints, {signal});
+        window.addEventListener('focus', this.hideHints, {signal});
+        window.addEventListener('blur', this.hideHints, {signal});
+        window.addEventListener('click', this.hideHints, {signal});
 
         this.hintsShowing = true;
     }
@@ -154,10 +138,10 @@ class Shortcuts {
 
         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';
+        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);
 
@@ -170,14 +154,8 @@ class Shortcuts {
     hideHints() {
         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);
-
+        this.hintAbortController?.abort();
         this.hintsShowing = false;
     }
-}
 
-export default Shortcuts;
\ No newline at end of file
+}