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 element? });