Python 33.5%
JavaScript 30.1%
TypeScript 25%
CSS 10%
Shell 1.4%
1/* Groupe KA — ka-id.js : SDK client KA ID v2 (personnalisation).2 * SOURCE CANONIQUE : ka-ui.git/kaid/ka-id.js — copié dans frontend/public/3 * (ou public/) de chaque app par sync-kaid.sh. Corriger ICI puis redistribuer.4 *5 * Rôle : signaux navigateur que le serveur ne voit pas — position des clics6 * dans les résultats, temps passé sur une fiche (dwell), visites de retour —7 * plus les aides d'interface : KAID.hide() (« Pas pour moi »),8 * KAID.saveSearch() (recherche sauvegardée), badge « Recommandé pour vous ».9 *10 * Intégration (index.html, AVANT le bundle) :11 * <script>12 * window.KAID_CONF = {13 * card: "a[href^='/emploi/']", // sélecteur des cartes résultat14 * detail: "^/emploi/(.+)$", // regex fiche → capture l'uid15 * entity: "job", // entity_type des événements16 * };17 * </script>18 * <script src="/ka-id.js" defer></script>19 *20 * Vie privée : ne fait RIEN si l'utilisateur n'est pas connecté via KA ID21 * (vérifié auprès de /api/kaid/status) ; le hub respecte ensuite ses22 * réglages Historique/Personnalisation (voir groupe-ka.com/mon-ka).23 */24(function () {25 "use strict";26 var CONF = window.KAID_CONF || {};27 var API = CONF.api || "/api/kaid";28 var connected = null; // null = inconnu, sinon bool29 var queue = [];30 var flushTimer = null;3132 /* ---------- transport (lots de 10, sendBeacon à la fermeture) ---------- */3334 function post(events, useBeacon) {35 if (!events.length) return;36 var body = JSON.stringify({ events: events });37 if (useBeacon && navigator.sendBeacon) {38 navigator.sendBeacon(API + "/events",39 new Blob([body], { type: "application/json" }));40 return;41 }42 fetch(API + "/events", {43 method: "POST",44 headers: { "Content-Type": "application/json" },45 body: body,46 keepalive: true,47 credentials: "same-origin",48 }).catch(function () {});49 }5051 function flush(useBeacon) {52 if (!queue.length) return;53 var batch = queue.splice(0, queue.length);54 post(batch, useBeacon);55 }5657 function track(type, data) {58 if (connected === false) return;59 var e = { type: type };60 if (data) for (var k in data) if (data[k] != null) e[k] = data[k];61 queue.push(e);62 if (queue.length >= 10) flush(false);63 else if (!flushTimer)64 flushTimer = setTimeout(function () { flushTimer = null; flush(false); }, 4000);65 }6667 /* ---------- connexion ---------- */6869 function init(cb) {70 try {71 var cached = sessionStorage.getItem("kaid_status");72 if (cached) {73 var c = JSON.parse(cached);74 if (Date.now() - c.t < 600000) { connected = c.on; cb(); return; }75 }76 } catch (err) { /* stockage indisponible */ }77 fetch(API + "/status", { credentials: "same-origin" })78 .then(function (r) { return r.json(); })79 .then(function (d) {80 connected = !!d.connected;81 try {82 sessionStorage.setItem("kaid_status",83 JSON.stringify({ on: connected, t: Date.now() }));84 } catch (err) { /* stockage indisponible */ }85 cb();86 })87 .catch(function () { connected = false; });88 }8990 /* ---------- clics sur les cartes résultat ---------- */9192 function uidFromHref(el) {93 if (!CONF.detail) return null;94 var href = el.getAttribute("href") || "";95 var path = href.replace(/^https?:\/\/[^/]+/, "").split("?")[0];96 var m = path.match(new RegExp(CONF.detail));97 return m ? decodeURIComponent(m[1]) : null;98 }99100 function watchClicks() {101 if (!CONF.card) return;102 document.addEventListener("click", function (ev) {103 var el = ev.target && ev.target.closest && ev.target.closest(CONF.card);104 if (!el) return;105 var uid = (CONF.uid ? CONF.uid(el) : null) || uidFromHref(el);106 if (!uid) return;107 var cards = document.querySelectorAll(CONF.card);108 var pos = Array.prototype.indexOf.call(cards, el);109 track("click", {110 entity_type: CONF.entity, entity_id: uid,111 position: pos >= 0 ? pos : null,112 });113 }, true);114 }115116 /* ---------- temps passé sur une fiche (dwell) ---------- */117118 var dwell = { uid: null, t0: 0 };119120 function detailUid() {121 if (!CONF.detail) return null;122 var m = location.pathname.match(new RegExp(CONF.detail));123 return m ? decodeURIComponent(m[1]) : null;124 }125126 function endDwell(useBeacon) {127 if (!dwell.uid) return;128 var ms = Date.now() - dwell.t0;129 if (ms >= 8000)130 track("detail_dwell",131 { entity_type: CONF.entity, entity_id: dwell.uid, dwell_ms: ms });132 dwell.uid = null;133 if (useBeacon) flush(true);134 }135136 function checkRoute() {137 var uid = detailUid();138 if (uid !== dwell.uid) {139 endDwell(false);140 if (uid) dwell = { uid: uid, t0: Date.now() };141 }142 updateUi();143 }144145 function watchRoutes() {146 ["pushState", "replaceState"].forEach(function (fn) {147 var orig = history[fn];148 history[fn] = function () {149 var out = orig.apply(this, arguments);150 setTimeout(checkRoute, 50);151 return out;152 };153 });154 window.addEventListener("popstate", function () { setTimeout(checkRoute, 50); });155 document.addEventListener("visibilitychange", function () {156 if (document.visibilityState === "hidden") { endDwell(true); }157 else checkRoute();158 });159 window.addEventListener("pagehide", function () { endDwell(true); });160 checkRoute();161 }162163 /* ---------- interface : sauvegarder la recherche / pas pour moi ---------- */164165 var INK = "#141814";166 var PAPER = "#f5f3ee";167168 function injectCss() {169 if (document.getElementById("kaid-ui-css")) return;170 var st = document.createElement("style");171 st.id = "kaid-ui-css";172 st.textContent =173 ".kaid-pill{position:fixed;left:14px;bottom:14px;z-index:560;display:inline-flex;" +174 "align-items:center;gap:8px;padding:9px 14px;border-radius:999px;border:1.5px solid " + INK + ";" +175 "background:" + PAPER + ";color:" + INK + ";font:600 12.5px/1 system-ui,sans-serif;" +176 "box-shadow:0 2px 10px rgba(20,24,20,.18);cursor:pointer;max-width:78vw}" +177 ".kaid-pill b{font-weight:700}" +178 ".kaid-pill .kaid-x{margin-left:2px;opacity:.55;font-weight:700;cursor:pointer}" +179 ".kaid-pill a{color:inherit;text-decoration:underline}" +180 "@media (max-width:768px){.kaid-pill{bottom:calc(84px + env(safe-area-inset-bottom))}}";181 document.head.appendChild(st);182 }183184 function removePill() {185 var el = document.getElementById("kaid-pill");186 if (el) el.remove();187 }188189 function pillDismissed() {190 try {191 return Date.now() - Number(localStorage.getItem("kaid_pill_off") || 0) < 6048e5;192 } catch (err) { return false; }193 }194195 function showSavePill() {196 if (pillDismissed() || document.getElementById("kaid-pill")) return;197 injectCss();198 var el = document.createElement("div");199 el.id = "kaid-pill";200 el.className = "kaid-pill";201 el.innerHTML = "<span>☆ <b>Sauvegarder cette recherche</b></span>" +202 "<span class='kaid-x' role='button' aria-label='Ne plus proposer'>✕</span>";203 el.querySelector(".kaid-x").addEventListener("click", function (ev) {204 ev.stopPropagation();205 try { localStorage.setItem("kaid_pill_off", String(Date.now())); } catch (err) { /* privé */ }206 removePill();207 });208 el.addEventListener("click", function () {209 el.innerHTML = "<span>…</span>";210 var filters = {};211 new URLSearchParams(location.search).forEach(function (v, k) {212 if (v) filters[k] = v;213 });214 window.KAID.saveSearch({ alert: true, filters: filters,215 query: filters.q || null })216 .then(function (r) {217 el.innerHTML = r && r.ok !== false && !r.error218 ? "<span>✓ Sauvegardée · alerte activée — <a href='https://www.groupe-ka.com/mon-ka'>Mon KA</a></span>"219 : "<span>Connexion KA ID requise</span>";220 setTimeout(removePill, 6000);221 })222 .catch(function () { removePill(); });223 });224 document.body.appendChild(el);225 }226227 function showHideLink(uid) {228 if (document.getElementById("kaid-pill")) return;229 injectCss();230 var el = document.createElement("div");231 el.id = "kaid-pill";232 el.className = "kaid-pill";233 el.innerHTML = "<span>✕ Pas pour moi</span>" +234 "<span class='kaid-x' role='button' aria-label='Fermer'>✕</span>";235 el.querySelector(".kaid-x").addEventListener("click", function (ev) {236 ev.stopPropagation();237 try { localStorage.setItem("kaid_pill_off", String(Date.now())); } catch (err) { /* privé */ }238 removePill();239 });240 el.addEventListener("click", function () {241 el.innerHTML = "<span>…</span>";242 window.KAID.hide(uid).then(function () {243 el.innerHTML = "<span>Masquée — elle ne réapparaîtra plus dans vos résultats.</span>";244 setTimeout(removePill, 5000);245 }).catch(function () { removePill(); });246 });247 document.body.appendChild(el);248 }249250 function updateUi() {251 if (!connected || CONF.ui === false) return;252 removePill();253 if (pillDismissed()) return;254 var uid = detailUid();255 if (uid) showHideLink(uid);256 else if (location.search.length > 1 && CONF.savePill !== false)257 showSavePill();258 }259260 /* ---------- visite de retour ---------- */261262 function returnVisit() {263 try {264 var last = Number(localStorage.getItem("kaid_last") || 0);265 var now = Date.now();266 if (last && now - last > 72e6) track("return_visit", {}); // > 20 h267 localStorage.setItem("kaid_last", String(now));268 } catch (err) { /* stockage indisponible */ }269 }270271 /* ---------- API publique ---------- */272273 window.KAID = {274 track: track,275 connected: function () { return connected; },276 /** « Pas pour moi » — masque l'annonce partout et nourrit le signal négatif. */277 hide: function (itemId, features) {278 return fetch(API + "/hide", {279 method: "POST",280 headers: { "Content-Type": "application/json" },281 credentials: "same-origin",282 body: JSON.stringify({ item_id: itemId, on: true, features: features || null }),283 }).then(function (r) { return r.json(); });284 },285 /** Sauvegarde la recherche courante (par défaut : URL + titre du document). */286 saveSearch: function (opts) {287 opts = opts || {};288 return fetch(API + "/saved-searches", {289 method: "POST",290 headers: { "Content-Type": "application/json" },291 credentials: "same-origin",292 body: JSON.stringify({293 action: "add",294 label: opts.label || document.title.split("|")[0].split("—")[0].trim(),295 query: opts.query || null,296 filters: opts.filters || null,297 url: opts.url || (location.origin + location.pathname + location.search),298 alert: !!opts.alert,299 frequency: opts.frequency || null,300 }),301 }).then(function (r) { return r.json(); });302 },303 external: function (itemId, features) {304 track("external_click",305 { entity_type: CONF.entity, entity_id: itemId, features: features });306 flush(true);307 },308 };309310 /* ---------- démarrage ---------- */311312 function start() {313 if (!connected) return;314 watchClicks();315 watchRoutes();316 returnVisit();317 }318319 if (document.readyState === "loading")320 document.addEventListener("DOMContentLoaded", function () { init(start); });321 else init(start);322})();323