From d069bd536efe9b94a7dc758908f590149c686952 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Mon, 14 Jun 2021 00:39:27 +0500 Subject: [PATCH] Read Handlerug's code I have written some comments in order to understand it by myself. I created the class Shortcut with some static methods for better grouping. --- static/shortcuts.js | 92 ++++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/static/shortcuts.js b/static/shortcuts.js index 9feae7c..18fc794 100644 --- a/static/shortcuts.js +++ b/static/shortcuts.js @@ -1,10 +1,6 @@ -(() => { - const $ = document.querySelector.bind(document); - const $$ = (...args) => Array.prototype.slice.call(document.querySelectorAll(...args)); - - const isMac = /Macintosh/.test(window.navigator.userAgent); - - function keyEventToShortcut(event) { +class Shortcut { + // turns the given event into a string representation of it. + static fromEvent(event) { let elideShift = event.key.toUpperCase() === event.key && event.shiftKey; return (event.ctrlKey ? 'Ctrl+' : '') + (event.altKey ? 'Alt+' : '') + @@ -13,9 +9,11 @@ (event.key === ',' ? 'Comma' : event.key === ' ' ? 'Space' : event.key); } - function prettifyShortcut(shortcut) { + // Some keys look better with cool symbols instead of their long and boring names. + static prettify(shortcut, isMac) { let keys = shortcut.split('+'); + // Uh it places the cmd sign before the letter to follow the Mac conventions, I guess. if (isMac) { let cmdIdx = keys.indexOf('Meta'); if (cmdIdx !== -1 && keys.length - cmdIdx > 2) { @@ -26,39 +24,52 @@ } let lastKey = keys[keys.length - 1]; + // Uhh add Shift if the letter is uppercase?? if (!keys.includes('Shift') && lastKey.toUpperCase() === lastKey && lastKey.toLowerCase() !== lastKey) { keys.splice(keys.length - 1, 0, 'Shift'); } - for (let i = 0; i < keys.length; i++) { - if (isMac) { - switch (keys[i]) { - case 'Ctrl': keys[i] = '⌃'; break; - case 'Alt': keys[i] = '⌥'; break; - case 'Shift': keys[i] = '⇧'; break; - case 'Meta': keys[i] = '⌘'; break; - } + return keys.map((key, i) => { + // If last element and there is more than one element and it's a letter + if (i === keys.length - 1 && i > 0 && key.length === 1) { + // Show in upper case. ⌘K looks better ⌘k, no doubt. + key = key.toUpperCase(); } - if (i === keys.length - 1 && i > 0 && keys[i].length === 1) { - keys[i] = keys[i].toUpperCase(); - } + return `${Shortcut.symbolifyKey(key, isMac)}`; + }).join(isMac ? '' : ' + '); + } - switch (keys[i]) { - case 'ArrowLeft': keys[i] = '←'; break; - case 'ArrowRight': keys[i] = '→'; break; - case 'ArrowTop': keys[i] = '↑'; break; - case 'ArrowBottom': keys[i] = '↓'; break; - case 'Comma': keys[i] = ','; break; - case 'Enter': keys[i] = '↩'; break; - case ' ': keys[i] = 'Space'; break; + static symbolifyKey(key, isMac) { + if (isMac) { + switch (key) { + case 'Ctrl': return '⌃'; + case 'Alt': return '⌥'; + case 'Shift': return '⇧'; + case 'Meta': return '⌘'; } - - keys[i] = `${keys[i]}`; } - return keys.join(isMac ? '' : ' + '); + switch (key) { + case 'ArrowLeft': return '←'; + case 'ArrowRight': return '→'; + case 'ArrowTop': return '↑'; + case 'ArrowBottom': return '↓'; + case 'Comma': return ','; + case 'Enter': return '↩'; + case ' ': return 'Space'; + } + return key } +} + +(() => { + const $ = document.querySelector.bind(document); + const $$ = (...args) => Array.prototype.slice.call(document.querySelectorAll(...args)); + + // Some things look different on Mac. + // Note that the ⌘ command key is called Meta in JS for some reason. + const isMac = /Macintosh/.test(window.navigator.userAgent); function isTextField(element) { let name = element.nodeName.toLowerCase(); @@ -70,9 +81,12 @@ let notTextField = event => !(event.target instanceof Node && isTextField(event.target)); + // The whole shortcut table for current page. It is used for generating the dialog. let allShortcuts = []; + // Temporary variable for building a shortcut group. let shortcutsGroup = null; + // Advanced stuff. class ShortcutHandler { constructor(element, filter = () => true) { this.element = element; @@ -83,10 +97,6 @@ this.handleKeyDown = this.handleKeyDown.bind(this); this.resetActive = this.resetActive.bind(this); - this.addEventListeners(); - } - - addEventListeners() { this.element.addEventListener('keydown', this.handleKeyDown); } @@ -130,8 +140,11 @@ shortcutsGroup = null; } + // A dirty and shameful hack for inserting non-generated entries into the table. fakeItem(shortcut, description = null) { + // So it's a boolean, right? let list = shortcutsGroup || allShortcuts; + // And we push something into a boolean. I give up. list.push({ shortcut: description ? shortcut : null, description: description || shortcut, @@ -143,7 +156,7 @@ if (['Control', 'Alt', 'Shift', 'Meta'].includes(event.key)) return; if (!this.filter(event)) return; - let shortcut = keyEventToShortcut(event); + let shortcut = Shortcut.fromEvent(event); if (!this.active[shortcut]) { this.resetActive(); @@ -192,7 +205,7 @@ let shortcutsListDialog = null; function openShortcutsReference() { - if (!shortcutsListDialog) { + if (!shortcutsListDialog) { // I guess the dialog is reused for second and subsequent invocations. let wrap = document.createElement('div'); wrap.className = 'dialog-wrap'; shortcutsListDialog = wrap; @@ -213,7 +226,7 @@ let closeButton = document.createElement('button'); closeButton.className = 'dialog__close-button'; - closeButton.setAttribute('aria-label', 'Close this dialog'); + closeButton.setAttribute('aria-label', 'Close this dialog'); // a11y gang dialogHeader.appendChild(closeButton); for (let item of allShortcuts) { @@ -240,7 +253,7 @@ let shortcutColumn = document.createElement('div'); shortcutColumn.className = 'shortcut-row__keys'; shortcutColumn.innerHTML = shortcut.shortcut.split(',') - .map(shortcuts => shortcuts.trim().split(' ').map(prettifyShortcut).join(' ')) + .map(shortcuts => shortcuts.trim().split(' ').map((sc) => Shortcut.prettify(sc, isMac)).join(' ')) .join(' or '); listItem.appendChild(shortcutColumn); } @@ -295,6 +308,7 @@ // * Common shortcuts globalShortcuts.fakeItem('Common'); + // Nice indentation here globalShortcuts.groupStart(); globalShortcuts.fakeItem('g 1 – 9', 'First 9 header links'); bindLink('g h', '/', 'Home'); @@ -326,7 +340,7 @@ } } - // Hypha editor shortcuts + // * Editor shortcuts if (typeof editTextarea !== 'undefined') { let editorShortcuts = new ShortcutHandler(editTextarea); let bindElement = bindElementFactory(editorShortcuts);