"use strict"; (function () { const msgEl = document.getElementById("app-msg") || document.getElementById("login-msg"); const isLoginPage = !!document.getElementById("login-form"); let currentAccount = null; function showMessage(text, isError) { if (!msgEl) return; appendMsg(msgEl, text, isError); } function clearMessage() { showMessage("", false); } function byId(id) { return document.getElementById(id); } function setOptions(sel, items, valueOf, labelOf, keepSelection) { if (!sel) return; const current = keepSelection ? sel.value : ""; sel.innerHTML = ""; items.forEach(function (item) { const o = document.createElement("option"); o.value = valueOf(item); o.textContent = labelOf(item); sel.appendChild(o); }); if (current) { for (const o of sel.options) { if (o.value === current) o.selected = true; } } } /* ----------------------------- login page ----------------------------- */ function initLoginPage() { const form = byId("login-form"); if (!form) return; form.addEventListener("submit", function (e) { e.preventDefault(); clearMessage(); const email = form.email.value.trim(); const password = form.password.value; if (!email || !password) { showMessage("email and password required", true); return; } fetch("/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: email, password: password }), }) .then(function (r) { return r.json().then(function (data) { if (!r.ok) throw new Error(data.error || "login failed"); return data; }); }) .then(function () { window.location.href = "/"; }) .catch(function (err) { showMessage(err.message || "login failed", true); }); }); } /* ------------------------------ main app ------------------------------ */ function initMainApp() { req("/account/me") .then(function (account) { currentAccount = account; startMainApp(account); }) .catch(function () { window.location.href = "/login"; }); } function startMainApp(account) { const emailEl = byId("user-email"); if (emailEl) emailEl.textContent = account.email; const roleEl = byId("user-role"); if (roleEl) roleEl.textContent = account.is_admin ? "admin" : "user"; renderMyPerson(account); bindNoteForm(); bindGroupForm(); bindInviteForm(); if (account.is_admin) { bindAdminPersonForm(); bindAttachForm(); bindRightForm(); loadArticleTypes(); populateRightsDropdown(); refreshRights(); } refreshPersons(); refreshArticles(); refreshGroups(); } function renderMyPerson(account) { const box = byId("myperson"); const form = byId("note-form"); const hint = byId("note-hint"); if (!account.person_id) { if (box) box.innerHTML = "Your account is not linked to a person yet. Ask an admin to link it."; if (form) { Array.prototype.forEach.call(form.querySelectorAll("input, textarea, button"), function (el) { el.disabled = true; }); } if (hint) hint.textContent = "You can add notes, groups and invites once an admin links you to a person."; return; } req("/persons/" + encodeURIComponent(account.person_id)) .then(function (p) { if (!box) return; box.innerHTML = "" + escapeHtml(p.name || "(unnamed)") + "" + '' + (p.emails && p.emails.length ? "emails: " + escapeHtml(p.emails.join(", ")) : "no email shown") + ""; }) .catch(function () { if (box) box.textContent = "Person not found."; }); } function loadArticleTypes() { req("/api/types") .then(function (data) { setOptions( byId("note-type"), data.article_types || [], function (t) { return t; }, function (t) { return (data.article_type_labels && data.article_type_labels[t]) || t; } ); }) .catch(function () {}); } function populateRightsDropdown() { req("/api/types") .then(function (data) { const rights = data.rights || {}; setOptions( byId("right-right"), Object.keys(rights), function (k) { return k; }, function (k) { return k + " - " + rights[k]; } ); }) .catch(function () {}); } function bindNoteForm() { const form = byId("note-form"); if (!form) return; form.addEventListener("submit", function (e) { e.preventDefault(); clearMessage(); const isAdmin = currentAccount && currentAccount.is_admin; const personSel = byId("note-person"); const personId = isAdmin && personSel ? personSel.value : currentAccount && currentAccount.person_id; if (!personId) { showMessage("no person linked to your account", true); return; } const typeSel = byId("note-type"); const visSel = byId("note-visibility"); req("/articles", { method: "POST", body: { person_id: personId, article_type: isAdmin && typeSel ? typeSel.value : "notes", title: form.title.value, content: form.content.value, visibility: isAdmin && visSel ? visSel.value : "private", group_ids: [], }, }) .then(function () { form.reset(); refreshArticles(); }) .catch(function (err) { showMessage(err.message || "failed to save", true); }); }); } function refreshArticles() { req("/articles") .then(function (data) { const list = byId("articles-list"); if (!list) return; list.innerHTML = ""; if (!data.length) { list.innerHTML = '
  • Nothing here yet.
  • '; return; } data.forEach(function (a) { const li = document.createElement("li"); li.innerHTML = "" + escapeHtml(a.title || "(untitled)") + " " + '' + escapeHtml(a.article_type) + " ยท " + escapeHtml(a.visibility) + "" + (a.content ? "
    " +
                    escapeHtml(a.content) + "
    " : "") + (a.visibility === "group" && a.allowed_groups && a.allowed_groups.length ? 'groups: ' + escapeHtml(a.allowed_groups.join(", ")) + "" : ""); list.appendChild(li); }); }) .catch(function (err) { showMessage(err.message || "failed to load articles", true); }); } function refreshPersons() { req("/persons") .then(function (data) { const label = function (p) { return p.name || "(unnamed)"; }; setOptions(byId("invite-person"), data, function (p) { return p.id; }, label, true); const noteSel = byId("note-person"); setOptions(noteSel, data, function (p) { return p.id; }, label, true); if (noteSel && !noteSel.value && currentAccount && currentAccount.person_id) { noteSel.value = currentAccount.person_id; } setOptions(byId("attach-person"), data, function (p) { return p.id; }, label, true); const list = byId("admin-persons-list"); if (!list) return; list.innerHTML = ""; if (!data.length) { list.innerHTML = '
  • No persons yet.
  • '; return; } data.forEach(function (p) { const li = document.createElement("li"); li.innerHTML = "" + escapeHtml(p.name || "(unnamed)") + "" + '' + (p.emails && p.emails.length ? "emails: " + escapeHtml(p.emails.join(", ")) : "no emails") + ""; list.appendChild(li); }); }) .catch(function (err) { showMessage(err.message || "failed to load persons", true); }); } function bindAdminPersonForm() { const form = byId("admin-person-form"); if (!form) return; form.addEventListener("submit", function (e) { e.preventDefault(); clearMessage(); req("/admin/persons", { method: "POST", body: { name: form.name.value, email: form.email.value, password: form.password.value, }, }) .then(function (person) { form.reset(); showMessage("created person: " + (person.name || person.id), false); refreshPersons(); }) .catch(function (err) { showMessage(err.message || "failed to create person", true); }); }); } function bindAttachForm() { const form = byId("attach-form"); if (!form) return; const send = function (action, okText) { const personId = byId("attach-person").value; const email = byId("attach-email").value.trim(); if (!personId || !email) { showMessage("select a person and enter an email", true); return; } req("/persons/" + encodeURIComponent(personId) + "/emails/" + action, { method: "POST", body: { email: email }, }) .then(function () { showMessage(okText + " " + email, false); refreshPersons(); }) .catch(function (err) { showMessage(err.message || "failed to change email", true); }); }; form.addEventListener("submit", function (e) { e.preventDefault(); clearMessage(); send("attach", "attached"); }); const detach = byId("detach-email"); if (detach) { detach.addEventListener("click", function () { clearMessage(); send("detach", "detached"); }); } } function bindGroupForm() { const form = byId("group-form"); if (!form) return; form.addEventListener("submit", function (e) { e.preventDefault(); clearMessage(); if (!currentAccount || !currentAccount.person_id) { showMessage("no person linked to your account", true); return; } req("/groups", { method: "POST", body: { person_id: currentAccount.person_id, name: form.name.value }, }) .then(function () { form.reset(); refreshGroups(); }) .catch(function (err) { showMessage(err.message || "failed to create group", true); }); }); } function refreshGroups() { req("/groups") .then(function (data) { setOptions( byId("invite-group"), data, function (g) { return g.id; }, function (g) { return g.name || "(unnamed)"; }, true ); const list = byId("groups-list"); if (!list) return; list.innerHTML = ""; if (!data.length) { list.innerHTML = '
  • No groups yet. Create one above.
  • '; return; } data.forEach(function (g) { const li = document.createElement("li"); li.innerHTML = "" + escapeHtml(g.name || "(unnamed)") + " " + 'members: ' + (g.members && g.members.length ? escapeHtml(g.members.map(function (m) { return m.name || "(unnamed)"; }).join(", ")) : "none") + ""; list.appendChild(li); }); }) .catch(function (err) { showMessage(err.message || "failed to load groups", true); }); } function bindInviteForm() { const form = byId("invite-form"); if (!form) return; const addMember = function (groupId, personId) { if (!groupId || !personId) { showMessage("select a group and a person", true); return; } req("/groups/" + encodeURIComponent(groupId) + "/members/" + encodeURIComponent(personId), { method: "POST", body: {}, }) .then(function () { clearMessage(); refreshGroups(); }) .catch(function (err) { showMessage(err.message || "failed to add member", true); }); }; form.addEventListener("submit", function (e) { e.preventDefault(); clearMessage(); addMember(byId("invite-group").value, byId("invite-person").value); }); const join = byId("join-self"); if (join) { join.addEventListener("click", function () { clearMessage(); if (!currentAccount || !currentAccount.person_id) { showMessage("no person linked to your account", true); return; } addMember(byId("invite-group").value, currentAccount.person_id); }); } } function refreshRights() { req("/rights") .then(function (data) { const accountSel = byId("right-account"); setOptions( accountSel, data, function (a) { return a.id; }, function (a) { return a.email + (a.rights && a.rights.length ? " (" + a.rights.join(", ") + ")" : ""); }, true ); const list = byId("rights-list"); if (!list) return; list.innerHTML = ""; data.forEach(function (a) { const li = document.createElement("li"); li.innerHTML = "" + escapeHtml(a.email) + " " + '' + (a.rights && a.rights.length ? escapeHtml(a.rights.join(", ")) : "no rights") + ""; list.appendChild(li); }); }) .catch(function (err) { showMessage(err.message || "failed to load rights", true); }); } function bindRightForm() { const form = byId("right-form"); if (!form) return; form.addEventListener("submit", function (e) { e.preventDefault(); clearMessage(); if (!form.account_id.value || !form.right.value) { showMessage("select account and right", true); return; } req("/rights/grant", { method: "POST", body: { account_id: form.account_id.value, right: form.right.value }, }) .then(refreshRights) .catch(function (err) { showMessage(err.message || "failed to grant right", true); }); }); const revokeBtn = byId("revoke-right"); if (revokeBtn) { revokeBtn.addEventListener("click", function () { clearMessage(); const accountSel = byId("right-account"); const rightSel = byId("right-right"); if (!accountSel.value || !rightSel.value) { showMessage("select account and right to revoke", true); return; } req("/rights/revoke", { method: "POST", body: { account_id: accountSel.value, right: rightSel.value }, }) .then(refreshRights) .catch(function (err) { showMessage(err.message || "failed to revoke right", true); }); }); } } if (isLoginPage) { initLoginPage(); } else if (byId("app-msg")) { initMainApp(); } })();