diff options
-rw-r--r-- | modules/todo/todo.html | 236 | ||||
-rw-r--r-- | modules/todo/todo.js | 86 |
2 files changed, 322 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> diff --git a/modules/todo/todo.js b/modules/todo/todo.js new file mode 100644 index 0000000..3a86e68 --- /dev/null +++ b/modules/todo/todo.js @@ -0,0 +1,86 @@ +"use strict"; + +// {"key":"tasks","value":[{"id":1,"subject":"kaas rep","repweeks":0,"date":"2016-10-25T07:46:54.493Z"},{"id":2,"subject":"kaas","repweeks":0,"date":"2016-10-27T07:46:54.493Z"}]} + +var cmn=require("../$common.js"), + persist=require("node-persist"); + +var moddir=null; + +persist=persist.create({ + dir:"persist/todo", + continuous:false, + interval:false +}); +persist.initSync(); + +//tasks: [{id: Int,subject: String,repweeks: Int,date: Date}] +//repweeks==0 implies no repetition +var nextid=persist.getItemSync("nextid"); +if(nextid==null)nextid=1; +var tasks=persist.getItemSync("tasks"); +(function(){ + if(!tasks){ + tasks=[]; + persist.setItemSync("tasks",tasks); + } else { + for(var task of tasks){ + task.date=new Date(task.date); + if(nextid<=task.id)nextid=task.id+1; + } + persist.setItemSync("nextid",nextid); + } +})(); + + +module.exports=function(app,io,_moddir){ + moddir=_moddir; + // app.all(["/todo","/todo/*"],cmn.authgen()); + app.get("/todo",function(req,res){ + res.sendFile(moddir+"/todo.html"); + }); + app.get("/todo/list",function(req,res){ + res.json(tasks); + }); + app.delete("/todo/task",function(req,res){ + var id=+req.body; + var i; + var fail=false; + if(id<0||~~id!=id||isNaN(id)){ + fail=true; + } else { + for(i=0;i<tasks.length;i++)if(tasks[i].id==id)break; + if(i==tasks.length)fail=true; + else { + tasks.splice(i,1); + persist.setItemSync("tasks",tasks); + } + } + if(fail)res.status(404).send("Unknown id"); + else res.status(200).end(); + }); + app.post("/todo/task",function(req,res){ + var obj; + try { + obj=JSON.parse(req.body); + } catch(e){ + res.status(400).send("Invalid request"); + return; + } + var subject=obj.subject+"",repweeks=+obj.repweeks,date=new Date(obj.date); + if(subject.length==0|| + isNaN(repweeks)||repweeks<0||repweeks!=~~repweeks|| + isNaN(date.getTime())){ + res.status(400).send("Invalid data"); + } + tasks.push({ + id:nextid++, + subject:subject, + repweeks:repweeks, + date:date + }); + persist.setItemSync("tasks",tasks); + persist.setItemSync("nextid",nextid); + res.status(200).end(); + }); +}; |