FacilAuto Keys

Añade navegación por teclado en algunas páginas de la aplicación web de FacilAuto (test, selección de test).

当前为 2025-03-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name FacilAuto Keys
  3. // @namespace Violentmonkey Scripts
  4. // @match https://alumno.examentrafico.com/
  5. // @grant none
  6. // @version 1.0
  7. // @author victor-gp
  8. // @description Añade navegación por teclado en algunas páginas de la aplicación web de FacilAuto (test, selección de test).
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function testKeys() {
  13. 'use strict';
  14.  
  15. const script_id = 'facilauto-test-keys'
  16.  
  17. // Configuration: Map keys to CSS selectors
  18. const keySelectorMap = {
  19. 'A': 'img[src="/static/img/test/A.jpg"]',
  20. 'S': 'img[src="/static/img/test/B.jpg"]',
  21. 'D': 'img[src="/static/img/test/C.jpg"]',
  22. 'J': 'img[src="/static/img/test/back.png"]',
  23. 'K': 'img[src="/static/img/test/next.png"]',
  24. 'L': 'img[src="/static/img/test/end.png"]',
  25. 'W': 'button.help-button-1', // Ayuda
  26. 'E': 'button:has(svg.fa-images)', // Lamina
  27. 'R': 'button:has(svg.fa-volume-down)', // Audioexplicacion
  28. 'T': 'button:has(svg.fa-play)', // Videoexplicacion
  29. 'Enter': '.sweet-modal.is-visible button.btn-default', // Modal - White button
  30. 'Backspace': '.sweet-modal.is-visible button.btn-danger', // Modal - Red button
  31. };
  32.  
  33. // Configuration: Map keys to functions
  34. const keyFunctionMap = {
  35. 'Q': () => simulateClick(document.elementFromPoint(0, 0)), // Exit modal
  36. };
  37.  
  38. function isTargetPage() {
  39. const urlMatch = window.location.hash !== '#/test/block/test/exam/174/0';
  40. if (!urlMatch) return false;
  41. const contentMatch = document.querySelector('div.test-box-top') !== null;
  42. return contentMatch;
  43. }
  44.  
  45. function handleKeydown(event) {
  46. let key = event.key;
  47. // normalize letter keys
  48. if (/^[A-Za-z]$/.test(key)) {
  49. key = key.toUpperCase();
  50. }
  51.  
  52. if (keyFunctionMap[key]) {
  53. keyFunctionMap[key]();
  54. }
  55. else if (keySelectorMap[key]) {
  56. const element = document.querySelector(keySelectorMap[key]);
  57. simulateClick(element);
  58. }
  59. };
  60.  
  61. function simulateClick(element) {
  62. if (element) {
  63. element.click();
  64. }
  65. }
  66.  
  67. function handlePageChange() {
  68. if (isTargetPage()) {
  69. document.addEventListener('keydown', handleKeydown);
  70. console.debug(`${script_id}: load`);
  71. } else {
  72. document.removeEventListener('keydown', handleKeydown);
  73. console.debug(`${script_id}: remove`);
  74. }
  75. }
  76.  
  77. let lastUrl = window.location.href;
  78. function checkUrlChange() {
  79. const currentUrl = window.location.href;
  80. if (currentUrl !== lastUrl) {
  81. lastUrl = currentUrl;
  82. handlePageChange();
  83. }
  84. }
  85.  
  86. handlePageChange();
  87.  
  88. const observer = new MutationObserver(checkUrlChange);
  89. const config = { childList: true, subtree: true };
  90. observer.observe(document.body, config);
  91. })();
  92.  
  93. (function blockKeys() {
  94. "use strict";
  95.  
  96. const script_id = "facilauto-block-keys";
  97.  
  98. // Configuration: Map keys to CSS selectors _to focus_
  99. const keySelectorMap = {
  100. 'H': 'div.tests-block-item > .fail ~ .has-tooltip', // First failed test
  101. 'L': 'div.tests-block-item > div:first-child:not(.fail):not(.success) ~ .has-tooltip', // First not-taken test
  102. };
  103.  
  104. // Configuration: Map keys to functions
  105. const keyFunctionMap = {
  106. 'A': makeButtonsTabbable,
  107. 'Enter': () => simulateClick(document.activeElement),
  108. 'J': focusNextTest,
  109. 'K': focusPreviousTest,
  110. // 'Tab': next button (implicit)
  111. };
  112.  
  113. function isTargetPage() {
  114. const urlHashRegex = new RegExp("^#/test/block/");
  115. const urlMatch = window.location.hash.match(urlHashRegex);
  116. if (!urlMatch) return false;
  117. const contentMatch = document.querySelector("div.tests-index") !== null;
  118. return contentMatch;
  119. }
  120.  
  121. let tabbableElements;
  122. function makeButtonsTabbable() {
  123. // heuristic: elements with a tooltip seem to be buttons
  124. const tooltipElements = document.querySelectorAll(".has-tooltip");
  125. tabbableElements = Array.from(tooltipElements).filter(el => el.checkVisibility());
  126. tabbableElements.forEach((element) => {
  127. element.setAttribute("tabindex", "0");
  128. // element.style.border = "1px solid red";
  129. });
  130. tabbableElements[0].focus();
  131. }
  132.  
  133. function focusNextTest() {
  134. const currentIndex = tabbableElements.indexOf(document.activeElement);
  135. if (currentIndex !== -1) {
  136. tabbableElements[currentIndex + 4]?.focus();
  137. } else {
  138. tabbableElements[0].focus();
  139. }
  140. document.activeElement.scrollIntoView({ behavior: 'smooth', block: "center" });
  141. }
  142.  
  143. function focusPreviousTest() {
  144. const currentIndex = tabbableElements.indexOf(document.activeElement);
  145. if (currentIndex !== -1) {
  146. tabbableElements[currentIndex - 4]?.focus();
  147. } else {
  148. tabbableElements[0].focus();
  149. }
  150. document.activeElement.scrollIntoView({ behavior: 'smooth', block: "center" });
  151. }
  152.  
  153. function handleKeydown(event) {
  154. let key = event.key;
  155. // normalize letter keys
  156. if (/^[A-Za-z]$/.test(key)) {
  157. key = key.toUpperCase();
  158. }
  159.  
  160. if (keyFunctionMap[key]) {
  161. keyFunctionMap[key]();
  162. } else if (keySelectorMap[key]) {
  163. document.querySelector(keySelectorMap[key]).focus();
  164. }
  165. }
  166.  
  167. function simulateClick(element) {
  168. if (element) {
  169. element.click();
  170. }
  171. }
  172.  
  173. function waitUntil(condFn, execFn) {
  174. setTimeout(() => {
  175. if (condFn()) {
  176. execFn();
  177. } else {
  178. waitUntil(condFn, execFn)
  179. }
  180. }, 50)
  181. }
  182.  
  183. function handlePageChange() {
  184. if (isTargetPage()) {
  185. const isPageLoaded = () => {
  186. const tooltipElements = document.querySelectorAll(".has-tooltip");
  187. return tooltipElements.length !== 0;
  188. };
  189. const setUp = () => {
  190. makeButtonsTabbable();
  191. document.addEventListener("keydown", handleKeydown);
  192. console.debug(`${script_id}: load`);
  193. };
  194. waitUntil(isPageLoaded, setUp);
  195. } else {
  196. document.removeEventListener("keydown", handleKeydown);
  197. console.debug(`${script_id}: remove`);
  198. }
  199. }
  200.  
  201. let lastUrl = window.location.href;
  202. function checkUrlChange() {
  203. const currentUrl = window.location.href;
  204. if (currentUrl !== lastUrl) {
  205. lastUrl = currentUrl;
  206. handlePageChange();
  207. }
  208. }
  209.  
  210. handlePageChange();
  211.  
  212. const observer = new MutationObserver(checkUrlChange);
  213. const config = { childList: true, subtree: true };
  214. observer.observe(document.body, config);
  215. })();