1(function(){ 2 'use strict'; 3 4 function config(){ 5 return window.SDB_MDB || {}; 6 } 7 8 function announce(wrap, message){ 9 const status = wrap.querySelector('.sdb-mdb-load-status'); 10 if(status){ 11 status.textContent = message || ''; 12 } 13 } 14 15 function format(template, value){ 16 return String(template || '').replace('%d', String(value)); 17 } 18 19 function getGrid(wrap){ 20 return wrap.querySelector('.sdb-mdb-grid'); 21 } 22 23 function getLoadMoreButton(wrap){ 24 return wrap.querySelector('.sdb-mdb-load-more'); 25 } 26 27 function setLoadingState(wrap, isLoading){ 28 const grid = getGrid(wrap); 29 const button = getLoadMoreButton(wrap); 30 31 wrap.dataset.loading = isLoading ? '1' : '0'; 32 33 if(grid){ 34 grid.setAttribute('aria-busy', isLoading ? 'true' : 'false'); 35 } 36 37 if(button){ 38 button.disabled = isLoading; 39 if(isLoading){ 40 button.setAttribute('aria-busy', 'true'); 41 } else { 42 button.removeAttribute('aria-busy'); 43 } 44 } 45 } 46 47 function updateLoadMoreButton(wrap, hasMore){ 48 const button = getLoadMoreButton(wrap); 49 if(!button){ 50 return; 51 } 52 53 button.hidden = wrap.dataset.loadMore !== '1' || !hasMore; 54 button.disabled = false; 55 button.removeAttribute('aria-busy'); 56 } 57 58 function requestParams(wrap, page, type){ 59 const settings = config(); 60 return new URLSearchParams({ 61 action: settings.action || 'sdb_mdb_load_projects', 62 page: String(page), 63 per_page: wrap.dataset.perPage || '9', 64 type: type || '', 65 status: wrap.dataset.status || '', 66 tag: wrap.dataset.tag || '', 67 featured: wrap.dataset.featured || '0' 68 }); 69 } 70 71 async function fetchProjects(wrap, page, type){ 72 const settings = config(); 73 if(!settings.ajaxUrl){ 74 throw new Error('AJAX endpoint unavailable'); 75 } 76 77 const response = await fetch(settings.ajaxUrl, { 78 method: 'POST', 79 credentials: 'same-origin', 80 cache: 'no-store', 81 headers: { 82 'Accept': 'application/json', 83 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 84 }, 85 body: requestParams(wrap, page, type).toString() 86 }); 87 88 if(!response.ok){ 89 throw new Error('Request failed'); 90 } 91 92 const payload = await response.json(); 93 if(!payload || payload.success !== true || !payload.data){ 94 throw new Error('Invalid response'); 95 } 96 97 return payload.data; 98 } 99 100 function updateActiveTab(wrap, activeTab){ 101 const grid = getGrid(wrap); 102 103 wrap.querySelectorAll('.sdb-mdb-filters [role="tab"]').forEach(function(tab){ 104 const isActive = tab === activeTab; 105 tab.classList.toggle('is-active', isActive); 106 tab.setAttribute('aria-selected', isActive ? 'true' : 'false'); 107 tab.tabIndex = isActive ? 0 : -1; 108 }); 109 110 if(grid && activeTab.id){ 111 grid.setAttribute('aria-labelledby', activeTab.id); 112 } 113 } 114 115 function updateLocation(type){ 116 if(!window.history || !window.history.replaceState){ 117 return; 118 } 119 120 const url = new URL(window.location.href); 121 if(type){ 122 url.searchParams.set('mdb_tipo', type); 123 } else { 124 url.searchParams.delete('mdb_tipo'); 125 } 126 127 window.history.replaceState({}, '', url.toString()); 128 } 129 130 async function requestProjects(wrap, options){ 131 const grid = getGrid(wrap); 132 const settings = config(); 133 const page = options.page || 1; 134 const type = typeof options.type === 'string' ? options.type : (wrap.dataset.type || ''); 135 136 if(!grid || wrap.dataset.loading === '1'){ 137 return false; 138 } 139 140 setLoadingState(wrap, true); 141 announce(wrap, settings.loading || 'A carregar projetosâ¦'); 142 143 try { 144 const data = await fetchProjects(wrap, page, type); 145 const html = data.html || ''; 146 147 if(options.replace){ 148 grid.innerHTML = html; 149 } else if(html){ 150 grid.insertAdjacentHTML('beforeend', html); 151 } 152 153 wrap.dataset.page = String(data.page || page); 154 wrap.dataset.maxPages = String(data.max_pages || 1); 155 wrap.dataset.type = type; 156 updateLoadMoreButton(wrap, Boolean(data.has_more)); 157 158 if(options.activeTab){ 159 updateActiveTab(wrap, options.activeTab); 160 updateLocation(type); 161 } 162 163 if(data.has_more){
164 announce(wrap, format(settings.loaded || 'Foram carregados %d projetos adicionais.', data.count || 0)); 165 } else { 166 announce(wrap, settings.done || 'Todos os projetos disponÃveis foram carregados.'); 167 } 168 169 return true; 170 } catch(error){ 171 updateLoadMoreButton(wrap, true); 172 announce(wrap, settings.error || 'Não foi possÃvel carregar os projetos. Tente novamente.'); 173 return false; 174 } finally { 175 setLoadingState(wrap, false); 176 } 177 } 178 179 function loadMore(button){ 180 const wrap = button.closest('.sdb-mdb-progressive'); 181 if(!wrap || button.disabled){ 182 return; 183 } 184 185 const currentPage = parseInt(wrap.dataset.page || '1', 10); 186 requestProjects(wrap, { 187 page: currentPage + 1, 188 type: wrap.dataset.type || '', 189 replace: false 190 }); 191 } 192 193 function selectTab(tab){ 194 const wrap = tab.closest('.sdb-mdb-progressive'); 195 if(!wrap || tab.getAttribute('aria-selected') === 'true'){ 196 return; 197 } 198 199 requestProjects(wrap, { 200 page: 1, 201 type: tab.dataset.filter || '', 202 replace: true, 203 activeTab: tab 204 }); 205 } 206 207 function handleTabKeydown(event, tab){ 208 const tabs = Array.from(tab.closest('[role="tablist"]').querySelectorAll('[role="tab"]')); 209 const currentIndex = tabs.indexOf(tab); 210 let nextIndex = currentIndex; 211 212 if(event.key === 'ArrowRight'){ 213 nextIndex = (currentIndex + 1) % tabs.length; 214 } else if(event.key === 'ArrowLeft'){ 215 nextIndex = (currentIndex - 1 + tabs.length) % tabs.length; 216 } else if(event.key === 'Home'){ 217 nextIndex = 0; 218 } else if(event.key === 'End'){ 219 nextIndex = tabs.length - 1; 220 } else { 221 return; 222 } 223 224 event.preventDefault(); 225 tabs[nextIndex].focus(); 226 tabs[nextIndex].click(); 227 } 228 229 function initializeWrap(wrap){ 230 if(wrap.dataset.mdbInteractiveInit === '1'){ 231 return; 232 } 233 234 wrap.dataset.mdbInteractiveInit = '1'; 235 const button = getLoadMoreButton(wrap); 236 updateLoadMoreButton(wrap, Boolean(button && !button.hidden)); 237 } 238 239 function initialize(){ 240 document.querySelectorAll('.sdb-mdb-progressive').forEach(initializeWrap); 241 } 242 243 if(document.readyState === 'loading'){ 244 document.addEventListener('DOMContentLoaded', initialize); 245 } else { 246 initialize(); 247 } 248 249 document.addEventListener('click', function(event){ 250 const galleryButton = event.target && event.target.closest ? event.target.closest('.sdb-mdb-gallery-toggle') : null; 251 if(galleryButton){ 252 const gallery = document.getElementById(galleryButton.dataset.target); 253 if(gallery){ 254 gallery.hidden = !gallery.hidden; 255 galleryButton.setAttribute('aria-expanded', String(!gallery.hidden)); 256 } 257 return; 258 } 259 260 const tab = event.target && event.target.closest ? event.target.closest('.sdb-mdb-filters [role="tab"]') : null; 261 if(tab){ 262 event.preventDefault(); 263 selectTab(tab); 264 return; 265 } 266
267 const loadMoreButton = event.target && event.target.closest ? event.target.closest('.sdb-mdb-load-more') : null; 268 if(loadMoreButton){ 269 event.preventDefault(); 270 loadMore(loadMoreButton); 271 } 272 }); 273 274 document.addEventListener('keydown', function(event){ 275 const tab = event.target && event.target.closest ? event.target.closest('.sdb-mdb-filters [role="tab"]') : null; 276 if(tab){ 277 handleTabKeydown(event, tab); 278 } 279 }); 280})();
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.