';
}
html += '';
return html;
},
_buildSidebarRight() {
var L = this.lang;
return '
' + (L ? "À propos" : "About") + '
' +
'
' +
(L ? "Réseau social respectueux de la vie privée. Pas d'algorithme, pas de pub, pas de tracking."
: "Privacy-respecting social network. No algorithm, no ads, no tracking.") + '
';
},
_renderMain(html) {
var el = document.getElementById("social-main");
if (el) el.innerHTML = html;
},
toggleMobileNav() {
var nav = document.getElementById("socialHeaderNav");
if (!nav) return;
this._navOpen = !this._navOpen;
nav.classList.toggle("open", this._navOpen);
},
doSearch() {
var q = document.getElementById("socialSearchInput");
if (!q || !q.value.trim()) return;
this.navigate("community");
setTimeout(function() {
var si = document.getElementById("searchInput");
if (si) { si.value = q.value; si.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); }
}, 100);
},
async logout() {
await this.api("POST", "/api/social/auth/logout");
this.user = null;
this._unreadNotifs = 0;
this._unreadMessages = 0;
this.navigate("feed");
},
async checkSession() {
var data = await this.api("GET", "/api/social/auth/me");
if (data.error) return false;
this.user = data.user;
this._unreadNotifs = data.unreadNotifs || 0;
this._unreadMessages = data.unreadMessages || 0;
return true;
},
async refreshCounts() {
if (!this.user) return;
var data = await this.api("GET", "/api/social/auth/me");
if (!data.error) {
this._unreadNotifs = data.unreadNotifs || 0;
this._unreadMessages = data.unreadMessages || 0;
this._updateNavBadges();
}
},
_updateNavBadges() {
var nav = document.getElementById("socialHeaderNav");
if (nav) nav.innerHTML = this._buildNavLinks();
var layout = document.getElementById("social-layout");
if (layout) {
var left = layout.querySelector(".social-sidebar-left");
if (left) left.innerHTML = this._buildSidebarLeft();
}
},
};
// ═══════════════════════════════════════
// EVENT DELEGATION (CSP-safe)
// ═══════════════════════════════════════
(function() {
// Click delegation
document.addEventListener("click", function(e) {
var el = e.target.closest("[data-action]");
if (!el) return;
e.preventDefault();
var action = el.getAttribute("data-action");
var arg1 = el.getAttribute("data-arg1");
var arg2 = el.getAttribute("data-arg2");
switch (action) {
case "nav": ZS.navigate(arg1, arg2); break;
case "navFeed": ZS.navigate("feed"); break;
case "navProfile": ZS.navigate("profile"); break;
case "toggleMobileNav": ZS.toggleMobileNav(); break;
case "doSearch": ZS.doSearch(); break;
case "logout": ZS.logout(); break;
// Posts
case "toggleLike": toggleLike(parseInt(arg1), el); break;
case "toggleComments": toggleComments(parseInt(arg1)); break;
case "toggleNotes": toggleNotes(parseInt(arg1)); break;
case "reportItem": reportItem(arg1, parseInt(arg2)); break;
case "postComment": postComment(parseInt(arg1)); break;
case "deleteComment": deleteComment(parseInt(arg1), parseInt(arg2)); break;
case "submitPost": submitPost(); break;
case "submitFullPost": submitFullPost(); break;
// Emoji
case "toggleEmoji": _togglePicker("emojiPicker"); break;
case "toggleEmojiFull": _togglePicker("fullEmojiPicker"); break;
case "insertEmoji": _insertEmoji("composeText", arg1); break;
case "insertEmojiFull": _insertEmoji("fullComposeText", arg1); break;
// Auth
case "selectPrivacy": selectPrivacy(arg1); break;
case "confirmPrivacy": confirmPrivacy(); break;
// Profile
case "navigatePost": ZS.navigate("post"); break;
case "navigateSettings": ZS.navigate("settings"); break;
case "sendConnection": sendConnection(arg1); break;
case "removeConnection": removeConnection(arg1); break;
case "chatWith": ZS.navigate("chat", arg1); break;
// Follow
case "follow": followUser(parseInt(arg1)); break;
case "unfollow": unfollowUser(parseInt(arg1)); break;
// Dislike
case "toggleDislike": toggleDislike(parseInt(arg1)); break;
// Community
case "searchUsers": searchUsers(); break;
case "acceptConnection": acceptConnection(arg1); break;
case "rejectConnection": rejectConnection(arg1); break;
// Messages
case "chatWithUser": ZS.navigate("chat", arg1); break;
case "sendMsg": sendMsg(arg1); break;
// Notifications
case "markAllRead": markAllRead(); break;
// Settings
case "saveBio": saveBio(); break;
case "saveDisplayName": saveDisplayName(); break;
case "saveUsername": saveUsername(); break;
case "savePassword": savePassword(); break;
case "savePrivacy": savePrivacy(); break;
case "deleteAccount": deleteAccount(); break;
case "showSettingsTab": showSettingsTab(arg1, el); break;
case "deleteAvatar": deleteAvatar(); break;
// Verification
case "proposeVerify": proposeVerify(arg1); break;
case "acceptVerify": acceptVerify(parseInt(arg1)); break;
case "rejectVerify": rejectVerify(parseInt(arg1)); break;
// Mod
case "modAction": modAction(arg1, parseInt(arg2), el.getAttribute("data-action-type")); break;
case "modNoteAction": modNoteAction(parseInt(arg2), el.getAttribute("data-action-type")); break;
case "resolveReport": resolveReport(parseInt(arg1)); break;
case "switchModTab": switchModTab(arg1, el); break;
// Mobile nav: close on link click
default:
if (el.closest("#socialHeaderNav a")) {
var nav = document.getElementById("socialHeaderNav");
if (nav) nav.classList.remove("open");
ZS._navOpen = false;
}
}
});
function _togglePicker(id) {
var picker = document.getElementById(id);
if (picker) picker.classList.toggle("hidden");
}
function _insertEmoji(textareaId, emoji) {
var ta = document.getElementById(textareaId);
if (!ta) return;
var start = ta.selectionStart;
var end = ta.selectionEnd;
ta.value = ta.value.substring(0, start) + emoji + ta.value.substring(end);
ta.selectionStart = ta.selectionEnd = start + emoji.length;
ta.focus();
var pickerId = textareaId === "fullComposeText" ? "fullEmojiPicker" : "emojiPicker";
var picker = document.getElementById(pickerId);
if (picker) picker.classList.add("hidden");
}
// Keyboard delegation (Enter key for inputs)
document.addEventListener("keydown", function(e) {
if (e.key !== "Enter") return;
var el = e.target.closest("[data-key-action]");
if (!el) return;
var action = el.getAttribute("data-key-action");
var arg1 = el.getAttribute("data-arg1");
switch (action) {
case "doSearch": ZS.doSearch(); break;
case "searchUsers": searchUsers(); break;
case "postComment": postComment(parseInt(arg1)); break;
case "sendMsg": sendMsg(arg1); break;
}
});
// Form submit delegation
document.addEventListener("submit", function(e) {
e.preventDefault();
var el = e.target.closest("[data-form]");
if (!el) return;
var form = el.getAttribute("data-form");
switch (form) {
case "loginForm": _handleLogin(el); break;
case "signupForm": _handleSignup(el); break;
}
});
// Input delegation (for character counters, strength bars)
document.addEventListener("input", function(e) {
var el = e.target;
// Character counter
if (el.id === "composeText") {
var c = document.getElementById("composeCounter");
if (c) c.textContent = el.value.length + "/5000";
}
if (el.id === "fullComposeText") {
var c2 = document.getElementById("fullComposeCounter");
if (c2) c2.textContent = el.value.length + "/5000";
}
// Password strength
if (el.id === "suPass") {
var v = el.value, s = 0;
if (v.length >= 12) s++; if (v.length >= 16) s++;
if (/[A-Z]/.test(v)) s++; if (/[a-z]/.test(v)) s++;
if (/[0-9]/.test(v)) s++; if (/[^A-Za-z0-9]/.test(v)) s++;
var fill = document.getElementById("strFill");
var txt = document.getElementById("strText");
if (fill && txt) {
fill.style.width = Math.min((s / 6) * 100, 100) + "%";
if (s <= 2) { fill.style.background = "#d32f2f"; txt.textContent = ZS.t("Faible", "Weak"); }
else if (s <= 4) { fill.style.background = "#f57c00"; txt.textContent = ZS.t("Moyen", "Medium"); }
else { fill.style.background = "#2e7d32"; txt.textContent = ZS.t("Fort", "Strong"); }
}
}
});
// Avatar file input
document.addEventListener("change", function(e) {
var el = e.target;
if (el.id === "avatarInput") uploadAvatar(el);
});
})();
// Avatar upload
async function uploadAvatar(input) {
var file = input.files && input.files[0];
if (!file) return;
var status = document.getElementById("avatarStatus");
if (status) { status.classList.remove("hidden"); status.textContent = ZS.t("Envoi en cours...", "Uploading..."); status.className = "form-hint"; }
var fd = new FormData();
fd.append("avatar", file);
var data = await ZS.api("POST", "/api/social/user/avatar", fd);
if (data.error) {
if (status) { status.textContent = data.error; status.className = "form-hint text-error"; }
return;
}
ZS.user.avatarUrl = data.avatar_url;
var preview = document.getElementById("avatarPreview");
if (preview) preview.innerHTML = _avatar(ZS.user.username, 80, ZS.user.avatarUrl);
if (status) { status.textContent = ZS.t("Avatar mis à jour", "Avatar updated"); status.className = "form-hint text-success"; }
}
async function deleteAvatar() {
var data = await ZS.api("DELETE", "/api/social/user/avatar");
if (data.error) return;
ZS.user.avatarUrl = null;
var preview = document.getElementById("avatarPreview");
if (preview) preview.innerHTML = _avatar(ZS.user.username, 80, null);
showSettingsTab("bio", document.querySelector(".tab-btn"));
}
// Verification
async function proposeVerify(userId) {
if (!confirm(ZS.t("Envoyer une proposition de vérification ?", "Send a verification proposal?"))) return;
var data = await ZS.api("POST", "/api/social/verification/propose/" + userId);
if (data.error) { alert(data.error); return; }
alert(data.message);
}
async function acceptVerify(requestId) {
var data = await ZS.api("POST", "/api/social/verification/accept/" + requestId);
if (data.error) { alert(data.error); return; }
ZS.user.verifiedLevel = 1;
renderNotifications();
}
async function rejectVerify(requestId) {
var data = await ZS.api("POST", "/api/social/verification/reject/" + requestId);
if (data.error) { alert(data.error); return; }
renderNotifications();
}
// Login handler
async function _handleLogin(form) {
var errEl = document.getElementById("loginError");
if (errEl) errEl.style.display = "none";
var btn = form.querySelector('button[type="submit"]');
if (btn) btn.disabled = true;
var data = await ZS.api("POST", "/api/social/auth/login", {
login: form.querySelector('[name="login"]').value,
password: form.querySelector('[name="password"]').value,
});
if (data.error) {
if (errEl) { errEl.textContent = data.error; errEl.style.display = "block"; }
if (btn) btn.disabled = false;
return;
}
ZS.user = data.user;
if (data.needsPrivacyChoice) ZS.navigate("privacy-choice");
else ZS.navigate("feed");
}
// Signup handler
async function _handleSignup(form) {
var errEl = document.getElementById("suError");
if (errEl) errEl.style.display = "none";
var btn = form.querySelector('button[type="submit"]');
if (btn) btn.disabled = true;
var data = await ZS.api("POST", "/api/social/auth/signup", {
username: form.querySelector('[name="username"]').value,
email: form.querySelector('[name="email"]').value || undefined,
password: form.querySelector('[name="password"]').value,
birthdate: form.querySelector('[name="birthdate"]').value,
});
if (data.error) {
if (errEl) { errEl.textContent = data.error; errEl.style.display = "block"; }
if (btn) btn.disabled = false;
return;
}
ZS.user = data.user;
if (data.needsPrivacyChoice) ZS.navigate("privacy-choice");
else ZS.navigate("feed");
}