ToDoList/***USAGE: ToDoList()
Embed a TODO-list into a page. The TODO list allows users to creItems that are due are highlighted in yellow, items passed due arlist can be added to any page. The information of the TODO-list
NOTES:* only 1 TODO-list can be embedded per page
* this template requires the DekiAPI script extension* this template requires the jQuery script extension

CHANGE HISTORY:
1.0* initial version
1.1* show error message if dekiapi or jquery extensions are miss* verify if user is logged in or not; if not, disable add & u
1.2* allow custom user list to be passed in* publish changes to a channel***/
var userlist = $userlist;
var publish = $publish ?? 'default';
var message = $message ?? { };var allowed;
if(userlist) {// convert list of names to list of user objects
let userlist = [(u is map) ? u : wiki.getuser(u)foreach var u in userlist];
let allowed = [ string.tolower(u.name) foreach var u in userlist ];} else if(site.usercount <= 500) {// get list of users from site
let userlist = site.users;}

// check if required extensions are installed
if(__env.dekiapi is nil) {<span style="color: red; font-weight: bold">"ERROR: Template:TODO req} else if(__env.jquery is nil) {<span style="color: red; font-weight: bold">"ERROR: Template:TODO req} else {// includes
dekiapi();jquery.ui('smoothness');// input form
var disabled = (// anonymous users can't edit
user.anonymous ||// user cannot update page(wiki.pagepermissions().update is nil) ||// user is not listed in custom user list
(allowed && !list.contains(allowed, string.tolower(user.name))) ? "disabled" : nil);<div id="deki-todo"><form><input id="todoid" type="hidden" value="" /><div class="todo-assign">" Task: ";</div><div class="todo-who">" Who: ";// check if the userlist is provided, otherwise use a simif(userlist) {<select id="todowho"disabled=(disabled) >foreach(var u in userlist where !u.anonymous) {<option value=(u.name) selected=((u.id == useu.name</option>}</select>} else {<input id="todowho" type="text" value=(userlist) disa}</div><div class="todo-what">"What: "; <input id="todowhat" type="t<div class="todo-when">" When: "; <input id="todowhen" type="<div class="todo-submit"><input id="todoadd" type="submit" value="Add" disabled=(disabwhen($this.click) {@todoupdate({index: #todoid.val(),entry: {what: #todowhat.val(),
who: #todowho.val(),when: #todowhen.val(),author: {{user.name}},done: false}});@todoreset();return false;}
when(#tododelete.click) {@todoupdate({ index: #todoid.val(), entry: null });@todoreset();}when(@todoreset) {#todoid.val('');#todowhat.val('');#todoadd.val('Add');#tododelete.hide().removeAttr('disabled');#todoadd.blur();}when(@todoedit) {MindTouch.Deki.ReadPageProperty(null, 'urn:custom.minvar data = YAHOO.lang.JSON.parse(result.value ||#todoid.val(@todoedit.index);#todowhat.val(data[@todoedit.index].what);#todowhen.val(data[@todoedit.index].when);#todowho.val(data[@todoedit.index].who);#todoadd.val('Update');#tododelete.show();}, function(result) {alert('An error occurred trying to read the TODO});}when(@todoupdate) {MindTouch.Deki.ReadPageProperty(null, 'urn:custom.minvar op;
var data = eval('(' + (result.value || '[]') + ')if(@todoupdate.index !== '') {if(@todoupdate.entry === null) {op = 'remove';data.splice(@todoupdate.index, 1);} else {op = 'update';$.extend(data[@todoupdate.index], @todoup}} else {op = 'add';@todoupdate.index = data.length;
data.push(@todoupdate.entry);}
var completed = function() {var msg = {{ message }};msg.op = op;
msg.index = @todoupdate.index;
msg.entry = @todoupdate.entry;
msg.list = data;
Deki.publish({{ publish }}, msg);
MindTouch.Deki.Reload(#todolist, null, functi};
if(result.etag) {MindTouch.Deki.UpdatePageProperty(result.hrefalert('An error occurred trying to update});} else {MindTouch.Deki.CreatePageProperty(null, 'urn:alert('An error occurred trying to create});}}, function(result) {alert('An error occurred trying to read the TODO});}" /><input id="tododelete" type="button" style="display: none;" v</div></form>// add index to each todo item
var todo = json.parse(page.properties["todo"].text ?? "") ?? [ ];let todo = [ item .. { index: __index } foreach var item in todo
let invaliddates = [ item foreach var item in todo where !date.is
let todo = [ item foreach var item in todo where date.isvalid(itelet todo = list.sort(todo, _, _, "date.compare($left.when, $right<table id="todolist" cellspacing="0" cellpadding="0"><colgroup><col width="20" /><col width="20%" /><col /><col width="15%" /><col width="75" /></colgroup><tr><th>string.nbsp</th><th>"When"</th><th>"What"</th><th>"Who"</th><th>string.nbsp</th></tr>
foreach(var entry in todo) {var class = "";var now = date.now;if(entry.done) let class..= "completed ";else if(date.isvalid(entry.when)) {if(date.issameday(now, entry.when)) let class ..= "duelse if(date.isafter(now, entry.when)) let class ..=}
var title = "Created by " .. entry.author;<tr class=(class)><td><input type="checkbox" name="done" id=(entry.inde<td title=(title)><span>entry.when</span></td><td title=(title)><span>entry.what</span></td><td title=(title)><span>entry.who</span></td><td class="todo-edit">if(disabled) {string.nbps;} else {<a href="#" rel=(entry.index)>"edit"</a>
}
</td>
</tr>
}
</table></div>// global script code
<script type="text/jem">"function ToDoWireControls() {$('#todolist a').bind('click', function() {@todoedit({ index: $(this).attr('rel') });#todowhat.select();
return false;});$('.todochange').bind('click', function() {var $this = $(this);@todoupdate({ index: $this.val(), entry: { done: this.checked});}ToDoWireControls();"</script>// styles<html><head><style type="text/css">"//--- Start TODO styles --#deki-todo {margin: 0;
padding: 4px;
width: 98%;}
#deki-todo table {width: 100%;
margin: 0;}#deki-todo table td {border-bottom: 1px solid #000;
padding: 4px 2px;}
#deki-todo tr td.todo-edit {text-align: right;
padding-right: 8px;}
#deki-todo tr.due-today td {background-color: #ff0;}#deki-todo tr.completed td {background-color: #fff;}#deki-todo tr.completed td span {text-decoration: line-through;}#deki-todo tr.due-past td,

#deki-todo tr.due-past td.todo-edit a {color: #f00;}#deki-todo table th {font-size: 14px;
font-weight: bold;text-align: left;border-bottom: 2px solid #000;}#deki-todo form {padding: 4px;
background-color: #000;
color: #fff;
overflow: hidden;
margin-bottom: 8px;}
#deki-todo div.todo-assign {
font-weight: bold;}#deki-todo form,#deki-todo form input,#deki-todo form select {font-size: 11px;}#deki-todo form div {padding-right: 8px;
float: left;}#deki-todo div.todo-submit {float: right;
padding-right: 0px;}
#deki-todo div.todo-submit input {margin-right: 4px;}
#deki-todo table tr td.todo-edit a {background: url(/skins/common/icons/silk/page_white_edit.png) nocolor: #000;
text-decoration: none;
padding: 3px 3px 3px 21px;
font-size: 11px;}#deki-todo table tr td.todo-edit a:hover {text-decoration: underline;}

//--- End TODO styles --"</style></head></html>}