| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- /* global jQuery */
- /**
- * Tiny site-wide script. One purpose today: turn any element carrying a
- * `data-href` attribute into a clickable row / button without needing an
- * inline onclick (which would force `'unsafe-inline'` in the CSP).
- *
- * Middle-click / Ctrl+click / Cmd+click open in a new tab, matching
- * native anchor-link behaviour.
- */
- (function ($) {
- 'use strict';
- $(document).on('click', '[data-href]', function (ev) {
- // Ignore clicks that originated on an interactive descendant.
- if ($(ev.target).closest('a, button, input, select, textarea, label').length > 0) {
- return;
- }
- const href = String($(this).attr('data-href') || '');
- if (href === '') { return; }
- if (ev.metaKey || ev.ctrlKey || ev.button === 1) {
- window.open(href, '_blank');
- } else {
- window.location.href = href;
- }
- });
- // Make the row visibly clickable via keyboard + screen-readers.
- $('[data-href]').attr('role', 'link').attr('tabindex', '0');
- $(document).on('keydown', '[data-href]', function (ev) {
- if (ev.key === 'Enter') {
- ev.preventDefault();
- window.location.href = String($(this).attr('data-href') || '');
- }
- });
- })(jQuery);
- /**
- * Header hamburger menu. Vanilla JS — no jQuery. Toggles #app-menu's
- * [hidden] + aria-expanded on the trigger, closes on outside-click,
- * Escape (returning focus to the trigger), and on any menuitem click
- * (so following a link / submitting the sign-out form feels snappy).
- *
- * Pattern mirrors the owner-filter / columns dropdowns in
- * sprint-planner.js, minus jQuery.
- */
- (function () {
- 'use strict';
- const trigger = document.querySelector('[data-menu-trigger]');
- const menu = document.querySelector('[data-menu]');
- if (!trigger || !menu) { return; }
- function setOpen(open) {
- menu.hidden = !open;
- trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
- }
- trigger.addEventListener('click', function (ev) {
- ev.stopPropagation();
- setOpen(menu.hidden);
- });
- document.addEventListener('click', function (ev) {
- if (menu.hidden) { return; }
- if (ev.target === trigger || trigger.contains(ev.target)) { return; }
- if (!menu.contains(ev.target)) { setOpen(false); }
- });
- document.addEventListener('keydown', function (ev) {
- if (ev.key === 'Escape' && !menu.hidden) {
- setOpen(false);
- trigger.focus();
- }
- });
- menu.addEventListener('click', function (ev) {
- if (ev.target.closest('[role="menuitem"]')) { setOpen(false); }
- });
- })();
- /**
- * Phase 16: dark-mode toggle. theme-init.js in <head> already applied the
- * 'dark' class from localStorage before stylesheet resolution, so this
- * handler only cares about the on-click flip + the menu label's text.
- * localStorage writes are try/catch'd so private-window denials degrade
- * gracefully (toggle still works for the session, just doesn't persist).
- */
- (function () {
- 'use strict';
- const btn = document.querySelector('[data-theme-toggle]');
- const label = document.querySelector('[data-theme-label]');
- if (!btn || !label) { return; }
- function stamp() {
- label.textContent = document.documentElement.classList.contains('dark') ? 'Dark' : 'Light';
- }
- stamp();
- btn.addEventListener('click', function () {
- const nowDark = document.documentElement.classList.toggle('dark');
- try { localStorage.setItem('sp:theme', nowDark ? 'dark' : 'light'); } catch (e) { /* ignore */ }
- stamp();
- });
- })();
|