stylus/popup/hotkeys.js
tophf 9e487b03e5
tweak editor (#1063)
* also apply live-preview if an unsaved style was disabled

* use box-shadow instead of outline for focus everywhere

* allow focus outline on click in text/search input or textarea

* search inputs should use the same style as text inputs

* also use box-shadow focus on delete buttons

* remove URLSearchParams workaround, not needed since Chrome 55

* use `once` in addEventListener, available since Chrome 55

* update USO bug workarounds, remove obsolete ones

* ping/pong to fix openURL with `message` in FF

* use unprefixed CSS filter, available since Chrome 53

* use unprefixed CSS user-select, available since Chrome 54

* focus tweaks

* also use text query in inline search for Stylus category

* use event.key, available since Chrome 51

Co-authored-by: narcolepticinsomniac
2020-10-13 21:14:54 +03:00

176 lines
5.2 KiB
JavaScript

/* global $ $$ API debounce $create t */
'use strict';
/* exported hotkeys */
const hotkeys = (() => {
const entries = document.getElementsByClassName('entry');
let togglablesShown;
let togglables;
let enabled = false;
let ready = false;
window.addEventListener('showStyles:done', () => {
togglablesShown = true;
togglables = getTogglables();
ready = true;
setState(true);
initHotkeyInfo();
}, {once: true});
window.addEventListener('resize', adjustInfoPosition);
return {setState};
function setState(newState = !enabled) {
if (!ready) {
throw new Error('hotkeys no ready');
}
if (newState !== enabled) {
window[`${newState ? 'add' : 'remove'}EventListener`]('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' : '.checker', 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 && $('.checker', entry).checked !== enable || entry.classList.contains(match)) {
results.push(entry.id);
task = task
.then(() => API.toggleStyle(entry.styleId, enable))
.then(() => {
entry.classList.toggle('enabled', enable);
entry.classList.toggle('disabled', !enable);
$('.checker', 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.addEventListener('resize', adjustInfoPosition);
}
function open() {
window.removeEventListener('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;
}
}
})();