SHA256
505 lines
15 KiB
JavaScript
505 lines
15 KiB
JavaScript
"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 =
|
|
"<strong>" + escapeHtml(p.name || "(unnamed)") + "</strong>" +
|
|
'<span class="meta">' +
|
|
(p.emails && p.emails.length ? "emails: " + escapeHtml(p.emails.join(", ")) : "no email shown") +
|
|
"</span>";
|
|
})
|
|
.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 = '<li class="meta">Nothing here yet.</li>';
|
|
return;
|
|
}
|
|
data.forEach(function (a) {
|
|
const li = document.createElement("li");
|
|
li.innerHTML =
|
|
"<strong>" + escapeHtml(a.title || "(untitled)") + "</strong> " +
|
|
'<span class="meta">' +
|
|
escapeHtml(a.article_type) +
|
|
" · " + escapeHtml(a.visibility) +
|
|
"</span>" +
|
|
(a.content
|
|
? "<pre style='margin:4px 0;white-space:pre-wrap;font:inherit;background:#fff;border:1px solid #e4e7ee;padding:4px;border-radius:4px;'>" +
|
|
escapeHtml(a.content) + "</pre>"
|
|
: "") +
|
|
(a.visibility === "group" && a.allowed_groups && a.allowed_groups.length
|
|
? '<span class="meta">groups: ' + escapeHtml(a.allowed_groups.join(", ")) + "</span>"
|
|
: "");
|
|
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 = '<li class="meta">No persons yet.</li>';
|
|
return;
|
|
}
|
|
data.forEach(function (p) {
|
|
const li = document.createElement("li");
|
|
li.innerHTML =
|
|
"<strong>" + escapeHtml(p.name || "(unnamed)") + "</strong>" +
|
|
'<span class="meta">' +
|
|
(p.emails && p.emails.length ? "emails: " + escapeHtml(p.emails.join(", ")) : "no emails") +
|
|
"</span>";
|
|
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 = '<li class="meta">No groups yet. Create one above.</li>';
|
|
return;
|
|
}
|
|
data.forEach(function (g) {
|
|
const li = document.createElement("li");
|
|
li.innerHTML =
|
|
"<strong>" + escapeHtml(g.name || "(unnamed)") + "</strong> " +
|
|
'<span class="meta">members: ' +
|
|
(g.members && g.members.length
|
|
? escapeHtml(g.members.map(function (m) { return m.name || "(unnamed)"; }).join(", "))
|
|
: "none") +
|
|
"</span>";
|
|
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 =
|
|
"<strong>" + escapeHtml(a.email) + "</strong> " +
|
|
'<span class="meta">' +
|
|
(a.rights && a.rights.length ? escapeHtml(a.rights.join(", ")) : "no rights") +
|
|
"</span>";
|
|
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();
|
|
}
|
|
})();
|