';
ZS.renderShell(null, mainHtml, null);
var composeVis = document.getElementById("composeVis");
if (composeVis && ZS.user) composeVis.value = ZS.user.privacyLevel || "connections_only";
var data = await ZS.api("GET", "/api/social/posts");
var el = document.getElementById("feedPosts");
if (!el) return;
if (data.error) {
el.innerHTML = '
' + (ZS.user
? (L ? "Sois le premier à publier quelque chose." : "Be the first to post something.")
: (L ? "Connectez-vous pour voir les publications." : "Log in to see posts.")) + '
' +
(ZS.user ? ''
: '') +
'
';
return;
}
var html = "";
for (var i = 0; i < data.posts.length; i++) {
html += _postCard(data.posts[i]);
}
el.innerHTML = html;
}
async function submitPost() {
var textarea = document.getElementById("composeText");
if (!textarea || !textarea.value.trim()) return;
var vis = document.getElementById("composeVis").value;
var btn = document.querySelector(".compose-actions .btn-primary");
if (btn) { btn.disabled = true; btn.textContent = ZS.t("Envoi…", "Posting…"); }
var data = await ZS.api("POST", "/api/social/posts", { content: textarea.value.trim(), visibility: vis });
if (data.error) { alert(data.error); if (btn) { btn.disabled = false; btn.textContent = ZS.t("Publier", "Post"); } return; }
renderFeed();
}
// ═══════════════════════════════════════
// POST ACTIONS
// ═══════════════════════════════════════
async function toggleLike(postId, btn) {
if (!ZS.user) { ZS.navigate("login"); return; }
var data = await ZS.api("POST", "/api/social/posts/" + postId + "/like");
if (data.error) return;
btn.classList.toggle("liked", data.liked);
btn.innerHTML = ZS.icon("heart") + '' + data.count + '';
}
async function toggleDislike(postId) {
if (!ZS.user) { ZS.navigate("login"); return; }
var data = await ZS.api("POST", "/api/social/posts/" + postId + "/dislike");
if (data.error) return;
if (data.disliked) {
var card = document.getElementById("post-" + postId);
if (card) card.style.opacity = "0.3";
}
}
async function followUser(userId) {
if (!ZS.user) { ZS.navigate("login"); return; }
var data = await ZS.api("POST", "/api/social/follows/" + userId);
if (data.error) { alert(data.error); return; }
renderProfileView(ZS.viewData);
}
async function unfollowUser(userId) {
if (!ZS.user) { ZS.navigate("login"); return; }
var data = await ZS.api("DELETE", "/api/social/follows/" + userId);
if (data.error) { alert(data.error); return; }
renderProfileView(ZS.viewData);
}
async function saveUsername() {
var data = await ZS.api("POST", "/api/social/user/update-username", { username: document.getElementById("settingsUsername").value });
if (data.error) { alert(data.error); return; }
ZS.user.username = data.username;
alert(ZS.t("Username mis à jour", "Username updated"));
}
async function toggleComments(postId) {
var el = document.getElementById("comments-" + postId);
if (!el) return;
if (el.classList.contains("open")) { el.classList.remove("open"); return; }
el.classList.add("open");
var list = document.getElementById("commentsList-" + postId);
list.innerHTML = _spinner();
var data = await ZS.api("GET", "/api/social/posts/" + postId + "/comments");
if (!data.comments || data.comments.length === 0) {
list.innerHTML = '
';
}).join("");
}
function reportItem(type, id) {
var reason = prompt(ZS.t("Raison du signalement :", "Report reason:"));
if (!reason) return;
ZS.api("POST", "/api/social/report", { targetType: type, targetId: id, reason: reason }).then(function(d) {
alert(d.error || ZS.t("Signalement envoyé.", "Report sent."));
});
}
// ═══════════════════════════════════════
// AUTH
// ═══════════════════════════════════════
function renderLogin() {
var L = ZS.lang;
var html = '
' +
'
' + ZS.t("Connexion", "Log in") + '
' +
'' +
'
';
ZS.renderShell(null, html, null);
}
function renderSignup() {
var L = ZS.lang;
var html = '
' +
'
' + ZS.t("Inscription", "Sign up") + '
' +
'' +
'
';
ZS.renderShell(null, html, null);
}
// ═══════════════════════════════════════
// PRIVACY CHOICE
// ═══════════════════════════════════════
var _selectedPrivacy = null;
function renderPrivacyChoice() {
var L = ZS.lang;
var html = '
' +
_privOption("public", "globe", ZS.t("Public", "Public"), ZS.t("Tout le monde peut voir vos publications.", "Everyone can see your posts."),
[ZS.t("Visible par tous", "Visible to all"), ZS.t("Peut apparaître dans les moteurs de recherche", "May appear in search engines")]) +
_privOption("connections_only", "users", ZS.t("Connexions", "Connections"), ZS.t("Seules vos connexions approuvées.", "Only approved connections."),
[ZS.t("Seuls vos contacts voient vos posts", "Only your contacts see your posts"), ZS.t("Demandes de connexion", "Connection requests")]) +
_privOption("private", "lock", ZS.t("Privé", "Private"), ZS.t("Personne sauf vous.", "Nobody but you."),
[ZS.t("Seul vous voyez vos posts", "Only you see your posts"), ZS.t("Modération légale possible (DSA)", "Legal moderation possible (DSA)")]) +
'
' +
'
' +
'' +
'' +
'' +
'' +
'
';
ZS.renderShell(null, html, null);
}
function _privOption(level, iconName, title, desc, consequences) {
var items = consequences.map(function(c) { return '
' + c + '
'; }).join("");
return '
' +
'
' + ZS.icon("check") + '
' +
'
' + ZS.icon(iconName) + '
' +
'
' + title + '
' +
'
' + desc + '
' +
(items ? '
' + items + '
' : '') + '
';
}
function selectPrivacy(level) {
_selectedPrivacy = level;
document.querySelectorAll(".privacy-card").forEach(function(el) { el.classList.remove("selected"); });
document.querySelector('.privacy-card[data-level="' + level + '"]').classList.add("selected");
var sums = {
public: ZS.t("Mode PUBLIC : tous les utilisateurs et les moteurs de recherche pourront voir vos publications.", "Mode PUBLIC: all users and search engines will see your posts."),
connections_only: ZS.t("Mode CONNECTIONS : seuls les contacts que vous acceptez pourront voir vos publications.", "Mode CONNECTIONS: only contacts you accept will see your posts."),
private: ZS.t("Mode PRIVÉ : personne d'autre que vous ne pourra voir vos publications.", "Mode PRIVATE: nobody but you will see your posts."),
};
var acks = {
public: ZS.t("Je comprends que mes publications seront visibles par tout le monde.", "I understand my posts will be visible to everyone."),
connections_only: ZS.t("Je comprends que seuls mes contacts approuvés verront mes publications.", "I understand only approved contacts will see my posts."),
private: ZS.t("Je comprends que mes publications ne seront visibles que par moi-même.", "I understand my posts will only be visible to me."),
};
document.getElementById("privSummary").innerHTML = sums[level];
document.getElementById("privAckText").textContent = acks[level];
document.getElementById("privConfirm").style.display = "block";
document.getElementById("privAck").checked = false;
document.getElementById("privBtn").disabled = true;
document.getElementById("privAck").onchange = function() {
document.getElementById("privBtn").disabled = !this.checked;
};
document.getElementById("privConfirm").scrollIntoView({ behavior: "smooth", block: "nearest" });
}
async function confirmPrivacy() {
if (!_selectedPrivacy) return;
var errEl = document.getElementById("privError");
errEl.style.display = "none";
var data = await ZS.api("POST", "/api/social/user/privacy-choice", { level: _selectedPrivacy, acknowledged: true });
if (data.error) { errEl.textContent = data.error; errEl.style.display = "block"; return; }
ZS.user.privacyLevel = data.privacyLevel;
ZS.user.privacyChoiceMade = true;
ZS.navigate("feed");
}
// ═══════════════════════════════════════
// PROFILE
// ═══════════════════════════════════════
async function renderProfile() {
if (!ZS.user) { ZS.navigate("login"); return; }
await _renderProfilePage(ZS.user.username);
}
async function renderProfileView(username) {
await _renderProfilePage(username);
}
async function _renderProfilePage(username) {
var L = ZS.lang;
ZS.renderShell(null, _spinner(), null);
var data = await ZS.api("GET", "/api/social/user/" + encodeURIComponent(username));
if (data.error) {
ZS.renderShell(null, '
' + ZS.icon("close") + '
' + ZS.esc(data.error) + '
', null);
return;
}
if (data.isPrivate) {
ZS.renderShell(null, '
' + ZS.icon("lock") + '
' + ZS.t("Ce profil est privé.", "This profile is private.") + '
', null);
return;
}
var user = data.user;
var badges = data.badges || [];
var posts = data.posts || [];
var isOwn = ZS.user && user.username === ZS.user.username;
var roleLabel = { user: ZS.t("Membre", "Member"), moderator: ZS.t("Modérateur", "Moderator"), admin: ZS.t("Administrateur", "Admin") };
var joinDate = new Date(user.created_at + "Z").toLocaleDateString(L ? "fr-FR" : "en-US", { day: "numeric", month: "long", year: "numeric" });
var badgesHtml = "";
if (badges.length > 0) {
badgesHtml = '
' + badges.map(function(b) {
return '
' + b.icon + " " + ZS.esc(b.name) + '
';
}).join("") + '
';
}
var actionsHtml = "";
if (isOwn) {
actionsHtml = '
' +
'' +
'' +
'
';
} else if (ZS.user) {
var followBtn = user.isFollowing
? ''
: '';
var verifyBtn = '';
if (ZS.user.verifiedLevel === 2 && !user.verified_level) {
verifyBtn = '';
}
actionsHtml = '
' + ZS.t("3-30 caractères, lettres, chiffres et underscores. Changement possible une fois par mois.", "3-30 characters, letters, numbers and underscores. Changeable once per month.") + '
' + ZS.t("Action irréversible", "Irreversible action") + '' +
ZS.t("Toutes vos données seront supprimées.", "All your data will be deleted.") + '
' +
'
' +
'
' +
'
';
}
}
async function saveBio() {
var data = await ZS.api("POST", "/api/social/user/update-bio", { bio: document.getElementById("settingsBio").value });
alert(data.error || data.message);
}
async function saveDisplayName() {
var data = await ZS.api("POST", "/api/social/user/update-display-name", { displayName: document.getElementById("settingsDisplayName").value });
if (data.error) { alert(data.error); return; }
ZS.user.displayName = data.displayName;
alert(ZS.t("Nom d'affichage mis à jour", "Display name updated"));
}
async function savePassword() {
var data = await ZS.api("POST", "/api/social/user/update-password", { currentPassword: document.getElementById("settingsCurrentPass").value, newPassword: document.getElementById("settingsNewPass").value });
alert(data.error || data.message);
}
async function savePrivacy() {
if (!_selectedPrivacy) return;
var data = await ZS.api("POST", "/api/social/user/privacy-choice", { level: _selectedPrivacy, acknowledged: true });
if (data.error) { alert(data.error); return; }
ZS.user.privacyLevel = data.privacyLevel;
alert(ZS.t("Confidentialité mise à jour", "Privacy updated"));
}
async function deleteAccount() {
if (!confirm(ZS.t("Êtes-vous sûr ?", "Are you sure?"))) return;
var data = await ZS.api("POST", "/api/social/user/delete-account", { password: document.getElementById("settingsDeletePass").value });
if (data.error) { alert(data.error); return; }
ZS.user = null;
ZS.navigate("feed");
}
// ═══════════════════════════════════════
// COMMUNITY
// ═══════════════════════════════════════
async function renderCommunity() {
var L = ZS.lang;
var html = '
';
}).join("");
}
async function loadConnections() {
var el = document.getElementById("connectionsSection");
if (!el) return;
var data = await ZS.api("GET", "/api/social/connections");
var html = "";
if (data.incoming && data.incoming.length > 0) {
html += '
' + ZS.t("Demandes reçues", "Incoming") + '
';
html += data.incoming.map(function(c) {
return '
' + _avatar(c.username, 40, c.avatar_url) +
'
' + ZS.esc(c.username) + '
' +
'
' +
'' +
'' +
'
';
}).join("");
}
if (data.accepted && data.accepted.length > 0) {
html += '
', null);
var data = await ZS.api("GET", "/api/social/messages");
var el = document.getElementById("convList");
if (!data.conversations || data.conversations.length === 0) {
el.innerHTML = '
' + ZS.icon("mail") + '
' +
'
' + ZS.t("Aucune conversation", "No conversations") + '
' + ZS.esc(r.username) + ' ' +
ZS.t("souhaite vous attribuer un badge Vérifié", "wants to give you a Verified badge") + '
' +
'
' +
'' +
'' +
'
';
}).join("") + '
';
}
var data = await ZS.api("GET", "/api/social/notifications");
var el = document.getElementById("notifList");
if (!data.notifications || data.notifications.length === 0) {
el.innerHTML = '