1window.GistWidget = (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.timestamp && (Date.now() - parsed.timestamp < (60 * 60 * 1000))) { 13 return parsed.value; 14 } 15 } catch(e) {} 16 return null; 17 } 18 19 function setCache(key, value) { 20 if (window.ApiRateLimiter) { 21 window.ApiRateLimiter.setCache(key, value); 22 return; 23 } 24 try { 25 localStorage.setItem(key, JSON.stringify({ 26 timestamp: Date.now(), 27 value: value 28 })); 29 } catch(e) {} 30 } 31 32 function clearCache(key) { 33 if (window.ApiRateLimiter) { 34 window.ApiRateLimiter.clearCache(key); 35 return; 36 } 37 try { 38 localStorage.removeItem(key); 39 } catch(e) {} 40 } 41 42 function renderLoading(element) { 43 if (!element) return; 44 var container = element.querySelector('.project-live-stats'); 45 if (!container) { 46 container = document.createElement('span'); 47 container.className = 'project-live-stats gist-live-stats'; 48 element.appendChild(container); 49 } 50 container.innerHTML = '<span class="stat-chip stat-loading" title="Loading live stats..."><span class="stat-icon" style="-webkit-mask-image: url(\'/icons/hourglass.svg\'); mask-image: url(\'/icons/hourglass.svg\');"></span></span>'; 51 } 52 53 function renderError(element, onRetry) { 54 if (!element) return; 55 var container = element.querySelector('.project-live-stats'); 56 if (!container) { 57 container = document.createElement('span'); 58 container.className = 'project-live-stats gist-live-stats'; 59 element.appendChild(container); 60 } 61 container.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>'; 62 63 var errChip = container.querySelector('.stat-error'); 64 if (errChip && typeof onRetry === 'function') { 65 errChip.addEventListener('contextmenu', function(e) { 66 e.preventDefault(); 67 e.stopPropagation(); 68 onRetry(); 69 }); 70 } 71 } 72 73 function render(element, stats) { 74 if (!element || !stats) return; 75 var container = element.querySelector('.project-live-stats'); 76 if (!container) { 77 container = document.createElement('span'); 78 container.className = 'project-live-stats gist-live-stats'; 79 element.appendChild(container); 80 } 81 82 function makeIcon(file) { 83 return '<span class="stat-icon" style="-webkit-mask-image: url(\'/icons/' + file + '\'); mask-image: url(\'/icons/' + file + '\');"></span>'; 84 } 85 86 var html = ''; 87 if (typeof stats.files === 'number') { 88 html += '<span class="stat-chip stat-files" title="Files">' + makeIcon('file-code.svg') + ' ' + stats.files + ' file' + (stats.files !== 1 ? 's' : '') + '</span>'; 89 } 90 if (typeof stats.comments === 'number' && stats.comments > 0) { 91 html += '<span class="stat-chip stat-comments" title="Comments">' + makeIcon('comment.svg') + ' ' + stats.comments + '</span>'; 92 } 93 if (typeof stats.forks === 'number' && stats.forks > 0) { 94 html += '<span class="stat-chip stat-forks" title="Forks">' + makeIcon('code-fork.svg') + ' ' + stats.forks + '</span>'; 95 } 96 container.innerHTML = html; 97 } 98 99 function fetchGist(gistId, onSuccess, onError) { 100 var baseUrl = (window.ApiRateLimiter && window.ApiRateLimiter.getApiBaseUrl) ? window.ApiRateLimiter.getApiBaseUrl() : ''; 101 var proxyUrl = baseUrl + '/api/proxy/gist?id=' + gistId; 102 var taskFn = function() { 103 return fetch(proxyUrl) 104 .then(function(res) { 105 if (!res.ok) throw new Error('API Proxy returned ' + res.status); 106 return res.json(); 107 }) 108 .then(function(data) { 109 return { 110 files: data.files || 1, 111 comments: data.comments || 0, 112 forks: data.forks || 0 113 }; 114 }); 115 }; 116 117 if (window.ApiRateLimiter) { 118 window.ApiRateLimiter.enqueue(proxyUrl, taskFn, onSuccess, onError); 119 } else { 120 taskFn().then(onSuccess).catch(onError); 121 } 122 } 123 124 function initElement(el) { 125 var gistId = el.getAttribute('data-gist-id'); 126 if (!gistId) return; 127 128 var cacheKey = 'solvin_gist_stats_v1_' + gistId; 129 var cached = getCache(cacheKey); 130 if (cached) { 131 render(el, cached); 132 return; 133 } 134 135 renderLoading(el); 136 137 var baseUrl = (window.ApiRateLimiter && window.ApiRateLimiter.getApiBaseUrl) ? window.ApiRateLimiter.getApiBaseUrl() : ''; 138 var proxyUrl = baseUrl + '/api/proxy/gist?id=' + gistId; 139 140 function handleRetry() { 141 clearCache(cacheKey); 142 if (window.ApiRateLimiter) { 143 window.ApiRateLimiter.resetDomain(proxyUrl); 144 } 145 146 renderLoading(el); 147 fetchGist(gistId, function(stats) { 148 setCache(cacheKey, stats); 149 render(el, stats); 150 }, function(err) { 151 renderError(el, handleRetry); 152 }); 153 } 154 155 fetchGist(gistId, function(stats) { 156 setCache(cacheKey, stats); 157 render(el, stats); 158 }, function(err) { 159 renderError(el, handleRetry); 160 }); 161 } 162
163 function init() { 164 var elements = document.querySelectorAll('[data-gist-id]'); 165 elements.forEach(function(el) { 166 if (el.classList.contains('fav-collection-item-chip') || (el.closest && el.closest('.fav-collection-item-chip'))) return; 167 initElement(el); 168 }); 169 } 170 171 return { init: init, initElement: initElement }; 172})();
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.