1/* SkrivSikkert Unified Mobile Navigation V2 */ 2window.__disableMiscMobileFooter = true; 3 4(function () { 5 'use strict'; 6 7 var mql = window.matchMedia('(max-width: 767px)'); 8 var initialized = false; 9 var cleanupScrollHide = null; 10 var langSelectorLoadPromise = null; 11 var lastMoreActivationWasKeyboard = false; 12 var LANG_SELECTOR_SCRIPT = '/assets/js/lang-selector.js?v=20260519-mobile-more-lang-login-284'; 13 14 function getThemeLabel(isDarkNow) { 15 var isDa = true; 16 try { 17 var ui = localStorage.getItem('sb_ui_lang'); 18 if (ui && ui !== 'da') isDa = false; 19 } catch (_) {} 20 if (isDa) return isDarkNow ? 'Lys tilstand' : 'M\u00f8rk tilstand'; 21 return isDarkNow ? 'Light mode' : 'Dark mode'; 22 } 23 24 function init() { 25 if (initialized) return; 26 initialized = true; 27 document.body.classList.add('has-mn-bar'); 28 cleanupScrollHide = initScrollHide(); 29 } 30 31 function teardown() { 32 if (!initialized) return; 33 initialized = false; 34 document.body.classList.remove('has-mn-bar'); 35 setMoreOpen(false); 36 if (typeof cleanupScrollHide === 'function') cleanupScrollHide(); 37 cleanupScrollHide = null; 38 var fab = document.querySelector('.mn-fab-group'); 39 if (fab) fab.classList.remove('is-hidden'); 40 } 41 42 function handleBreakpoint(e) { 43 if (e.matches) { 44 init(); 45 } else { 46 teardown(); 47 } 48 } 49 50 if (mql.addEventListener) { 51 mql.addEventListener('change', handleBreakpoint); 52 } else if (mql.addListener) { 53 mql.addListener(handleBreakpoint); 54 } 55 56 if (mql.matches) init(); 57 58 function initScrollHide() { 59 var fabGroup = document.querySelector('.mn-fab-group'); 60 if (!fabGroup) return null; 61 62 var lastPositions = new Map(); 63 var scrollThreshold = 56; 64 var ticking = false; 65 var pendingTarget = null; 66 67 function getScrollPosition(target) { 68 if (!target || target === document || target === document.documentElement || target === document.body) { 69 return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0; 70 } 71 return target.scrollTop || 0; 72 } 73 74 function onScroll(e) { 75 pendingTarget = (e && e.target) || window; 76 if (ticking) return; 77 requestAnimationFrame(function () { 78 var target = pendingTarget || window; 79 var currentY = getScrollPosition(target); 80 var lastY = lastPositions.has(target) ? lastPositions.get(target) : 0; 81 82 if (currentY > lastY + scrollThreshold) { 83 fabGroup.classList.add('is-hidden'); 84 lastPositions.set(target, currentY); 85 } else if (currentY < lastY - 10) { 86 fabGroup.classList.remove('is-hidden'); 87 lastPositions.set(target, currentY); 88 } 89 pendingTarget = null; 90 ticking = false; 91 }); 92 ticking = true; 93 } 94 95 window.addEventListener('scroll', onScroll, { passive: true }); 96 document.addEventListener('scroll', onScroll, { passive: true, capture: true }); 97 98 return function cleanup() { 99 window.removeEventListener('scroll', onScroll); 100 document.removeEventListener('scroll', onScroll, true); 101 }; 102 } 103 104 function getMoreParts() { 105 return { 106 toggle: document.querySelector('[data-mn-more-toggle]'), 107 sheet: document.getElementById('bb-mobile-more-sheet'), 108 backdrop: document.querySelector('[data-mn-more-close]') 109 }; 110 } 111 112 function clearPointerMoreFocus(toggle) { 113 if (!toggle) return; 114 // Keep Safari's restored/late pointer focus from painting the keyboard ring. 115 toggle.style.outline = 'none'; 116 var clear = function () { 117 if (document.activeElement === toggle) toggle.blur(); 118 }; 119 clear(); 120 // iOS Safari may apply focus after the click handler/default action completes. 121 window.setTimeout(clear, 0); 122 window.setTimeout(clear, 50); 123 } 124 125 function setMoreOpen(open, returnFocus, focusFirst) { 126 var parts = getMoreParts(); 127 if (!parts.toggle || !parts.sheet || !parts.backdrop) return; 128 129 parts.toggle.setAttribute('aria-expanded', open ? 'true' : 'false'); 130 parts.sheet.hidden = !open; 131 parts.backdrop.hidden = !open;
132 document.body.classList.toggle('has-mn-more-open', open); 133 134 if (!open) { 135 var langControls = parts.sheet.querySelectorAll('[data-mn-lang-control]'); 136 for (var i = 0; i < langControls.length; i++) { 137 var langList = langControls[i].querySelector('[data-mn-lang-list]'); 138 var langToggle = langControls[i].querySelector('[data-mn-lang-toggle]'); 139 if (langList) langList.hidden = true; 140 if (langToggle) langToggle.setAttribute('aria-expanded', 'false'); 141 } 142 } 143 144 if (open && focusFirst) { 145 var firstTarget = parts.sheet.querySelector('.mn-more-link.is-active') || parts.sheet.querySelector('.mn-more-link'); 146 if (firstTarget) { 147 window.setTimeout(function () { 148 firstTarget.focus({ preventScroll: true }); 149 }, 0); 150 } 151 } else if (open && document.activeElement === parts.toggle) { 152 parts.toggle.blur(); 153 } else if (!open && !returnFocus && document.activeElement === parts.toggle) { 154 parts.toggle.blur(); 155 } else if (!open && returnFocus && document.contains(parts.toggle)) { 156 parts.toggle.focus({ preventScroll: true }); 157 } 158 } 159 160 function isMoreOpen() { 161 var parts = getMoreParts(); 162 return Boolean(parts.sheet && !parts.sheet.hidden); 163 } 164 165 function withLangSelector(trigger, attempts) { 166 if (window.LangSelector && typeof window.LangSelector.open === 'function') { 167 window.LangSelector.open(trigger); 168 return; 169 } 170 171 var existing = document.querySelector('script[src*="/assets/js/lang-selector.js"]'); 172 if (existing && !langSelectorLoadPromise && attempts < 20) { 173 window.setTimeout(function () { 174 withLangSelector(trigger, attempts + 1); 175 }, 50); 176 return; 177 } 178 179 if (!langSelectorLoadPromise) { 180 langSelectorLoadPromise = new Promise(function (resolve, reject) { 181 var script = document.createElement('script'); 182 script.src = LANG_SELECTOR_SCRIPT; 183 script.defer = true; 184 script.addEventListener('load', resolve, { once: true }); 185 script.addEventListener('error', reject, { once: true }); 186 document.head.appendChild(script); 187 }); 188 } 189 190 langSelectorLoadPromise.then(function () { 191 if (window.LangSelector && typeof window.LangSelector.open === 'function') { 192 window.LangSelector.open(trigger); 193 } 194 }).catch(function () {}); 195 } 196 197 document.addEventListener('click', function (e) { 198 var moreToggle = e.target.closest('[data-mn-more-toggle]'); 199 if (moreToggle) { 200 e.preventDefault(); 201 var opening = !isMoreOpen(); 202 var keyboardActivation = e.detail === 0; 203 lastMoreActivationWasKeyboard = keyboardActivation; 204 if (keyboardActivation) moreToggle.style.removeProperty('outline'); 205 setMoreOpen(opening, keyboardActivation, opening && keyboardActivation); 206 if (!keyboardActivation) clearPointerMoreFocus(moreToggle); 207 return; 208 } 209 210 if (e.target.closest('[data-mn-more-close]')) { 211 var backdropKeyboardActivation = e.detail === 0; 212 lastMoreActivationWasKeyboard = backdropKeyboardActivation; 213 setMoreOpen(false, backdropKeyboardActivation); 214 if (!backdropKeyboardActivation) clearPointerMoreFocus(getMoreParts().toggle); 215 return; 216 } 217 218 var moreLink = e.target.closest('.mn-more-link'); 219 if (moreLink && !moreLink.hasAttribute('data-mn-action')) { 220 setMoreOpen(false, false); 221 return; 222 } 223 224 var fab = e.target.closest('[data-mn-action]'); 225 if (!fab) return; 226 227 var action = fab.getAttribute('data-mn-action'); 228 229 switch (action) { 230 case 'new-document': { 231 var newDocBtn = document.getElementById('sk-doc-new-btn'); 232 if (newDocBtn) newDocBtn.click(); 233 break; 234 } 235 236 case 'new-task': { 237 var taskInput = document.getElementById('task-input'); 238 if (taskInput) { 239 taskInput.focus(); 240 taskInput.scrollIntoView({ behavior: 'smooth', block: 'center' }); 241 } 242 break; 243 } 244 245 case 'compose-email': { 246 var composeBtn = document.getElementById('email-compose-btn'); 247 if (composeBtn) composeBtn.click(); 248 break; 249 } 250 251 case 'new-event': { 252 var eventModal = document.getElementById('event-modal');
253 if (eventModal) { 254 eventModal.removeAttribute('inert'); 255 eventModal.classList.add('active'); 256 var eventTitle = document.getElementById('ev-title'); 257 if (eventTitle) { 258 window.setTimeout(function () { 259 eventTitle.focus({ preventScroll: true }); 260 }, 300); 261 } 262 } 263 break; 264 } 265 266 case 'open-sofia': { 267 if (window.openMobileAiSheet) { 268 window.openMobileAiSheet('ai'); 269 } else { 270 var askAiToolbarBtn = document.querySelector('[data-action="ask-ai"]'); 271 var aiTab = document.getElementById('option-ai') || document.querySelector('[data-option="ai-panel"]'); 272 if (askAiToolbarBtn) { 273 askAiToolbarBtn.click(); 274 } else if (aiTab) { 275 aiTab.click(); 276 } else { 277 var aiPanel = document.getElementById('ai-panel'); 278 if (aiPanel) { 279 aiPanel.style.display = 'flex'; 280 aiPanel.scrollIntoView({ behavior: 'smooth', block: 'start' }); 281 } 282 } 283 } 284 window.setTimeout(function () { 285 var aiInput = document.getElementById('ai-card-input'); 286 if (aiInput) aiInput.focus({ preventScroll: true }); 287 }, 250); 288 break; 289 } 290 291 case 'open-ask-ai': { 292 var askAiBtn = document.getElementById('email-ask-ai-btn'); 293 if (askAiBtn) askAiBtn.click(); 294 break; 295 } 296 297 case 'go-today': { 298 if (window._calendarInstance && typeof window._calendarInstance.today === 'function') { 299 window._calendarInstance.today(); 300 } else { 301 var todayBtn = document.querySelector('.fc-today-button'); 302 if (todayBtn) todayBtn.click(); 303 } 304 break; 305 } 306 307 case 'toggle-theme': { 308 var isDark = document.documentElement.getAttribute('data-theme') === 'dark'; 309 var next = isDark ? 'light' : 'dark'; 310 document.documentElement.setAttribute('data-theme', next); 311 try { 312 localStorage.setItem('skrivsikkert_theme', next); 313 localStorage.setItem('darkMode', next === 'dark' ? 'true' : 'false'); 314 localStorage.setItem('ss_theme', next); 315 localStorage.setItem('theme', next); 316 document.cookie = 'ss_theme=' + next + ';path=/;max-age=31536000;SameSite=Lax'; 317 document.cookie = 'ss-dark-mode=' + (next === 'dark' ? '1' : '0') + ';path=/;max-age=31536000;SameSite=Lax'; 318 } catch (_) {} 319 var moonEl = document.querySelector('.mn-icon-moon'); 320 var sunEl = document.querySelector('.mn-icon-sun'); 321 var labelEl = document.querySelector('.mn-theme-label'); 322 if (moonEl) moonEl.style.display = next === 'dark' ? 'none' : ''; 323 if (sunEl) sunEl.style.display = next === 'dark' ? '' : 'none'; 324 if (labelEl) labelEl.textContent = getThemeLabel(next === 'dark'); 325 var sidebarToggle = document.getElementById('theme-toggle'); 326 if (sidebarToggle) sidebarToggle.checked = next === 'dark'; 327 setMoreOpen(false, false); 328 break; 329 } 330 331 case 'open-language': { 332 if (window.LangSelector && typeof window.LangSelector.open === 'function') { 333 window.LangSelector.open(fab); 334 } else { 335 withLangSelector(fab, 0); 336 } 337 break; 338 } 339 } 340 }); 341 342 document.addEventListener('pointerdown', function () { 343 lastMoreActivationWasKeyboard = false; 344 var toggle = getMoreParts().toggle; 345 if (toggle) toggle.style.outline = 'none'; 346 }, true); 347 348 document.addEventListener('touchstart', function () { 349 lastMoreActivationWasKeyboard = false; 350 var toggle = getMoreParts().toggle; 351 if (toggle) toggle.style.outline = 'none'; 352 }, { passive: true, capture: true }); 353 354 window.addEventListener('pagehide', function () { 355 if (lastMoreActivationWasKeyboard) return; 356 var toggle = getMoreParts().toggle; 357 if (toggle && document.activeElement === toggle) toggle.blur(); 358 }); 359 360 window.addEventListener('pageshow', function () { 361 if (!lastMoreActivationWasKeyboard) clearPointerMoreFocus(getMoreParts().toggle); 362 }); 363 364 window.addEventListener('popstate', function () { 365 if (!lastMoreActivationWasKeyboard) clearPointerMoreFocus(getMoreParts().toggle); 366 }); 367 368 (function initThemeToggle() { 369 var isDark = document.documentElement.getAttribute('data-theme') === 'dark'; 370 var moonEl = document.querySelector('.mn-icon-moon'); 371 var sunEl = document.querySelector('.mn-icon-sun'); 372 var labelEl = document.querySelector('.mn-theme-label'); 373 if (moonEl) moonEl.style.display = isDark ? 'none' : ''; 374 if (sunEl) sunEl.style.display = isDark ? '' : 'none'; 375 if (labelEl) labelEl.textContent = getThemeLabel(isDark); 376 })(); 377 378 window.addEventListener('ss:lang-changed', function () { 379 var labelEl = document.querySelector('.mn-theme-label'); 380 if (labelEl) { 381 var isDark = document.documentElement.getAttribute('data-theme') === 'dark'; 382 labelEl.textContent = getThemeLabel(isDark); 383 } 384 }); 385 386 window.ssSetBadge = function (page, count) { 387 // querySelectorAll, not querySelector: email now carries TWO badges â one in the More 388 // sheet where the link lives, one mirrored on the More tab so unread mail is visible 389 // without opening the sheet. Updating only the first would silently strand the other. 390 var mobileBadges = document.querySelectorAll('[data-mn-badge="' + page + '"]'); 391 for (var i = 0; i < mobileBadges.length; i++) { 392 if (count > 0) { 393 mobileBadges[i].textContent = count > 99 ? '99+' : String(count); 394 mobileBadges[i].hidden = false;
395 } else { 396 mobileBadges[i].hidden = true; 397 } 398 } 399 400 var sidebarBadges = document.querySelectorAll('[data-sidebar-badge="' + page + '"]'); 401 for (var j = 0; j < sidebarBadges.length; j++) { 402 sidebarBadges[j].hidden = !(count > 0); 403 } 404 }; 405 406 window.mnSetBadge = window.ssSetBadge; 407 408 document.addEventListener('keydown', function (e) { 409 var toggle = getMoreParts().toggle; 410 if (toggle) toggle.style.removeProperty('outline'); 411 if (e.key === 'Escape' && isMoreOpen()) { 412 lastMoreActivationWasKeyboard = true; 413 setMoreOpen(false, true); 414 } 415 }); 416})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.