app.js 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. /* global jQuery */
  2. /**
  3. * Tiny site-wide script. One purpose today: turn any element carrying a
  4. * `data-href` attribute into a clickable row / button without needing an
  5. * inline onclick (which would force `'unsafe-inline'` in the CSP).
  6. *
  7. * Middle-click / Ctrl+click / Cmd+click open in a new tab, matching
  8. * native anchor-link behaviour.
  9. */
  10. (function ($) {
  11. 'use strict';
  12. $(document).on('click', '[data-href]', function (ev) {
  13. // Ignore clicks that originated on an interactive descendant.
  14. if ($(ev.target).closest('a, button, input, select, textarea, label').length > 0) {
  15. return;
  16. }
  17. const href = String($(this).attr('data-href') || '');
  18. if (href === '') { return; }
  19. if (ev.metaKey || ev.ctrlKey || ev.button === 1) {
  20. window.open(href, '_blank');
  21. } else {
  22. window.location.href = href;
  23. }
  24. });
  25. // Make the row visibly clickable via keyboard + screen-readers.
  26. $('[data-href]').attr('role', 'link').attr('tabindex', '0');
  27. $(document).on('keydown', '[data-href]', function (ev) {
  28. if (ev.key === 'Enter') {
  29. ev.preventDefault();
  30. window.location.href = String($(this).attr('data-href') || '');
  31. }
  32. });
  33. })(jQuery);
  34. /**
  35. * Header hamburger menu. Vanilla JS — no jQuery. Toggles #app-menu's
  36. * [hidden] + aria-expanded on the trigger, closes on outside-click,
  37. * Escape (returning focus to the trigger), and on any menuitem click
  38. * (so following a link / submitting the sign-out form feels snappy).
  39. *
  40. * Pattern mirrors the owner-filter / columns dropdowns in
  41. * sprint-planner.js, minus jQuery.
  42. */
  43. (function () {
  44. 'use strict';
  45. const trigger = document.querySelector('[data-menu-trigger]');
  46. const menu = document.querySelector('[data-menu]');
  47. if (!trigger || !menu) { return; }
  48. function setOpen(open) {
  49. menu.hidden = !open;
  50. trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
  51. }
  52. trigger.addEventListener('click', function (ev) {
  53. ev.stopPropagation();
  54. setOpen(menu.hidden);
  55. });
  56. document.addEventListener('click', function (ev) {
  57. if (menu.hidden) { return; }
  58. if (ev.target === trigger || trigger.contains(ev.target)) { return; }
  59. if (!menu.contains(ev.target)) { setOpen(false); }
  60. });
  61. document.addEventListener('keydown', function (ev) {
  62. if (ev.key === 'Escape' && !menu.hidden) {
  63. setOpen(false);
  64. trigger.focus();
  65. }
  66. });
  67. menu.addEventListener('click', function (ev) {
  68. if (ev.target.closest('[role="menuitem"]')) { setOpen(false); }
  69. });
  70. })();