stylus/popup/hotkeys.js
tophf fdbfb23547
API groups + use executeScript for early injection (#1149)
* parserlib: fast section extraction, tweaks and speedups
* csslint: "simple-not" rule
* csslint: enable and fix "selector-newline" rule
* simplify db: resolve with result
* simplify download()
* remove noCode param as it wastes more time/memory on copying
* styleManager: switch style<->data names to reflect their actual contents
* inline method bodies to avoid indirection and enable better autocomplete/hint/jump support in IDE
* upgrade getEventKeyName to handle mouse clicks
* don't trust location.href as it hides text fragment
* getAllKeys is implemented since Chrome48, FF44
* allow recoverable css errors + async'ify usercss.js
* openManage: unminimize windows
* remove the obsolete Chrome pre-65 workaround
* fix temporal dead zone in apply.js
* ff bug workaround for simple editor window
* consistent window scrolling in scrollToEditor and jumpToPos
* rework waitForSelector and collapsible <details>
* blank paint frame workaround for new Chrome
* extract stuff from edit.js and load on demand
* simplify regexpTester::isShown
* move MozDocMapper to sections-util.js
* extract fitSelectBox()
* initialize router earlier
* use helpPopup.close()
* fix autofocus in popups, follow-up to 5bb1b5ef
* clone objects in prefs.get() + cosmetics
* reuse getAll result for INC
2021-01-01 17:27:58 +03:00

169 lines
5.0 KiB
JavaScript

/* global $ $$ $create */// dom.js
/* global API */// msg.js
/* global debounce */// toolbox.js
/* global t */// localization.js
'use strict';
const hotkeys = (() => {
const entries = document.getElementsByClassName('entry');
let togglablesShown = true;
let togglables = getTogglables();
let enabled;
window.on('resize', adjustInfoPosition);
initHotkeyInfo();
return {
setState(newState = !enabled) {
if (!newState !== !enabled) {
window[newState ? 'on' : 'off']('keydown', onKeyDown);
enabled = newState;
}
},
};
function onKeyDown(event) {
if (event.ctrlKey || event.altKey || event.metaKey || !enabled ||
/^(text|search)$/.test((document.activeElement || {}).type)) {
return;
}
let entry;
let {key, code, shiftKey} = event;
if (key >= '0' && key <= '9') {
entry = entries[(Number(key) || 10) - 1];
} else if (code >= 'Digit0' && code <= 'Digit9') {
entry = entries[(Number(code.slice(-1)) || 10) - 1];
} else if (key === '`' || key === '*' || code === 'Backquote' || code === 'NumpadMultiply') {
invertTogglables();
} else if (key === '-' || code === 'NumpadSubtract') {
toggleState(entries, 'enabled', false);
} else if (key === '+' || code === 'NumpadAdd') {
toggleState(entries, 'disabled', true);
} else if (key.length === 1) {
shiftKey = false; // typing ':' etc. needs Shift so we hide it here to avoid opening editor
key = key.toLocaleLowerCase();
entry = [...entries].find(e => e.innerText.toLocaleLowerCase().startsWith(key));
}
if (!entry) {
return;
}
const target = $(shiftKey ? '.style-edit-link' : 'input', entry);
target.dispatchEvent(new MouseEvent('click', {cancelable: true}));
}
function getTogglables() {
const enabledOrAll = $('.entry.enabled') ? $$('.entry.enabled') : [...entries];
return enabledOrAll.map(entry => entry.id);
}
function countEnabledTogglables() {
let num = 0;
for (const id of togglables) {
num += $(`#${id}`).classList.contains('enabled');
}
return num;
}
function invertTogglables() {
togglables = togglables.length ? togglables : getTogglables();
togglablesShown = countEnabledTogglables() > togglables.length / 2;
toggleState(togglables, null, !togglablesShown);
togglablesShown = !togglablesShown;
}
function toggleState(list, match, enable) {
const results = [];
let task = Promise.resolve();
for (let entry of list) {
entry = typeof entry === 'string' ? $('#' + entry) : entry;
if (!match && $('input', entry).checked !== enable || entry.classList.contains(match)) {
results.push(entry.id);
task = task
.then(() => API.styles.toggle(entry.styleId, enable))
.then(() => {
entry.classList.toggle('enabled', enable);
entry.classList.toggle('disabled', !enable);
$('input', entry).checked = enable;
});
}
}
if (results.length) task.then(API.refreshAllTabs);
return results;
}
function initHotkeyInfo() {
const container = $('#hotkey-info');
let title;
container.onclick = ({target}) => {
if (target.localName === 'button') {
close();
} else if (!container.dataset.active) {
open();
}
};
function close() {
delete container.dataset.active;
document.body.style.height = '';
container.title = title;
window.on('resize', adjustInfoPosition);
}
function open() {
window.off('resize', adjustInfoPosition);
debounce.unregister(adjustInfoPosition);
title = container.title;
container.title = '';
container.style = '';
container.dataset.active = true;
if (!container.firstElementChild) {
buildElement();
}
const height = 3 +
container.firstElementChild.scrollHeight +
container.lastElementChild.scrollHeight;
if (height > document.body.clientHeight) {
document.body.style.height = height + 'px';
}
}
function buildElement() {
const keysToElements = line =>
line
.split(/(<.*?>)/)
.map(s => (!s.startsWith('<') ? s :
$create('mark', s.slice(1, -1))));
const linesToElements = text =>
text
.trim()
.split('\n')
.map((line, i, array) =>
$create(i < array.length - 1 ? {
tag: 'p',
appendChild: keysToElements(line),
} : {
tag: 'a',
target: '_blank',
href: 'https://github.com/openstyles/stylus/wiki/Popup',
textContent: line,
}));
[
linesToElements(t('popupHotkeysInfo')),
$create('button', t('confirmOK')),
].forEach(child => {
container.appendChild($create('div', child));
});
}
}
function adjustInfoPosition(debounced) {
if (debounced !== true) {
debounce(adjustInfoPosition, 100, true);
return;
}
}
})();
hotkeys.setState(true);