events.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /* global $ $$ $remove animateElement getEventKeyName moveFocus */// dom.js
  2. /* global API */// msg.js
  3. /* global getActiveTab */// toolbox.js
  4. /* global resortEntries tabURL */// popup.js
  5. /* global t */// localization.js
  6. 'use strict';
  7. const MODAL_SHOWN = 'data-display'; // attribute name
  8. const Events = {
  9. async configure(event) {
  10. const {styleId, styleIsUsercss} = getClickedStyleElement(event);
  11. if (styleIsUsercss) {
  12. const [style] = await Promise.all([
  13. API.styles.get(styleId),
  14. require(['/popup/hotkeys']), /* global hotkeys */
  15. require(['/js/dlg/config-dialog']), /* global configDialog */
  16. ]);
  17. hotkeys.setState(false);
  18. await configDialog(style);
  19. hotkeys.setState(true);
  20. } else {
  21. Events.openURLandHide.call(this, event);
  22. }
  23. },
  24. copyContent(event) {
  25. event.preventDefault();
  26. const target = document.activeElement;
  27. const message = $('.copy-message');
  28. navigator.clipboard.writeText(target.textContent);
  29. target.classList.add('copied');
  30. message.classList.add('show-message');
  31. setTimeout(() => {
  32. target.classList.remove('copied');
  33. message.classList.remove('show-message');
  34. }, 1000);
  35. },
  36. delete(event) {
  37. const entry = getClickedStyleElement(event);
  38. const box = $('#confirm');
  39. box.dataset.id = entry.styleId;
  40. $('b', box).textContent = $('.style-name', entry).textContent;
  41. Events.showModal(box, '[data-cmd=cancel]');
  42. },
  43. getExcludeRule(type) {
  44. const u = new URL(tabURL);
  45. return type === 'domain'
  46. ? u.origin + '/*'
  47. : escapeGlob(u.origin + u.pathname); // current page
  48. },
  49. async hideModal(box, {animate} = {}) {
  50. window.off('keydown', box._onkeydown);
  51. box._onkeydown = null;
  52. if (animate) {
  53. box.style.animationName = '';
  54. await animateElement(box, 'lights-on');
  55. }
  56. box.removeAttribute(MODAL_SHOWN);
  57. },
  58. indicator(event) {
  59. const entry = getClickedStyleElement(event);
  60. const info = t.template.regexpProblemExplanation.cloneNode(true);
  61. $remove('#' + info.id);
  62. $$('a', info).forEach(el => (el.onclick = Events.openURLandHide));
  63. $$('button', info).forEach(el => (el.onclick = closeExplanation));
  64. entry.appendChild(info);
  65. },
  66. isStyleExcluded({exclusions}, type) {
  67. if (!exclusions) {
  68. return false;
  69. }
  70. const rule = Events.getExcludeRule(type);
  71. return exclusions.includes(rule);
  72. },
  73. maybeEdit(event) {
  74. if (!(
  75. event.button === 0 && (event.ctrlKey || event.metaKey) ||
  76. event.button === 1 ||
  77. event.button === 2)) {
  78. return;
  79. }
  80. // open an editor on middleclick
  81. const el = event.target;
  82. if (el.matches('.entry, .style-edit-link') || el.closest('.style-name')) {
  83. this.onmouseup = () => $('.style-edit-link', this).click();
  84. this.oncontextmenu = event => event.preventDefault();
  85. event.preventDefault();
  86. return;
  87. }
  88. },
  89. name(event) {
  90. $('input', this).dispatchEvent(new MouseEvent('click'));
  91. event.preventDefault();
  92. },
  93. async openEditor(event, options) {
  94. event.preventDefault();
  95. await API.openEditor(options);
  96. window.close();
  97. },
  98. async openManager(event) {
  99. event.preventDefault();
  100. const isSearch = tabURL && (event.shiftKey || event.button === 2);
  101. await API.openManage(isSearch ? {search: tabURL, searchMode: 'url'} : {});
  102. window.close();
  103. },
  104. async openURLandHide(event) {
  105. event.preventDefault();
  106. await API.openURL({
  107. url: this.href || this.dataset.href,
  108. index: (await getActiveTab()).index + 1,
  109. message: this._sendMessage,
  110. });
  111. window.close();
  112. },
  113. showModal(box, cancelButtonSelector) {
  114. const oldBox = $(`[${MODAL_SHOWN}]`);
  115. if (oldBox) box.style.animationName = 'none';
  116. // '' would be fine but 'true' is backward-compatible with the existing userstyles
  117. box.setAttribute(MODAL_SHOWN, 'true');
  118. box._onkeydown = e => {
  119. const key = getEventKeyName(e);
  120. switch (key) {
  121. case 'Tab':
  122. case 'Shift-Tab':
  123. e.preventDefault();
  124. moveFocus(box, e.shiftKey ? -1 : 1);
  125. break;
  126. case 'Escape': {
  127. e.preventDefault();
  128. window.onkeydown = null;
  129. $(cancelButtonSelector, box).click();
  130. break;
  131. }
  132. }
  133. };
  134. window.on('keydown', box._onkeydown);
  135. moveFocus(box, 0);
  136. if (oldBox) Events.hideModal(oldBox);
  137. },
  138. async toggleState(event) {
  139. // when fired on checkbox, prevent the parent label from seeing the event, see #501
  140. event.stopPropagation();
  141. await API.styles.toggle((getClickedStyleElement(event) || {}).styleId, this.checked);
  142. resortEntries();
  143. },
  144. toggleExclude(event, type) {
  145. const entry = getClickedStyleElement(event);
  146. if (event.target.checked) {
  147. API.styles.addExclusion(entry.styleMeta.id, Events.getExcludeRule(type));
  148. } else {
  149. API.styles.removeExclusion(entry.styleMeta.id, Events.getExcludeRule(type));
  150. }
  151. },
  152. toggleMenu(event) {
  153. const entry = getClickedStyleElement(event);
  154. const menu = $('.menu', entry);
  155. if (menu.hasAttribute(MODAL_SHOWN)) {
  156. Events.hideModal(menu, {animate: true});
  157. } else {
  158. $('.menu-title', entry).textContent = $('.style-name', entry).textContent;
  159. Events.showModal(menu, '.menu-close');
  160. }
  161. },
  162. };
  163. function closeExplanation() {
  164. $('#regexp-explanation').remove();
  165. }
  166. function escapeGlob(text) {
  167. return text.replace(/\*/g, '\\*');
  168. }
  169. function getClickedStyleElement(event) {
  170. return event.target.closest('.entry');
  171. }