1/* ============================================================================ 2 NETWORK ENGINEERING â live content refresh 3 4 data/products.js and data/news.js still ship and still render first, so the 5 catalogue is in the page on first paint (fast, works offline, visible to 6 crawlers) and the site keeps working if Sanity is unreachable. 7 8 On top of that this asks Sanity for the current content and â only if 9 something actually changed â swaps the globals and asks app.js to re-render. 10 Publishing in the Studio therefore shows up on the site immediately, with no 11 export and no upload. 12 13 Two CDN queries per page view, on the free plan's 1,000,000/month. 14 15 Load it AFTER app.js: 16 <script src="data/products.js"></script> 17 <script src="js/app.js"></script> 18 <script src="js/sanity-live.js"></script> 19 20 CORS is the gate. Sanity rejects a browser request from any origin not on 21 the project allowlist, and www.networkenglb.com does NOT redirect to the 22 apex â it is a separate origin. Both hostnames must be allowed or the site 23 silently stops updating on one of them. file:// can never work: its origin 24 is null. That is exactly why the shipped globals stay as the fallback. 25 ========================================================================== */ 26(function () { 27 'use strict'; 28 29 var PROJECT = 'rbyd9cc3'; 30 var DATASET = 'production'; 31 var IMG = '?w=1200&auto=format&fit=max'; 32 33 if (typeof fetch !== 'function' || location.protocol === 'file:') return; 34 35 var Q_CONTENT = 36 '{"products": *[_type=="product" && published==true]|order(order asc, name asc){' + 37 'name, summary, description, specs, featured,' + 38 '"slug": slug.current,' + 39 '"brand": brand->slug.current,' + 40 '"brandName": brand->name,' + 41 '"categories": categories[]->key.current,' + 42 '"series": series->slug.current,' + 43 '"images": images[].asset->url,' + 44 '"datasheets": datasheets[]{title, url, "file": file.asset->url}},' + 45 '"categories": *[_type=="category"]|order(order asc, label asc){' + 46 'label, blurb, featured, "key": key.current},' + 47 '"brands": *[_type=="brand"]|order(order asc, name asc){' + 48 'name, tagline, blurb, representation, website,' + 49 '"key": slug.current, "logo": logo.asset->url},' + 50 '"news": *[_type=="newsPost" && published==true]|order(date desc, order asc){' + 51 'title, summary, body, date, sourceUrl,' + 52 '"slug": slug.current, "image": image.asset->url},' + 53 '"settings": *[_id=="siteSettings"][0]{' + 54 'title, tagline, founded, intro, phones, email, address, mapQuery,' + 55 '"productOverview": productOverview.asset->url}}'; 56 57 function url(q) { 58 return 'https://' + PROJECT + '.apicdn.sanity.io/v2024-01-01/data/query/' + 59 DATASET + '?query=' + encodeURIComponent(q); 60 } 61 62 /* Same reshaping as sanity/export-content.mjs. The two must agree, or a page 63 would look different before and after this fetch lands. */ 64 function shape(res) { 65 var products = (res.products || []).map(function (p) { 66 var docs = (p.datasheets || []).map(function (d) { 67 return {title: d.title, url: d.file || d.url}; 68 }).filter(function (d) { return d.url; }); 69 var out = { 70 slug: p.slug, 71 name: p.name, 72 summary: p.summary || '', 73 body: p.description || '', 74 specs: (p.specs || []).map(function (s) { 75 return {label: s.label, value: s.value}; 76 }), 77 categories: p.categories || [], 78 brand: p.brand || '', 79 brandName: p.brandName || '', 80 images: (p.images || []).map(function (u) { return u + IMG; }), 81 docs: docs, 82 pdfs: docs.map(function (d) { return d.url; }), 83 models: [] 84 }; 85 if (p.series) out.parent = p.series; 86 if (p.featured) out.featured = true; 87 return out; 88 }); 89 90 // build the series -> models backlinks the site reads 91 var bySlug = {}; 92 products.forEach(function (p) { bySlug[p.slug] = p; }); 93 products.forEach(function (p) { 94 if (p.parent && bySlug[p.parent]) { 95 bySlug[p.parent].models.push({slug: p.slug, name: p.name}); 96 } 97 }); 98 99 var categories = (res.categories || []).map(function (c) { 100 return { 101 key: c.key, 102 label: c.label, 103 blurb: c.blurb || '', 104 featured: c.featured !== false, 105 count: products.filter(function (p) { 106 return !p.parent && p.categories.indexOf(c.key) !== -1; 107 }).length 108 }; 109 }); 110 111 return { 112 products: products, 113 categories: categories, 114 brands: res.brands || [], 115 news: res.news || [], 116 settings: res.settings || {} 117 }; 118 } 119 120 fetch(url(Q_CONTENT)) 121 .then(function (r) { 122 // 403 here almost always means this origin is missing from the project's 123 // CORS allowlist rather than anything being wrong with the query. 124 if (!r.ok) throw new Error('Sanity ' + r.status); 125 return r.json(); 126 }) 127 .then(function (json) { 128 var next = shape(json.result || {}); 129 if (!next.products.length) return; // never blank the page 130 131 var changed = JSON.stringify(next.products) !== JSON.stringify(window.NE_PRODUCTS) || 132 JSON.stringify(next.categories) !== JSON.stringify(window.NE_CATEGORIES) || 133 JSON.stringify(next.news) !== JSON.stringify(window.NE_NEWS); 134 135 window.NE_PRODUCTS = next.products; 136 window.NE_CATEGORIES = next.categories; 137 window.NE_BRANDS = next.brands; 138 window.NE_NEWS = next.news; 139 window.NE_SITE = next.settings; 140 141 // the header and footer read NE_SITE / NE_BRANDS, so a phone number or a 142 // principal edited in the Studio lands on all seven pages 143 if (window.NE_CHROME) window.NE_CHROME.refresh(); 144 if (changed && typeof window.NE_RENDER === 'function') window.NE_RENDER(); 145 }) 146 .catch(function () { /* shipped data stays on screen */ }); 147})();
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.