1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-01-06 01:50:26 +00:00

Common shortcuts

p, Alt + ←: Previous hypha
n, Alt + →: Next hypha
s, Alt + ↑: Parent hypha

gh: Home hypha
gl: List of hyphae
gr: Recent changes
gu: User's hypha

g[1-9]: First 9 header links

Follow-up to the previous commit message: actually, you can press them
simultaneously too, just tested it. Ilya approves.
This commit is contained in:
handlerug 2021-06-13 13:48:18 +07:00
parent aeb05336e9
commit 3a2678df4d
No known key found for this signature in database
GPG Key ID: 38009F0605051491

View File

@ -1,94 +1,134 @@
const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);
(() => {
const $ = document.querySelector.bind(document);
const $$ = (...args) => Array.prototype.slice.call(document.querySelectorAll(...args));
function keyEventToShortcut(event) {
let elideShift = event.key.toUpperCase() === event.key && event.shiftKey;
return (event.ctrlKey ? 'Ctrl+' : '') +
(event.altKey ? 'Alt+' : '') +
(event.metaKey ? 'Meta+' : '') +
(!elideShift && event.shiftKey ? 'Shift+' : '') +
event.key;
}
function isTextField(element) {
let name = element.nodeName.toLowerCase();
return name === 'textarea' ||
name === 'select' ||
(name === 'input' && !['submit', 'reset', 'checkbox', 'radio'].includes(element.type)) ||
element.isContentEditable;
}
class ShortcutHandler {
constructor(element, filter = () => {}) {
this.element = element;
this.map = {};
this.active = this.map;
this.filter = filter;
this.timeout = null;
this.handleKeyDown = this.handleKeyDown.bind(this);
this.resetActive = this.resetActive.bind(this);
this.addEventListeners();
function keyEventToShortcut(event) {
let elideShift = event.key.toUpperCase() === event.key && event.shiftKey;
return (event.ctrlKey ? 'Ctrl+' : '') +
(event.altKey ? 'Alt+' : '') +
(event.metaKey ? 'Meta+' : '') +
(!elideShift && event.shiftKey ? 'Shift+' : '') +
event.key;
}
addEventListeners() {
this.element.addEventListener('keydown', this.handleKeyDown);
function isTextField(element) {
let name = element.nodeName.toLowerCase();
return name === 'textarea' ||
name === 'select' ||
(name === 'input' && !['submit', 'reset', 'checkbox', 'radio'].includes(element.type)) ||
element.isContentEditable;
}
add(text, action) {
let shortcuts = text.split(',').map(shortcut => shortcut.trim().split(' '));
class ShortcutHandler {
constructor(element, filter = () => {}) {
this.element = element;
this.map = {};
this.active = this.map;
this.filter = filter;
this.timeout = null;
for (let shortcut of shortcuts) {
let node = this.map;
for (let key of shortcut) {
if (!node[key]) {
node[key] = {};
}
node = node[key];
if (node.action) {
delete node.action;
this.handleKeyDown = this.handleKeyDown.bind(this);
this.resetActive = this.resetActive.bind(this);
this.addEventListeners();
}
addEventListeners() {
this.element.addEventListener('keydown', this.handleKeyDown);
}
add(text, action, description = null) {
let shortcuts = text.split(',').map(shortcut => shortcut.trim().split(' '));
for (let shortcut of shortcuts) {
let node = this.map;
for (let key of shortcut) {
if (!node[key]) {
node[key] = {};
}
node = node[key];
if (node.action) {
delete node.action;
delete node.shortcut;
delete node.description;
}
}
node.action = action;
node.shortcut = shortcut;
node.description = description;
}
}
handleKeyDown(event) {
if (event.defaultPrevented) return;
if (['Control', 'Alt', 'Shift', 'Meta'].includes(event.key)) return;
if (!this.filter(event)) return;
let shortcut = keyEventToShortcut(event);
if (!this.active[shortcut]) {
this.resetActive();
return;
}
node.action = action;
this.active = this.active[shortcut];
if (this.active.action) {
this.active.action(event);
this.resetActive();
return;
}
if (this.timeout) clearTimeout(this.timeout);
this.timeout = window.setTimeout(this.resetActive, 1500);
}
resetActive() {
this.active = this.map;
if (this.timeout) {
clearTimeout(this.timeout)
this.timeout = null;
}
}
}
handleKeyDown(event) {
if (event.defaultPrevented) return;
if (['Control', 'Alt', 'Shift', 'Meta'].includes(event.key)) return;
if (!this.filter(event)) return;
let shortcut = keyEventToShortcut(event);
if (!this.active[shortcut]) {
this.resetActive();
return;
}
this.active = this.active[shortcut];
if (this.active.action) {
this.active.action(event);
this.resetActive();
return;
}
if (this.timeout) clearTimeout(this.timeout);
this.timeout = window.setTimeout(this.resetActive, 1500);
function bindElementFactory(handler) {
return (shortcut, element, ...other) => {
element = typeof element === 'string' ? $(element) : element;
if (!element) return;
handler.add(shortcut, () => {
if (isTextField(element)) {
element.focus();
} else {
element.click();
}
}, ...other);
};
}
resetActive() {
this.active = this.map;
if (this.timeout) {
clearTimeout(this.timeout)
this.timeout = null;
}
function bindLinkFactory(handler) {
return (shortcut, link, ...other) => handler.add(shortcut, () => window.location.href = link, ...other);
}
}
let notFormField = event => !(event.target instanceof Node && isTextField(event.target));
let globalShortcuts = new ShortcutHandler(document, notFormField);
window.addEventListener('load', () => {
let notFormField = event => !(event.target instanceof Node && isTextField(event.target));
let globalShortcuts = new ShortcutHandler(document, notFormField);
globalShortcuts.add('p', () => alert('hello p'));
globalShortcuts.add('h', () => alert('hi h!'));
globalShortcuts.add('g h', () => alert('hi g h!!!'));
let bindElement = bindElementFactory(globalShortcuts);
let bindLink = bindLinkFactory(globalShortcuts);
bindElement('p, Alt+ArrowLeft', '.prevnext__prev', 'Next hypha');
bindElement('n, Alt+ArrowRight', '.prevnext__next', 'Previous hypha');
bindElement('s, Alt+ArrowTop', $$('.navi-title a').slice(1, -1).slice(-1)[0], 'Parent hypha');
bindLink('g h', '/', 'Home');
bindLink('g l', '/list/', 'List of hyphae');
bindLink('g r', '/recent-changes/', 'Recent changes');
bindElement('g u', '.header-links__entry_user .header-links__link', 'Your profiles hypha')
let headerLinks = $$('.header-links__link');
for (let i = 1; i <= headerLinks.length && i < 10; i++) {
bindElement(`g ${i}`, headerLinks[i-1], `Header link #${i}`);
}
});
})();