1export function formatShorthand(num) { 2 if (num >= 1000000) { 3 return (Math.round(num / 100000) / 10).toFixed(1).replace(/\.0$/, '') + 'M'; 4 } 5 if (num >= 1000) { 6 return (Math.round(num / 100) / 10).toFixed(1).replace(/\.0$/, '') + 'K'; 7 } 8 return num.toString(); 9} 10 11export function renderInteractionPillHtml(interactions = 1000, users = 500) { 12 const numInteractions = parseInt(interactions) || 1000; 13 const numUsers = parseInt(users) || 500; 14 return ` 15 <div class="interaction-counter-pill"> 16 <span class="interaction-counter-icon">â¡</span> 17 <span class="interaction-counter-text">${formatShorthand(numInteractions)} interactions by ${formatShorthand(numUsers)} users</span> 18 </div> 19 `.trim(); 20} 21 22export function initInteractionCounter(key, defaultInteractions, defaultUsers, containerId) { 23 const container = document.getElementById(containerId); 24 if (!container) return null; 25 26 let data = null; 27 try { 28 const raw = localStorage.getItem(key); 29 if (raw && raw !== 'undefined') data = JSON.parse(raw); 30 } catch (_) { 31 data = null; 32 } 33 const visitKey = `${key}_visited`; 34 let isNewUser = false; 35 try { 36 isNewUser = !localStorage.getItem(visitKey); 37 } catch (_) { 38 isNewUser = false; 39 } 40 41 if (!data) { 42 data = { 43 interactions: parseInt(defaultInteractions) || 1000, 44 users: (parseInt(defaultUsers) || 500) + (isNewUser ? 1 : 0) 45 }; 46 try { 47 localStorage.setItem(key, JSON.stringify(data)); 48 localStorage.setItem(visitKey, 'true'); 49 } catch (_) {} 50 } else if (isNewUser) { 51 data.users = (data.users || 0) + 1; 52 try { 53 localStorage.setItem(key, JSON.stringify(data)); 54 localStorage.setItem(visitKey, 'true'); 55 } catch (_) {} 56 } 57 58 const renderUI = () => { 59 container.className = 'interaction-counter-bar'; 60 const targetText = `${formatShorthand(data.interactions)} interactions by ${formatShorthand(data.users)} users`; 61 const textEl = container.querySelector('.interaction-counter-text'); 62 if (textEl) { 63 if (textEl.textContent !== targetText) { 64 textEl.textContent = targetText; 65 } 66 } else { 67 container.innerHTML = renderInteractionPillHtml(data.interactions, data.users); 68 } 69 }; 70 71 renderUI(); 72 updateSchemaInteractions(data.interactions); 73 74 return { 75 incrementInteraction: () => { 76 data.interactions += 1; 77 localStorage.setItem(key, JSON.stringify(data)); 78 renderUI(); 79 updateSchemaInteractions(data.interactions); 80 } 81 }; 82} 83 84export function updateSchemaInteractions(count) { 85 const script = document.getElementById('seo-jsonld'); 86 if (script) { 87 try { 88 const schema = JSON.parse(script.textContent); 89 const updateObj = (obj) => { 90 if (obj && (obj["@type"] === "WebApplication" || obj["@type"] === "SoftwareApplication" || obj["@type"] === "Course" || obj["@type"] === "WebPage")) { 91 obj.interactionStatistic = [ 92 { 93 "@type": "InteractionCounter", 94 "interactionType": "https://schema.org/UseAction", 95 "userInteractionCount": count 96 } 97 ]; 98 } 99 }; 100 101 if (Array.isArray(schema)) { 102 schema.forEach(updateObj); 103 } else if (schema["@graph"]) { 104 schema["@graph"].forEach(updateObj); 105 } else { 106 updateObj(schema); 107 } 108 script.textContent = JSON.stringify(schema, null, 2); 109 } catch (e) { 110 console.error('Error updating schema interactions:', e); 111 } 112 } 113}
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.