PageSourceSearch

https://valtioneuvosto.fi/o/yja-common-ui-theme-contributor/js/yja…=chrome&languageId=sv_SE&t=1790013988764

js valtioneuvosto.fi collected 2026-09-24 08:39:58 UTC 25,138 bytes, 792 lines download raw bytes

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.