diff options
Diffstat (limited to 'modules/todo/todo.html')
-rw-r--r-- | modules/todo/todo.html | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/modules/todo/todo.html b/modules/todo/todo.html new file mode 100644 index 0000000..ae13163 --- /dev/null +++ b/modules/todo/todo.html @@ -0,0 +1,236 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>TODO</title> +<script> +"use strict"; + +function fetch(method,url,data/*?*/,cb){ + if(!cb){ + cb=data; + data=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); + }; + 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){ + return new Date(date.getTime()-date.getHours()*3600*1000-date.getMinutes()*60*1000-date.getSeconds()*1000-date.getMilliseconds()); +} + +function fancytime(timeS){ + return pad(timeS.hour,2,"0")+":"+pad(timeS.min,2,"0")+(timeS.sec==0?"":":"+pad(timeS.sec,2,"0")); +} + +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 "In "+diffweeks+" week"+(diffweeks==1?"":"s")+", "+diffdays%7+" day"+(diffdays%7==1?"":"s"); + } + return "In "+diffdays+" day"+(diffdays==1?"":"s")+", "+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(fancydate(task.date))); + e.setAttribute("title",task.date.toString()); + 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); + }); +} + +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; + cursor:help; +} +.taskdelete{ + margin-left:60px; + font-size:10px; + font-family:sans-serif; + color:red; + cursor:pointer; +} +#addtaskform{ + border:1px #ddd solid; + display:inline-block; + padding:5px; +} +#addtaskform.invisible{ + display:none; +} +</style> +</head> +<body> +<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> |