2021-06-13 19:39:27 +00:00
|
|
|
|
class Shortcut {
|
|
|
|
|
// turns the given event into a string representation of it.
|
|
|
|
|
static fromEvent(event) {
|
2021-06-13 06:48:18 +00:00
|
|
|
|
let elideShift = event.key.toUpperCase() === event.key && event.shiftKey;
|
|
|
|
|
return (event.ctrlKey ? 'Ctrl+' : '') +
|
|
|
|
|
(event.altKey ? 'Alt+' : '') +
|
|
|
|
|
(event.metaKey ? 'Meta+' : '') +
|
|
|
|
|
(!elideShift && event.shiftKey ? 'Shift+' : '') +
|
2021-06-13 08:14:03 +00:00
|
|
|
|
(event.key === ',' ? 'Comma' : event.key === ' ' ? 'Space' : event.key);
|
2021-06-13 05:40:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// Some keys look better with cool symbols instead of their long and boring names.
|
|
|
|
|
static prettify(shortcut, isMac) {
|
2021-06-13 16:14:18 +00:00
|
|
|
|
let keys = shortcut.split('+');
|
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// Uh it places the cmd sign before the letter to follow the Mac conventions, I guess.
|
2021-06-13 16:14:18 +00:00
|
|
|
|
if (isMac) {
|
|
|
|
|
let cmdIdx = keys.indexOf('Meta');
|
|
|
|
|
if (cmdIdx !== -1 && keys.length - cmdIdx > 2) {
|
|
|
|
|
let tmp = keys[cmdIdx + 1];
|
|
|
|
|
keys[cmdIdx + 1] = 'Meta';
|
|
|
|
|
keys[cmdIdx] = tmp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let lastKey = keys[keys.length - 1];
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// Uhh add Shift if the letter is uppercase??
|
2021-06-13 16:14:18 +00:00
|
|
|
|
if (!keys.includes('Shift') && lastKey.toUpperCase() === lastKey && lastKey.toLowerCase() !== lastKey) {
|
|
|
|
|
keys.splice(keys.length - 1, 0, 'Shift');
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
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();
|
2021-06-13 16:14:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
return `<kbd>${Shortcut.symbolifyKey(key, isMac)}</kbd>`;
|
|
|
|
|
}).join(isMac ? '' : ' + ');
|
|
|
|
|
}
|
2021-06-13 16:28:02 +00:00
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
static symbolifyKey(key, isMac) {
|
|
|
|
|
if (isMac) {
|
|
|
|
|
switch (key) {
|
|
|
|
|
case 'Ctrl': return '⌃';
|
|
|
|
|
case 'Alt': return '⌥';
|
|
|
|
|
case 'Shift': return '⇧';
|
|
|
|
|
case 'Meta': return '⌘';
|
2021-06-13 16:14:18 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
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
|
2021-06-13 16:14:18 +00:00
|
|
|
|
}
|
2021-06-13 19:39:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(() => {
|
|
|
|
|
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);
|
2021-06-13 16:14:18 +00:00
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
function isTextField(element) {
|
|
|
|
|
let name = element.nodeName.toLowerCase();
|
|
|
|
|
return name === 'textarea' ||
|
|
|
|
|
name === 'select' ||
|
|
|
|
|
(name === 'input' && !['submit', 'reset', 'checkbox', 'radio'].includes(element.type)) ||
|
|
|
|
|
element.isContentEditable;
|
2021-06-13 05:40:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 16:14:18 +00:00
|
|
|
|
let notTextField = event => !(event.target instanceof Node && isTextField(event.target));
|
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// The whole shortcut table for current page. It is used for generating the dialog.
|
2021-06-13 16:14:18 +00:00
|
|
|
|
let allShortcuts = [];
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// Temporary variable for building a shortcut group.
|
2021-06-13 16:14:18 +00:00
|
|
|
|
let shortcutsGroup = null;
|
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// Advanced stuff.
|
2021-06-13 06:48:18 +00:00
|
|
|
|
class ShortcutHandler {
|
2021-06-13 08:14:03 +00:00
|
|
|
|
constructor(element, filter = () => true) {
|
2021-06-13 06:48:18 +00:00
|
|
|
|
this.element = element;
|
|
|
|
|
this.map = {};
|
|
|
|
|
this.active = this.map;
|
|
|
|
|
this.filter = filter;
|
|
|
|
|
this.timeout = null;
|
2021-06-13 05:40:06 +00:00
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
|
|
|
this.resetActive = this.resetActive.bind(this);
|
|
|
|
|
this.element.addEventListener('keydown', this.handleKeyDown);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add(text, action, description = null) {
|
|
|
|
|
let shortcuts = text.split(',').map(shortcut => shortcut.trim().split(' '));
|
|
|
|
|
|
2021-06-13 16:14:18 +00:00
|
|
|
|
if (shortcutsGroup) {
|
|
|
|
|
shortcutsGroup.push({
|
|
|
|
|
action,
|
|
|
|
|
shortcut: text,
|
|
|
|
|
description,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2021-06-13 05:40:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
node.action = action;
|
|
|
|
|
node.shortcut = shortcut;
|
|
|
|
|
node.description = description;
|
|
|
|
|
}
|
2021-06-13 05:40:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 16:14:18 +00:00
|
|
|
|
groupStart() {
|
|
|
|
|
shortcutsGroup = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
groupEnd() {
|
|
|
|
|
if (shortcutsGroup && shortcutsGroup.length) allShortcuts.push(shortcutsGroup);
|
|
|
|
|
shortcutsGroup = null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// A dirty and shameful hack for inserting non-generated entries into the table.
|
2021-06-13 16:14:18 +00:00
|
|
|
|
fakeItem(shortcut, description = null) {
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// So it's a boolean, right?
|
2021-06-13 16:14:18 +00:00
|
|
|
|
let list = shortcutsGroup || allShortcuts;
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// And we push something into a boolean. I give up.
|
2021-06-13 16:14:18 +00:00
|
|
|
|
list.push({
|
|
|
|
|
shortcut: description ? shortcut : null,
|
|
|
|
|
description: description || shortcut,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
handleKeyDown(event) {
|
|
|
|
|
if (event.defaultPrevented) return;
|
|
|
|
|
if (['Control', 'Alt', 'Shift', 'Meta'].includes(event.key)) return;
|
|
|
|
|
if (!this.filter(event)) return;
|
2021-06-13 05:40:06 +00:00
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
let shortcut = Shortcut.fromEvent(event);
|
2021-06-13 05:40:06 +00:00
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
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);
|
2021-06-13 05:40:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
resetActive() {
|
|
|
|
|
this.active = this.map;
|
|
|
|
|
if (this.timeout) {
|
|
|
|
|
clearTimeout(this.timeout)
|
|
|
|
|
this.timeout = null;
|
|
|
|
|
}
|
2021-06-13 05:40:06 +00:00
|
|
|
|
}
|
2021-06-13 06:48:18 +00:00
|
|
|
|
}
|
2021-06-13 05:40:06 +00:00
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
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);
|
|
|
|
|
};
|
2021-06-13 05:40:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
function bindLinkFactory(handler) {
|
|
|
|
|
return (shortcut, link, ...other) => handler.add(shortcut, () => window.location.href = link, ...other);
|
2021-06-13 05:40:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 16:14:18 +00:00
|
|
|
|
let prevActiveElement = null;
|
|
|
|
|
let shortcutsListDialog = null;
|
|
|
|
|
|
|
|
|
|
function openShortcutsReference() {
|
2021-06-13 19:39:27 +00:00
|
|
|
|
if (!shortcutsListDialog) { // I guess the dialog is reused for second and subsequent invocations.
|
2021-06-13 16:14:18 +00:00
|
|
|
|
let wrap = document.createElement('div');
|
|
|
|
|
wrap.className = 'dialog-wrap';
|
|
|
|
|
shortcutsListDialog = wrap;
|
|
|
|
|
|
|
|
|
|
let dialog = document.createElement('div');
|
|
|
|
|
dialog.className = 'dialog shortcuts-modal';
|
|
|
|
|
dialog.tabIndex = 0;
|
|
|
|
|
wrap.appendChild(dialog);
|
|
|
|
|
|
|
|
|
|
let dialogHeader = document.createElement('div');
|
|
|
|
|
dialogHeader.className = 'dialog__header';
|
|
|
|
|
dialog.appendChild(dialogHeader);
|
|
|
|
|
|
|
|
|
|
let title = document.createElement('h1');
|
|
|
|
|
title.className = 'dialog__title';
|
|
|
|
|
title.textContent = 'List of shortcuts';
|
|
|
|
|
dialogHeader.appendChild(title);
|
|
|
|
|
|
|
|
|
|
let closeButton = document.createElement('button');
|
|
|
|
|
closeButton.className = 'dialog__close-button';
|
2021-06-13 19:39:27 +00:00
|
|
|
|
closeButton.setAttribute('aria-label', 'Close this dialog'); // a11y gang
|
2021-06-13 16:14:18 +00:00
|
|
|
|
dialogHeader.appendChild(closeButton);
|
|
|
|
|
|
|
|
|
|
for (let item of allShortcuts) {
|
|
|
|
|
if (item.description && !item.shortcut) {
|
|
|
|
|
let heading = document.createElement('h2');
|
|
|
|
|
heading.className = 'shortcuts-group-heading';
|
|
|
|
|
heading.textContent = item.description;
|
|
|
|
|
dialog.appendChild(heading);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
let list = document.createElement('ul');
|
|
|
|
|
list.className = 'shortcuts-group';
|
|
|
|
|
|
|
|
|
|
for (let shortcut of item) {
|
|
|
|
|
let listItem = document.createElement('li');
|
|
|
|
|
listItem.className = 'shortcut-row';
|
|
|
|
|
list.appendChild(listItem);
|
|
|
|
|
|
|
|
|
|
let descriptionColumn = document.createElement('div')
|
|
|
|
|
descriptionColumn.className = 'shortcut-row__description';
|
|
|
|
|
descriptionColumn.textContent = shortcut.description;
|
|
|
|
|
listItem.appendChild(descriptionColumn);
|
|
|
|
|
|
|
|
|
|
let shortcutColumn = document.createElement('div');
|
|
|
|
|
shortcutColumn.className = 'shortcut-row__keys';
|
|
|
|
|
shortcutColumn.innerHTML = shortcut.shortcut.split(',')
|
2021-06-13 19:39:27 +00:00
|
|
|
|
.map(shortcuts => shortcuts.trim().split(' ').map((sc) => Shortcut.prettify(sc, isMac)).join(' '))
|
2021-06-13 16:14:18 +00:00
|
|
|
|
.join(' or ');
|
|
|
|
|
listItem.appendChild(shortcutColumn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dialog.appendChild(list);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let handleClose = (event) => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
closeShortcutsReference();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let dialogShortcuts = new ShortcutHandler(dialog, notTextField);
|
|
|
|
|
|
|
|
|
|
dialogShortcuts.add('Escape', handleClose);
|
|
|
|
|
closeButton.addEventListener('click', handleClose);
|
|
|
|
|
wrap.addEventListener('click', handleClose);
|
|
|
|
|
|
|
|
|
|
dialog.addEventListener('click', event => event.stopPropagation());
|
|
|
|
|
|
|
|
|
|
document.body.appendChild(wrap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.body.overflow = 'hidden';
|
|
|
|
|
shortcutsListDialog.hidden = false;
|
|
|
|
|
prevActiveElement = document.activeElement;
|
|
|
|
|
shortcutsListDialog.children[0].focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeShortcutsReference() {
|
|
|
|
|
if (shortcutsListDialog) {
|
|
|
|
|
document.body.overflow = '';
|
|
|
|
|
shortcutsListDialog.hidden = true;
|
|
|
|
|
|
|
|
|
|
if (prevActiveElement) {
|
|
|
|
|
prevActiveElement.focus();
|
|
|
|
|
prevActiveElement = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
window.addEventListener('load', () => {
|
2021-06-13 16:14:18 +00:00
|
|
|
|
let globalShortcuts = new ShortcutHandler(document, notTextField);
|
2021-06-13 05:40:06 +00:00
|
|
|
|
|
2021-06-13 08:14:03 +00:00
|
|
|
|
// Global shortcuts
|
|
|
|
|
|
2021-06-13 06:48:18 +00:00
|
|
|
|
let bindElement = bindElementFactory(globalShortcuts);
|
|
|
|
|
let bindLink = bindLinkFactory(globalShortcuts);
|
|
|
|
|
|
2021-06-13 08:14:03 +00:00
|
|
|
|
// * Common shortcuts
|
2021-06-13 16:14:18 +00:00
|
|
|
|
globalShortcuts.fakeItem('Common');
|
2021-06-13 08:14:03 +00:00
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// Nice indentation here
|
2021-06-13 16:14:18 +00:00
|
|
|
|
globalShortcuts.groupStart();
|
|
|
|
|
globalShortcuts.fakeItem('g 1 – 9', 'First 9 header links');
|
|
|
|
|
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 profile′s hypha');
|
|
|
|
|
globalShortcuts.groupEnd();
|
2021-06-13 06:48:18 +00:00
|
|
|
|
|
|
|
|
|
let headerLinks = $$('.header-links__link');
|
|
|
|
|
for (let i = 1; i <= headerLinks.length && i < 10; i++) {
|
|
|
|
|
bindElement(`g ${i}`, headerLinks[i-1], `Header link #${i}`);
|
|
|
|
|
}
|
2021-06-13 06:57:40 +00:00
|
|
|
|
|
2021-06-13 16:21:49 +00:00
|
|
|
|
// * Hypha shortcuts
|
|
|
|
|
if (typeof editTextarea === 'undefined') {
|
|
|
|
|
globalShortcuts.fakeItem('Hypha');
|
|
|
|
|
|
|
|
|
|
globalShortcuts.groupStart();
|
|
|
|
|
globalShortcuts.fakeItem('1 – 9', 'First 9 hypha′s links');
|
2021-06-13 20:03:45 +00:00
|
|
|
|
bindElement('p, Alt+ArrowLeft, Ctrl+Alt+ArrowLeft', '.prevnext__prev', 'Next hypha');
|
|
|
|
|
bindElement('n, Alt+ArrowRight, Ctrl+Alt+ArrowRight', '.prevnext__next', 'Previous hypha');
|
|
|
|
|
bindElement('s, Alt+ArrowTop, Ctrl+Alt+ArrowTop', $$('.navi-title a').slice(1, -1).slice(-1)[0], 'Parent hypha');
|
2021-06-13 20:36:55 +00:00
|
|
|
|
bindElement('e, Ctrl+Enter', '.hypha-tabs__link[href^="/edit/"]', 'Edit this hypha');
|
2021-06-13 16:21:49 +00:00
|
|
|
|
globalShortcuts.groupEnd();
|
|
|
|
|
|
|
|
|
|
let hyphaLinks = $$('article .wikilink');
|
|
|
|
|
for (let i = 1; i <= hyphaLinks.length && i < 10; i++) {
|
|
|
|
|
bindElement(i.toString(), hyphaLinks[i-1], `Hypha link #${i}`);
|
|
|
|
|
}
|
2021-06-13 06:57:40 +00:00
|
|
|
|
}
|
2021-06-13 08:14:03 +00:00
|
|
|
|
|
2021-06-13 19:39:27 +00:00
|
|
|
|
// * Editor shortcuts
|
2021-06-13 08:14:03 +00:00
|
|
|
|
if (typeof editTextarea !== 'undefined') {
|
|
|
|
|
let editorShortcuts = new ShortcutHandler(editTextarea);
|
2021-06-13 16:28:02 +00:00
|
|
|
|
let bindElement = bindElementFactory(editorShortcuts);
|
2021-06-13 08:14:03 +00:00
|
|
|
|
|
|
|
|
|
let shortcuts = [
|
|
|
|
|
// Inspired by MS Word, Pages, Google Docs and Telegram desktop clients.
|
|
|
|
|
// And by myself, too.
|
|
|
|
|
|
|
|
|
|
// Win+Linux Mac Action Description
|
2021-06-13 16:14:18 +00:00
|
|
|
|
['Ctrl+b', 'Meta+b', wrapBold, 'Format: Bold'],
|
|
|
|
|
['Ctrl+i', 'Meta+i', wrapItalic, 'Format: Italic'],
|
|
|
|
|
['Ctrl+M', 'Meta+Shift+m', wrapMonospace, 'Format: Monospaced'],
|
|
|
|
|
['Ctrl+I', 'Meta+Shift+i', wrapHighlighted, 'Format: Highlight'],
|
|
|
|
|
['Ctrl+.', 'Meta+.', wrapLifted, 'Format: Superscript'],
|
|
|
|
|
['Ctrl+Comma', 'Meta+Comma', wrapLowered, 'Format: Subscript'],
|
2021-06-13 08:14:03 +00:00
|
|
|
|
// Strikethrough conflicts with 1Password on my machine but
|
2021-06-13 16:14:18 +00:00
|
|
|
|
// I'm probably the only Mycorrhiza user who uses 1Password. -handlerug
|
|
|
|
|
['Ctrl+X', 'Meta+Shift+x', wrapStrikethrough, 'Format: Strikethrough'],
|
|
|
|
|
['Ctrl+k', 'Meta+k', wrapLink, 'Format: Link'],
|
2021-06-13 08:14:03 +00:00
|
|
|
|
];
|
|
|
|
|
|
2021-06-13 16:14:18 +00:00
|
|
|
|
editorShortcuts.fakeItem('Editor');
|
2021-06-13 08:14:03 +00:00
|
|
|
|
|
2021-06-13 16:14:18 +00:00
|
|
|
|
editorShortcuts.groupStart();
|
2021-06-13 08:14:03 +00:00
|
|
|
|
for (let shortcut of shortcuts) {
|
|
|
|
|
if (isMac) {
|
|
|
|
|
editorShortcuts.add(shortcut[1], ...shortcut.slice(2))
|
|
|
|
|
} else {
|
|
|
|
|
editorShortcuts.add(shortcut[0], ...shortcut.slice(2))
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-13 16:14:18 +00:00
|
|
|
|
editorShortcuts.groupEnd();
|
|
|
|
|
|
2021-06-13 16:28:02 +00:00
|
|
|
|
editorShortcuts.groupStart();
|
|
|
|
|
bindElement(isMac ? 'Meta+Enter' : 'Ctrl+Enter', $('.edit-form__save'), 'Save changes');
|
|
|
|
|
editorShortcuts.groupEnd();
|
|
|
|
|
|
|
|
|
|
// Help shortcut
|
2021-06-13 16:14:18 +00:00
|
|
|
|
editorShortcuts.add(isMac ? 'Meta+/' : 'Ctrl+/', openShortcutsReference);
|
2021-06-13 08:14:03 +00:00
|
|
|
|
}
|
2021-06-13 16:21:49 +00:00
|
|
|
|
|
|
|
|
|
// * Meta shortcuts
|
|
|
|
|
globalShortcuts.add('?', openShortcutsReference);
|
2021-06-13 06:48:18 +00:00
|
|
|
|
});
|
2021-06-13 16:14:18 +00:00
|
|
|
|
})();
|