1/** 2 * UCL Dynamically Populating Footer Blogs 3 * Decoupled from legacy mock DB and hooked to Supabase with dynamic script loading self-healing pattern 4 */ 5 6// Helper to inject dependency scripts dynamically 7function loadUCLScript(src) { 8 return new Promise((resolve, reject) => { 9 const scripts = Array.from(document.getElementsByTagName('script')); 10 if (scripts.some(s => s.src && s.src.includes(src))) { 11 resolve(); 12 return; 13 } 14 const script = document.createElement('script'); 15 script.src = src; 16 script.type = 'text/javascript'; 17 script.onload = resolve; 18 script.onerror = (err) => { 19 console.error(`Failed to load script: ${src}`, err); 20 reject(err); 21 }; 22 document.body.appendChild(script); 23 }); 24} 25 26async function initFooterBlogs() { 27 const footerList = document.getElementById('footer-dynamic-blogs'); 28 if (!footerList) return; 29 30 try { 31 // Dynamic dependency loading to support all site static pages elegantly 32 if (typeof supabase === 'undefined') { 33 await loadUCLScript('https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2'); 34 } 35 if (typeof window.supabaseClient === 'undefined') { 36 await loadUCLScript('js/modules/supabase-client.js'); 37 } 38 if (typeof window.UCLBlogAPI === 'undefined') { 39 await loadUCLScript('js/modules/blog-api.js'); 40 } 41 42 const api = window.UCLBlogAPI; 43 if (!api) { 44 throw new Error("UCLBlogAPI is unavailable."); 45 } 46 47 // Fetch the 5 most recent published blogs 48 const latestBlogs = await api.getBlogs({ status: 'published', limit: 5 }); 49 50 if (latestBlogs.length === 0) { 51 footerList.innerHTML = ` 52 <li class="menu-item"><a href="#" style="color: #aaa; font-style: italic;">No posts uploaded yet.</a></li> 53 <li class="menu-item" style="margin-top: 15px; border-top: 1px dashed rgba(255,255,255,0.2); padding-top: 10px;"> 54 <a href="blogs.html" style="color: #D22B2B; font-weight: 600; font-size: 15px; transition: color 0.2s; display: inline-flex; align-items: center; gap: 5px;"> 55 View All Blogs <i class="fa fa-long-arrow-right"></i> 56 </a> 57 </li> 58 `; 59 return; 60 } 61 62 // Dynamically inject list items (Query strings on local file://, pretty paths on live server) 63 footerList.innerHTML = latestBlogs.map(blog => { 64 const detailUrl = window.location.protocol === 'file:' 65 ? `blog-detail.html?slug=${blog.slug}` 66 : `/${blog.slug}`; 67 return ` 68 <li class="menu-item" style="margin-bottom: 8px;"> 69 <a href="${detailUrl}" style="color: #ffffff; font-size: 15px; transition: color 0.2s; display: inline-block;"> 70 ${blog.title} 71 </a> 72 </li> 73 `; 74 }).join('') + ` 75 <li class="menu-item" style="margin-top: 15px; border-top: 1px dashed rgba(255,255,255,0.2); padding-top: 10px;"> 76 <a href="blogs.html" style="color: #D22B2B; font-weight: 600; font-size: 15px; transition: color 0.2s; display: inline-flex; align-items: center; gap: 5px;"> 77 View All Blogs <i class="fa fa-long-arrow-right"></i> 78 </a> 79 </li> 80 `; 81 82 } catch (error) { 83 console.error("UCL Footer Blogs Error:", error); 84 footerList.innerHTML = '<li class="menu-item"><a href="#" style="color: #D22B2B;">Temporary Unavailable</a></li>'; 85 } 86} 87 88// Initialize as soon as the DOM is interactive/loaded 89if (document.readyState === 'loading') { 90 document.addEventListener('DOMContentLoaded', initFooterBlogs); 91} else { 92 initFooterBlogs(); 93} 94
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.