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('&', '&amp;')
201            .replace('<', '&lt;')
202            .replace('>', '&gt;')
203            .replace('"', '&quot;');
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 ? '&nbsp;(' + 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('&nbsp;');
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('&nbsp;(' + 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