1(function () { 2 const LOCAL_JSON = '/assets/writing-cache.json'; // optional static fallback (absolute path) 3 const BYPASS_LOCAL = window.WRITING_BYPASS_LOCAL === true; // set true to skip local cache entirely 4 const TARGET = document.getElementById('writing-cards'); 5 const FALLBACK = document.getElementById('writing-fallback'); 6 7 // Determine desired item count (script var -> data-count -> default) 8 const ITEM_COUNT = Number((window.WRITING_ITEM_COUNT ?? TARGET?.dataset?.count ?? 7)); 9 console.debug('[writing] ITEM_COUNT =', ITEM_COUNT); 10 11 // Use `count=` (matches writing.php) and add a cache buster 12 const FEED_URL = `/assets/writing.php?count=${ITEM_COUNT}&_=${Date.now()}`; 13 console.debug('[writing] FEED_URL =', FEED_URL); 14 15 function showFallback(msg) { 16 if (FALLBACK) { 17 FALLBACK.style.display = 'block'; 18 if (msg) FALLBACK.textContent = 'Canât load feed right now. ' + msg; 19 } 20 } 21 22 function formatDate(d) { 23 if (!d) return ''; 24 // If already looks like a human date, return as-is 25 if (/[A-Za-z]+\s+\d{1,2},\s*\d{4}/.test(d)) return d; 26 const t = Date.parse(d); 27 if (isNaN(t)) return d; // fallback to raw string 28 try { 29 const date = new Date(t); 30 return date.toLocaleDateString('en-US', { 31 year: 'numeric', 32 month: 'long', 33 day: 'numeric' 34 }); 35 } catch { 36 return d; 37 } 38 } 39 40 function renderFromJson(items) { 41 if (!Array.isArray(items) || !items.length) return false; 42 43 const frag = document.createDocumentFragment(); 44 if (FALLBACK) FALLBACK.style.display = 'none'; 45 console.debug('[writing] renderFromJson input length =', items.length); 46 47 // Prepend a static "Read all" card 48 const staticCard = document.createElement('article'); 49 staticCard.className = 'card card-wrap featured-card'; 50 staticCard.innerHTML = ` 51 <a href="https://www.theatlantic.com/author/ian-bogost/" class="card-link" target="_blank" rel="noopener"> 52 <img src="/images/atlantic-card.png" alt="The Atlantic" class="card-img" /> 53 <p>Read all my stories at <em>The Atlantic</em></p> 54 </a> 55 `; 56 frag.appendChild(staticCard); 57 58 // Render feed items (limited to ITEM_COUNT) 59 let cCount = 0; 60 let cMAX = 5; 61 const list = items.slice(0, ITEM_COUNT); 62 console.debug('[writing] rendering', list.length, 'items'); 63 list.forEach((it) => { 64 const title = it.title || ''; 65 const link = it.link || '#'; 66 const rawDate = it.date || it.pubDate || it.updated || it.published || ''; 67 const when = formatDate(rawDate) || ''; 68 const excerpt = it.excerpt || it.summary || ''; 69 70 const article = document.createElement('article'); 71 article.className = 'card card-wrap'; 72 73 let c = 0; 74 if (Math.random() > 0.5) { 75 c = Math.floor(Math.random() * 25); 76 if (c>0) cCount++; 77 if (cCount >= cMAX) c = 0; 78 } 79 80 article.innerHTML = ` 81 <a href="${link}" class="card-link" target="_blank" rel="noopener"> 82 <h3 class="card-title">${title}</h3> 83 <p class="muted date">${when}</p> 84 <p class="muted excerpt line-clamp">${excerpt}</p> 85 <img src="/assets/creatures/${c}.png" alt="" class="mascot" /> 86 </a> 87 `; 88 frag.appendChild(article); 89 }); 90 91 TARGET.innerHTML = ''; 92 TARGET.appendChild(frag); 93 return true; 94 } 95 96 async function getJSON(url) { 97 console.debug('[writing] fetch â', url); 98 const res = await fetch(url, { 99 cache: 'no-store', 100 credentials: 'same-origin', 101 headers: { 'Accept': 'application/json' } 102 }); 103 console.debug('[writing] status', res.status, res.ok ? 'OK' : 'ERROR'); 104 if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`); 105 const json = await res.json(); 106 console.debug('[writing] json length =', Array.isArray(json) ? json.length : '(non-array)'); 107 return json; 108 } 109 110 async function load() { 111 if (!TARGET) { 112 console.warn('[writing] No #writing-cards element found in DOM.'); 113 return; 114 } 115 116 // 1) Try live PHP endpoint first 117 try { 118 const live = await getJSON(FEED_URL); 119 console.info('[writing] Loaded PHP JSON:', live); 120 if (Array.isArray(live)) { 121 if (renderFromJson(live)) { 122 console.debug('[writing] DONE (live)'); 123 return; 124 } 125 showFallback('(empty live array)'); 126 return; 127 } else if (live && live.error) { 128 console.warn('[writing] PHP returned error:', live); 129 // fall through to local cache 130 } else { 131 console.warn('[writing] Unexpected PHP payload shape:', live); 132 // fall through to local cache 133 } 134 } catch (e) { 135 console.warn('[writing] PHP proxy fetch failed:', e.message); 136 // fall through to local cache 137 } 138 139 // 2) Optional local JSON fallback (unless explicitly bypassed) 140 if (!BYPASS_LOCAL) {
141 try { 142 const local = await getJSON(LOCAL_JSON); 143 console.info('[writing] Loaded local JSON cache:', LOCAL_JSON, local); 144 if (renderFromJson(local)) { 145 console.debug('[writing] DONE (local cache)'); 146 return; 147 } 148 showFallback('(empty local array)'); 149 return; 150 } catch (e) { 151 console.info('[writing] No local cache JSON or failed to load:', e.message); 152 } 153 } else { 154 console.info('[writing] Local cache bypassed by flag.'); 155 } 156 157 // 3) Give up gracefully 158 showFallback(); 159 } 160 161 load(); 162})();
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.