summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2016-10-25 11:28:04 +0200
committertomsmeding <tom.smeding@gmail.com>2016-10-25 11:28:04 +0200
commit9bc35041d52041b9dcd34fb590070d3cc78108fd (patch)
tree5a3266b67555fde6647743525435b190fe01b03d /modules
parentcf8ad726b11fb6d76bcef22196c4b6b3f32e6d1f (diff)
Add TODO module
Diffstat (limited to 'modules')
-rw-r--r--modules/todo/todo.html236
-rw-r--r--modules/todo/todo.js86
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();
+ });
+};