From 4702402484a773c0eccf7415d0318e367fb996e1 Mon Sep 17 00:00:00 2001 From: Alexis Hovorka Date: Sat, 27 Jul 2024 19:43:38 -0600 Subject: [refactor] Try out some client-side UI libraries --- app/auth.js | 8 +- app/public/api.js | 91 ++++++++++++++++++++++ app/public/index.html | 12 ++- app/public/main.js | 186 ++++++++++++++++----------------------------- app/public/note-ponys.html | 102 +++++++++++++++++++++++++ app/public/ponys.js | 1 + app/public/sock.js | 5 +- app/public/utils.js | 10 +++ 8 files changed, 282 insertions(+), 133 deletions(-) create mode 100644 app/public/api.js create mode 100644 app/public/note-ponys.html create mode 100644 app/public/ponys.js create mode 100644 app/public/utils.js (limited to 'app') diff --git a/app/auth.js b/app/auth.js index 8a55857..4d5c1cb 100644 --- a/app/auth.js +++ b/app/auth.js @@ -165,7 +165,7 @@ const checkReferer = req => { if (req.headers["referer"] && !req.headers["referer"].includes(DOMAIN)) console.log(Date.now()+" [WARN] Unexpected HTTP Referer: "+req.headers["referer"]); }; -async function login(req, res, match, data) { +async function signIn(req, res, match, data) { const currentToken = parseCookies(req)?.token; if (currentToken || !data.username || !data.password) return err400(res); const error = {success:false, msg:"Bad username or password"}; @@ -206,7 +206,7 @@ async function login(req, res, match, data) { } else return err500(res); } -function logout(req, res) { +function signOut(req, res) { const token = parseCookies(req)?.token; const tokenData = getToken(token); if (tokenData) { @@ -419,8 +419,8 @@ export function authed(fn) { return rateLimit((req, res, ...rest) => { }); } export const attach = (app) => { // TODO make endpoints RESTier? - app.jpost("/login", rateLimit(login)); // {username, password[, keepSession]} -> {success[, msg][, mustChangePassword]} - app.post("/logout", rateLimit(logout)); + app.jpost("/sign-in", rateLimit(signIn)); // {username, password[, keepSession]} -> {success[, msg][, mustChangePassword]} + app.post("/sign-out", rateLimit(signOut)); app.jpost("/change-password", rateLimit(changePassword)); // {password, newPassword[, username[, keepSession]]} -> {success[, msg]} app.jpost("/change-username", authed(changeUsernameReq)); // {newUsername, password} -> {success[, msg]} app.get("/session-list", authed(sessionList)); // -> {active:[{id:, ...}, ...], recent:[...]} diff --git a/app/public/api.js b/app/public/api.js new file mode 100644 index 0000000..6321909 --- /dev/null +++ b/app/public/api.js @@ -0,0 +1,91 @@ +export async function getNote(id) { + const res = await fetch(`${id}`); + const note = await res.json(); + //console.log(note); + return note; +} + +export async function getList() { + const res = await fetch("/list"); + const list = await res.json(); + //console.log(list); + + return Promise.all(list.map(id => getNote(id))); +} + +export async function saveNote(id, content) { + const res = await fetch("/"+id, { + method: "POST", + body: JSON.stringify({id, content}), + }); + + const note = await res.json(); + //console.log(note); +} + +export async function newNote() { + const res = await fetch("/new", { + method: "POST", + }); + + const note = await res.json(); + //console.log(note); + return note; +} + +export async function getUserData() { + const res = await fetch(`/user`); + const user = await res.json(); + //console.log(user); + return user; +} + +export async function getUserSessions() { + const res = await fetch(`/session-list`); + const sessions = await res.json(); + //console.log(sessions); + return sessions; +} + +export async function signIn({username, password, keepSession}) { + const res = await fetch("/sign-in", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({username, password, keepSession}) + }); + + if (res.ok) return res.json(); + return {code: res.status, error: res.statusText}; // TODO +} + +export async function changePassword({username, password, newPassword, keepSession}) { + const res = await fetch("/change-password", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({username, password, newPassword, keepSession}) + }); + + return res.json(); +} + +export async function changeUsername({newUsername, password}) { + const res = await fetch("/change-username", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({newUsername, password}) + }); + + return res.json(); +} + +export async function signOut() { + const res = await fetch("/sign-out", { + method: "POST", + }); +} + +export async function signOutEverywhere() { + const res = await fetch("/deauth-all", { + method: "POST", + }); +} diff --git a/app/public/index.html b/app/public/index.html index 593e6b3..d900888 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -89,8 +89,8 @@
- - + +
@@ -110,7 +110,11 @@ - - + + + diff --git a/app/public/main.js b/app/public/main.js index 2a30c45..a9119b4 100644 --- a/app/public/main.js +++ b/app/public/main.js @@ -1,4 +1,7 @@ -document.addEventListener("DOMContentLoaded", async () => { "use strict"; +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} @@ -27,79 +30,37 @@ function debounce(fn, delay) { // .then(() => console.log("Service worker registered")); //} -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); +//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); } -async function getNote(id) { - const res = await fetch(`${id}`); - const note = await res.json(); - console.log(note); - drawNote(note); -} - -async function getList() { - const res = await fetch("/list"); - const list = await res.json(); - console.log(list); - - return Promise.all(list.map(id => getNote(id))); -} - -async function saveNote(id, content) { - const res = await fetch("/"+id, { - method: "POST", - body: JSON.stringify({id, content}), - }); - - const note = await res.json(); - console.log(note); -} - -async function newNote() { - const res = await fetch("/new", { - method: "POST", - }); - - const note = await res.json(); - console.log(note); - - drawNote(note); -} - -$("#add-btn").addEventListener("click", newNote); - -async function getUserData() { - const res = await fetch(`/user`); - const user = await res.json(); - console.log(user); -} - -async function getUserSessions() { - const res = await fetch(`/session-list`); - const sessions = await res.json(); - console.log(sessions); -} +$("#add-btn").addEventListener("click", async () => { + drawNote(await newNote()); +}); function showSignIn() { const hash = location.hash.slice(2).replace(/^sign-in(\/to\/)?/,""); @@ -131,32 +92,27 @@ $("#sign-in-basic-go").addEventListener("click", async e => { inputs.forEach(e => e.disabled = true); $("#sign-in-form").classList.add("loading"); - const res = await fetch("/login", { - method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({ - username: $("#sign-in-username").value, - password: $("#sign-in-password").value, - keepSession: $("#sign-in-keep-session").checked, - }) + 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.ok) { - $("#sign-in-error").textContent = res.status+" "+res.statusText; + if (res.error) { + $("#sign-in-error").textContent = res.code+" "+res.error; return; } - const json = await res.json(); - console.log(json); + console.log(res); - if (!json.success) { - $("#sign-in-error").textContent = json.msg; + if (!res.success) { + $("#sign-in-error").textContent = res.msg; return; } - if (json.mustChangePassword) { + if (res.mustChangePassword) { alert("Must change password (TODO)"); // TODO return; @@ -167,66 +123,54 @@ $("#sign-in-basic-go").addEventListener("click", async e => { // TODO load location getUserData(); - getList(); + const noteList = await getList(); + for (const note of noteList) drawNote(note); $("#sign-in-dialog").close(); }); $("#change-password").addEventListener("click", async () => { - const res = await fetch("/change-password", { - method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({ - username: $("#username").value, // TODO - password: $("#password").value, // TODO - newPassword: $("#new-password").value, - keepSession: $("#keep-session").checked, - }) + const res = await changePassword({ + username: $("#username").value, // TODO + password: $("#password").value, // TODO + newPassword: $("#new-password").value, + keepSession: $("#keep-session").checked, }); - const json = await res.json(); - console.log(json); + console.log(res); getUserData(); }); $("#change-username").addEventListener("click", async () => { - const res = await fetch("/change-username", { - method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({ - newUsername: $("#new-username").value, - password: $("#password").value, // TODO - }) + const res = await changeUsername({ + newUsername: $("#new-username").value, + password: $("#password").value, // TODO }); - const json = await res.json(); - console.log(json); + console.log(res); getUserData(); }); -$("#log-out").addEventListener("click", async () => { - const res = await fetch("/logout", { - method: "POST", - }); - - //const json = await res.json(); - //console.log(json); - console.log("Logged out"); +$("#sign-out").addEventListener("click", async () => { + await signOut(); + console.log("Signed out"); $("#sign-in-dialog").showModal(); }); -$("#log-out-everywhere").addEventListener("click", async () => { - const res = await fetch("/deauth-all", { - method: "POST", - }); - - console.log("Logged out everywhere"); +$("#sign-out-everywhere").addEventListener("click", async () => { + signOutEverywhere(); + console.log("Signed out everywhere"); showSignIn(); }); -try { await getUserData(); await getList(); await getUserSessions(); } catch(e) { showSignIn(); } +try { + await getUserData(); + const noteList = await getList(); + for (const note of noteList) drawNote(note); + await getUserSessions(); +} catch(e) { showSignIn(); } (() => { let prevScroll = 0; diff --git a/app/public/note-ponys.html b/app/public/note-ponys.html new file mode 100644 index 0000000..bcbe7ed --- /dev/null +++ b/app/public/note-ponys.html @@ -0,0 +1,102 @@ +
+
+ + +
+
+ + + + diff --git a/app/public/ponys.js b/app/public/ponys.js new file mode 100644 index 0000000..adc54ab --- /dev/null +++ b/app/public/ponys.js @@ -0,0 +1 @@ +/* ponys v0.3.6 */export default class{static define(t,r,n,o=""){if(!r.content){let e=document.createElement("template");e.innerHTML=r,r=e}let l=(r=r.content).querySelector("script[setup]")||r.querySelector("script");return import("data:text/javascript;base64,"+btoa(l?.text?.replace(/(?<=(import|from)\s*?("|'))\.{0,2}\/.*?[^\\](?=\2)/g,(e=>new URL(e,new URL(o,location.origin)))))).then((o=>{l?.remove();class c extends(o.default||HTMLElement){constructor(){super();let t=this;try{t=t.attachShadow({mode:"open"})}catch{}this.$=e=>t.querySelector(e),this.$$=e=>t.querySelectorAll(e);let n=r.cloneNode(!0);e(this,n),t.append(n)}}return customElements.define(t,c,n),c}))}static defineAll(e=document){return Promise.allSettled([...e.querySelectorAll("template[name]")].map((e=>{let t={};for(let{name:r,value:n}of e.attributes)t[r]=n;return t.src?this.import(t.name,t.src,t):this.define(t.name,e,t)})))}static import(e,t,r){return fetch(t).then((e=>e.ok?e.text():Promise.reject(Error(t)))).then((n=>this.define(e,n,r,t)))}}function e(t,r){for(let n of r.children)n.host=t,n.$=t.$,n.$$=t.$$,e(t,n)} diff --git a/app/public/sock.js b/app/public/sock.js index b4905e5..72e00ec 100644 --- a/app/public/sock.js +++ b/app/public/sock.js @@ -1,4 +1,3 @@ -const sock = (() => { "use strict"; const refresh = () => setTimeout(() => location.reload(), 1e3); let ws, pingTimeout; @@ -12,7 +11,7 @@ const handlers = { }], }; -const sock = { +export const sock = { init: url => { ws = new WebSocket(url); ws.addEventListener("close", refresh); @@ -39,5 +38,3 @@ const sock = { else prequeue.push([type, data]); }, }; - -return sock })(); diff --git a/app/public/utils.js b/app/public/utils.js new file mode 100644 index 0000000..2e08012 --- /dev/null +++ b/app/public/utils.js @@ -0,0 +1,10 @@ +export function debounce(fn, delay) { + let timeout = null; + return (...args) => { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => { + timeout = null; + fn.apply(null, args); + }, delay||500); + } +} -- cgit v1.2.3-70-g09d2