1 2// 3// Add user operating system details <html class=""> 4// 5 6initalizeHTMLTag(); 7 8/* 9 * This function gets loaded when everything, including the portlets, is on 10 * the page. 11 */ 12Liferay.on('allPortletsReady', function () { 13 addAlbumGalleryJS(); 14 initSummaries(); 15}); 16 17 18function initalizeHTMLTag() { 19 const htmlElement = document.getElementsByTagName("html")[0] 20 21 if (!htmlElement) return; 22 htmlElement.classList.add('js'); 23 htmlElement.classList.add(getSimpleUserOS()); 24 htmlElement.classList.add(getCurrentBrowser()); 25 26} 27 28function getSimpleUserOS() { 29 const platform = navigator?.userAgentData?.platform || navigator?.platform; 30 let osName = "unknown"; 31 32 if (platform.toLowerCase().includes("win")) { 33 osName = "win"; 34 } else if (platform.toLowerCase().includes("mac")) { 35 osName = "mac"; 36 } else if (platform.toLowerCase().includes("x11") || platform.includes("linux")) { 37 osName = "linux"; 38 } else if (platform.toLowerCase().includes("android")) { 39 osName = "android"; 40 } else if (platform.toLowerCase().includes("iphone") || platform.includes("ipad")) { 41 osName = "ios"; 42 } 43 44 return osName; 45} 46 47function getCurrentBrowser() { 48 var userAgent = navigator.userAgent; 49 if (userAgent.indexOf("Edg") > -1) { 50 return "edge"; 51 } else if (userAgent.indexOf("Chrome") > -1) { 52 return "chrome"; 53 } else if (userAgent.indexOf("Firefox") > -1) { 54 return "firefox"; 55 } else if (userAgent.indexOf("Safari") > -1) { 56 return "safari"; 57 } else if (userAgent.indexOf("Opera") > -1) { 58 return "opera"; 59 } else if (userAgent.indexOf("Trident") > -1 || userAgent.indexOf("MSIE") > -1) { 60 return "ie"; 61 } 62 63 return "Unknown"; 64} 65 66function addAlbumGalleryJS() { 67 const mainContentElement = document.getElementById('main-content'); 68 const albumGalleryJsFile = 'yja.album-gallery.js' 69 70 if (!mainContentElement) return; 71 const albumGalleryElements = mainContentElement.querySelectorAll('div.nanogallery'); 72 73 if (albumGalleryElements?.length > 0) { 74 75 const scripts = [...document.querySelectorAll('script')]; 76 let isAlbumGalleryJsAdded = false 77 78 // Check that is yja.album-gallery.js already added 79 scripts?.forEach(script => { 80 if (script.src.indexOf(albumGalleryJsFile) !== -1) isAlbumGalleryJsAdded = true; 81 }); 82 83 if (!isAlbumGalleryJsAdded) { 84 // add JS and CS files 85 addAlbumGalleryCSS(); 86 addAlbumGalleryScript(albumGalleryJsFile); 87 88 setTimeout(() => { 89 initAlbumGallery(albumGalleryElements); 90 }, 500); 91 } 92 } 93} 94 95function addAlbumGalleryScript(fileName) { 96 if (!fileName || typeof fileName !== 'string') return; 97 const commonsUiUrl = '/o/common-ui-resources/js'; 98 99 const url = `${commonsUiUrl}/${fileName}`; 100 const scriptTag = document.createElement('script');
101 scriptTag.src = url + insertStaticTimeStamp(); 102 scriptTag.classList.add('yja-cookie-filter'); 103 104 // Add script to page 105 document.body.appendChild(scriptTag); 106 107} 108function addAlbumGalleryCSS() { 109 const link = document.createElement('link'); 110 link.href = '/o/common-ui-resources/css/yja.album-gallery.css' + insertStaticTimeStamp(); 111 link.rel = 'stylesheet' 112 113 document.head.appendChild(link); 114} 115function initAlbumGallery(albums) { 116 waitForAlbumGallery(() => { 117 const gallery = albumGallery(); 118 gallery.init(albums); 119 }); 120 121} 122 123 124function waitForAlbumGallery(callback, interval = 100) { 125 const check = setInterval(() => { 126 if (typeof albumGallery === 'function') { 127 clearInterval(check); 128 callback(); 129 } 130 }, interval); 131} 132 133// Modify this only, if there are changes done to Gallery scripts 134function insertStaticTimeStamp() { 135 return '?t=1761642667779'; 136} 137 138 139 140// Summary field logic 141function initSummaries() { 142 const summaries = document.querySelectorAll('.asset-display-summary'); 143 144 if (!summaries) return; 145 summaries.forEach(summary => { 146 summary.setAttribute("tabIndex", "-1") 147 if (summary.classList.contains('asset-display-summary--editor')) { 148 initEditorSummary(summary) 149 } else { 150 handleSummaryActions(summary) 151 initTooltip(summary) 152 } 153 154 }); 155} 156function initEditorSummary(summary) { 157 const content = summary.querySelector('.asset-display-summary__content'); 158 content.setAttribute('aria-hidden', 'true'); 159 160 const overlay = document.createElement('div'); 161 overlay.classList.add('asset-display-summary__overlay'); 162 163 const footer = document.createElement('div') 164 footer.classList.add('asset-display-summary__footer'); 165 166 const button = document.createElement('button'); 167 button.classList.add('asset-display-summary__button', 'btn', 'btn-primary'); 168 button.setAttribute('aria-expanded', 'false'); 169 170 button.setAttribute('aria-label', `${t('show')} ${t('summary')}`); 171 button.textContent = t('show'); 172 173 if (content) content.prepend(overlay); 174 175 footer.appendChild(button); 176 summary.appendChild(footer); 177 summary.classList.add('asset-display-summary--visible'); 178 179 // Listen to button 180 handleSummaryActions(summary); 181} 182 183 184function handleSummaryActions(summary) { 185 const toggler = summary.querySelector('button.asset-display-summary__button'); 186 const content = summary.querySelector('.asset-display-summary__content'); 187 const heading = summary.querySelector('#summary_content_title'); 188 if (heading) summary.setAttribute("aria-labelledby", "summary_content_title") 189 190 if (!toggler) return; 191 192 toggler.addEventListener('click', () => { 193 toggler.setAttribute('aria-expanded', toggler.getAttribute('aria-expanded') === 'false' ? 'true' : 'false'); 194 if ( content ) { 195 content.setAttribute('aria-hidden', toggler.getAttribute('aria-expanded') === 'false' ? 'true' : 'false'); 196 } 197 198 toggler.textContent = toggler.getAttribute('aria-expanded') === 'false' ? t('show') : t('hide'); 199 toggler.setAttribute('aria-label', toggler.getAttribute('aria-expanded') === 'false' ? `${t('show')} ${t('summary')}`: `${t('hide')} ${t('summary')}`); 200 201 summary.classList.toggle('asset-display-summary--expanded'); 202 setTimeout(() => { 203 if (summary.classList.contains('asset-display-summary--expanded')) { 204 // scroll to heading with offset to top 205 if (heading) scrollToPosition(heading, 50); 206 else content.scrollIntoView({ behavior: 'smooth', block: 'start' }); 207 summary.focus(); 208 } 209 },100); 210 }); 211} 212function scrollToPosition(element, offset = 0) { 213 if (!element) return; 214 const elementPosition = element.getBoundingClientRect().top; 215 const offsetPosition = elementPosition + window.pageYOffset - offset; 216 217 window.scrollTo({ 218 top: offsetPosition, 219 behavior: "smooth" 220 }); 221} 222function getCurLanguage() { 223 const browserLang = document.documentElement.lang; 224 const params = new URLSearchParams(window.location.search); 225 const languageId = params.get('languageId'); 226 const allowed = ['fi', 'sv', 'en']; 227 228 let lang = browserLang.slice(0, 2).toLowerCase(); 229 230 231 if (languageId) { 232 lang = languageId.slice(0, 2).toLowerCase() 233 } 234 if (!allowed.includes(lang)) lang = 'en' 235 236 return lang; 237} 238 239function t(word) { 240 const lang = getCurLanguage(); 241 242 const i18n = { 243 fi: { 244 show: "Näytä", 245 hide: "Piilota", 246 summary: "Lyhesti" 247 }, 248 sv: { 249 show:"Visa", 250 hide: "Dölja", 251 summary: "Kortfattat" 252 }, 253 en: { 254 show: "Show", 255 hide: "Hide", 256 summary: "Briefly" 257 } 258 } 259 return i18n[lang] && i18n[lang][word] ? i18n[lang][word] : word; 260} 261 262function initTooltip(summary) { 263 const toggler = summary.querySelector('#summary_info_toggler'); 264 let isOpen = false;
265 if (!toggler) return; 266 267 toggler.addEventListener('click', (evt) => { 268 const sibling = toggler.nextElementSibling; 269 toggler.setAttribute('aria-expanded',toggler.getAttribute('aria-expanded') === 'true' ? 'false' : 'true'); 270 271 isOpen = toggler.getAttribute('aria-expanded') === 'true'; 272 if (sibling && sibling.classList.contains('summary-tooltip__content')) { 273 if (isOpen) { 274 setTimeout(() => { 275 sibling.classList.remove('d-none'); 276 sibling.focus(); 277 278 sibling.addEventListener('focusout', (event) => { 279 const next = event.relatedTarget; 280 sibling.classList.add('d-none'); 281 if (next !== toggler) { 282 toggler.focus(); 283 toggler.setAttribute('aria-expanded','false'); 284 } 285 }); 286 287 document.addEventListener('keydown', (event) => { 288 if (event.key === 'Escape') { 289 toggler.focus(); 290 toggler.setAttribute('aria-expanded','false'); 291 } 292 }); 293 },0) 294 } else { 295 sibling.classList.add('d-none'); 296 } 297 298 } 299 }) 300} 301 302function translateText (text) { 303 const locale = Liferay.ThemeDisplay.getLanguageId(); 304 const i18n = { 305 "fi_FI" : { 306 "title" : "Vastaa käyttäjäkyselyyn", 307 "close" : "Sulje ponnahdusikkuna", 308 "content": "Auta meitä kehittämään verkkopalvelumme sisältöjä. Kysely vie muutaman minuutin.", 309 "link": "Siirry kyselyyn", 310 }, 311 "sv_SE": { 312 "title" : "WebWebropol kysely (SV)", 313 "close" : "Stäng popup" 314 }, 315 "en_US": { 316 "title" : "WebWebropol kysely (EN)", 317 "close" : "Close popup" 318 } 319 } 320 return i18n[locale][text] ? i18n[locale][text] : 'WebWebropol kysely (EN)' 321} 322 323// -------------------------------------- 324// Popup modal for YJA 325// --------------------------------------- 326function createYjaPopupModal(config = {}) { 327 const settings = { 328 autoShowDelay: 500, 329 focusDelay: 200, 330 language: 'fi_FI', 331 showOnlyFor: null, // ["fi_FI"] 332 showSkipper: false, 333 hideForAdmin: true, 334 cssClassesToExclude: ["has-control-menu"], 335 // Dismissal storage 336 storageType: "session", 337 storageKey: "yja_popup", // unique session key 338 339 // Content 340 title: translateText("title"), 341 content: translateText("content"), 342 linkText: translateText("link"), 343 closeText: translateText("close"), 344 linkUrl: "", 345 template: null, // can use templates 346 347 // Callbacks 348 onOpen: null, // function(modal) {} 349 onClose: null, // function(modal) {} 350 351 // Internal 352 containerId: "yja-popup-modal", 353 closeBtnId: "yja-popup-modal-close", 354 355 // Location 356 modalWidth: "300px", 357 cssTransition: ".5s", 358 startLocation: { 359 positionX: "left", 360 positionY: "bottom", 361 offsetX: `-320px`, 362 offsetY: "10vh", 363 opacity: "1" 364 }, 365 endLocation: { 366 positionX: "left", 367 positionY: "bottom", 368 offsetX: "0", 369 offsetY: "10vh", 370 opacity: "1" 371 }, 372 373 374 ...config 375 }; 376 377 const api = { 378 element: null, 379 closeBtn: null, 380 init, 381 open, 382 close 383 }; 384 385 const MODAL_SHOWN_AT_KEY = "yja:autoPopupShowStartedAt"; 386 387 // ----------------------------------- 388 // Init logic 389 // ----------------------------------- 390 function init() { 391 if (shouldAbort()) return; 392 createModal(); 393 open(); 394 } 395 396 function shouldAbort() { 397 const { showOnlyFor, language, storageType, storageKey, hideForAdmin, cssClasse
397sToExclude } = settings; 398 399 if (showOnlyFor && !showOnlyFor.includes(language)) { 400 return true; 401 } 402 403 if (storageKey && storageType) { 404 const store = storageType === "local" ? localStorage : sessionStorage; 405 if (store.getItem(storageKey)) return true; 406 } 407 408 if (hideForAdmin) { 409 return settings.cssClassesToExclude.some(cls => document.body.classList.contains(cls)) 410 } 411 412 return false; 413 } 414 415 // ----------------------------------- 416 // Modal Creation 417 // ----------------------------------- 418 function createModal() { 419 const { 420 template, containerId, closeBtnId, 421 title, content, linkText, linkUrl, closeText 422 } = settings; 423 424 // Custom template override 425 if (template) { 426 const wrapper = document.createElement("div"); 427 wrapper.innerHTML = template.trim(); 428 api.element = wrapper; 429 document.body.appendChild(wrapper); 430 431 api.closeBtn = wrapper.querySelector("[data-close]"); 432 if (api.closeBtn) api.closeBtn.addEventListener("click", close); 433 434 enableFocusTrap(wrapper); 435 return; 436 } 437 438 // initialize styles 439 initializeModalCssVariables(); 440 441 // Default modal structure 442 const el = document.createElement("div"); 443 el.id = containerId; 444 el.classList.add("yja-modal--dialog"); 445 446 el.innerHTML = ` 447 <div class="yja-modal__header"> 448 <button class="yja-modal__button--close" id="${closeBtnId}"> 449 <span class="icon icon-remove"></span> 450 <span class="sr-only">${closeText}</span> 451 </button> 452 </div> 453 454 <div class="yja-modal__body"> 455 <h2 class="h4">${title}</h2> 456 <p>${content}</p> 457 458 ${linkUrl ? ` 459 <p> 460 <a href="${linkUrl}" target="_blank"> 461 ${linkText} 462 </a> 463 </p> 464 ` : ""} 465 </div> 466 `; 467 468 document.body.appendChild(el); 469 470 api.element = el; 471 api.closeBtn = el.querySelector(`#${closeBtnId}`); 472 api.closeBtn.addEventListener("click", close); 473 474 enableFocusTrap(el); 475 } 476 477 // ----------------------------------- 478 // Open 479 // ----------------------------------- 480 function open() { 481 // Set modal show settings 482 scheduleModalAutoShow(api, settings); 483 484 setTimeout(() => { 485 if (api.closeBtn) api.closeBtn.focus(); 486 }, settings.focusDelay); 487 } 488 489 // ----------------------------------- 490 // Close 491 // ----------------------------------- 492 function close() { 493 api.element.classList.remove("yja-modal--open"); 494 495 const {storageType, storageKey, showSkipper} = settings; 496 497 if (storageKey && storageType) { 498 const store = storageType === "local" ? localStorage : sessionStorage; 499 store.setItem(storageKey, "true"); 500 } 501 502 if (showSkipper) { 503 const skip = document.getElementById("skip-to-content"); 504 if (skip) skip.focus(); 505 } 506 507 if (settings.onClose) settings.onClose(api); 508 } 509 510 // ----------------------------------- 511 // Focus Trap 512 // ----------------------------------- 513 function enableFocusTrap(container) { 514 const selector = ` 515 a[href], 516 button, 517 textarea, 518 input[type="text"], 519 input[type="radio"], 520 input[type="checkbox"], 521 select, 522 [tabindex]:not([tabindex="-1"]) 523 `; 524 525 const focusable = () => Array.from(container.querySelectorAll(selector)) 526 .filter(el => !el.disabled && el.offsetParent !== null); 527 528 container.addEventListener("keydown", (e) => { 529 if (e.key !== "Tab") return; 530 531 const items = focusable(); 532 if (items.length === 0) return; 533 534 const first = items[0]; 535 const last = items[items.length - 1]; 536 537 if (e.shiftKey) { 538 if (document.activeElement === first) { 539 e.preventDefault(); 540 last.focus(); 541 } 542 } else { 543 if (document.activeElement === last) { 544 e.preventDefault(); 545 first.focus(); 546 } 547 } 548 }); 549 } 550 551 // position logic 552 function initializeModalCssVariables() { 553 let computedOffset = ''; 554 555 if (settings.modalWidth !== '300px') { 556 applyAdditionalCssVariables(
557 '--yja-modal-dialog-max-width', 558 computedWidth(settings.modalWidth) 559 ); 560 computedOffset = computedWidth(settings.modalWidth, true, true); 561 } 562 563 // START position 564 const startXCenter = applyCssVariablesByAxis('start', 'x', { 565 position: settings.startLocation.positionX, 566 offset: computedOffset !== '' ? computedOffset : settings.startLocation.offsetX 567 }); 568 569 const startYCenter = applyCssVariablesByAxis('start', 'y', { 570 position: settings.startLocation.positionY, 571 offset: settings.startLocation.offsetY 572 }); 573 574 // END position 575 const endXCenter = applyCssVariablesByAxis('end', 'x', { 576 position: settings.endLocation.positionX, 577 offset: settings.endLocation.offsetX 578 }); 579 580 const endYCenter = applyCssVariablesByAxis('end', 'y', { 581 position: settings.endLocation.positionY, 582 offset: settings.endLocation.offsetY 583 }); 584 585 // Transform logic 586 applyTransform('start', startXCenter, startYCenter); 587 applyTransform('end', endXCenter, endYCenter); 588 589 // Additional settings 590 applyAdditionalCssVariables('--yja-modal-dialog-start-opacity', settings.startLocation.opacity); 591 applyAdditionalCssVariables('--yja-modal-dialog-end-opacity', settings.endLocation.opacity); 592 applyAdditionalCssVariables( 593 '--yja-modal-dialog-transition', 594 settings.cssTransition ? `all ${settings.cssTransition} linear` : '' 595 ); 596 } 597 598 function applyTransform(prefix, xCenter, yCenter) { 599 const transformVar = `--yja-modal-dialog-${prefix}-transform`; 600 601 if (xCenter && yCenter) { 602 document.documentElement.style.setProperty( 603 transformVar, 604 'translate(-50%, -50%)' 605 ); 606 } 607 else if (xCenter) { 608 document.documentElement.style.setProperty( 609 transformVar, 610 'translateX(-50%)' 611 ); 612 } 613 else if (yCenter) { 614 document.documentElement.style.setProperty( 615 transformVar, 616 'translateY(-50%)' 617 ); 618 } 619 else { 620 document.documentElement.style.removeProperty(transformVar); 621 } 622 } 623 624 // Session storage logic 625 function getAutoShowStartedAt() { 626 const raw = sessionStorage.getItem(MODAL_SHOWN_AT_KEY); 627 const ts = raw ? Number(raw) : NaN; 628 return Number.isFinite(ts) ? ts : null; 629 } 630 631 function markAutoShowStartedNow() { 632 sessionStorage.setItem(MODAL_SHOWN_AT_KEY, String(Date.now())); 633 } 634 635 function clearAutoShowStarted() { 636 sessionStorage.removeItem(MODAL_SHOWN_AT_KEY); 637 } 638 639 function scheduleModalAutoShow(api, settings) { 640 const delay = Number(settings.autoShowDelay) || 0; if (delay <= 0) { 641 // No delay requested; open now. 642 api.element.classList.add("yja-modal--open"); 643 if (settings.onOpen) settings.onOpen(api); 644 return; 645 } 646 const startedAt = getAutoShowStartedAt(); 647 const now = Date.now(); 648 // If this is the first time in the session, set the start time. 649 if (!startedAt) { 650 markAutoShowStartedNow(); 651 } 652 // Re-read to ensure we have a timestamp, even if it was just set 653 const effectiveStartedAt = getAutoShowStartedAt() || now; 654 const elapsed = now - effectiveStartedAt; 655 const remaining = Math.max(delay - elapsed, 0); 656 if (remaining === 0) { 657 // Delay has already been satisfied in this session 658 api.element.classList.add("yja-modal--open"); 659 if (settings.onOpen) settings.onOpen(api); 660 // Optional: clear, if you only want this to happen once per session 661 // clearAutoShowStarted(); 662 } else { 663 // Wait only the remaining time 664 setTimeout(() => { 665 api.element.classList.add("yja-modal--open"); 666 if (settings.onOpen) settings.onOpen(api); 667 // Optional: clear, if you only want this to happen once per session 668 clearAutoShowStarted(); 669 }, remaining); 670 } 671 } 672 673 674 function applyCssVariablesByAxis(prefix, axis, { position, offset }) { 675 const isX = axis === 'x'; 676 677 const propA = isX ? 'left' : 'top'; 678 const propB = isX ? 'right' : 'bottom'; 679 680 const varA = `--yja-modal-dialog-${prefix}-${axis}-offset-${propA}`; 681 const varB = `--yja-modal-dialog-${prefix}-${axis}-offset-${propB}`; 682 683 if (position === propA) { 684 document.documentElement.style.setProperty(varA, offset); 685 document.documentElement.style.setProperty(varB, 'auto'); 686 } 687 else if (position === propB) { 688 document.documentElement.style.setProperty(varA, 'auto'); 689 document.documentElement.style.setProperty(varB, offset); 690 } 691 else if (position === 'center') { 692 document.documentElement.style.setProperty(varA, '50%'); 693 document.documentElement.style.setProperty(varB, 'auto'); 694 return true; // kertoo että tämä akseli on center 695 } 696 697 return false;
698 } 699 700 701 function applyAdditionalCssVariables(name, value) { 702 if (value !== '') { 703 document.documentElement.style.setProperty(name, value); 704 } else { 705 document.documentElement.style.removeProperty(name); 706 } 707 } 708 709 function computedWidth(cssValue, addMargin = false, offViewport = false) { 710 const value = parseFloat(cssValue); 711 const unit = cssValue.replace(value, ""); 712 713 return `${offViewport ? '-' : ''}${(value + (addMargin ? 20 : 0)) + (unit ? unit : 'px')}`; 714 } 715 716 return api; 717 718} 719 720// YJAY-5436 - auto-focus page tree 721(function autoFocusPageTree() { 722 if (!document.body) { 723 return setTimeout(autoFocusPageTree, 50); 724 } 725 726 function waitFor(selector, root = document) { 727 return new Promise(resolve => { 728 const existing = root.querySelector(selector); 729 if (existing) return resolve(existing); 730 731 const obs = new MutationObserver(() => { 732 const el = root.querySelector(selector); 733 if (el) { 734 obs.disconnect(); 735 resolve(el); 736 } 737 }); 738 739 obs.observe(root, { childList: true, subtree: true }); 740 }); 741 } 742 743 waitFor('.lfr-tree') 744 .then(tree => { 745 console.debug('Page Tree detected'); 746 return waitFor('.tree-node-selected', tree); 747 }) 748 .then(selected => { 749 console.debug('Scrolling to selected node'); 750 selected.scrollIntoView({ block: 'center', behavior: 'auto' }); 751 }); 752})(); 753 754//YJA73PROD-179 & YJAY-6153 suppress multiple errors 755(function errorSuppressionObserver() { 756 if (!document.body) { 757 return setTimeout(errorSuppressionObserver, 50); 758 } 759 const seenErrors = new Set(); 760 761 const observer = new MutationObserver((mutations) => { 762 for (const m of mutations) { 763 for (const node of m.addedNodes) { 764 if (!(node instanceof HTMLElement)) { 765 continue; 766 } 767 768 if (node.classList.contains('alert') 769 && node.classList.contains('alert-dismissible') 770 && node.classList.contains('alert-danger')) { 771 if (seenErrors.has(node.innerText)) { 772 node.style.display = 'none'; 773 console.debug("Error suppressed"); 774 } else { 775 seenErrors.add(node.innerText); 776 } 777 } 778 } 779 } 780 }); 781 782 observer.observe(document.body, { 783 childList: true, 784 subtree: true 785 }); 786 787 setTimeout(() => { 788 observer.disconnect(); 789 seenErrors.clear(); 790 console.debug('Error suppression observer disabled'); 791 }, 5000); 792})();
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.