1/* Magazine Astra: independent of jQuery/Bootstrap for navigation and loading. */ 2(() => { 3 'use strict'; 4 const ready = fn => document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', fn, {once:true}) : fn(); 5 ready(() => { 6 if (!document.body.classList.contains('theme-astra')) return; 7 document.querySelectorAll('[data-astra-toggle="collapse"]').forEach(button => { 8 const target = document.querySelector(button.getAttribute('data-target')); 9 if (!target) return; 10 button.addEventListener('click', () => { 11 const open = !target.classList.contains('show'); 12 target.classList.toggle('show', open); button.setAttribute('aria-expanded', String(open)); 13 }); 14 target.addEventListener('keydown', event => { 15 if (event.key === 'Escape') { target.classList.remove('show'); button.setAttribute('aria-expanded','false'); button.focus(); } 16 }); 17 }); 18 document.querySelectorAll('[data-astra-toggle="dropdown"]').forEach(toggle => { 19 toggle.setAttribute('aria-expanded','false'); 20 toggle.addEventListener('click', event => { 21 event.preventDefault(); 22 const menu = toggle.parentElement.querySelector('.dropdown-menu'); 23 if (menu) { const open = !menu.classList.contains('show'); menu.classList.toggle('show',open); toggle.setAttribute('aria-expanded',String(open)); } 24 }); 25 }); 26 document.addEventListener('keydown', event => { 27 if(event.key !== 'Escape') return; 28 document.querySelectorAll('[data-astra-toggle="dropdown"][aria-expanded="true"]').forEach(toggle=>{toggle.parentElement.querySelector('.dropdown-menu')?.classList.remove('show');toggle.setAttribute('aria-expanded','false');toggle.focus();}); 29 }); 30 // La columna más corta acompaña el scroll, limitada por el final de la fila. 31 const columns = document.getElementById('main-content'); 32 const content = columns?.querySelector(':scope > #content'); 33 const sidebar = columns?.querySelector(':scope > #sidebar'); 34 const sidebarInner = sidebar?.querySelector('.sidebar__inner'); 35 if (content && sidebar && sidebarInner) { 36 const contentInner = document.createElement('div'); 37 contentInner.className = 'astra-content-inner'; 38 while (content.firstChild) contentInner.append(content.firstChild); 39 content.append(contentInner); 40 columns.classList.add('astra-balanced-columns'); 41 let pending = false; 42 const measureColumns = () => { 43 pending = false; 44 const mobile = window.innerWidth < 768; 45 const leftHeight = contentInner.getBoundingClientRect().height; 46 const rightHeight = sidebarInner.getBoundingClientRect().height; 47 const leftShorter = leftHeight + 1 < rightHeight; 48 content.classList.toggle('astra-column-follow', !mobile && leftShorter); 49 sidebar.classList.toggle('astra-column-follow', !mobile && !leftShorter); 50 const menuHeight = document.getElementById('navigato2')?.offsetHeight || 0; 51 [[content, leftHeight], [sidebar, rightHeight]].forEach(([column, height]) => { 52 const top = Math.min(menuHeight + 16, window.innerHeight - height - 16); 53 column.style.setProperty('--astra-column-top', `${top}px`); 54 }); 55 }; 56 const scheduleColumns = () => { 57 if (!pending) { pending = true; requestAnimationFrame(measureColumns); } 58 }; 59 window.addEventListener('resize', scheduleColumns, {passive:true}); 60 window.addEventListener('load', scheduleColumns, {once:true}); 61 if (window.ResizeObserver) { 62 const observer = new ResizeObserver(scheduleColumns); 63 observer.observe(contentInner); 64 observer.observe(sidebarInner); 65 const menu = document.getElementById('navigato2'); 66 if (menu) observer.observe(menu); 67 } 68 measureColumns(); 69 } 70 // Isotope masonry: cada noticia conserva su altura, incluida su publicidad. 71 const masonryGrids = []; 72 document.querySelectorAll('.astra-list, .astra-category-band__grid, .astra-legacy-grid').forEach((grid, gridIndex) => { 73 if (!window.Isotope) return; // El CSS original sigue siendo legible si falla la librerÃa. 74 const legacy = grid.classList.contains('astra-legacy-grid'); 75 const itemAttribute = `astra-masonry-${gridIndex}`; 76 let instance = null, pending = false, lastWidth = -1; 77 const seen = new WeakSet(); 78 const sizes = new WeakMap(); 79 const schedule = () => { 80 if (!pending) { pending = true; requestAnimationFrame(layout); } 81 }; 82 const itemObserver = window.ResizeObserver ? new ResizeObserver(entries => { 83 let changed = false;
84 entries.forEach(entry => { 85 const next = `${entry.contentRect.width}:${entry.contentRect.height}`; 86 if (sizes.get(entry.target) !== next) { sizes.set(entry.target, next); changed = true; } 87 }); 88 if (changed) schedule(); 89 }) : null; 90 function layout() { 91 pending = false; 92 const items = Array.from(grid.children).filter(el => !['SCRIPT','STYLE','TEMPLATE'].includes(el.tagName)); 93 if (!items.length || grid.clientWidth < 2) return; 94 grid.classList.add('astra-isotope'); 95 items.forEach(item => { 96 item.setAttribute('data-astra-masonry-item', itemAttribute); 97 if (!seen.has(item)) { seen.add(item); itemObserver?.observe(item); } 98 }); 99 const width = Math.min(...items.map(item => item.getBoundingClientRect().width).filter(w => w > 0)); 100 if (!Number.isFinite(width)) return; 101 const options = { 102 itemSelector: `[data-astra-masonry-item="${itemAttribute}"]`, 103 layoutMode: 'masonry', percentPosition: true, transitionDuration: 0, 104 masonry: {columnWidth: width, gutter: legacy ? 0 : 24, horizontalOrder: false} 105 }; 106 if (!instance) instance = new window.Isotope(grid, options); 107 else { instance.option(options); instance.reloadItems(); instance.arrange(); } 108 } 109 grid.addEventListener('load', schedule, true); 110 grid.addEventListener('error', schedule, true); 111 grid.addEventListener('loadedmetadata', schedule, true); 112 new MutationObserver(schedule).observe(grid, {childList:true, subtree:true}); 113 if (window.ResizeObserver) new ResizeObserver(entries => { 114 const width = entries[0].contentRect.width; 115 if (width !== lastWidth) { lastWidth = width; schedule(); } 116 }).observe(grid); 117 window.addEventListener('resize', schedule, {passive:true}); 118 window.addEventListener('load', schedule, {once:true}); 119 document.fonts?.ready.then(schedule); 120 masonryGrids.push({grid, schedule}); 121 layout(); 122 }); 123 const externalScripts = new Set(Array.from(document.scripts).filter(s=>s.src).map(s=>s.src)); 124 // Only server-rendered cards (escaped) and existing trusted administrator ads enter this fragment. 125 const activateAds = async nodes => { 126 for (const node of nodes) { 127 for (const inert of node.querySelectorAll('script')) { 128 if (inert.type && !['text/javascript','application/javascript','module'].includes(inert.type)) continue; 129 const script = document.createElement('script'); 130 for (const attr of inert.attributes) script.setAttribute(attr.name,attr.value); 131 script.textContent = inert.textContent; 132 if (script.src && externalScripts.has(script.src)) { inert.remove(); continue; } 133 if (script.src) { 134 externalScripts.add(script.src); 135 if (!script.async) await new Promise(resolve => { script.onload=resolve; script.onerror=resolve; inert.replaceWith(script); }); 136 else inert.replaceWith(script); 137 } else inert.replaceWith(script); 138 } 139 } 140 }; 141 document.querySelectorAll('[data-astra-feed]').forEach(feed => { 142 const button = feed.querySelector('[data-astra-more]'); 143 if (!button || !window.fetch || !window.AbortController) return; 144 const items = feed.querySelector('[data-astra-items]'); 145 const status = feed.querySelector('[role="status"]'); 146 const fallback = feed.querySelector('[data-astra-fallback]'); 147 const context = JSON.parse(feed.dataset.context); 148 let loading = false; 149 button.hidden=false; if(fallback)fallback.hidden=true; 150 button.addEventListener('click', async () => { 151 if(loading) return; 152 loading=true; button.disabled=true; button.textContent='Cargandoâ¦'; feed.setAttribute('aria-busy','true'); status.textContent=''; 153 const controller=new AbortController(); const timeout=setTimeout(()=>controller.abort(),20000); 154 try { 155 const url=new URL(feed.dataset.endpoint,document.baseURI); 156 Object.entries(context).forEach(([key,value])=>url.searchParams.set(key,value)); 157 url.searchParams.set('cursor',button.dataset.cursor); 158 const response=await fetch(url,{signal:controller.signal,headers:{Accept:'application/json'},credentials:'same-origin'}); 159 if(!response.ok) throw new Error('HTTP '+response.status); 160 const data=await response.json(); 161 if(!data.success || typeof data.html!=='string' || typeof data.has_more!=='boolean' || (data.has_more && typeof data.cursor!=='string')) throw new Error('Respuesta inválida'); 162 const template=document.createElement('template'); template.innerHTML=data.html;
163 const known=new Set(Array.from(items.querySelectorAll('[data-news-id]'),el=>el.dataset.newsId)); 164 template.content.querySelectorAll('[data-news-id]').forEach(el=>{if(known.has(el.dataset.newsId))el.remove();}); 165 const nodes=Array.from(template.content.children); const first=template.content.querySelector('.astra-card__title a'); 166 items.append(template.content); 167 masonryGrids.forEach(entry => { if (entry.grid === items) entry.schedule(); }); 168 button.dataset.cursor=data.cursor; 169 if(fallback && typeof data.fallback==='string') fallback.href=data.fallback; 170 button.hidden=!data.has_more; 171 status.textContent=data.count ? `${data.count} noticias cargadas.${data.has_more?'':' No hay más noticias.'}` : 'No hay más noticias.'; 172 if(first) first.focus({preventScroll:true}); 173 if(fallback) fallback.hidden=true; 174 activateAds(nodes).catch(()=>{}); 175 } catch(error) { 176 status.textContent='No se pudieron cargar más noticias. Intentar nuevamente.'; 177 // Original link remains a usable fallback for the next SSR page. 178 if(fallback) fallback.hidden=false; 179 } finally { 180 clearTimeout(timeout); loading=false; button.disabled=false; button.textContent='VER MÃS'; feed.removeAttribute('aria-busy'); 181 } 182 }); 183 }); 184 }); 185})();
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.