stylus/popup/hotkeys.js
2018-01-03 10:43:56 +03:00

217 lines
6.3 KiB
JavaScript

/* global applyOnMessage installed */
'use strict';
// eslint-disable-next-line no-var
var hotkeys = (() => {
const entries = document.getElementsByClassName('entry');
let togglablesShown;
let togglables;
let enabled = false;
let ready = false;
window.addEventListener('showStyles:done', function _() {
window.removeEventListener('showStyles:done', _);
togglablesShown = true;
togglables = getTogglables();
ready = true;
setState(true);
initHotkeyInfo();
});
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) {
return;
}
let entry;
const {which: k, key, code} = event;
if (code.startsWith('Digit') || code.startsWith('Numpad') && code.length === 7) {
entry = entries[(Number(code.slice(-1)) || 10) - 1];
} else if (
code === 'Backquote' || code === 'NumpadMultiply' ||
key && (key === '`' || key === '*') ||
k === 192 || k === 106) {
invertTogglables();
} else if (
code === 'NumpadSubtract' ||
key && key === '-' ||
k === 109) {
toggleState(entries, 'enabled', false);
} else if (
code === 'NumpadAdd' ||
key && key === '+' ||
k === 107) {
toggleState(entries, 'disabled', true);
} else if (
// any single character
key && key.length === 1 ||
k >= 65 && k <= 90) {
const letter = new RegExp(key ? '^' + key : '^\\x' + k.toString(16), 'i');
entry = [...entries].find(entry => letter.test(entry.textContent));
}
if (!entry) {
return;
}
const target = $(event.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.saveStyle({
id: entry.styleId,
enabled: enable,
notify: false,
})).then(() => {
entry.classList.toggle('enabled', enable);
entry.classList.toggle('disabled', !enable);
$('.checker', entry).checked = enable;
});
}
}
if (results.length) {
task.then(refreshAllTabs);
}
return results;
}
function refreshAllTabs() {
API.getStyles({matchUrl: location.href, enabled: true, asHash: true})
.then(styles => applyOnMessage({method: 'styleReplaceAll', styles}));
queryTabs().then(tabs =>
tabs.forEach(tab => (!FIREFOX || tab.width) &&
refreshTab(tab)));
}
function refreshTab(tab) {
const tabId = tab.id;
chrome.webNavigation.getAllFrames({tabId}, frames => {
frames = frames && frames[0] ? frames : [{frameId: 0}];
frames.forEach(({frameId}) =>
API.getStyles({matchUrl: tab.url, enabled: true, asHash: true}).then(styles => {
const message = {method: 'styleReplaceAll', tabId, frameId, styles};
invokeOrPostpone(tab.active, sendMessage, message, ignoreChromeError);
if (frameId === 0) {
setTimeout(API.updateIcon, 0, {tab, styles});
}
}));
ignoreChromeError();
});
}
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;
}
}
})();