PageSourceSearch

https://sol.vin/js/widgets/itch_jam_widget.js

js sol.vin collected 2026-10-09 21:58:33 UTC 7,621 bytes, 208 lines download raw bytes

1window.ItchJamWidget = (function() {
2  'use strict';
3
4  function getCache(key) {
5    if (window.ApiRateLimiter) {
6      return window.ApiRateLimiter.getCache(key);
7    }
8    try {
9      var data = localStorage.getItem(key);
10      if (!data) return null;
11      var parsed = JSON.parse(data);
12      if (parsed && parsed.value && Object.keys(parsed.value).length > 0) {
13        if (Date.now() - parsed.timestamp < (60 * 60 * 1000)) {
14          return parsed.value;
15        }
16      }
17    } catch(e) {}
18    return null;
19  }
20
21  function setCache(key, value) {
22    if (window.ApiRateLimiter) {
23      window.ApiRateLimiter.setCache(key, value);
24      return;
25    }
26    try {
27      if (!value || Object.keys(value).length === 0) return;
28      localStorage.setItem(key, JSON.stringify({
29        timestamp: Date.now(),
30        value: value
31      }));
32    } catch(e) {}
33  }
34
35  function clearCache(key) {
36    if (window.ApiRateLimiter) {
37      window.ApiRateLimiter.clearCache(key);
38      return;
39    }
40    try {
41      localStorage.removeItem(key);
42    } catch(e) {}
43  }
44
45  function renderLoading(element) {
46    if (!element) return;
47    element.innerHTML = '<span class="stat-chip stat-loading" title="Loading live jam stats..."><span class="stat-icon" style="-webkit-mask-image: url(\'/icons/hourglass.svg\'); mask-image: url(\'/icons/hourglass.svg\');"></span></span>';
48  }
49
50  function renderError(element, onRetry) {
51    if (!element) return;
52    element.innerHTML = '<span class="stat-chip stat-error" title="Data Couldn\'t Load (Right-click to retry)"><span class="stat-icon" style="-webkit-mask-image: url(\'/icons/xmark.svg\'); mask-image: url(\'/icons/xmark.svg\');"></span></span>';
53
54    var errChip = element.querySelector('.stat-error');
55    if (errChip && typeof onRetry === 'function') {
56      errChip.addEventListener('contextmenu', function(e) {
57        e.preventDefault();
58        e.stopPropagation();
59        onRetry();
60      });
61    }
62  }
63
64  function makeIcon(file) {
65    return '<span class="stat-icon" style="-webkit-mask-image: url(\'/icons/' + file + '\'); mask-image: url(\'/icons/' + file + '\');"></span>';
66  }
67
68  function render(element, stats) {
69    if (!element || !stats) return;
70
71    var cleanHost = (stats.host || '').replace(/skip to main content/gi, '').trim();
72
73    // Client-side date check safeguard for status
74    var effectiveStatus = stats.status;
75    var endDateStr = stats.voting_end_date || stats.end_date;
76    if (endDateStr) {
77      try {
78        var endDate = new Date(endDateStr.replace(' ', 'T') + (endDateStr.includes('T') ? '' : 'Z'));
79        if (!isNaN(endDate.getTime()) && Date.now() > endDate.getTime()) {
80          effectiveStatus = 'Ended';
81        }
82      } catch(e) {}
83    }
84
85    var html = '';
86    if (cleanHost && element.id !== 'jam-modal-live-stats') {
87      html += '<span class="stat-chip stat-host" title="Organizer(s) / Host(s)">' + makeIcon('user.svg') + ' ' + cleanHost + '</span>';
88    }
89    if (stats.entries) {
90      html += '<span class="stat-chip stat-entries" title="Jam Entries (Games)">' + makeIcon('gamepad.svg') + ' ' + stats.entries + ' entries</span>';
91    }
92    if (stats.ratings) {
93      html += '<span class="stat-chip stat-ratings" title="Total Ratings Received">' + makeIcon('star.svg') + ' ' + stats.ratings + ' ratings</span>';
94    }
95    if (stats.rating_comments) {
96      html += '<span class="stat-chip stat-rating-comments" title="Rating Comments & Feedback">' + makeIcon('comment.svg') + ' ' + stats.rating_comments + ' feedback comments</span>';
97    }
98    if (stats.submissions) {
99      html += '<span class="stat-chip stat-submissions" title="Submissions Count">' + makeIcon('file-code.svg') + ' ' + stats.submissions + ' submissions</span>';
100    }
101    if (stats.joined) {
102      html += '<span class="stat-chip stat-joined" title="Participants / Joined">' + makeIcon('users.svg') + ' ' + stats.joined + ' joined</span>';
103    }
104    if (stats.comments && stats.comments !== '0' && !stats.rating_comments) {
105      html += '<span class="stat-chip stat-comments" title="Comments">' + makeIcon('comment.svg') + ' ' + stats.comments + ' comments</span>';
106    }
107    if ((stats.start_date || stats.end_date) && element.id !== 'jam-modal-live-stats') {
108      var dateStr = (stats.start_date && stats.end_date) ? (stats.start_date.split(' ')[0] + ' to ' + stats.end_date.split(' ')[0]) : (stats.start_date || stats.end_date);
109      html += '<span class="stat-chip stat-dates" title="Jam Timeline Dates">' + makeIcon('calendar.svg') + ' ' + dateStr + '</span>';
110    }
111    if (effectiveStatus) {
112      html += '<span class="stat-chip stat-status" title="Jam Status">' + makeIcon('clock.svg') + ' ' + effectiveStatus + '</span>';
113    }
114
115    element.innerHTML = html;
116
117    if (element.id === 'jam-modal-live-stats') {
118      var modalHost = document.getElementById('jam-modal-host');
119      var modalDates = document.getElementById('jam-modal-dates');
120      if (modalHost && cleanHost) {
121        modalHost.textContent = cleanHost;
122      }
123      if (modalDates && (stats.start_date || stats.end_date)) {
124        var dateRange = (stats.start_date && stats.end_date) ? (stats.start_date.split(' ')[0] + ' to ' + stats.end_date.split(' ')[0]) : (stats.start_date || stats.end_date);
125        modalDates.textContent = dateRange;
126      }
127    }
128  }
129
130  function fetchItchJamStats(jam, onSuccess, onError) {
131    var baseUrl = (window.ApiRateLimiter && window.ApiRateLimiter.getApiBaseUrl) ? window.ApiRateLimiter.getApiBaseUrl() : '';
132    var proxyUrl = baseUrl + '/api/proxy/itch_jam?jam=' + jam;
133    var taskFn = function() {
134      return fetch(proxyUrl)
135        .then(function(res) {
136          if (!res.ok) throw new Error('HTTP ' + res.status);
137          return res.json();
138        });
139    };
140
141    if (window.ApiRateLimiter) {
142      window.ApiRateLimiter.enqueue(proxyUrl, taskFn, onSuccess, onError);
143    } else {
144      taskFn().then(onSuccess).catch(onError);
145    }
146  }
147
148  function initElement(el) {
149    var jam = el.getAttribute('data-itch-jam');
150    if (!jam) return;
151
152    var cacheKey = 'solvin_itch_jam_stats_v1_' + jam;
153    var cached = getCache(cacheKey);
154    if (cached && Object.keys(cached).length > 0) {
155      render(el, cached);
156      return;
157    }
158
159    renderLoading(el);
160
161    var baseUrl = (window.ApiRateLimiter && window.ApiRateLimiter.getApiBaseUrl) ? window.ApiRateLimiter.getApiBaseUrl() : '';
162    var proxyUrl = baseUrl + '/api/proxy/itch_jam?jam=' + jam;
163
164    function handleRetry() {
165      clearCache(cacheKey);
166      if (window.ApiRateLimiter) {
167        window.ApiRateLimiter.resetDomain(proxyUrl);
168      }
169
170      renderLoading(el);
171      fetchItchJamStats(jam, function(stats) {
172        if (stats && Object.keys(stats).length > 0) {
173          setCache(cacheKey, stats);
174          render(el, stats);
175        } else {
176          renderError(el, handleRetry);
177        }
178      }, function(err) {
179        renderError(el, handleRetry);
180      });
181    }
182
183    fetchItchJamStats(jam, function(stats) {
184      if (stats && Object.keys(stats).length > 0) {
185        setCache(cacheKey, stats);
186        render(el, stats);
187      } else {
188        renderError(el, handleRetry);
189      }
190    }, function(err) {
191      renderError(el, handleRetry);
192    });
193  }
194
195  function init() {
196    var jamElements = document.querySelectorAll('[data-itch-jam]');
197    jamElements.forEach(function(el) {
198      if (el.classList.contains('fav-collection-item-chip') || (el.closest && el.closest('.fav-collection-item-chip'))) return;
199      initElement(el);
200    });
201  }
202
203  return {
204    init: init,
205    initElement: initElement,
206    fetchItchJamStats: fetchItchJamStats
207  };
208})();

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.