"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();
}
})();