From 9bc35041d52041b9dcd34fb590070d3cc78108fd Mon Sep 17 00:00:00 2001
From: tomsmeding <tom.smeding@gmail.com>
Date: Tue, 25 Oct 2016 11:28:04 +0200
Subject: Add TODO module

---
 modules/todo/todo.html | 236 +++++++++++++++++++++++++++++++++++++++++++++++++
 modules/todo/todo.js   |  86 ++++++++++++++++++
 2 files changed, 322 insertions(+)
 create mode 100644 modules/todo/todo.html
 create mode 100644 modules/todo/todo.js

(limited to 'modules/todo')

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();
+	});
+};
-- 
cgit v1.2.3-70-g09d2