1let lastLink = ""; 2let timeout = null; 3 4const seo = { 5 parent: { 6 title: document.title, 7 description: document.querySelector("meta[name='description']")?.content || "", 8 keywords: document.querySelector("meta[name='keywords']")?.content || "", 9 }, 10}; 11 12function safeSetMeta(name, value) { 13 const el = document.querySelector(`meta[name='${name}']`); 14 if (el && typeof value === "string") el.content = value; 15} 16 17function getBackURLFromPathname() { 18 const parts = window.location.pathname.split("/"); 19 parts.pop(); 20 return window.location.origin + parts.join("/"); 21} 22 23function normalizeUrl(url) { 24 if (!url) return ""; 25 try { 26 return new URL(url, window.location.origin).toString(); 27 } catch { 28 return ""; 29 } 30} 31 32document.addEventListener("click", (e) => { 33 const hit = e.target?.closest?.(".listing .accordion > div:first-child"); 34 if (!hit) return; 35 36 e.preventDefault(); 37 38 const listing = hit.closest(".listing"); 39 const parent = hit.closest(".listing .accordion"); 40 if (!listing || !parent) return; 41 42 const div = parent.querySelector(".description"); 43 if (!div) return; 44 45 const h = div.scrollHeight; 46 const href = parent.getAttribute("href"); 47 48 const seotitle = parent.dataset.seotitle || ""; 49 const seodescription = parent.dataset.seodescripton || parent.dataset.seodescription || ""; 50 const seokeywords = parent.dataset.seokeywords || ""; 51 52 if (parent.classList.contains("open") || parent.classList.contains("active")) { 53 div.removeAttribute("style"); 54 parent.classList.remove("active", "open"); 55 hit.setAttribute("aria-expanded", "false"); 56 57 if (!div.classList.contains("active") && href) { 58 history.pushState(document.title, "", getBackURLFromPathname()); 59 lastLink = ""; 60 61 document.title = seo.parent.title; 62 safeSetMeta("description", seo.parent.description); 63 safeSetMeta("keywords", seo.parent.keywords); 64 } 65 } else { 66 listing.querySelectorAll(".accordion").forEach((el) => { 67 if (el.querySelector(".toggle")) el.classList.remove("open", "active"); 68 }); 69 listing.querySelectorAll(".accordion .description").forEach((el) => el.removeAttribute("style")); 70 71 div.style.maxHeight = `${h}px`; 72 setTimeout(() => { 73 div.style.overflow = "visible"; 74 }, 200); 75 76 parent.classList.add("open"); 77 hit.setAttribute("aria-expanded", "true"); 78 79 setTimeout(() => { 80 hit.scrollIntoView({ behavior: "smooth", block: "center" }); 81 }, 200); 82 83 if (href && lastLink !== href) { 84 if (lastLink === "") history.pushState(seotitle || document.title, "", href); 85 else history.replaceState(seotitle || document.title, "", href); 86 87 lastLink = href; 88 89 if (seotitle) document.title = seotitle; 90 if (seodescription) safeSetMeta("description", seodescription); 91 if (seokeywords) safeSetMeta("keywords", seokeywords); 92 } else if (lastLink !== "") { 93 history.pushState(document.title, "", getBackURLFromPathname()); 94 lastLink = ""; 95 } 96 } 97}); 98 99window.addEventListener("popstate", () => { 100 if (lastLink !== "") { 101 document.querySelectorAll(".listing .accordion").forEach((el) => el.classList.remove("open", "active")); 102 document.querySelectorAll(".listing .accordion .description").forEach((el) => (el.style.maxHeight = 0)); 103 lastLink = ""; 104 } 105}); 106 107window.addEventListener("load", () => { 108 const active = document.querySelector(".accordion.active"); 109 const bannerContainer = document.querySelector(".banner .container"); 110 const header = document.querySelector("header"); 111 112 if (active && bannerContainer && header) { 113 const marginTop = parseInt(getComputedStyle(bannerContainer).marginTop || "0", 10); 114 window.scroll({ 115 top: bannerContainer.getBoundingClientRect().top + window.scrollY - header.offsetHeight - marginTop, 116 behavior: "smooth", 117 }); 118 } 119}); 120 121function reinitCustomSelect(scope) { 122 if (!scope) return; 123 124 scope.querySelectorAll("select.custom-select + .custom-select").forEach((el) => el.remove()); 125 126 scope.querySelectorAll("select.custom-select").forEach((el) => { 127 try { 128 new CustomSelect(el); 129 } catch (_) {} 130 }); 131} 132 133function getListingFromHTMLDoc(htmlDoc, listing) { 134 const id = listing.getAttribute("id"); 135 if (id) return htmlDoc.querySelector(`#${id}`) || htmlDoc.querySelector(".listing"); 136 return htmlDoc.querySelector(".listing"); 137} 138 139function listingFilter(listing, filter, resetFilter = true, scrollToTop = true, options = {}) { 140 if (!listing) return; 141 142 const append = !!options.append; 143 const isRecruitment = listing.classList.contains("recruitment-offers"); 144 145 const currentQS = new URLSearchParams(window.location.search); 146 const cmsPage = isRecruitment ? currentQS.get("page") || "" : ""; 147 148 const qs = resetFilter ? new URLSearchParams() : new URLSearchParams(window.location.search); 149 150 if (isRecruitment && cmsPage) qs.set("page", cmsPage); 151 152 let resetPagination = true; 153 154 if (filter) { 155 const v = typeof filter.q === "string" ? filter.q : typeof filter.search === "string" ? filter.search : null; 156 if (v !== null) { 157 filter.q = v; 158 filter.search = v; 159 } 160 } 161 162 for (const key in filter) { 163 if (!Object.prototype.hasOwnProperty.call(filter, key)) continue; 164
165 const val = filter[key]; 166 167 if (qs.has(key)) { 168 if (val !== undefined && val !== null && String(val) !== "") qs.set(key, val); 169 else qs.delete(key); 170 } else if (val !== undefined && val !== null && String(val) !== "") { 171 qs.append(key, val); 172 } 173 174 if ((!isRecruitment && key === "page") || (isRecruitment && key === "p")) resetPagination = false; 175 } 176 177 if (resetPagination) { 178 if (isRecruitment) qs.delete("p"); 179 else qs.delete("page"); 180 } 181 182 qs.delete("url"); 183 184 if (isRecruitment && cmsPage) qs.set("page", cmsPage); 185 186 if (filter && "url" in filter && !isRecruitment) { 187 if (filter.url) { 188 const u = new URL(filter.url, window.location.origin); 189 history.replaceState(document.title, "", `${u.pathname}${qs.toString() ? `?${qs.toString()}` : ""}`); 190 } else if (filter.url === "") { 191 const back = getBackURLFromPathname(); 192 history.replaceState(document.title, "", `${back}${qs.toString() ? `?${qs.toString()}` : ""}`); 193 } 194 } else { 195 history.replaceState( 196 document.title, 197 "", 198 `${window.location.origin}${window.location.pathname}${qs.toString() ? `?${qs.toString()}` : ""}` 199 ); 200 } 201 202 (async () => { 203 try { 204 const itemsEl = listing.querySelector(".items"); 205 if (itemsEl && !append) { 206 itemsEl.classList.add("fade-out"); 207 await new Promise((r) => setTimeout(r, 0)); 208 } 209 210 const fetchUrl = filter?.url ? normalizeUrl(filter.url) : `${window.location.origin}${window.location.pathname}`; 211 212 const response = await fetch(fetchUrl, { 213 method: "POST", 214 body: qs, 215 }); 216 217 const htmlText = await response.text(); 218 const htmlDoc = new DOMParser().parseFromString(htmlText, "text/html"); 219 220 const incomingListing = getListingFromHTMLDoc(htmlDoc, listing); 221 if (!incomingListing) throw new Error("Listing não encontrada na resposta."); 222 223 const target = { 224 items: incomingListing.querySelector(".items"), 225 pagination: incomingListing.querySelector(".pagination"), 226 filter_fields: incomingListing.querySelector(".filter_fields"), 227 }; 228 229 const currentItems = listing.querySelector(".items"); 230 231 if (target.items && currentItems) { 232 if (append) currentItems.insertAdjacentHTML("beforeend", target.items.innerHTML); 233 else currentItems.innerHTML = target.items.innerHTML; 234 } 235 236 const currentPagination = listing.querySelector(".pagination"); 237 if (target.pagination) { 238 if (currentPagination) currentPagination.outerHTML = target.pagination.outerHTML; 239 else currentItems?.insertAdjacentHTML("afterend", target.pagination.outerHTML); 240 } else if (currentPagination) { 241 currentPagination.classList.add("d-none"); 242 } 243 244 const isAppendPaginationOnly = 245 append && 246 filter && 247 Object.keys(filter).length === 1 && 248 Object.prototype.hasOwnProperty.call(filter, "p"); 249 250 if (!isAppendPaginationOnly) { 251 const currentFilters = listing.querySelector(".filter_fields"); 252 if (target.filter_fields && currentFilters) { 253 currentFilters.innerHTML = target.filter_fields.innerHTML; 254 reinitCustomSelect(currentFilters); 255 } 256 } 257 258 if (!append) { 259 reinitCustomSelect(listing.querySelector(".items")); 260 if (listing.querySelector(".filter_fields")) reinitCustomSelect(listing.querySelector(".filter_fields")); 261 } 262 263 if (scrollToTop && !append && window.innerWidth > 768) { 264 const header = document.querySelector("header"); 265 const headerH = header ? header.offsetHeight : 0; 266 window.scroll({ 267 top: listing.getBoundingClientRect().top + window.scrollY - headerH - 60, 268 behavior: "smooth", 269 }); 270 } 271 272 if (itemsEl && !append) itemsEl.classList.remove("fade-out"); 273 } catch (err) { 274 console.error("listingFilter error:", err); 275 const itemsEl = listing?.querySelector?.(".items"); 276 if (itemsEl) itemsEl.classList.remove("fade-out"); 277 } 278 })(); 279} 280 281document.addEventListener("click", (e) => { 282 const btn = e.target?.closest?.(".listing .filter-button"); 283 if (!btn) return; 284 285 const listing = btn.closest(".listing"); 286 const container = listing?.querySelector(".filter-container"); 287 if (!container) return; 288 289 container.classList.toggle("open"); 290 document.documentElement.classList.toggle("lock", container.classList.contains("open")); 291 document.body.classList.toggle("lock", container.classList.contains("open")); 292}); 293 294document.addEventListener("click", (e) => { 295 const btn = e.target?.closest?.(".listing .mobile-close"); 296 if (!btn) return; 297 298 const listing = btn.closest(".listing"); 299 const container = listing?.querySelector(".filter-container"); 300 if (!container) return; 301 302 container.classList.remove("open"); 303 document.documentElement.classList.remove("lock"); 304 document.body.classList.remove("lock"); 305}); 306 307document.addEventListener("change", (e) => { 308 if (!e.target) return; 309 310 const listing = e.target.closest(".listing"); 311 if (!listing) return; 312 313 if (listing.classList.contains("recruitment-offers")) { 314 if (!e.target.matches("select[name='especializacao'], select[name='localizacao']")) return; 315 316 const esp = listing.querySelector("select[name='especializacao']")?.value || ""; 317 const loc = listing.querySelector("select[name='localizacao']")?.value || ""; 318 319 listingFilter( 320 listing, 321 { 322 especializacao: esp, 323 localizacao: loc, 324 p: 1, 325 }, 326 false, 327 true, 328 { append: false } 329 ); 330 return; 331 } 332 333 if (!e.target.matches(".listing .filter-container select")) return; 334 335 const name = e.target.name; 336 const value = (e.target.value || "").trim(); 337 338 if (name === "category") { 339 if (value === "") { 340 listingFilter(listing, { url: "" }, false); 341 } else { 342 listingFilter(listing, { url: value }, true); 343 } 344 return; 345 } 346 347 if (name === "archive") { 348 listingFilter(listing, { archive: value }, false); 349 return; 350 } 351}); 352
353function updateSearchUI(input) { 354 const listing = input.closest(".listing"); 355 if (!listing) return; 356 const btn = listing.querySelector(".search-wrapper a[submit]"); 357 if (!btn) return; 358 359 if (input.value.trim() !== "") { 360 input.classList.add("has-value"); 361 btn.classList.add("clear-search"); 362 } else { 363 input.classList.remove("has-value"); 364 btn.classList.remove("clear-search"); 365 } 366} 367 368document.addEventListener("input", (e) => { 369 const input = e.target; 370 if (!input || !input.matches(".listing input[name='blog-search']")) return; 371 372 updateSearchUI(input); 373 374 const listing = input.closest(".listing"); 375 if (!listing) return; 376 377 const value = input.value || ""; 378 379 clearTimeout(timeout); 380 timeout = setTimeout(() => { 381 listingFilter(listing, { search: value }, true); 382 }, 400); 383}); 384 385document.addEventListener("keydown", (e) => { 386 const input = e.target; 387 if (!input || !input.matches(".listing input[name='blog-search']")) return; 388 389 if (e.key === "Enter") { 390 e.preventDefault(); 391 const listing = input.closest(".listing"); 392 if (!listing) return; 393 listingFilter(listing, { q: input.value || "" }, true); 394 } 395}); 396 397document.addEventListener("click", (e) => { 398 const btn = e.target?.closest?.(".listing .search-wrapper a[submit]"); 399 if (!btn) return; 400 401 e.preventDefault(); 402 403 const listing = btn.closest(".listing"); 404 const input = listing?.querySelector("input[name='blog-search']"); 405 if (!listing || !input) return; 406 407 if (btn.classList.contains("clear-search")) { 408 input.value = ""; 409 updateSearchUI(input); 410 listingFilter(listing, { search: "" }, true); 411 return; 412 } 413 414 listingFilter(listing, { q: input.value || "" }, true); 415}); 416 417document.addEventListener("click", (e) => { 418 const btn = e.target?.closest?.(".listing .apply-mobile a[submit]"); 419 if (!btn) return; 420 421 e.preventDefault(); 422 423 const listing = btn.closest(".listing"); 424 if (!listing) return; 425 426 const filter = {}; 427 428 const cat = listing.querySelector("select[name='category']"); 429 const arch = listing.querySelector("select[name='archive']"); 430 const q = listing.querySelector("input[name='blog-search']"); 431 432 if (cat && cat.value) filter.url = cat.value; 433 if (arch) filter.archive = arch.value || ""; 434 if (q) filter.q = q.value || ""; 435 436 listingFilter(listing, filter, true); 437 438 const container = listing.querySelector(".filter-container"); 439 if (container) container.classList.remove("open"); 440 document.documentElement.classList.remove("lock"); 441 document.body.classList.remove("lock"); 442}); 443 444document.addEventListener("click", (e) => { 445 const more = e.target?.closest?.(".listing.recruitment-offers .pagination a.more-results"); 446 if (more) { 447 const listing = more.closest(".listing"); 448 const page = more.dataset.page; 449 if (!listing || !page) return; 450 451 e.preventDefault(); 452 453 listingFilter(listing, { p: page }, false, false, { append: true }); 454 return; 455 } 456 457 const a = e.target?.closest?.(".listing .pagination a"); 458 if (!a) return; 459 460 const listing = a.closest(".listing"); 461 const page = a.dataset.page; 462 if (!listing || !page) return; 463 464 if (listing.classList.contains("recruitment-offers")) { 465 e.preventDefault(); 466 listingFilter(listing, { p: page }, false, true, { append: false }); 467 return; 468 } 469 470 e.preventDefault(); 471 listingFilter(listing, { page }, false, true); 472}); 473 474function adjustLine(listing) { 475 let line = listing.querySelector(".line"), 476 offset; 477 478 listing.querySelectorAll(".active").forEach((el) => { 479 if (listing.classList.contains("glossary-filter")) { 480 line.style.width = el.offsetWidth; 481 offset = el.offsetLeft; 482 } else { 483 offset = el.offsetLeft + el.offsetWidth / 2 - line.offsetWidth / 2; 484 } 485 line.style.transform = `translateX(${offset}px)`; 486 }); 487}
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.