https://pythonmagazine.com/theme/netcity_postype/js/rara-htmlopt-20260929-tail-sub-1-97b34fe4473b.js
1 2(function() { 3 var memoLinks = Array.prototype.slice.call(document.querySelectorAll('.win_memo[data-nc-memo-unread]')); 4 5 if (!memoLinks.length) { 6 return; 7 } 8 9 var firstLink = memoLinks[0]; 10 var memberKey = firstLink.getAttribute('data-nc-memo-key') || 'member'; 11 var checkUrl = firstLink.getAttribute('data-nc-memo-check-url') || ''; 12 var storeKey = 'ncPostypeMemoUnread:' + memberKey; 13 var soundPending = false; 14 var queuedSoundCount = 0; 15 var pollTimer = null; 16 17 function toCount(value) { 18 var count = parseInt(value, 10); 19 return isNaN(count) || count < 0 ? 0 : count; 20 } 21 22 function readStoredCount() { 23 try { 24 return toCount(window.localStorage.getItem(storeKey)); 25 } catch (e) { 26 return 0; 27 } 28 } 29 30 function writeStoredCount(count) { 31 try { 32 window.localStorage.setItem(storeKey, String(toCount(count))); 33 } catch (e) { 34 } 35 } 36 37 function formatCount(count) { 38 return String(count).replace(/\B(?=(\d{3})+(?!\d))/g, ','); 39 } 40 41 function updateMemoBadges(count) { 42 count = toCount(count); 43 memoLinks.forEach(function(link) { 44 var badge = link.querySelector('.nc-top-auth__badge, .nc-mobile-memo__badge'); 45 var badgeClass = link.classList.contains('nc-mobile-memo') ? 'nc-mobile-memo__badge' : 'nc-top-auth__badge'; 46 47 link.setAttribute('data-nc-memo-unread', String(count)); 48 link.classList.toggle('is-unread', count > 0); 49 link.setAttribute('aria-label', count > 0 ? '쪽ì§í¨ ì ì½ì ìª½ì§ ' + count + 'ê°' : '쪽ì§í¨'); 50 51 if (count > 0) { 52 if (!badge) { 53 badge = document.createElement('strong'); 54 badge.className = badgeClass; 55 link.appendChild(badge); 56 } 57 badge.textContent = formatCount(count); 58 } else if (badge) { 59 badge.parentNode.removeChild(badge); 60 } 61 }); 62 } 63 64 function playMemoTone() { 65 return new Promise(function(resolve, reject) { 66 var AudioContext = window.AudioContext || window.webkitAudioContext; 67 68 if (!AudioContext) { 69 reject(new Error('AudioContext unavailable')); 70 return; 71 } 72 73 var context = new AudioContext(); 74 75 function startTone() { 76 var now = context.currentTime; 77 var gain = context.createGain(); 78 var first = context.createOscillator(); 79 var second = context.createOscillator(); 80 81 gain.gain.setValueAtTime(0.0001, now); 82 gain.gain.exponentialRampToValueAtTime(0.16, now + 0.025); 83 gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.38); 84 85 first.type = 'sine'; 86 first.frequency.setValueAtTime(880, now); 87 first.frequency.exponentialRampToValueAtTime(990, now + 0.16); 88 89 second.type = 'sine'; 90 second.frequency.setValueAtTime(1175, now + 0.12); 91 92 first.connect(gain); 93 second.connect(gain); 94 gain.connect(context.destination); 95 96 first.start(now); 97 first.stop(now + 0.22); 98 second.start(now + 0.12); 99 second.stop(now + 0.38); 100 101 window.setTimeout(function() { 102 if (context.close) { 103 context.close(); 104 } 105 resolve(); 106 }, 460); 107 } 108 109 if (context.state === 'suspended' && context.resume) { 110 context.resume().then(startTone).catch(function(error) { 111 if (context.close) { 112 context.close(); 113 } 114 reject(error); 115 }); 116 return; 117 } 118 119 try { 120 startTone(); 121 } catch (error) { 122 if (context.close) { 123 context.close(); 124 } 125 reject(error); 126 } 127 }); 128 } 129 130 function queueGesturePlayback(count) { 131 if (queuedSoundCount >= count) { 132 return; 133 } 134 135 queuedSoundCount = count; 136 var played = false;
137 138 function cleanup() { 139 document.removeEventListener('pointerdown', onGesture, true); 140 document.removeEventListener('keydown', onGesture, true); 141 document.removeEventListener('touchstart', onGesture, true); 142 } 143 144 function onGesture() { 145 if (played) { 146 return; 147 } 148 played = true; 149 playMemoTone().then(function() { 150 writeStoredCount(count); 151 queuedSoundCount = 0; 152 cleanup(); 153 }).catch(function() { 154 cleanup(); 155 }); 156 } 157 158 document.addEventListener('pointerdown', onGesture, true); 159 document.addEventListener('keydown', onGesture, true); 160 document.addEventListener('touchstart', onGesture, true); 161 } 162 163 function notifyIfNeeded(count) { 164 count = toCount(count); 165 updateMemoBadges(count); 166 167 var lastCount = readStoredCount(); 168 169 if (count <= 0) { 170 queuedSoundCount = 0; 171 writeStoredCount(0); 172 return; 173 } 174 175 if (count < lastCount) { 176 queuedSoundCount = 0; 177 writeStoredCount(count); 178 return; 179 } 180 181 if (count <= lastCount || soundPending) { 182 return; 183 } 184 185 soundPending = true; 186 playMemoTone().then(function() { 187 writeStoredCount(count); 188 soundPending = false; 189 }).catch(function() { 190 queueGesturePlayback(count); 191 soundPending = false; 192 }); 193 } 194 195 function pollMemoCount() { 196 if (!checkUrl || !window.fetch || document.hidden) { 197 return; 198 } 199 200 fetch(checkUrl, { 201 credentials: 'same-origin', 202 cache: 'no-store', 203 headers: { 204 'Accept': 'application/json' 205 } 206 }).then(function(response) { 207 return response.json(); 208 }).then(function(data) { 209 if (!data || data.ok === false) { 210 return; 211 } 212 notifyIfNeeded(data.count); 213 }).catch(function() { 214 }); 215 } 216 217 var initialCount = memoLinks.reduce(function(max, link) { 218 return Math.max(max, toCount(link.getAttribute('data-nc-memo-unread'))); 219 }, 0); 220 221 notifyIfNeeded(initialCount); 222 223 if (checkUrl && window.fetch) { 224 pollTimer = window.setInterval(pollMemoCount, 45000); 225 document.addEventListener('visibilitychange', function() { 226 if (!document.hidden) { 227 pollMemoCount(); 228 } 229 }); 230 } 231})();
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.