mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-06 02:24:33 +08:00
Started implementation of UI shortcuts system
This commit is contained in:
@ -43,6 +43,7 @@ import popup from "./popup.js"
|
||||
import settingAppColorPicker from "./setting-app-color-picker.js"
|
||||
import settingColorPicker from "./setting-color-picker.js"
|
||||
import shelfSort from "./shelf-sort.js"
|
||||
import shortcuts from "./shortcuts";
|
||||
import sidebar from "./sidebar.js"
|
||||
import sortableList from "./sortable-list.js"
|
||||
import submitOnChange from "./submit-on-change.js"
|
||||
@ -101,6 +102,7 @@ const componentMapping = {
|
||||
"setting-app-color-picker": settingAppColorPicker,
|
||||
"setting-color-picker": settingColorPicker,
|
||||
"shelf-sort": shelfSort,
|
||||
"shortcuts": shortcuts,
|
||||
"sidebar": sidebar,
|
||||
"sortable-list": sortableList,
|
||||
"submit-on-change": submitOnChange,
|
||||
|
119
resources/js/components/shortcuts.js
Normal file
119
resources/js/components/shortcuts.js
Normal file
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* The default mapping of unique id to shortcut key.
|
||||
* @type {Object<string, string>}
|
||||
*/
|
||||
const defaultMap = {
|
||||
"edit": "e",
|
||||
"global_search": "/",
|
||||
};
|
||||
|
||||
function reverseMap(map) {
|
||||
const reversed = {};
|
||||
for (const [key, value] of Object.entries(map)) {
|
||||
reversed[value] = key;
|
||||
}
|
||||
return reversed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends {Component}
|
||||
*/
|
||||
class Shortcuts {
|
||||
|
||||
setup() {
|
||||
this.container = this.$el;
|
||||
this.mapById = defaultMap;
|
||||
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.setupListeners();
|
||||
}
|
||||
|
||||
setupListeners() {
|
||||
window.addEventListener('keydown', event => {
|
||||
|
||||
if (event.target.closest('input, select, textarea')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcutId = this.mapByShortcut[event.key];
|
||||
if (shortcutId) {
|
||||
const wasHandled = this.runShortcut(shortcutId);
|
||||
if (wasHandled) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', event => {
|
||||
if (event.key === '?') {
|
||||
this.hintsShowing ? this.hideHints() : this.showHints();
|
||||
this.hintsShowing = !this.hintsShowing;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the given shortcut, and return a boolean to indicate if the event
|
||||
* was successfully handled by a shortcut action.
|
||||
* @param {String} id
|
||||
* @return {boolean}
|
||||
*/
|
||||
runShortcut(id) {
|
||||
const el = this.container.querySelector(`[data-shortcut="${id}"]`);
|
||||
console.info('Shortcut run', el);
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (el.matches('input, textarea, select')) {
|
||||
el.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (el.matches('a, button')) {
|
||||
el.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
console.error(`Shortcut attempted to be ran for element type that does not have handling setup`, el);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
showHints() {
|
||||
const shortcutEls = this.container.querySelectorAll('[data-shortcut]');
|
||||
for (const shortcutEl of shortcutEls) {
|
||||
const id = shortcutEl.getAttribute('data-shortcut');
|
||||
const key = this.mapById[id];
|
||||
this.showHintLabel(shortcutEl, key);
|
||||
}
|
||||
}
|
||||
|
||||
showHintLabel(targetEl, key) {
|
||||
const targetBounds = targetEl.getBoundingClientRect();
|
||||
const label = document.createElement('div');
|
||||
label.classList.add('shortcut-hint');
|
||||
label.textContent = key;
|
||||
this.container.append(label);
|
||||
|
||||
const labelBounds = label.getBoundingClientRect();
|
||||
|
||||
label.style.insetInlineStart = `${((targetBounds.x + targetBounds.width) - (labelBounds.width + 12))}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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Shortcuts;
|
Reference in New Issue
Block a user