1var PluginDo = { 2 3 /******************************* 4 * General functions * 5 *******************************/ 6 7 /** 8 * Create a floating, draggable overlay 9 * 10 * @param {string} title The title line 11 * @param {string} id The id to assign to the overlay 12 * @param {string} fieldsetcontent Content of the fieldset as HTML 13 * @param {string} submitcaption Label for the submit button 14 * @param {Function} submitaction callback What to do when submit is pressed 15 * @returns HTMLElement The created overlay DOMObject 16 */ 17 createOverlay: function (title, id, fieldsetcontent, submitcaption, submitaction) { 18 // create overlay div 19 var $dialog = jQuery(document.createElement('div')) 20 .dialog({ 21 autoOpen: false, 22 draggable: true, 23 title: title, 24 resizable: false 25 }) 26 .html('<fieldset>' + 27 fieldsetcontent + 28 '<p class="button_wrap"><button class="button plugin_do_closetask">' + 29 submitcaption + 30 '</button></p>' + 31 '</fieldset>') 32 .parent() 33 .attr('id', id) 34 .addClass('plugin_do_popup') 35 .hide() 36 .appendTo('.dokuwiki:first'); 37 38 // add event handlers 39 jQuery('#' + id + ' .ui-dialog-titlebar-close').click(function () { // close button 40 $dialog.toggle(); 41 }); 42 43 $dialog 44 .keydown(function (e) { 45 if (e.keyCode !== 13) { //Enter 46 return true; 47 } 48 49 submitaction(); 50 $dialog.hide(); 51 52 e.preventDefault(); 53 e.stopPropagation(); 54 return false; 55 }) 56 .find('.plugin_do_closetask').click(function (e) { 57 submitaction(); 58 $dialog.hide(); 59 60 e.preventDefault(); 61 return false; 62 }); 63 64 return $dialog; 65 }, 66 67 /******************************************** 68 * functions for interactive tasks in pages * 69 ********************************************/ 70 71 /** 72 * Toggle status of task, when symbol of task is clicked 73 * 74 * @param event 75 * @returns {boolean} 76 */ 77 toggle_status: function (event) { 78 if (jQuery(this).parent().hasClass('plugin_do_done')) { 79 //mark undone 80 PluginDo.save_update_SingleTask(this); 81 82 } else { 83 //mark done, popup for commitmessage 84 var $popup = jQuery('#do__commit_popup') 85 .toggle() 86 .css({ 87 'position': 'absolute', 88 'top': (event.pageY + 10) + 'px', 89 'left': (event.pageX - 150) + 'px' 90 }); 91 $popup[0].__me = this; 92 jQuery('#do__popup_msg').focus(); 93 } 94 event.preventDefault(); 95 event.stopPropagation(); 96 return false; 97 }, 98 99 /** 100 * callback performs close action of Close dialog 101 */ 102 closeSingleTask: function () { 103 PluginDo.save_update_SingleTask(jQuery('#do__commit_popup')[0].__me); 104 jQuery('#do__popup_msg').val(''); 105 }, 106 107 /** 108 * Switch the status of a image src 109 * 110 * @param {boolean} done true for done tasks, false for undone 111 * @param {jQuery} $applyto jQuery object with DOM elements where to update images 112 */ 113 switchDoNr: function (done, $applyto) { 114 var newImg = done ? 'undone.png' : 'done.png'; 115 116 $applyto.find('img').attr('src', DOKU_BASE + 'lib/plugins/do/pix/' + newImg); 117 }, 118 119 /** 120 * Set the task title 121 * 122 * @param {Array} assignees assignees for the task, undefined for autodetect 123 * @param {string} due the task's due date, undefined for autodetect 124 * @param {string} closedby who closed the task, undefined for not closed, yet 125 * @param {string} closedon when was the task closed, undefined for not closed, yet 126 * @param {jQuery} $applyto jQuery object with DOM elements where to add the title tag, undefined for rootNode 127 */ 128 buildTitle: function ($applyto, assignees, due, closedby, closedon) { 129 var titelNo = 4; 130 131 // determine assignees 132 if (!assignees || !assignees.length) { 133 //take the assignees of the first task or table row when tasks are duplicated 134 var $assigneeobjs = $applyto.first().find('span.plugin_do_meta_user'); 135 if ($assigneeobjs.length === 0) { 136 $assigneeobjs = $applyto.parent('td').parent().first().find('td.plugin_do_assignee'); 137 } 138 assignees = []; 139 $assigneeobjs.each(function (i, assignee) { 140 assignees.push(PluginDo.stripTags(jQuery(assignee).html())); 141 }); 142 } 143 if (assignees.length > 0) { 144 titelNo -= 2; 145 } 146 147 // determine due date 148 if (!due) { 149 var $due = $applyto.first().find('span.plugin_do_meta_date'); 150 if ($due.length === 0) { 151 $due = $applyto.parent('td').parent().first().find('td.plugin_do_date'); 152 } 153 due = PluginDo.stripTags($due.length ? $due.html() : ''); 154 } 155 if (due !== '') { 156 titelNo -= 1; 157 } 158 var newTitle = PluginDo.getLang('title' + titelNo, assignees.join(', '), due); 159 160 // is closed? 161 if (closedon) { 162 newTitle += ' ' + PluginDo.getLang('done', closedon); 163 } 164 165 // who closed it? 166 if (closedby || closedby === '') { 167 if (closedby === '') closedby = LANG.plugins['do'].by_unknown; 168 newTitle += ' ' + PluginDo.getLang('closedby', closedby); 169 } 170 171 // apply the title 172 $applyto.attr('title', newTitle); 173 }, 174 175 /** 176 * get a localized string by name. 177 * 178 * if a arg is given it replaces %s with arg 179 * 180 * @return {string} localized text. 181 */ 182 getLang: function (name, arg1, arg2) { 183 var lang = LANG.plugins['do'][name]; 184 185 if (arg1 === null) { 186 return lang; 187 } else if (arg2 === null) { 188 return lang.replace(/%(1\$)?(s|d)/, arg1); 189 } else { 190 return lang.replace(/%(1\$)?(s|d)/, arg1) 191 .replace(/%(2\$)?(s|d)/, arg2); 192 } 193 }, 194 195 /** 196 * Escapes html entities from a string. 197 */ 198 hsc: function (text) { 199 return text 200 .replace('&', '&') 201 .replace('<', '<') 202 .replace('>', '>') 203 .replace('"', '"'); 204 }, 205 206 /** 207 * Removes tags from string 208 * 209 * @param {string} text 210 * @returns {string} untagged text 211 */ 212 stripTags: function (text) { 213 return text.replace(/(<([^>]+)>)/ig, ""); 214 }, 215 216 /** 217 * Determine if a element is late 218 * 219 * @param {HTMLElement} ele span with due date 220 * @returns {boolean} whether task is still open and exceed due date 221 */ 222 isLate: function (ele) { 223 if (typeof ele.parentNode == 'undefined') { 224 return false; 225 } 226 var $ele = jQuery(ele); 227 228 if ($ele.parent().parent().hasClass('plugin_do_done')) { 229 return false; 230 } 231 var currentdate = new Date(), 232 duedate = new Date($ele.html()); 233 234 return duedate.getTime() < currentdate.getTime(); 235 }, 236 237 /** 238 * Returns value of requested url parameter 239 * 240 * @param {string} url 241 * @param {string} keyname 242 * @returns {*} 243 */ 244 urlParam: function (url, keyname) { 245 var results = new RegExp('[\\?&]' + keyname + '=([^&#]*)').exec(url); 246 if (results === null) { 247 return null; 248 } else { 249 return results[1] || 0; 250 } 251 }, 252 253 /** 254 * Update statistics in the page task status view 255 * 256 * done: All tasks done 257 * undone: There are %1$d open tasks 258 * late: There are %1$d open tasks, %2$d are late.' 259 * 260 * @param {string} response result return by ajax toggle request 261 * @param {jQuery} $itemspan 262 */ 263 updatePageTaskView: function (response, $itemspan) { 264 var $pagestat = jQuery('.plugin__do_pagetasks'); 265 if ($pagestat.length > 0) { 266 var count = parseInt($pagestat.children().first().html(), 10), 267 latecount = 0, 268 newClass, 269 oldClass = $pagestat.children().first().attr('class'), 270 $cdate = $itemspan.find('span.plugin_do_meta_date'); 271 272 if (response) { 273 // task is marked done 274 if (count == 1) { 275 newClass = 'do_done'; 276 } else if (oldClass != 'do_late') { 277 newClass = 'do_undone'; 278 } else { 279 newClass = 'do_undone'; 280 jQuery('.plugin_do_meta_date') 281 .each(function (i, doitem) { 282 if (PluginDo.isLate(doitem)) { 283 newClass = 'do_late'; 284 latecount++; 285 } 286 }); 287 } 288 count -= 1; 289 } else { 290 //task is marked undone 291 if (count === 0) { 292 if ($cdate.length && PluginDo.isLate($cdate[0])) { 293 newClass = 'do_late'; 294 latecount++; 295 } else { 296 newClass = 'do_undone'; 297 } 298 } else { 299 newClass = 'do_undone'; 300 jQuery('.plugin_do_meta_date') 301 .each(function (i, doitem) { 302 if (PluginDo.isLate(doitem)) { 303 newClass = 'do_late'; 304 latecount++; 305 } 306 }); 307 } 308 count += 1; 309 } 310 311 var title = PluginDo.getLang('title_' + newClass.substr(3), count, latecount); 312 313 $pagestat 314 .attr('title', title) 315 .children().first() 316 .html(count) 317 .removeClass().addClass(newClass); 318 } 319 }, 320 321 /** 322 * Save the task change to wiki and update the html in the page 323 * 324 * @param {HTMLElement} me clicked url element 325 */ 326 save_update_SingleTask: function (me) { 327 var $me = jQuery(me), 328 $itemspan = $me.parent(), 329 md5v = $itemspan.attr('class').match(/plugin_do_([a-f0-9]{32})/)[1], 330 $dotags = jQuery('.plugin_do_' + md5v), 331 332 done = $itemspan.hasClass('plugin_do_done'), 333 param = { 334 call: 'plugin_do', 335 do_page: decodeURIComponent(PluginDo.urlParam(me.search.substring(1), 'do_page')), 336 do_md5: decodeURIComponent(PluginDo.urlParam(me.search.substring(1), 'do_md5')), 337 do_commit: jQuery('#do__popup_msg').val() 338 }; 339 340 if (!done) { 341 var commitmsg = PluginDo.hsc(param.do_commit); 342 //update table(s) 343 $dotags.parent('td').parent().find('td.plugin_do_commit').html(commitmsg ? commitmsg : ''); 344 //update task (inclusive duplicates) 345 commitmsg = (commitmsg ? ' (' + PluginDo.getLang("note_done") + commitmsg + ')' : ''); 346 $dotags.find('span.plugin_do_commit').html(commitmsg); 347 } else { 348 $dotags.parent('td').parent().find('td.plugin_do_commit').html(''); 349 $dotags.find('span.plugin_do_commit').html(''); 350 } 351 352 var $image = $me.find('img'); 353 var donr = !$image.is("img[src*='undone.png']"); 354 $image.attr('src', DOKU_BASE + 'lib/images/throbber.gif'); 355 356 /** 357 * callback to update task when it is toggled 358 * @param response 359 */ 360 var updateSingleTask = function (response) { 361 var closedby = null, 362 closedon = null; 363 if (response == "-1" || response == "-2") { 364 var langkey = 'notallowed'; 365 if (response == "-1") { 366 langkey = "notloggedin"; 367 } 368 alert(PluginDo.getLang(langkey)); 369 370 //remove throbber 371 PluginDo.switchDoNr(!donr, $me); 372 return; 373 } 374 if (response) { 375 $dotags.addClass('plugin_do_done'); 376 if (JSINFO.plugin_do_user_name) { 377 $dotags.parent('td').parent().find('td.plugin_do_status span span').html(JSINFO.plugin_do_user_name); 378 } 379 380 closedby = JSINFO.plugin_do_user_clean; 381 closedon = response; 382 } else { 383 $dotags.removeClass('plugin_do_done'); 384 $dotags.parent('td').parent().find('td.plugin_do_status span span').html(' '); 385 } 386 PluginDo.switchDoNr(donr, $dotags); 387 PluginDo.buildTitle($dotags, [], '', closedby, closedon); 388 389 //update statistics in the page task status view 390 PluginDo.updatePageTaskView(response, $itemspan); 391 }; 392 393 //save changes, ajax returns data to mark fail or success 394 jQuery.ajax({ 395 type: "POST", 396 url: DOKU_BASE + 'lib/exe/ajax.php', 397 data: param, 398 success: updateSingleTask, 399 dataType: 'json' 400 }); 401 }, 402 403 /** 404 * callback which updates do's in the page 405 * so that task changes after last page render are displayed correctly 406 * 407 * @param {Array} doStates ajax response with info from sqlite about tasks in this page 408 */ 409 updateItems: function (doStates) { 410 411 jQuery.each(doStates, function (i, state) { 412 var $dotags = jQuery('.plugin_do_' + state.md5); 413 414 PluginDo.buildTitle($dotags, [], '', state.closedby, state.status); 415 PluginDo.switchDoNr(!state.status, $dotags); 416 if (state.status) { 417 $dotags.addClass('plugin_do_done'); 418 } 419 420 if (state.msg) { 421 var msg = PluginDo.hsc(state.msg); 422 $dotags.find('span.plugin_do_commit').html(' (' + PluginDo.getLang("note_done") + msg + ')'); 423 } 424 }); 425 }, 426 427 /******************************************** 428 * Toolbar functions * 429 ********************************************/ 430 431 old_select: null, 432 $toolbardialog: null, 433 textarea: null, 434 435 initDialog: function (edid) { 436 PluginDo.textarea = jQuery('#' + edid)[0]; 437 438 var fieldsetcontent = ''; 439 jQuery.each(['assign', 'date'], function (i, input) { 440 fieldsetcontent += 441 '<p>' + 442 '<label for="do__popup_' + input + '">' + 443 LANG.plugins['do']['popup_' + input] + 444 '</label>' + 445 '<input class="edit" id="do__popup_' + input + '" />' + 446 '</p>'; 447 }); 448 449 // prepare hidden overlay 450 PluginDo.$toolbardialog = PluginDo.createOverlay( 451 LANG.plugins['do'].popup_title, 452 'do__popup', 453 fieldsetcontent, 454 LANG.plugins['do'].popup_submit, 455 PluginDo.insertDoSyntax 456 ); 457 458 jQuery('#do__popup_date').datepicker({ 459 dateFormat: "yy-mm-dd", 460 changeMonth: true, 461 changeYear: true 462 }); 463 464 // if the bureaucracy plugin is installed, use its user autocompletion 465 jQuery('#do__popup_assign').addClass('userspicker'); 466 }, 467 468 toggleToolbarDialog: function (e) { 469 PluginDo.old_select = DWgetSelection(PluginDo.textarea); 470 var $popup_date = jQuery('#do__popup_date'); 471 var $popup_assign = jQuery('#do__popup_assign'); 472 473 //check if a task was selected and load it's data 474 var txt = PluginDo.old_select.getText(); 475 $popup_date.val(''); 476 $popup_assign.val(''); 477 var m = txt.match(/<do ([^>]*)>[\s\S]*<\/do>/); 478 if (m) { 479 var users = m[1]; 480 m = users.match(/\d\d\d\d-\d\d-\d\d/); 481 if (m) { 482 var date = m[0]; 483 users = users.replace(date, ''); 484 $popup_date.val(date); 485 } 486 users = users.replace(/^\s+/, ''); 487 users = users.replace(/\s+$/, ''); 488 $popup_assign.val(users); 489 } 490 491 PluginDo.$toolbardialog.css({ 492 'position': 'absolute', 493 'top': (e.pageY + 10) + 'px', 494 'left': (e.pageX - 100) + 'px' 495 }); 496 return PluginDo.$toolbardialog.toggle(); 497 }, 498 499 /** 500 * The submit action of toolbar dialog 501 */ 502 insertDoSyntax: function () { 503 // Validate data 504 var $popup_date = jQuery('#do__popup_date'); 505 var $popup_assign = jQuery('#do__popup_assign'); 506 507 var pre = '<do'; 508 if ($popup_date.val() && $popup_date.val().match(/^[0-9]{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])/)) { 509 pre += ' ' + $popup_date.val(); 510 } 511 if ($popup_assign.val()) { 512 pre += ' ' + $popup_assign.val(); 513 } 514 pre += '>'; 515 516 var sel = DWgetSelection(PluginDo.textarea); 517 if (sel.start === 0 && sel.end === 0) sel = PluginDo.old_select; 518 519 var stxt = sel.getText(); 520 521 //strip any previous do tags 522 var m = stxt.match(/<do ([^>]*)>[\s\S]*<\/do>/); 523 if (m) { 524 // we have previous tags, replace them 525 stxt = stxt.replace(/<do ([^>]*)>/, pre); 526 } else { 527 // no selection or previous tags, add them 528 stxt = pre + stxt + '</do>'; 529 } 530 531 pasteText(sel, stxt); 532 $popup_date.val(''); 533 $popup_assign.val(''); 534 } 535}; 536