PageSourceSearch

https://takorkortappen.se/app/templates/default/assets/js/utils.1782817259.js

js takorkortappen.se collected 2026-09-24 14:27:33 UTC 33,604 bytes, 943 lines download raw bytes

1var APP = APP || {};
2// Utilities
3APP.utils = (function () {
4    "use strict";
5
6    let popoverEventListenerAdded = false;
7
8    return {
9
10        checkUsername: function (msisdn, type) {
11            switch (type) {
12                case 'tel':
13                    return this.checkNorwegianMsisdn(msisdn)
14                case 'email':
15                    return this.checkEmail(msisdn)
16            }
17        },
18
19        checkNorwegianMsisdn: function (msisdn) {
20            return msisdn.replace(/^[^49]|[^0-9]/g, '');
21        },
22
23        checkEmail: function (email) {
24            return email.replace(/[^a-zA-Z0-9@._+-]/g, '');
25        },
26
27        validateUsername: function (msisdn, type) {
28            switch (type) {
29                case 'tel':
30                    return this.validateNorwegianMsisdn(msisdn)
31                case 'email':
32                    return this.validateEmail(msisdn)
33            }
34        },
35
36
37        validateNorwegianMsisdn: function (msisdn) {
38            msisdn = this.stripSpaces(msisdn);
39
40            var regex = new RegExp('^([4]|[9])[0-9]{7}$');
41            return regex.test(msisdn);
42        },
43
44        validateEmail: function (email) {
45            var that        = this,
46                validate    = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
47
48            return validate.test(that.stripSpaces(email));
49        },
50
51        stripSpaces: function (str) {
52            if (str && str.length > 2 && str.indexOf(' ') !== -1) {
53                str = str.split(' ').join('');
54            }
55            return str;
56        },
57        pushGoogle: function (eventCategory, eventAction, eventLabel, nonInteraction) {
58            nonInteraction = nonInteraction || false;
59            if (typeof gtag !== 'undefined' && eventCategory && eventAction && eventLabel) {
60                gtag('event', eventAction, {
61                    'event_category' : eventCategory,
62                    'event_label' : eventLabel,
63                    'non_interaction': nonInteraction
64                });
65            }
66        },
67         pushGoogleSelectItem: function () {
68            if (!this.canSubmitEcommerceEvent()) {
69                return;
70            }
71
72            gtag("event", "select_item", {
73                items: [this.getEcommerceItem()],
74            });
75        },
76        pushGoogleViewItem: function () {
77            if (!this.canSubmitEcommerceEvent()) {
78                return;
79            }
80
81            gtag("event", "view_item", {
82                currency: window.currency,
83                value: parseInt(APP.ecommerce.price),
84                items: [this.getEcommerceItem()],
85            });
86        },
87        pushGoogleViewItemListOnConsentEvent: function (items) {
88            window.addEventListener('consent-updated', () => {
89                if (typeof gtag === 'undefined') {
90                    return;
91                }
92
93                gtag('event', 'view_item_list', {
94                    "currency": window.currency,
95                    "items": items
96                });
97            }, { once: true });
98        },
99        pushGoogleBeginCheckout: function () {
100            if (!this.canSubmitEcommerceEvent()) {
101                return;
102            }
103                
104            gtag("event", "begin_checkout", {
105                currency: window.currency,
106                value: parseInt(APP.ecommerce.price),
107                items: [this.getEcommerceItem()],
108            });
109        },
110        pushGooglePurchase: function () {
111            if (!this.canSubmitEcommerceEvent()) {
112                return;
113            }
114
115            gtag("event", "purchase", {
116                transaction_id: APP.transaction.token_id,
117                value: APP.transaction.total_price,
118                currency: window.currency,
119                items: [this.getEcommerceItem()],
120            });
121
122            if (typeof fbq !== "undefined") {
123                fbq("track", "Purchase", { currency: window.currency, value: 1 });
124            }
125        },
126        canSubmitEcommerceEvent: function () {
127            return typeof gtag !== "undefined" && typeof APP.ecommerce !== "undefined";
128        },
129        getEcommerceItem: function () {
130            return {
131                item_id: APP.ecommerce.item_id,
132                item_name: APP.ecommerce.item_name,
133                item_brand: APP.ecommerce.item_brand,
134                item_category: APP.ecommerce.item_category,
135                item_variant: APP.ecommerce.item_variant,
136                index: APP.ecommerce.index,
137                item_list_name: APP.ecommerce.item_list_name,
138                quantity: APP.ecommerce.quantity,
139                price: APP.ecommerce.price,
140            };
141        },
142        ajaxCall: function (prop, callback, failCallback) {
143            if (typeof prop === 'object') {
144
145				if (APP.core.getLanguageUri() !== "") {
146					prop.url = APP.core.getLanguageUri() + prop.url;
147				}
148
149                $.ajax(prop).done(function(resp) {
150
151                    if (callback !== undefined) {
152                        if (resp !== undefined) {
153                            callback(resp);
154                        } else {
155                            callback();
156                        }
157                    } else {
158                        return resp;
159                    }
160
161                }).fail(function(resp) {
162                    if (failCallback !== undefined) {
163                        failCallback(resp);
164                    }
165                });
166            } else {
167                APP.utils.ajaxLogger({
168                    type: 'ajax',
169                    message: '[ERROR] ajaxCall: prop is not an object, type: ' + typeof(prop)
170                });
171            }
172        },
173
174
175        /** LOG MESSAGE TO SERVER.
176         *
177         * @param data [object]
178         * data.type        [optional] logfile, default: ajax.
179         * data.message     [required] message to be logged. Example: "[WARN] var g undefined, should be one of bla bla."
180         */
181        ajaxLogger: function (data) {
182
183            if (!data.type) {
184                data.type = 'ajax';
185            }
186
187            if (data.message) {
188
189                $.ajax({
190                    type: 'POST',
191                    url: '/ajax/log',
192                    data: {
193                        type: data.type,
194                        message: data.message
195                    },
196                    dataType: 'json'
197                }).done(function() {
198
199                });
200            }
201        },
202
203
204        redirect: function (uri) {
205            if (uri === undefined) {
206                return;
207            }
208
209            if (uri === location.pathname) {
210                window.location = uri;
211                return;
212            }
213
214            let langPrefix = APP.core.getLanguageUri();
215            let destination = uri;
216
217            if (langPrefix === '/en' && uri.indexOf('/en/') < 0) {
218                destination = (uri === '/') ? langPrefix : langPrefix + uri;
219            }
220
221            window.location = destination;
222        },
223
224
225        showLoading: function (obj) {
226            if (!$.trim($(obj).find('.loading').html()).length) {
227                $(obj).find('.loading').removeClass('hidden').append('<span class="loading-spinner"></span> ' + $t('Vennligst vent'));
228            } else {
229                $(obj).find('.loading').removeClass('hidden').slideDown(100);
230            }
231        },
232
233
234        // bootstrap 4 loading spinner
235        loadingButton: function(button, disabled) {
236            if (disabled !== undefined) {
237                $(button).prop({disabled: disabled});
238            }
239
240            if ($(button).find('.spinner-border').length > 0) {
241                $(button).find('.spinner-border').remove();
242
243                return;
244            }
245
246            $(button).prepend('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="false"></span> ');
247        },
248
249
250        getCookie: function (check_name) {
251            // first we'll split this cookie up into name/value pairs
252            // note: document.cookie only returns name=value, not the other components
253            var a_all_cookies   = document.cookie.split( ';' ),
254                a_temp_cookie   = '',
255                cookie_name     = '',
256                cookie_value    = '',
257                b_cookie_found  = false;
258
259            for (var i = 0; i < a_all_cookies.length; i++ ) {
260                // now we'll split apart each name=value pair
261                a_temp_cookie = a_all_cookies[i].split( '=' );
262
263                // and trim left/right whitespace while we're at it
264                cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');
265
266                if ( cookie_name === check_name ) {
267                    b_cookie_found = true;
268                    if ( a_temp_cookie.length > 1 ) {
269                        cookie_value = unescape( a_temp_cookie[1].replace(/^\s+|\s+$/g, '') );
270                    }
271                    // note that in cases where cookie is initialized but no value, null is returned
272                    return cookie_value;
273                }
274                a_temp_cookie = null;
275                cookie_name = '';
276            }
277            if (!b_cookie_found) {
278                return null;
279            }
280        },
281
282
283        setCookie: function (cookie) {
284
285            var now     = new Date(),
286                expires;
287
288            now.setTime( now.getTime() );
289
290            if (cookie.name !== undefined) {
291
292                if (cookie.expires) {
293                    expires = cookie.expires * 1000 * 60 * 60 * 24; // Days
294                } else {
295                    expires = 3600000; // Default 1 hour
296                }
297
298                expires = new Date(now.getTime() + expires);
299
300                document.cookie =
301                    cookie.name + '=' + cookie.value +
302                    '; expires=' + expires.toUTCString() +
303                    '; path=/' +
304                    '; domain=.' + this.getBaseDomain(window.location.hostname);
305
306            }
307        },
308
309        getBaseDomain: function(hostname) {
310            const parts = hostname.split('.');
311            const length = parts.length;
312
313            if (length > 2) {
314                return parts.slice(-2).join('.');
315            }
316
317            return hostname;
318        },
319
320        validateForm: function (obj, validateObj) {
321
322            for (var key in obj) {
323                if (obj.hasOwnProperty(key)) {
324                    // Fields without validation functions, validate length only.
325                    if ($.inArray(key, validateObj.inputs) > -1) {
326                        if ($(obj[key]).val().trim().length > 0) {
327                            $(obj[key]).closest('.form-group').removeClass('has-error');
328                        } else {
329                            $(obj[key]).closest('.form-group').addClass('has-error');
330                        }
331                    }
332
333                    if ($.inArray(key, validateObj.textarea) > -1) {
334                        if ($(obj[key]).val().trim().length < 10) {
335                            $(obj[key]).closest('.form-group').addClass('has-error');
336                        } else {
337                            $(obj[key]).closest('.form-group').removeClass('has-error');
338                        }
339                    }
340
341                    if ($.inArray(key, validateObj.zipcode) > -1) {
342
343                        if ($(obj[key]).val().trim().length > 3 && $(obj[key]).val().length < 5) {
344                            $(obj[key]).closest('.form-group').removeClass('has-error');
345                        } else {
346                            $(obj[key]).closest('.form-group').addClass('has-error');
347                        }
348                    }
349
350                    // If first option is selected, show error.
351                    if ($.inArray(key, validateObj.selects) > -1) {
352
353                        if ($(obj[key]).prop('selectedIndex') < 1) {
354                            $(obj[key]).closest('.form-group').addClass('has-error');
355                        } else {
356                            $(obj[key]).closest('.form-group').removeClass('has-error');
357                        }
358                    }
359
360                    // Match two fields
361                    if ($.inArray(key, validateObj.match) > -1) {
362
363                        if ($(obj[key][0]).val() !== $(obj[key][1]).val()) {
364                            $(obj[key][1]).closest('.form-group').addClass('has-error');
365                        } else {
366                            $(obj[key][1]).closest('.form-group').removeClass('has-error');
367                        }
368                    }
369
370                    // Fields with email validation
371                    if ($.inArray(key, validateObj.validateEmail) > -1) {
372
373                        if (!this.validateEmail($(obj[key]).val())) {
374                            $(obj[key]).closest('.form-group').addClass('has-error');
375                        } else {
376                            $(obj[key]).closest('.form-group').removeClass('has-error');
377                        }
378                    }
379
380                    // Fields with msisdn validation
381                    if ($.inArray(key, validateObj.validateNorwegianMsisdn) > -1) {
382
383                        if (!this.validateNorwegianMsisdn($(obj[key]).val())) {
384                            $(obj[key]).closest('.form-group').addClass('has-error');
385                        } else {
386                            $(obj[key]).closest('.form-group').removeClass('has-error');
387                        }
388                    }
389                }
390            }
391        },
392
393
394
395        /** CLOCK COUNTDOWN FOR TOKENS
396         *
397         * When time is up we trigger a event called '/clock_stop'. Listen on that if you want to do something fancy.
398         */
399        startClock: function (timestamp) {
400            if (!timestamp) {
401                return;
402            }
403
404            var self = this;
405            var timer;
406
407            window.start = parseFloat(timestamp);
408
409            if (!isNaN(window.start) && typeof window.start === "number") {
410                window.start = window.start - 1;
411                if (window.start >= 0) {
412                    timer = setTimeout(function () {
413                        self.updateClock()
414                    }, 1000);
415                } else {
416                    clearTimeout(timer);
417                    $(document).trigger('/clock_stop');
418                }
419            } else {
420                window.start = 0;
421            }
422        },
423
424        updateClock: function () {
425            this.startClock(window.start);
426        },
427
428        debounce: function(func, wait, immediate) {
429            var timeout;
430            return function() {
431                var context = this, args = arguments;
432                var later = function() {
433                    timeout = null;
434                    if (!immediate) func.apply(context, args);
435                };
436                var callNow = immediate && !timeout;
437                clearTimeout(timeout);
438                timeout = setTimeout(later, wait);
439                if (callNow) func.apply(context, args);
440            };
441        },
442
443        initVimeoPlayerEmbed: function ($playButton, options, callback) {
444            if (typeof Vimeo !== 'object') {
445                return;
446            }
447
448            var elementId = $playButton.data('player-id');
449            var $playerContainer = $('#' + elementId);
450
451            if ($playerContainer.hasClass('vimeo-loading') || $playerContainer.hasClass('vimeo-loaded')) {
452                return;
453            }
454
455            $playerContainer.addClass('video-loading');
456            $playerContainer.parent().find('.video-error').remove();
457
458            var player = new Vimeo.Player(elementId, {
459                url: $playButton.attr('href'),
460                width: 640,
461                autoplay: true
462            });
463
464            player.ready().catch(function(resp) {
465                APP.utils.ajaxLogger({
466                    type: 'error',
467                    message: '[ERROR] vimeo ('+window.location.href+'): ' + resp.message,
468                })
469
470                $playerContainer.parent().append('<div class="video-error slide-in">' + $t('En feil oppsto under avspilling, prøv igjen.') + '</div>');
471                $playButton.removeClass('d-none');
472                $playerContainer.removeClass('video-loading');
473
474                // Cleanup the player and remove from dom
475                player.destroy();
476            })
477
478            // some mobile browsers won't allow autoplay without muting the video
479            if (APP.core.isMobile()) {
480                player.setMuted(true);
481            }
482
483
484            if ($playerContainer.data('seekable') === false) {
485                var watched15Sec = false;
486                var watched5Sec = false;
487                var timeWatched = 0;
488
489                player.on("timeupdate", function(data) {
490                    if (data.seconds - 1 < timeWatched && data.seconds > timeWatched) {
491                        timeWatched = data.seconds;
492                    }
493
494                    if (data.seconds <= timeWatched && options.triggerCallbackNearEnd) {
495                        if (!watched15Sec && (data.duration - timeWatched) < 15) {
496                            watched15Sec = true;
497                            if (callback && typeof callback === "function") {
498                                callback(elementId);
499                            }
500                        }
501
502                        if (!watched5Sec && (data.duration - timeWatched) < 5) {
503                            watched15Sec = true;
504                            if (callback && typeof callback === "function") {
505                                callback(elementId);
506                            }
507                        }
508                    }
509                });
510
511                player.on("seeked", function(data) {
512                    if (timeWatched < data.seconds) {
513                        player.setCurrentTime(timeWatched);
514                    }
515                });
516            }
517
518            player.on('play', function() {
519                if (!$playerContainer.data('play')) {
520                    $playerContainer.data('play', true);
521
522                    if (options && options.origin) {
523                        APP.utils.pushGoogle('user-action', options.origin, 'play-video');
524                    }
525
526                    if (options && options.playCallback && typeof options.playCallback === "function") {
527                        options.playCallback($playerContainer);
528                    }
529                }
530            });
531
532            player.on('ended', function () {
533                if (!$playerContainer.data('ended') && options.origin) {
534                    $playerContainer.data('ended', true);
535                    APP.utils.pushGoogle('user-action', options.origin, 'finished-video');
536                }
537
538                if (callback && typeof callback === "function") {
539                    callback(elementId);
540                }
541            })
542
543            player.on('loaded', function() {
544                $playButton.addClass('d-none');
545                $playerContainer.removeClass('video-loading').addClass('video-loaded');
546            });
547
548            return player;
549        },
550
551        /**
552         * Using new vimeo player sdk
553         * @param elementId
554         * @param origin
555         * @param callback
556         * @param options
557         */
558        initVimeoPlayerEvents: function (elementId, origin, callback, options) {
559            if (typeof Vimeo !== 'object') {
560                return;
561            }
562
563            var $playerIframe = $('#' + elementId);
564
565            var player = new Vimeo.Player(elementId);
566            player.ready().then(function() {
567
568                if ($playerIframe.data('seekable') === false) {
569                    var isWatched15Sec = false;
570                    var isWatched5Sec = false;
571                    var timeWatched = 0;
572
573                    player.on("timeupdate", function(data) {
574                        if (data.seconds - 1 < timeWatched && data.seconds > timeWatched) {
575                            timeWatched = data.seconds;
576                        }
577
578                        if (data.seconds <= timeWatched && options.triggerCallbackNearEnd) {
579                            if (!isWatched15Sec && (data.duration - timeWatched) < 15) {
580                                isWatched15Sec = true;
581                                if (callback && typeof callback === "function") {
582                                    callback(elementId);
583                                }
584                            }
585
586                            if (!isWatched5Sec && (data.duration - timeWatched) < 5) {
587                                isWatched5Sec = true;
588                                if (callback && typeof callback === "function") {
589                                    callback(elementId);
590                                }
591                            }
592                        }
593                    });
594
595                    player.on("seeked", function(data) {
596                        if (timeWatched < data.seconds) {
597                            player.setCurrentTime(timeWatched);
598                        }
599                    });
600                }
601
602                player.on('play', function() {
603                    if (!$playerIframe.data('play')) {
604                        $playerIframe.data('play', true)
605                        APP.utils.pushGoogle('user-action', origin, 'play-video');
606                    }
607                });
608
609                player.on('ended', function () {
610                    if (!$playerIframe.data('ended')) {
611                        $playerIframe.data('ended', true)
612                        APP.utils.pushGoogle('user-action', origin, 'finished-video');
613                    }
614
615                    if (callback && typeof callback === "function") {
616                        callback(elementId);
617                    }
618                })
619            });
620        },
621
622        siteNotification: function (obj) {
623
624            var self = this;
625            if (obj.message) {
626
627                APP.modal.show({
628                    modalHeader: {
629                        content: obj.title || '',
630                        classes: [obj.modalHeaderClasses]
631                    },
632                    modalBody: {
633                        content: obj.message
634                    },
635                    modalFooter: {
636                        content: '<a href="#" class="btn ' + (obj.modalFooterClass ? obj.modalFooterClass : 'btn-default') + '" data-bs-dismiss="modal">' + $t('Lukk') + '</a>'
637                    }
638                });
639
640                if (obj.redirect !== undefined) {
641                    $('.modal').on('hidden.bs.modal',  function() {
642                        self.redirect(obj.redirect);
643                    });
644                }
645            }
646        },
647
648        isElementInViewport: function (el, offset) {
649            if (!offset) {
650                offset = 0;
651            }
652            var rect = el.getBoundingClientRect();
653            var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
654
655            return ((rect.top + offset) <= windowHeight) && (((rect.top + offset) + rect.height) >= 0);
656        },
657
658        createIntersectionObserver: function(elem, callback, options) {
659            if (!elem) {
660                return;
661            }
662
663
664            var observer = new IntersectionObserver(callback, options || {});
665            // observer.USE_MUTATION_OBSERVER = false; // don't check for intersections when DOM changes
666            observer.observe(elem);
667
668            return observer
669        },
670
671        copyToClipboard: function(element, value) {
672            var $temp = $("<input>");
673            $("body").append($temp);
674            $temp.val(value).select();
675            document.execCommand("copy");
676            $temp.remove();
677            $(element).find('svg').addClass('single-pulse').delay(300).queue(function(){
678                $(this).removeClass("single-pulse").dequeue();
679            });
680        },
681
682        waitForImagesToLoad: async function (element) {
683            if (!element) {
684                return Promise.resolve();
685            }
686            const elementPosition = element.getBoundingClientRect()?.top;
687            const images = document.querySelectorAll('img');
688
689            const promises = Array.from(images).map(img => {
690                if (img.complete || img.getBoundingClientRect()?.top > elementPosition) {
691                    return Promise.resolve();
692                }
693                img.loading = 'eager';
694
695                return new Promise(resolve => img.addEventListener('load', resolve, { once: true }));
696            });
697
698            return Promise.all(promises);
699        },
700
701
702        scrollToElement: function (selector){
703            const element = document.querySelector(selector);
704            if(!element){
705                return;
706            }
707
708            window.scrollTo({
709                top: element.getBoundingClientRect().top + window.scrollY - 25,
710                behavior: 'smooth'
711            })
712        },
713
714        switchTheme: function (element) {
715            const activeTheme = document.documentElement.getAttribute('data-bs-theme');
716            const currentTheme = activeTheme === 'light' ? 'dark' : 'light';
717
718            document.documentElement.setAttribute('data-bs-theme', currentTheme)
719
720            this.setCookie({
721                name:    'theme',
722                value:   currentTheme,
723                expires: 30
724            });
725        },
726
727        initTooltips: function() {
728            const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
729            if (tooltipTriggerList && tooltipTriggerList.length) {
730                tooltipTriggerList.forEach(function(tooltipTriggerEl) {
731                    new bootstrap.Tooltip(tooltipTriggerEl)
732                });
733            }
734        },
735
736        initPopovers: function (selector, options) {
737            const popoverTriggerList = document.querySelectorAll(selector);
738            if (popoverTriggerList && popoverTriggerList.length) {
739
740                popoverTriggerList.forEach(function(popoverTriggerEl) {
741                    const popoverTarget = popoverTriggerEl.getAttribute('data-bs-target');
742                    const popoverTargetEl = document.querySelector(popoverTarget)
743
744                    let popover = bootstrap.Popover.getInstance(popoverTriggerEl)
745
746                    if (!popover) {
747                        popover = new bootstrap.Popover(popoverTriggerEl, {
748                            html: true,
749                            sanitize: false,
750                            container: '.root-container',
751                            content: popoverTargetEl ? popoverTargetEl.innerHTML : '',
752                            trigger: options.trigger
753                        });
754
755                        if (options.trigger === 'manual') {
756                            popoverTriggerEl.addEventListener('click', function(event) {
757                                event.preventDefault();
758                                event.stopPropagation();
759                                popover.toggle();
760                            });
761                        }
762                    }
763
764                });
765
766                if (options.trigger === 'manual' && !this.popoverEventListenerAdded) {
767                    document.addEventListener('click', function(event) {
768                        if (event.target.closest('.popover')) {
769                            return;
770                        }
771
772                        document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (popover) {
773                            const popoverInstance = bootstrap.Popover.getInstance(popover);
774                            if (popoverInstance) {
775                                popoverInstance.hide();
776                            }
777                        })
778                    });
779                    this.popoverEventListenerAdded = true;
780                }
781            }
782        },
783
784        /**
785         * Prevents rapid repeated clicks on buttons and links.
786         * First click behaves normally, subsequent clicks are blocked if they happen within the duration window.
787         */
788        preventRapidClicks: function (el, duration = 2000) {
789            let clicked = false;
790            el.addEventListener('click', function (event) {
791                if (!clicked) {
792                    clicked = true;
793                    setTimeout(function() {
794                        clicked = false;
795                    }, duration);
796                } else {
797                    event.stopImmediatePropagation();
798                    event.preventDefault();
799                }
800            }, true);
801        },
802
803        bindRapidClickPreventionToElements: function(selectors, duration = 2000) {
804            const links = document.querySelectorAll(selectors);
805            links.forEach(function (link) {
806                APP.utils.preventRapidClicks(link, duration)
807            });
808        }
809    };
810
811})();
812
813var __local = __local || {};
814__local = (function () {
815    "use strict";
816
817    return {
818        getUser:function(){
819            var __user;
820            try{
821                if(store && store.enabled ) {
822                    __user = store.get('user');
823                    if(!__user){
824                        __user = store.set('user', { history: [], jsError: [], userAgent: navigator.userAgent });
825                    }else{
826                        if(!__user.history){
827                            __user.history = [];
828                        }
829                        if(!__user.jsError){
830                            __user.jsError = [];
831                        }
832                        __user = store.set('user', { history: __user.history, jsError: __user.jsError, userAgent: navigator.userAgent });
833                    }
834                }
835            }catch(err){/*Ignore error*/}
836            return __user;
837        },
838        pushHistory:function(action){
839            try{
840                var __user = this.getUser();
841                if(__user && __user.history){
842                    if(__user.history.length >= 100){
843                        __user.history.pop();
844                    }
845                    __user.history.unshift(action);
846                    store.set('user', { history: __user.history, jsError: __user.jsError, userAgent: navigator.userAgent });
847                }
848            }catch(err){/*Ignore error*/}
849        },
850        resetStore:function(){
851            try{
852                var __user = this.getUser();
853                if(__user && __user.history){
854                    store.set('user', { history: [], jsError: [], userAgent: navigator.userAgent });
855                }
856            }catch(err){/*Ignore error*/}
857        },
858        pushError:function(error){
859            try{
860                var __user = this.getUser();
861                if(__user && __user.jsError){
862                    if(__user.jsError.length >= 10){
863                        __user.jsError.pop();
864                    }
865                    var payload = {error: error, url: window.location.href, timestamp: Date('now')};
866                    __user.jsError.unshift(payload);
867                    store.set('user', { history: __user.history, jsError: __user.jsError, userAgent: navigator.userAgent });
868                }
869            }catch(err){/*Ignore error*/}
870        }
871    }
872})();
873
874
875// Polyfills
876if ( !Object.prototype.hasOwnProperty ) {
877	Object.prototype.hasOwnProperty = function(prop) {
878		var proto = this.__proto__ || this.constructor.prototype;
879		return (prop in this) && (!(prop in proto) || proto[prop] !== this[prop]);
880	};
881}
882
883
884// handler for client side browser errors
885window.onerror = function (msg, file, line, col, err) {
886    "use strict";
887    var dontLog = [
888        'Error loading script',
889        'Script error.',
890        'Object required',
891        'postMessage', // cause: plusone apis.google.com
892        'getElementById(a).src', // cause: plusone apis.google.com
893        'NPObject', // cause: symantec norton
894        '__gCrWeb', // cause: google chrome for iOS
895        'checkSessionState', // cause: plusone apis.google.com
896        'Kan ikke hente egenskapen src', // cause: plusone apis.google.com
897        'DealPly'
898    ];
899
900    var log = true;
901    var message = msg;
902
903    if (msg) {
904        for (var i = 0; i < dontLog.length; i++) {
905            if (msg.indexOf(dontLog[i]) > -1) {
906                log = false;
907                break;
908            }
909        }
910    }
911
912	if (log && (!file || file.startsWith(window.location.protocol + '//' + window.location.hostname))) {
913        if (onerror.num++ < onerror.max) {
914            var errorObj = {
915                "message": msg,
916                "url": document.location.href,
917                "handled_by": "window.onerror",
918                "file": file,
919                "line": line + (col ? ":" + col : "")
920            };
921
922            if(__local){
923                __local.pushError(errorObj);
924            }
925
926            message += "\n" + JSON.stringify(errorObj, null, 4);
927
928            if (err !== undefined) {
929                message += "\nstack: " + err.stack;
930            }
931
932            $.ajax({
933                type: 'POST',
934                url: '/ajax/log',
935                data: {"type": "client", "message": message}
936            });
937
938            //return true;
939        }
940	}
941};
942onerror.max = 5;
943onerror.num = 0;

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.