1(() => { 2 const cfg = window.CJ_MENU || {}; 3 const DEBUG = true; 4 const log = (...a) => DEBUG && console.log("[CJ COURSES]", ...a); 5 6 function decodeHtmlEntities(str) { 7 if (str == null) return ""; 8 const txt = document.createElement("textarea"); 9 txt.innerHTML = String(str); 10 return txt.value; 11 } 12 13 14 const overlay = document.getElementById(cfg.overlayId || "headermenu_mobile"); 15 if (!overlay) return log("STOP: overlay non trovato"); 16 17 const menuBox = document.getElementById(cfg.menuContainerId || "mobile_menu_"); 18 const courseBox = document.getElementById(cfg.courseContainerId || "mobile_course_"); 19 if (!menuBox || !courseBox) return log("STOP: menuBox/courseBox non trovati"); 20 21 const chooseBtn = overlay.querySelector(cfg.chooseCoursesSelector || "#choose_course_btn a"); 22 if (!chooseBtn) return log("STOP: chooseBtn non trovato"); 23 24 const chooseBtnWrap = document.getElementById("choose_course_btn"); 25 const cont_btn_header = document.getElementById("cont_btn_header"); 26 27 const courseHeaderRow = courseBox.querySelector('[data-id="47b52158"]'); 28 if (!courseHeaderRow) return log("STOP: courseHeaderRow non trovato"); 29 30 if (courseBox.querySelector(".cjcourses__viewport")) return; 31 32 const courseTitleWrap = document.getElementById(cfg.courseTitleId || "mobile_course_title"); 33 const courseTitleP = courseTitleWrap ? courseTitleWrap.querySelector("p") : null; 34 35 const courseBackWrap = document.getElementById(cfg.courseBackId || "mobile_course_back"); 36 if (courseBackWrap) { 37 courseBackWrap.style.cursor = "pointer"; 38 courseBackWrap.setAttribute("role", "button"); 39 courseBackWrap.setAttribute("tabindex", "0"); 40 courseBackWrap.setAttribute("aria-label", "Indietro"); 41 } 42 43 const viewport = document.createElement("div"); 44 viewport.className = "cjcourses__viewport"; 45 46 const track = document.createElement("div"); 47 track.className = "cjcourses__track"; 48 49 viewport.appendChild(track); 50 courseHeaderRow.insertAdjacentElement("afterend", viewport); 51 52 let depth = 0; 53 let crumbs = []; 54 const rootTitle = cfg.coursesRootTitle || "Scegli il tuo corso"; 55 56 function setCourseTitle() { 57 const txt = crumbs.length ? crumbs.join(" > ") : rootTitle; 58 if (courseTitleP) courseTitleP.textContent = txt; 59 else if (courseTitleWrap) courseTitleWrap.textContent = txt; 60 } 61 62 function translate() { 63 track.style.transform = `translateX(-${depth * 100}%)`; 64 } 65 66 async function api(url) { 67 const res = await fetch(url, { credentials: "same-origin" }); 68 if (!res.ok) { 69 const err = new Error(`API error ${res.status}`); 70 err.status = res.status; 71 throw err; 72 } 73 return res.json(); 74 } 75 76 // --- Anti-loop helpers --- 77 const idsOf = (arr) => 78 (Array.isArray(arr) ? arr : []) 79 .map(x => String(x?.id ?? "")) 80 .filter(Boolean) 81 .sort() 82 .join(","); 83 84 function makePanel(items, type, panelMeta = {}) { 85 const panel = document.createElement("div"); 86 panel.className = "cjcourses__panel"; 87 panel.dataset.type = type; 88 89 // per anti-loop: salviamo gli ID di questo pannello 90 if (type === "category") { 91 panel.dataset.ids = idsOf(items); 92 // opzionale: salva anche slug parent (utile debug) 93 if (panelMeta.parentSlug) panel.dataset.parentSlug = panelMeta.parentSlug; 94 } 95 96 const ul = document.createElement("ul"); 97 ul.className = "cjcourses__list"; 98 99 items.forEach((it) => { 100 const li = document.createElement("li"); 101 li.className = "cjcourses__item"; 102 103 if (type === "category") { 104 const btn = document.createElement("button"); 105 btn.type = "button"; 106 btn.className = "cjcourses__btn"; 107 108 const count = (typeof it.count === "number") ? it.count : null; 109 110 btn.innerHTML = ` 111 <div> 112 <span class="cjcourses__label">${decodeHtmlEntities(it.name ?? "")}</span> 113 ${count !== null ? `<sup class="cjcourses__count">(${count})</sup>` : ``} 114 </div> 115 <span class="cjcourses__chev"> 116 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512.01 512.01"> 117 <path d="M498.8,244.43H11.64C5.21,244.43,0,249.64,0,256.06s5.21,11.64,11.64,11.64h487.17c6.43,0,11.64-5.21,11.64-11.64s-5.21-11.64-11.64-11.64Z"></path> 118 <path d="M243.58,12.48v487.17c0,6.43,5.21,11.64,11.64,11.64s11.64-5.21,11.64-11.64V12.48c0-6.43-5.21-11.64-11.64-11.64s-11.64,5.21-11.64,11.64Z"></path> 119 </svg> 120 </span> 121 `; 122 123 btn.addEventListener("click", async () => { 124 const currentPanel = track.querySelectorAll(".cjcourses__panel")[depth]; 125 const currentIds = currentPanel?.dataset?.ids || ""; 126 127 // 1) prova sottocategorie 128 let subcats = null; 129 try { 130 // endpoint consigliato 131 subcats = await api(`/wp-json/cj/v1/course-categories?parent_slug=${encodeURIComponent(it.slug || "")}`); 132 } catch (e) { 133 subcats = null; 134 } 135 136 const subIds = idsOf(subcats); 137 138 // â Anti-loop: 139 // se la lista sottocategorie è vuota, oppure uguale al pannello corrente, NON è un vero livello. 140 const isRealSublevel = 141 Array.isArray(subcats) && 142 subcats.length > 0 && 143 subIds && 144 subIds !== currentIds && 145 !subIds.split(",").includes(String(it.id)); // evita caso in cui torna sé stessa 146 147 if (isRealSublevel) { 148 crumbs.push(decodeHtmlEntities(it.name ?? "")); 149 setCourseTitle(); 150 151 track.appendChild(makePanel(subcats, "category", { parentSlug: it.slug || "" })); 152 depth += 1; 153 translate(); 154 return; 155 } 156 157 // 2) fallback: carica corsi 158 try { 159 const courses = await api(`/wp-json/cj/v1/courses?cat=${encodeURIComponent(it.slug || "")}&include_children=0`); 160 161 crumbs.push(decodeHtmlEntities(it.name ?? "")); 162 setCourseTitle(); 163 164 track.appendChild(makePanel(courses, "course")); 165 depth += 1; 166 translate(); 167 } catch (e) { 168 log("Errore caricamento corsi:", e); 169 } 170 }); 171 172 li.appendChild(btn); 173 } else { 174 const a = document.createElement("a"); 175 a.className = "cjcourses__link"; 176 a.href = it.url; 177 a.textContent = decodeHtmlEntities(it.title); 178 li.appendChild(a); 179 } 180 181 ul.appendChild(li); 182 }); 183 184 panel.appendChild(ul); 185 return panel; 186 } 187 188 async function openCourses() { 189 if (chooseBtnWrap) chooseBtnWrap.classList.add("btn_disabled"); 190 if (cont_btn_header) cont_btn_header.classList.add("all_disabled"); 191 192 menuBox.style.display = "none"; 193 courseBox.style.display = "block"; 194 195 track.innerHTML = ""; 196 depth = 0; 197 crumbs = []; 198 setCourseTitle(); 199 translate(); 200 201 // root categories 202 const cats = await api("/wp-json/cj/v1/course-categories?parent=0"); 203 track.appendChild(makePanel(cats, "category", { parentSlug: "" })); 204 } 205 206 function backCourses() { 207 if (depth > 0) { 208 const panels = track.querySelectorAll(".cjcourses__panel"); 209 if (panels.length) panels[panels.length - 1].remove(); 210 depth -= 1; 211 crumbs.pop(); 212 setCourseTitle(); 213 translate(); 214 return; 215 } 216 217 courseBox.style.display = "none"; 218 menuBox.style.display = "block"; 219 if (chooseBtnWrap) chooseBtnWrap.classList.remove("btn_disabled"); 220 if (cont_btn_header) cont_btn_header.classList.remove("all_disabled"); 221 } 222 223 courseBox.style.display = "none"; 224 menuBox.style.display = "block"; 225 226 chooseBtn.addEventListener("click", (e) => { 227 e.preventDefault(); 228 e.stopPropagation(); 229 openCourses().catch(console.error); 230 }); 231 232 if (courseBackWrap) { 233 courseBackWrap.addEventListener("click", (e) => { 234 e.preventDefault(); 235 e.stopPropagation(); 236 backCourses(); 237 }); 238 courseBackWrap.addEventListener("keydown", (e) => { 239 if (e.key === "Enter" || e.key === " ") { 240 e.preventDefault(); 241 backCourses(); 242 } 243 }); 244 } 245})();
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.