<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>TODO</title> <script> "use strict"; function fetch(method,url,data/*?*/,creds/*?*/,cb){ if(!creds){ cb=data; data=undefined; creds=undefined; } else if(!cb){ cb=creds; creds=undefined; } if(!cb)throw new Error("No callback passed to fetch"); var xhr=new XMLHttpRequest(); xhr.onreadystatechange=function(ev){ if(xhr.readyState<4)return; cb(xhr.status,xhr.responseText); }; if(creds){ xhr.open(method,url,true,creds[0],creds[1]); } else { xhr.open(method,url); } xhr.send(data); } function datesplit(date){ return { year:date.getFullYear(), month:date.getMonth(), day:date.getDate(), hour:date.getHours(), min:date.getMinutes(), sec:date.getSeconds() }; } function pad(s,n,c){ if(c==null)c=" "; else c=c[0]; s=s+""; while(s.length<n)s=c+s; return s; } function beginOfDay(date){ var d=new Date(date.getTime()); d.setHours(0); d.setMinutes(0); d.setSeconds(0); d.setMilliseconds(0); return d; } function fancytime(timeS){ return pad(timeS.hour,2,"0")+":"+pad(timeS.min,2,"0")+(timeS.sec==0?"":":"+pad(timeS.sec,2,"0")); } var weekdays=["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]; function fancydate(date){ var now=new Date(); var nowS=datesplit(new Date()); var dateS=datesplit(date); var diffdays=Math.round((beginOfDay(date).getTime()-beginOfDay(now).getTime())/1000/3600/24); if(date.getTime()<now.getTime()){ if(diffdays==0)return "Earlier this day"; return -diffdays+" day"+(diffdays==-1?"":"s")+" ago"; } if(dateS.year==nowS.year&&dateS.month==nowS.month&&dateS.day==nowS.day){ return "Today, "+fancytime(dateS); } var diffweeks=~~(diffdays/7); if(diffweeks>0){ return weekdays[(date.getDay()+6)%7]+" in "+diffweeks+" week"+(diffweeks==1?"":"s"); } if(diffdays==1){ return "Tomorrow, "+fancytime(dateS); } return weekdays[(date.getDay()+6)%7]+", "+fancytime(dateS); } function tablerowfor(task){ var div=document.createElement("div"); div.classList.add("task"); var e=document.createElement("span"); e.classList.add("tasksubject"); e.appendChild(document.createTextNode(task.subject)); div.appendChild(e); var float=document.createElement("div"); float.classList.add("taskfloat"); e=document.createElement("span"); e.classList.add("taskdate"); e.appendChild(document.createTextNode((task.repweeks!=0?"(rep) ":"")+fancydate(task.date))); e.setAttribute("title",task.date.toString()); e.appendChild(document.createElement("br")); var e2=document.createElement("span"); e2.classList.add("subdate"); e2.appendChild(document.createTextNode(task.date.toString())); e.appendChild(e2); float.appendChild(e); e=document.createElement("span"); e.classList.add("taskdelete"); e.appendChild(document.createTextNode("X")); e.addEventListener("click",function(ev){ if(!confirm("Really delete task \""+task.subject+"\"?"))return fetch("DELETE","/todo/task",task.id,function(status,body){ if(status==200)getlist(); else alert("Delete failed: "+body); }); }); float.appendChild(e); div.appendChild(float); return div; } function refreshlist(list){ var listelem=document.getElementById("todolist"); listelem.innerHTML=""; var rows=[]; var i,task; var now=new Date(); for(i=0;i<list.length;i++){ task=list[i]; if(task.repweeks!=0){ while(task.date<now){ task.date=new Date(task.date.getTime()+task.repweeks*7*24*3600*1000); } } rows.push([task.date,tablerowfor(task)]); } if(rows.length==0){ var div=document.createElement("div"); div.classList.add("notasks"); div.appendChild(document.createTextNode("No tasks :D")); listelem.appendChild(div); return; } rows.sort(function(a,b){return a[0]-b[0];}); //ascending sort on the dates for(i=0;i<rows.length;i++){ listelem.appendChild(rows[i][1]); } } function getlist(){ fetch("GET","/todo/list",function(status,json){ var list; try { list=JSON.parse(json); } catch(e){ alert("An error occurred!"); return; } var i; for(i=0;i<list.length;i++)list[i].date=new Date(list[i].date); refreshlist(list); }); } function doOpenAddTask(ev){ document.getElementById("addtaskform").classList.remove("invisible"); ev.target.parentNode.removeChild(ev.target); } function doAddTask(ev){ var subject=document.getElementById("addtasksubject").value; var repweeks=+document.getElementById("addtaskrepweeks").value; var date=document.getElementById("addtaskdate").value; if(typeof date=="string")date=new Date(date); if(isNaN(repweeks)){ alert("Invalid repweeks!"); return; } fetch("POST","/todo/task",JSON.stringify({ subject:subject, repweeks:repweeks, date:date }),function(status,body){ if(status==200){ location.href=location.href; return; } alert("Error while adding: "+body); }); } function logoutReload(){ fetch("GET","/todo/authfail",undefined,["baduser","badpass"],function(status,body){ location.href=location.href; }); } window.addEventListener("load",getlist); </script> <style> body{ font-family:Georgia,Times,serif; font-size:14px; } .task{ border:1px #ddd solid; border-bottom-width:0px; padding:10px; background-color:#f8f8f8; width:500px; } .task:last-child{ border-bottom-width:1px; } .tasksubject{ font-size:18px; font-weight:bold; margin-left:10px; } .taskfloat{ float:right; text-align:right; } .taskdate{ font-size:12px; font-style:italic; display:inline-block; } .subdate{ color:#ccc; font-size:8px; } .taskdelete{ margin-left:60px; font-size:10px; font-family:sans-serif; color:red; cursor:pointer; vertical-align:top; } #addtaskform{ border:1px #ddd solid; display:inline-block; padding:5px; } #addtaskform.invisible{ display:none; } #logoutwrapper{ float:right; } </style> </head> <body> <div id="logoutwrapper"> <input type="button" onclick="logoutReload();" value="Logout"> </div> <h1>TODO</h1> <div id="todolist"></div> <br><br> <input type="button" onclick="doOpenAddTask(event)" value="Add task"> <div id="addtaskform" class="addtaskform invisible"> Subject: <input type="text" id="addtasksubject" placeholder="Subject"> <br> Repweeks: <input type="number" id="addtaskrepweeks" placeholder="Repweeks" value="0" min="0"> (0 = no repeat) <br> Date: <input type="datetime" id="addtaskdate" placeholder="YYYY-MM-DD HH:MM:SS" size="25"> <br> <input type="button" onclick="doAddTask(event)" value="Add"> </div> </body> </html>