PageSourceSearch

https://img.rtvslo.si/_static/r2609028/common/widgets/notifications/dist/build.js?v=2025-12-08

js rtvslo.si collected 2026-09-24 08:33:41 UTC 22,725 bytes, 628 lines download raw bytes

1let lastAuthorArticles = (function () {
2    const is_dev = window.location.href.indexOf('dev.rtvslo.si') > -1;
3    const LAST_ARTICLE_KEY = 'notification:last_article_id';
4    const LAST_ARTICLES_DATA = 'notification:last_article_data';
5    const LAST_ARTICLE_RESET = 'notification:last_article_reset';
6    const CLIENT_ID = '82013fb3a531d5414f478747c1aca622';
7    const SUBSCRIPTIONS_API_URL = is_dev 
8        ? 'https://api.dev.rtvslo.si/news/getSubscriptions?client_id=' + CLIENT_ID + '&kindId=0&pageNumber=0&pageSize=1000'
9        : 'https://api.rtvslo.si/news/getSubscriptions?client_id=' + CLIENT_ID + '&kindId=0&pageNumber=0&pageSize=1000';
10    // Cache for subscribed author IDs
11    let subscribedAuthorIds = null;
12
13    /**
14     * Fetches subscribed authors from the API
15     * @returns {Promise<string[]>} Array of subscribed author IDs
16     */
17    async function fetchSubscribedAuthors() {
18        try {
19            const response = await fetch(SUBSCRIPTIONS_API_URL, {
20                credentials: 'include',
21                method: 'GET'
22            });
23            if (!response.ok) {
24                throw new Error(`HTTP error! status: ${response.status}`);
25            }
26            const data = await response.json();
27
28            // Extract author_ids from the nested items array
29            const items = data.response?.items || [];
30            const authorIds = [];
31
32            items.forEach(itemGroup => {
33                if (Array.isArray(itemGroup)) {
34                    itemGroup.forEach(author => {
35                        if (author.author_id) {
36                            authorIds.push(author.author_id.toString());
37                        }
38                    });
39                }
40            });
41
42            subscribedAuthorIds = authorIds;
43            //console.log("Subscribed authors fetched:", authorIds);
44            return authorIds;
45        } catch (error) {
46            console.error('Error fetching subscribed authors:', error);
47            return [];
48        }
49    }
50
51    /**
52     * Filters articles to only include those from subscribed authors
53     */
54    function filterArticlesBySubscribedAuthors(articles, authorIds) {
55        if (!authorIds || authorIds.length === 0) {
56            return [];
57        }
58
59        return articles.filter(article => {
60            const articleAuthorId = article.author?.id?.toString();
61            return articleAuthorId && authorIds.includes(articleAuthorId);
62        });
63    }
64
65    /**
66     * Checks if there are new articles from subscribed authors
67     * @returns {Promise<{hasNew: boolean, newArticles: Array}>}
68     */
69    async function checkForNewArticles() {
70        const articles = window._globalLastAuthorArticles;
71
72        // 1. Basic validation
73        if (!articles || !Array.isArray(articles) || articles.length === 0) {
74            console.log("No global articles found.");
75            return { hasNew: false, newArticles: [] };
76        }
77
78        // 2. OPTIMIZATION: Get stored ID and compare immediately 
79        // before doing expensive API calls or filtering.
80        const storedId = localStorage.getItem(LAST_ARTICLE_KEY);
81        const lastSeenId = storedId ? parseInt(storedId, 10) : 0;
82        
83        // Assumes articles are sorted descending (newest first). 
84        // If the newest global article is not newer than what we have stored, stop here.
85        const globalNewestId = parseInt(articles[0].id, 10);
86        
87        if (globalNewestId <= lastSeenId) {
88            console.log("No new content at all (subscribed or otherwise)");
89            // No new content at all (subscribed or otherwise)
90
91            let hasClientCleared = localStorage.getItem(LAST_ARTICLE_RESET) === 'true';
92
93            if( hasClientCleared ) {
94                // If the client has cleared, reset the last seen ID to the global newest
95                localStorage.setItem(LAST_ARTICLE_KEY, globalNewestId.toString());
96                return { hasNew: false, newArticles: [] };
97            }
98            return { hasNew: false, newArticles: JSON.parse(localStorage.getItem(LAST_ARTICLES_DATA)) || [] };
99        }
100
101        localStorage.removeItem(LAST_ARTICLE_RESET); // Clear reset flag if we have new content
102
103        // 3. Fetch subscribed authors if not cached
104        if (subscribedAuthorIds === null) {
105            await fetchSubscribedAuthors();
106        }
107
108        if (!subscribedAuthorIds || subscribedAuthorIds.length === 0) {
109            console.log("No subscribed authors found.");
110            return { hasNew: false, newArticles: [] };
111        }
112
113        // 4. Filter articles to only those from subscribed authors
114        const subscribedArticles = filterArticlesBySubscribedAuthors(articles, subscribedAuthorIds);
115
116        if (subscribedArticles.length === 0) {
117            // We have new global articles, but none are from subscribed authors.
118            // We should update the ID to the global newest to prevent re-checking this batch?
119            // Depending on logic, usually we leave the ID alone until we find a match, 
120            // OR update it to skip checks. 
121            // For now, based on your request, we only update if we find a subscribed article.
122            return { hasNew: false, newArticles: [] };
123        }
124
125        // 5. Find new articles (those with ID greater than last seen)
126        const newArticles = subscribedArticles.filter(article => {
127            const articleId = parseInt(article.id, 10);
128            return articleId > lastSeenId;
129        });
130
131        if (newArticles.length > 0) {
132            const newestArticle = newArticles[0];
133            const newestId = parseInt(newestArticle.id, 10);
134            
135            console.log(`New content available. Newest: ${newestId}, Last Seen: ${lastSeenId}, Count: ${newArticles.length}`);
136            
137            // 6. AUTO-SAVE: Update storage immediately
138            localStorage.setItem(LAST_ARTICLE_KEY, newestId.toString());
139            localStorage.setItem(LAST_ARTICLES_DATA, JSON.stringify(newArticles));
140
141            return { hasNew: true, newArticles: newArticles };
142        }
143
144        return { hasNew: false, newArticles: [] };
145    }
146
147    /**
148     * Clears the cached subscribed authors
149     */
150    function clearSubscriptionCache() {
151        subscribedAuthorIds = null;
152    }
153
154    /**
155     * Gets the current cached subscribed author IDs
156     */
157    function getSubscribedAuthorIds() {
158        return subscribedAuthorIds;
159    }
160
161    function clientHasCleared() {
162        localStorage.setItem(LAST_ARTICLE_RESET, 'true');
163    }
164
165    return {
166        checkForNewArticles: checkForNewArticles,
167        fetchSubscribedAuthors: fetchSubscribedAuthors,
168        clearSubscriptionCache: clearSubscriptionCache,
169        getSubscribedAuthorIds: getSubscribedAuthorIds,
170        clientHasCleared: clientHasCleared
171        // Removed manual updateLastSeen as it is now handled automatically
172    };
173
174})();
175let notificationsModule = (function(cookieHelper) {
176
177    let is_dev = window.location.href.indexOf('dev.rtvslo.si') > -1;
178    const CLIENT_ID = '?client_id=82013fb3a531d5414f478747c1aca622';
179
180    async function fetchWithCache(url, options) {
181        return new Promise(function(resolve, reject) {
182            // Check if supports Cache API
183            if ('caches' in window) {
184                
185                caches.open('notifications-cache').then((cache) => {
186                    cache.match(url).then(response => {
187                        // If cache exists use it from cache
188                        if (response) {
189                            //console.log('Iz pomnilnika:', response);
190                            resolve(response);
191                        // If cache doesn't exists fetch
192                        } else {
193                            var session = cookieHelper.getCookie('APISESSION_USER_ID');   
194                            if (!session){
195                                //console.log('session', session);
196                                return reject('No session');
197                            }
198                           
199                            fetch(url, options).then((response) => {
200                                console.log('Pomnilnik prazen. Dodajam:', response);
201                                cache.put(url, response.clone());
202                                resolve(response);
203                            },
204                            (reason) => resolve(reason));
205                        }
206                    });
207                });
208            } else {
209                fetch(url, options).then((response) => resolve(response), (reason) => resolve(reason));
210            }
211        });
212    }
213
214    async function getNotifications(clb) {
215        return new Promise(function(resolve, reject) {
216            var session = cookieHelper.getCookie('APISESSION_USER_ID'); 
217            if (!session) resolve('No session');
218            
219            let url = 'https://api.' + (is_dev ? 'dev.' : '') + 'rtvslo.si/notification'  + CLIENT_ID;
220
221            fetchWithCache(url, {
222                credentials: 'include',
223            })
224            .then((response) => response.json(), (reason) => { console.log('Error fetching: ' + reason); resolve(reason); })
225            .then((data) => {
226
227                if(data && data.response){
228                    if (clb) clb(data.response);
229                    resolve(data.response);
230                }
231                resolve('No data'); 
232            });
233        });
234    }
235
236    function clearNotifications() {
237        var session = cookieHelper.getCookie('APISESSION_USER_ID'); 
238        if(!session) return;
239
240        let url = 'https://api.' + (is_dev ? 'dev.' : '') + 'rtvslo.si/notification/readAll'+ CLIENT_ID;
241
242        fetch(url, {
243            credentials: 'include',
244            method: 'POST'
245        })
246            .then((response) => response.json(), (reason) => { console.log('Error fetching: ' + reason) })
247            .then((data) => {
248                //console.log('clearNotifications',data);
249                if ('caches' in window) caches.delete('notifications-cache'); // Delete cache
250        });
251
252    }
253
254    function deleteAllNotifications() {
255        var session = cookieHelper.getCookie('APISESSION_USER_ID'); 
256        if(!session) return;
257
258        let url = 'https://api.' + (is_dev ? 'dev.' : '') + 'rtvslo.si/notification/deleteAll' + CLIENT_ID;
259
260        fetch(url, {
261            credentials: 'include',
262            method: 'DELETE'
263        })
264            .then((response) => response.json(), (reason) => { console.log('Error fetching: ' + reason) })
265            .then((data) => {
266                console.log('deleteAllNotifications',data);
267                if ('caches' in window) caches.delete('notifications-cache'); // Delete cache
268        });
269
270    }
271
272
273    return {
274        getNotifications: getNotifications,
275        clearNotifications: clearNotifications,
276        deleteAllNotifications: deleteAllNotifications
277    }
278
279})(cookieHelper);
280let lastSeenModule = (function (cookieHelper) {
281
282  const last_seen_timestamp = 'last_seen_timestamp';
283  const last_seen_nr = 'last_seen_nr';
284
285  function resetLastSeen() {
286    if (!cookieHelper) return;
287    cookieHelper.deleteCookie(last_seen_timestamp);
288    cookieHelper.deleteCookie(last_seen_nr);
289  }
290  
291  //Detect changes
292  function checkForNewArticles(UNIX_timestamp) {
293  
294    //console.log("checkForNewArticles", UNIX_timestamp);
295    //console.log("_globalLastPublishedItems", window._globalLastPublishedItems);
296  
297    try {
298      if (!Array.isArray(window._globalLastPublishedItems)) return -1;
299    }
300    catch (e) { 
301      console.log("checkForNewArticles error", e);
302      return -1;
303    }
304  
305    let last_seen = cookieHelper.getCookie(last_seen_timestamp);
306    
307    //last_seen = window._globalLastPublishedItems[5];//REMOVE THIS IN PRODUCTION
308    //console.debug("last_seen DEBUG MODE REMOVE THIS IN PRODUCTION", last_seen);
309    
310    if (!last_seen) {
311      cookieHelper.setCookie(last_seen_timestamp, UNIX_timestamp, 1);
312      return -1;
313    }
314  
315    let items = window._globalLastPublishedItems;
316    items.sort(function (a, b) { return b - a; });
317    let nmr = 20;
318    items.map(function (item, i) { if (nmr === 20 && last_seen > item) nmr = i; });
319  
320  
321    if (nmr === 0) return -1;
322    if (nmr > 15) {
323      cookieHelper.deleteCookie(last_seen_timestamp);
324      return -1;
325    }
326  
327    cookieHelper.setCookie(last_seen_nr, nmr, 1);
328    if (nmr > 9) return '9+';
329  
330    return nmr;
331  }
332
333  function resetLastSeenPage() { 
334   
335    if (!cookieHelper) return;
336    let pathname = window.location.pathname;
337
338   
339    if (pathname == '/zadnje') {
340      let last_seen = cookieHelper.getCookie(last_seen_nr);
341      last_seen = parseInt(last_seen, 10);
342      try {
343        if (Number.isInteger(last_seen)) {
344          if (last_seen > 15) return;
345          if (last_seen === 0) return;
346          let mainContainer = document.getElementById("main-container");
347          let articleArchiveItem = mainContainer.getElementsByClassName('article-archive-item');
348          //console.log("articleArchiveItem", articleArchiveItem);
349          let index = 0;
350          for (let el of articleArchiveItem) {
351            if (index >= last_seen) break;
352            el.classList.add("notification-dot");
353            index++;
354          }
355        }
356      }
357      catch (e) {
358        console.log("resetLastSeenPage error", e);
359      }
360
361      resetLastSeen();
362    }
363  }
364
365  
366  return {
367    checkForNewArticles: checkForNewArticles,
368    resetLastSeenPage: resetLastSeenPage,
369    resetLastSeen: resetLastSeen
370  }
371
372
373})(cookieHelper);
374let faviconModule = (function() {
375  return {
376    current: 0,
377    result: null,
378    favicon: null,
379    
380    init: function() {
381      this.favicon =  new Favico({
382        animation: 'none'
383      });
384    },
385    
386    add: function() {
387      this.current++;
388      (this.current > 9) ? this.result = '9+' : this.result = this.current;
389      this.favicon.badge(this.result);
390    },
391
392    reset: function() {
393      this.favicon.reset();
394    }
395  };
396})();
397
398(function (cookieHelper, lastSeenModule, notificationsModule, faviconModule, lastAuthorArticles) {
399
400  var notificationWidget = null;
401
402  function unixTimestamp2AgoConverter(UNIX_timestamp) {
403        var result = '';
404        var date = new Date(UNIX_timestamp * 1000);
405        //console.log(date);
406        if (timeRelative) result = timeRelative.calc(date, false);
407
408        return result;
409  }
410
411  //2023-11-12 22:08:14 to unix timestamp
412  function date2Timestamp(date) {
413    var timestamp = Date.parse(date);
414    return timestamp / 1000;
415  }
416
417  function clearCache(minutes) {
418    var now = new Date().getTime();
419    var cacheSetupTime = localStorage.getItem('cacheSetupTime');
420    if (cacheSetupTime == null) {
421      localStorage.setItem('cacheSetupTime', now)
422    } else {
423      if( now - cacheSetupTime > minutes*60*1000) {
424        if ('caches' in window) caches.delete('notifications-cache');
425        localStorage.setItem('cacheSetupTime', now);
426      }
427    }
428  }
429
430  // Set global because is called from DOM directly
431  window.setMutedNotificationsCookie = function ( duration ) {
432    if ( duration == 0 ) {
433      docCookies.removeItem('mutedNotifications', '/');
434      return;
435    }
436
437    var d = new Date();
438    d.setTime(d.getTime() + ( duration * 1000) );
439    docCookies.setItem('mutedNotifications', d.toISOString() , d.toUTCString(), '/');
440
441    setMutedNotificationsState( d.toISOString() );
442  }
443
444  function setMutedNotificationsState( time ) {
445    if ( notificationWidget ) {
446      notificationWidget.mute(true);
447    }
448
449    document.querySelectorAll('.notifications-muted-duration').forEach( function( el ) {
450      el.innerText = timeRelative.calc( time );
451    });
452  }
453
454
455  function Init() {
456
457    if(!document.body.classList.contains('advanced-user')){
458      //return console.log('skip ...');
459    } 
460
461    notificationWidget = new Notifications({
462
463      notificationsSeenHook: () => {
464        notificationsModule.clearNotifications();
465        faviconModule.reset();
466        if (typeof recordUIEvent == 'function') recordUIEvent('notifications', 'action=open_notifications');
467      },
468      
469      markAsReadHook: () => {
470        //DEPRECATED
471        if (typeof recordUIEvent == 'function') recordUIEvent('notifications', 'action=mark_read');
472      },
473      
474      clearAllHook: () => {
475        lastSeenModule.resetLastSeen()
476        notificationsModule.deleteAllNotifications();
477        lastAuthorArticles.clientHasCleared();
478        if (typeof recordUIEvent == 'function') recordUIEvent('notifications', 'action=clear_all');
479      }
480      
481    });
482
483    // If muted notifications cookie exists set muted state and return / exit
484    var mutedNotifications = docCookies.getItem('mutedNotifications');
485    if (mutedNotifications) {
486      setMutedNotificationsState(mutedNotifications);
487      return;
488    }
489
490    faviconModule.init();
491    lastSeenModule.resetLastSeenPage();
492    clearCache(3); // Clear every 2 minutes
493    
494
495    notificationsModule.getNotifications()
496    .then((notifications) => {
497      return new Promise ((resolve, reject) => {
498  
499        if(!Array.isArray(notifications.items)) {
500          resolve();
501          return;
502        }
503        
504        notifications.items.map(function(item) {    
505          if(typeof item.type !== 'string') {
506            resolve();
507            return;
508          }
509
510          let content = item.content;
511          try {
512            content = JSON.parse(content);
513          } 
514          catch(e) {
515            console.log('parse error', e);
516            resolve();
517          }
518
519          switch (item.type) {
520            case "comment_replay":
521              notificationWidget.insertNotification(item.unread, '<strong>' + content.username + '</strong> je odgovoril na vaš komentar', null, null, '//img.rtvslo.si/_up/avatars/' + content.user_id + '.png', '/komentar/' + content.comment_id, 'comment_reply');
522              if (item.unread) {
523                notificationWidget.addUnreadCount();
524                faviconModule.add();
525              }
526              break;   
527            case "recording":
528              //console.log('content', content);
529              let thumb = "https://365.rtvslo.si/icons/android-chrome-192x192.png";
530              let path = 'https://img.rtvcdn.si/_up/ava/ava_misc/show_logos/';
531              if(typeof content.thumb === 'string' && content.thumb.length > 0){     
532                  thumb = path + content.thumb;        
533              }  
534              let publish_date = content.publish_date;
535              //check if string 
536              if (typeof publish_date !== 'string') {
537                break;
538              }
539              //replace white space with T
540              publish_date = publish_date.replace(/\s/g, 'T');
541              notificationWidget.insertNotification(item.unread,  '<strong>' + content.name + '</strong><br>' 
542                  + content.show_name + ', ' + unixTimestamp2AgoConverter(date2Timestamp(publish_date))  , null, null, thumb, 'https://365.rtvslo.si/arhiv/n/' + content.id, 'ava');
543
544              if (item.unread) {
545                notificationWidget.addUnreadCount();
546                faviconModule.add();
547              }
548              break;
549            default:
550              break;
551          }
552        });
553        resolve();  
554      })
555    })
556    .then(() => {
557      // Last seen news is fetched separately
558      fetch('https://api.rtvslo.si/misc/time')
559      .then((response) => response.json(), (reason) => { console.log('Error fetching: ' + reason) })
560      .then((data) => {
561        if (!data.response) return;
562        //let isJournalist = cookieHelper.getCookie('isJournalist');
563        //if (isJournalist) return;
564        
565        var unreadArticles = lastSeenModule.checkForNewArticles(data.response);
566        //console.log("unreadArticles", unreadArticles);
567        
568        if (unreadArticles !== -1) {
569
570          // If loged out & unseen ecists show login notice right after unseen notice
571          if (typeof cookieHelper === 'object' && cookieHelper.getCookie('APISESSION_USER_NAME') == null) {
572            notificationWidget.insertNotification(false,' Za več obvestil se prosimo <strong>prijavite</strong>.', null, 'user', null, '/prijava', 'login');
573          }
574
575          notificationWidget.insertNotification(true,
576            unreadArticles + ' novih <strong>neprebranih novic</strong>',
577            'Zadnja objava: ' + unixTimestamp2AgoConverter(window._globalLastPublishedItems[0]), 'history', null, '/zadnje', 'last_seen');
578          notificationWidget.addUnreadCount();
579          faviconModule.add();
580        }
581          //window.plusOneTest.add();
582      })
583      .catch((error) => {
584        console.log('Error fetching last seen news:', error);
585      });
586
587      // Check for new articles from subscribed authors (runs independently)
588      if (typeof lastAuthorArticles === 'object' && typeof lastAuthorArticles.checkForNewArticles === 'function') {
589        lastAuthorArticles.checkForNewArticles()
590          .then((result) => {
591            if (result.hasNew && result.newArticles.length > 0) {
592              const count = result.newArticles.length;
593              const newestArticle = result.newArticles[0];
594              const authorName = newestArticle.author?.title || 'naročenega avtorja';
595              
596              notificationWidget.insertNotification(
597                true,
598                count + ' novih člankov od <strong>naročenih avtorjev</strong>',
599                'Zadnji članek: ' + authorName,
600                'user',
601                null,
602                '/mojrtv/moje-rtv-vsebine/avtorji',
603                'author_articles'
604              );
605
606              if(result.hasNew) {
607                notificationWidget.addUnreadCount();
608                faviconModule.add();
609              }
610            }
611          })
612          .catch((error) => {
613            console.log('Error checking for author articles:', error);
614          });
615      }
616    })
617    
618  }
619
620  (typeof Notifications === 'function' ) ? Init() : window.addEventListener('jsbundleload', Init);
621
622
623})(cookieHelper, lastSeenModule, notificationsModule, faviconModule, lastAuthorArticles);
624
625
626
627
628

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.