hotkeys.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. /* global $ $$ $create */// dom.js
  2. /* global API */// msg.js
  3. /* global debounce */// toolbox.js
  4. /* global t */// localization.js
  5. 'use strict';
  6. const hotkeys = (() => {
  7. const entries = document.getElementsByClassName('entry');
  8. let togglablesShown = true;
  9. let togglables = getTogglables();
  10. let enabled;
  11. window.on('resize', adjustInfoPosition);
  12. initHotkeyInfo();
  13. return {
  14. setState(newState = !enabled) {
  15. if (!newState !== !enabled) {
  16. window[newState ? 'on' : 'off']('keydown', onKeyDown);
  17. enabled = newState;
  18. }
  19. },
  20. };
  21. function onKeyDown(event) {
  22. if (event.ctrlKey || event.altKey || event.metaKey || !enabled ||
  23. /^(text|search)$/.test((document.activeElement || {}).type)) {
  24. return;
  25. }
  26. let entry;
  27. let {key, code, shiftKey} = event;
  28. if (key >= '0' && key <= '9') {
  29. entry = entries[(Number(key) || 10) - 1];
  30. } else if (code >= 'Digit0' && code <= 'Digit9') {
  31. entry = entries[(Number(code.slice(-1)) || 10) - 1];
  32. } else if (key === '`' || key === '*' || code === 'Backquote' || code === 'NumpadMultiply') {
  33. invertTogglables();
  34. } else if (key === '-' || code === 'NumpadSubtract') {
  35. toggleState(entries, 'enabled', false);
  36. } else if (key === '+' || code === 'NumpadAdd') {
  37. toggleState(entries, 'disabled', true);
  38. } else if (key.length === 1) {
  39. shiftKey = false; // typing ':' etc. needs Shift so we hide it here to avoid opening editor
  40. key = key.toLocaleLowerCase();
  41. entry = [...entries].find(e => e.innerText.toLocaleLowerCase().startsWith(key));
  42. }
  43. if (!entry) {
  44. return;
  45. }
  46. const target = $(shiftKey ? '.style-edit-link' : 'input', entry);
  47. target.dispatchEvent(new MouseEvent('click', {cancelable: true}));
  48. }
  49. function getTogglables() {
  50. const enabledOrAll = $('.entry.enabled') ? $$('.entry.enabled') : [...entries];
  51. return enabledOrAll.map(entry => entry.id);
  52. }
  53. function countEnabledTogglables() {
  54. let num = 0;
  55. for (const id of togglables) {
  56. num += $(`#${id}`).classList.contains('enabled');
  57. }
  58. return num;
  59. }
  60. function invertTogglables() {
  61. togglables = togglables.length ? togglables : getTogglables();
  62. togglablesShown = countEnabledTogglables() > togglables.length / 2;
  63. toggleState(togglables, null, !togglablesShown);
  64. togglablesShown = !togglablesShown;
  65. }
  66. function toggleState(list, match, enable) {
  67. const results = [];
  68. let task = Promise.resolve();
  69. for (let entry of list) {
  70. entry = typeof entry === 'string' ? $('#' + entry) : entry;
  71. if (!match && $('input', entry).checked !== enable || entry.classList.contains(match)) {
  72. results.push(entry.id);
  73. task = task
  74. .then(() => API.styles.toggle(entry.styleId, enable))
  75. .then(() => {
  76. entry.classList.toggle('enabled', enable);
  77. entry.classList.toggle('disabled', !enable);
  78. $('input', entry).checked = enable;
  79. });
  80. }
  81. }
  82. if (results.length) task.then(API.refreshAllTabs);
  83. return results;
  84. }
  85. function initHotkeyInfo() {
  86. const container = $('#hotkey-info');
  87. let title;
  88. container.onclick = ({target}) => {
  89. if (target.localName === 'button') {
  90. close();
  91. } else if (!container.dataset.active) {
  92. open();
  93. }
  94. };
  95. function close() {
  96. delete container.dataset.active;
  97. document.body.style.height = '';
  98. container.title = title;
  99. window.on('resize', adjustInfoPosition);
  100. }
  101. function open() {
  102. window.off('resize', adjustInfoPosition);
  103. debounce.unregister(adjustInfoPosition);
  104. title = container.title;
  105. container.title = '';
  106. container.style = '';
  107. container.dataset.active = true;
  108. if (!container.firstElementChild) {
  109. buildElement();
  110. }
  111. const height = 3 +
  112. container.firstElementChild.scrollHeight +
  113. container.lastElementChild.scrollHeight;
  114. if (height > document.body.clientHeight) {
  115. document.body.style.height = height + 'px';
  116. }
  117. }
  118. function buildElement() {
  119. const keysToElements = line =>
  120. line
  121. .split(/(<.*?>)/)
  122. .map(s => (!s.startsWith('<') ? s :
  123. $create('mark', s.slice(1, -1))));
  124. const linesToElements = text =>
  125. text
  126. .trim()
  127. .split('\n')
  128. .map((line, i, array) =>
  129. $create(i < array.length - 1 ? {
  130. tag: 'p',
  131. appendChild: keysToElements(line),
  132. } : {
  133. tag: 'a',
  134. target: '_blank',
  135. href: 'https://github.com/openstyles/stylus/wiki/Popup',
  136. textContent: line,
  137. }));
  138. [
  139. linesToElements(t('popupHotkeysInfo')),
  140. $create('button', t('confirmOK')),
  141. ].forEach(child => {
  142. container.appendChild($create('div', child));
  143. });
  144. }
  145. }
  146. function adjustInfoPosition(debounced) {
  147. if (debounced !== true) {
  148. debounce(adjustInfoPosition, 100, true);
  149. return;
  150. }
  151. }
  152. })();
  153. hotkeys.setState(true);