import { getNote, getList, saveNote, newNote, getUserData, getUserSessions,
signIn, changePassword, changeUsername, signOut, signOutEverywhere } from "./api.js";
document.addEventListener("DOMContentLoaded", async () => {
const $ = (s,c) => (c||document).querySelector(s);
function $$(x,y,z,a){a=(z||document).querySelectorAll(x);if(typeof y=="function")[].forEach.call(a,y);return a}
function m(a,b,c){c=document;b=c.createElement(b||"p");b.innerHTML=a.trim();for(a=c.createDocumentFragment();c=b.firstChild;)a.appendChild(c);return a.firstChild}
function debounce(fn, delay) {
let timeout = null;
return (...args) => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
fn.apply(null, args);
}, delay||500);
}
}
//const secure = location.protocol === "https:";
//sock.init(`ws${secure?"s":""}://${location.host}/ws`);
//sock.on("hello", e => {
// console.log("hello", e);
// sock.send("world", {foo:"bar"});
//});
//if ("serviceWorker" in navigator) {
// navigator.serviceWorker.register("sw.js")
// .then(() => console.log("Service worker registered"));
//}
//console.log("outside", $("note-card").textContent);
//$("note-card").addEventListener("edit", () => console.log("edit event fired"));
function drawNote(note) {
//const el = m(`
`);
//const ta = $("textarea", el)
//ta.addEventListener("input",
// debounce(() => { saveNote(note.id, ta.value); }, 500));
//function resizeTextarea() { // TODO simplify
// el.style.height = (el.scrollHeight) + "px";
// ta.style.height = ""; ta.style.height = (ta.scrollHeight) + "px";
// el.style.height = "";
//}
//ta.addEventListener("input", resizeTextarea);
//ta.addEventListener("focus", resizeTextarea);
//window.addEventListener("resize", resizeTextarea);
//setTimeout(resizeTextarea);
const el = m(`${note.content.trim()}`);
$("#notes").appendChild(el);
}
$("#add-btn").addEventListener("click", async () => {
drawNote(await newNote());
});
function showSignIn() {
const hash = location.hash.slice(2).replace(/^sign-in(\/to\/)?/,"");
history.replaceState("sign-in","","#/sign-in"+(hash?"/to/"+hash:""));
$("#sign-in-dialog").showModal();
}
$("#sign-in-dialog").addEventListener("cancel", e => e.preventDefault());
$("#sign-in-username").addEventListener("input", e => e.target.classList.remove("error"));
$("#sign-in-password").addEventListener("input", e => e.target.classList.remove("error"));
$("#sign-in-basic-go").addEventListener("click", async e => {
e.preventDefault();
$("#sign-in-error").textContent = "";
const inputs = [
$("#sign-in-username"),
$("#sign-in-password"),
$("#sign-in-keep-session"),
$("#sign-in-basic-go"),
];
const validatedInputs = inputs.slice(0,2);
if (validatedInputs.map(e => {
if (!e.checkValidity()) {
e.classList.add("error");
return true; }
}).reduce((a,e) => a||e, false)) return;
inputs.forEach(e => e.disabled = true);
$("#sign-in-form").classList.add("loading");
const res = await signIn({
username: $("#sign-in-username").value,
password: $("#sign-in-password").value,
keepSession: $("#sign-in-keep-session").checked,
});
inputs.forEach(e => e.disabled = false);
$("#sign-in-form").classList.remove("loading");
if (res.error) {
$("#sign-in-error").textContent = res.code+" "+res.error;
return;
}
console.log(res);
if (!res.success) {
$("#sign-in-error").textContent = res.msg;
return;
}
if (res.mustChangePassword) {
alert("Must change password (TODO)");
// TODO
return;
}
const hash = location.hash.slice(9);
history.replaceState("","","#/"+hash);
// TODO load location
getUserData();
const noteList = await getList();
for (const note of noteList) drawNote(note);
$("#sign-in-dialog").close();
});
$("#change-password").addEventListener("click", async () => {
const res = await changePassword({
username: $("#username").value, // TODO
password: $("#password").value, // TODO
newPassword: $("#new-password").value,
keepSession: $("#keep-session").checked,
});
console.log(res);
getUserData();
});
$("#change-username").addEventListener("click", async () => {
const res = await changeUsername({
newUsername: $("#new-username").value,
password: $("#password").value, // TODO
});
console.log(res);
getUserData();
});
$("#sign-out").addEventListener("click", async () => {
await signOut();
console.log("Signed out");
$("#sign-in-dialog").showModal();
});
$("#sign-out-everywhere").addEventListener("click", async () => {
signOutEverywhere();
console.log("Signed out everywhere");
showSignIn();
});
try {
await getUserData();
const noteList = await getList();
for (const note of noteList) drawNote(note);
await getUserSessions();
} catch(e) { showSignIn(); }
(() => {
let prevScroll = 0;
window.addEventListener("scroll", () => {
document.querySelector("#toolbar").classList[
(window.scrollY && window.scrollY > prevScroll)?"add":"remove"]("hide");
prevScroll = window.scrollY;
});
})();
// TODO make sure to save unsynced delta even if token has expired, then sync when logged in
// TODO Cookie consent on signup; "only necessary for maintaining user session and cached data"
// TODO "Review sessions" popup client-side when relogin required on a token which shouldn't have expired yet
// TODO prevent data: and javascript: links?
// Also embedding of external content?
// encodeURI *AND* encode for attribute (quote marks etc)
// https://github.com/cure53/DOMPurify
//
// elem.textContent = dangerVariable; !!!!!!!!!
// elem.insertAdjacentText(dangerVariable);
// elem.className = dangerVariable;
// elem.setAttribute(safeName, dangerVariable);
// formfield.value = dangerVariable;
// document.createTextNode(dangerVariable);
// document.createElement(dangerVariable);
// elem.innerHTML = DOMPurify.sanitize(dangerVar);
// https://github.com/cure53/DOMPurify/blob/main/src/attrs.js
// https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
// TODO note kebab menu could be a