PageSourceSearch

https://www.isjd.pt/js/listings.js?t=01:35:01

js isjd.pt collected 2026-09-25 00:35:13 UTC 15,001 bytes, 487 lines download raw bytes

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.