root/branches/release-29/mt-static/mt.js @ 1308

Revision 1308, 94.7 kB (checked in by bsmith, 23 months ago)

Merging commits from release-28 changesets 1274 to 1306

  • Property svn:mime-type set to text/javascript
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1/*
2# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
3# This program is distributed under the terms of the
4# GNU General Public License, version 2.
5#
6# $Id$
7*/
8
9var pager;
10var CMSScriptURI;
11var ScriptURI;
12var ScriptBaseURI;
13var StaticURI;
14var HelpBaseURI;
15var Lexicon = {};
16var itemset_options = {};
17
18if ((!(navigator.appVersion.indexOf('MSIE') != -1) &&
19      (parseInt(navigator.appVersion)==4))) {
20    document.write("<style type=\"text/css\">");
21    document.write("body { margin-top: -8px; margin-left: -8px; }"); 
22    document.write("</style>");
23}
24
25var origWidth, origHeight;
26if ((navigator.appName == 'Netscape') &&
27    (parseInt(navigator.appVersion) == 4)) {
28    origWidth = innerWidth;
29    origHeight = innerHeight;
30    window.onresize = restore;
31}
32
33function restore () {
34    if (innerWidth != origWidth || innerHeight != origHeight)
35        location.reload();
36}
37
38function doRebuild (blogID, otherParams) {
39    window.open(CMSScriptURI + '?__mode=rebuild_confirm&blog_id=' + blogID + '&' + otherParams, 'rebuild', 'width=400,height=400,resizable=yes');
40}
41
42function openManual (section, page) {
43    var url;
44    if (page)
45        url = HelpBaseURI + 'help/' + section + '/' + page + '/';
46    else if (section)
47        url = HelpBaseURI + 'help/' + section + '/';
48    else
49        url = HelpBaseURI + 'help/';
50    window.open(url, 'mt_help', 
51'scrollbars=yes,status=yes,resizable=yes,toolbar=yes,location=yes,menubar=yes');
52    return false;
53}
54
55function countMarked (f, nameRestrict) {
56    var count = 0;
57    var e = f.id;
58    if (!e) return 0;
59    if (e.type && e.type == 'hidden') return 1;
60    if (e.value && e.checked)
61        count++;
62    else
63    if (nameRestrict) {
64        for (i=0; i<e.length; i++)
65            if (e[i].checked && (e[i].name == nameRestrict))
66                count++;
67    } else {
68        for (i=0; i<e.length; i++)
69            if (e[i].checked)
70                count++;
71    }
72   return count;
73}
74
75//For make-js script
76//trans('delete');
77//trans('remove');
78//trans('enable');
79//trans('disable');
80function doRemoveItems (f, singular, plural, nameRestrict, args, params) {
81    if (params && (typeof(params) == 'string')) {
82        params = { 'mode': params };
83    } else if (!params) {
84        params = {}
85    }
86    var verb = params['verb'] || trans('delete');
87    var mode = params['mode'] || 'delete';
88    var object_type;
89    if (params['type']) {
90        object_type = params['type'];
91    } else {
92        for (var i = 0; i < f.childNodes.length; i++) {
93            if (f.childNodes[i].name == '_type') {
94                object_type = f.childNodes[i].value;
95                break;
96            }
97        }
98    }
99    var count = countMarked(f, nameRestrict);
100    if (!count) {
101        alert(params['none_prompt'] || trans('You did not select any [_1] to [_2].', plural, verb));
102        return false;
103    }
104    var singularMessage = params['singular_prompt'] || trans('Are you sure you want to [_2] this [_1]?');
105    var pluralMessage = params['plural_prompt'] || trans('Are you sure you want to [_3] the [_1] selected [_2]?');
106    if (object_type == 'role') {
107        singularMessage = trans('Are you certain you want to remove this role? By doing so you will be taking away the permissions currently assigned to any users and groups associated with this role.');
108        pluralMessage = trans('Are you certain you want to remove these [_1] roles? By doing so you will be taking away the permissions currently assigned to any users and groups associated with these roles.');
109    }
110    if (confirm(count == 1 ? trans(singularMessage, singular, verb) : trans(pluralMessage, count, plural, verb))) {
111        return doForMarkedInThisWindow(f, singular, plural, nameRestrict, mode, args);
112    }
113}
114
115/* Widget support functions */
116// function addWidget(el, f) {
117//     if (!TC.Client) return;
118//     // Add a new widget to the top of the 'el' element
119//     var args = DOM.getFormData( f );
120//     args['json'] = '1';
121//     TC.Client.call({
122//         'load': function(c) { addWidgetToPage(el, c); },
123//         'error': function() { showMsg("Error adding widget.", "widget-updated", "alert"); },
124//         'method': 'POST',
125//         'uri': ScriptURI,
126//         'arguments': args
127//     });
128//     return false;
129// }
130//
131// function addWidgetToPage(el, c) {
132//     el = TC.elementOrId(el);
133//     var result;
134//     try {
135//         result = eval('(' + c.responseText + ')');
136//     } catch(e) {
137//         showMsg("Error adding widget.", "widget-updated", "alert");
138//         return;
139//     }
140//     var new_node = document.createElement('div');
141//     new_node.innerHTML = result.result.html;
142//     var next;
143//     if (el.hasChildNodes()) {
144//         if (el.firstChild.nodeType != Node.ELEMENT_NODE)
145//             next = DOM.getNextElement(el.firstChild);
146//         else
147//             next = el.firstChild;
148//     }
149//     if (next) {
150//         el.insertBefore(new_node, next);
151//     } else {
152//         el.appendChild(new_node);
153//     }
154// }
155
156function removeWidget(id) {
157    var f = getByID(id + '-form');
158    f['widget_action'].value = 'remove';
159    if ((f['widget_singular'].value == '1') || (!TC.Client)) {
160        f.submit();
161        return;
162    }
163    var args = DOM.getFormData(f);
164    args['json'] = '1';
165    TC.Client.call({
166        'load': function(c) { removedWidget(id, c); },
167        'error': function() { showMsg("Error removing widget.", "widget-updated", "alert"); },
168        'method': 'POST',
169        'uri': ScriptURI,
170        'arguments': args
171    });
172}
173
174function removedWidget(id, c) {
175    var el = getByID(id);
176    var parent = el.parentNode;
177    parent.removeChild(el);
178}
179
180function updateWidget(id) {
181    var f = getByID(id + "-form");
182    if (!f) return false;
183    f['widget_action'].value = 'save';
184    if (!TC.Client) return true;
185    // if (f['widget_refresh'] && f['widget_refresh'].value) {
186    //     return true;
187    // }
188
189    var args = DOM.getFormData( f );
190    args['json'] = '1';
191    TC.Client.call({
192        'load': function(c, responseText) { updatedWidget(id, responseText); },
193        'error': function() { showMsg("Error updating widget.", "widget-updated", "alert"); },
194        'method': 'POST',
195        'uri': ScriptURI,
196        'arguments': args
197    });
198    return false;
199}
200
201function updatedWidget(id, responseText) {
202    var el = TC.elementOrId(id);
203    var result;
204    try {
205        result = eval('(' + responseText + ')');
206    } catch(e) {
207        showMsg("Error updating widget.", "widget-updated", "alert");
208        return;
209    }
210    if (result.result.html) {
211        // updatePrefs has returned a new widget
212        el.innerHTML = result.result.html;
213    }
214    if (result.result.message) {
215        showMsg(result.result.message, "widget-updated", "info");
216    }
217}
218
219function setObjectStatus (f, singular, plural, new_status, nameRestrict, args) {
220    var count = countMarked(f, nameRestrict);
221    var status_mode = 'enable';
222    var named_status = trans('enable');
223    if (new_status == 0) {
224        status_mode = 'disable';
225        named_status = trans('disable');
226    }
227    if (!count) {
228        alert(trans('You did not select any [_1] to [_2].', plural, named_status));
229        return false;
230    }
231    var toSet = "";
232    for (var i = 0; i < f.childNodes.length; i++) {
233        if (f.childNodes[i].name == '_type') {
234            toSet = f.childNodes[i].value;
235            break;
236        }
237    }
238    if (toSet) {
239        singularMessage = trans('Are you sure you want to [_2] this [_1]?');
240        pluralMessage = trans('Are you sure you want to [_3] the [_1] selected [_2]?');
241        if (confirm(count == 1 ? trans(singularMessage, singular, named_status) : trans(pluralMessage, count, plural, named_status))) {
242            return doForMarkedInThisWindow(f, singular, plural, nameRestrict, status_mode + '_object', args);
243        }
244    } 
245}
246
247function doForMarkedInThisWindow (f, singular, plural, nameRestrict, 
248                                  mode, args, phrase) {
249    var count = countMarked(f, nameRestrict);
250    if (!count) {
251        alert(trans('You did not select any [_1] [_2].', plural, phrase));
252        return false;
253    }
254    f.target = "_top";
255    if (f.elements['itemset_action_input'])
256        f.elements['itemset_action_input'].value = '';
257    if (args) {
258        var opt;
259        var input;
260        if (opt = itemset_options[args['action_name']]) {
261            if (opt['min'] && (count < opt['min'])) {
262                alert(trans('You can only act upon a minimum of [_1] [_2].', opt['min'], plural));
263                return false;
264            } else if (opt['max'] && (count > opt['max'])) {
265                alert(trans('You can only act upon a maximum of [_1] [_2].', opt['max'], plural));
266                return false;
267            } else if (opt['input']) {
268                if (input = prompt(opt['input'])) {
269                    f.elements['itemset_action_input'].value = input;
270                } else {
271                    return false;
272                }
273            } else if (opt['continue_prompt']) {
274                if (!confirm(opt['continue_prompt'])) {
275                    return false;
276                }
277            }
278            if (opt['dialog']) {
279                f.target = "dialog_iframe";
280                show("dialog-container");
281                DOM.addEventListener( document.body, "keypress", dialogKeyPress, true );
282            }
283        }
284        for (var arg in args) {
285            if (f.elements[arg]) f.elements[arg].value = args[arg];
286        }
287    }
288    f.elements["__mode"].value = mode;
289    f.submit();
290}
291
292function submitFormConfirm(f, mode, message) {
293    log.warn('submitFormConfirm() deprecated');
294    if (confirm(message)) {
295        if (f.elements["__mode"] && mode)
296            f.elements["__mode"].value = mode;
297        f.submit();
298    }
299}
300
301function submitForm(f, mode) {
302    log.warn('submitForm() deprecated');
303    if (f.elements["__mode"] && mode)
304        f.elements["__mode"].value = mode;
305    f.submit();
306}
307
308function doPluginAction(f, plural, phrase) {
309    if (!f) {
310        var forms = document.getElementsByTagName( "form" ); 
311        for ( var i = 0; i < forms.length; i++ ) { 
312            var pas = truth( forms[ i ][ 'plugin_action_selector' ] );
313            if (pas) {
314                f = forms[ i ];
315                break;
316            }
317        }
318    }
319    if (!f)
320        return;
321    var sel = f['plugin_action_selector'];
322    if (sel.length && sel[0].options) sel = sel[0];
323    var action = sel.options[sel.selectedIndex].value;
324    if (action == '0' || action == '') {
325        alert(trans('You must select an action.'));
326        return;
327    }
328    if (itemset_options[action]) {
329        if (itemset_options[action]['js']) {
330            return eval(itemset_options[action]['js'] + '(f,action);');
331        }
332    }
333    return doForMarkedInThisWindow(f, '', plural, 'id', 'itemset_action', {'action_name': action}, phrase);
334}
335
336function updatePluginAction(s) {
337    var frm = s.form;
338    frm.elements['plugin_action_selector'].value = s[s.selectedIndex].value;
339    // synchronize top and bottom plugin action selection
340    var el = frm[s.name];
341    for (var i = 0; i < el.length; i++)
342        if (el[i].selectedIndex != s.selectedIndex)
343            el[i].selectedIndex = s.selectedIndex;
344}
345
346function doItemsAreJunk (f, type, plural, nameRestrict) {
347    doForMarkedInThisWindow(f, type, plural, nameRestrict,
348        'handle_junk', {}, trans('to mark as spam'));
349}
350
351function doItemsAreNotJunk (f, type, plural, nameRestrict) {
352    doForMarkedInThisWindow(f, type, plural, nameRestrict,
353        'not_junk', {}, trans('to remove spam status'));
354}
355
356function dialogKeyPress(e) {
357    if (e.keyCode == 27) {
358        // escape key...
359        DOM.removeEventListener( document.body, "keypress", dialogKeyPress, true );
360        closeDialog();
361    }
362}
363
364function openDialog(f, mode, params) {
365    var url = ScriptURI;
366    url += '?__mode=' + mode;
367    if (params) url += '&' + params;
368    url += '&__type=dialog';
369    if (app) app.closeFlyouts();
370    show("dialog-container");
371    // handle escape key for closing modal dialog
372    DOM.addEventListener( document.body, "keypress", dialogKeyPress, true );
373    openDialogUrl(url);
374    if ( document.all && DOM.getElement( "dialog-container" ) ) {
375        DOM.addClassName( "dialog-container", "hidden" );
376        new Timer(function() {
377            DOM.removeClassName( "dialog-container", "hidden" );
378        }, 500, 1 );
379    }
380    return false;
381}
382
383function openDialogUrl(url) {
384    var iframe = getByID("dialog-iframe");
385    var frame_d = iframe.contentDocument;
386    if (!frame_d) {
387        // Sometimes the contentWindow is unavailable because we've just
388        // unhidden the container div that holds the iframe. If this happens
389        // we have to wait for the contentWindow object to be created
390        // before we can access the document within. This may take an extra
391        // try using a setTimeout on this window.
392        if (iframe.contentWindow)
393            frame_d = iframe.contentWindow.document || iframe.document;
394    }
395    if (frame_d) {
396        frame_d.open();
397        frame_d.write("<html><head><style type=\"text/css\">\n"
398            + "#dialog-indicator {\nposition: relative;\ntop: 200px;\n"
399            + "background: url(" + StaticURI + "images/indicator.gif) "
400            + "no-repeat;\nwidth: 66px;\nheight: 66px;\nmargin: 0 auto;"
401            + "\n}\n</style><script type=\"text/javascript\">\n"
402            + "function init() {\ndocument.location = \"" + url + "\";\n}\n"
403            + "if (window.navigator.userAgent.match(/ AppleWebKit\\//))\n"
404            + "window.setTimeout(\"init()\", 1500);\n"
405            + "else window.onload = init;\n</scr"+"ipt></head><body>"
406            + "<div align=\"center\"><div id=\"dialog-indicator\"></div>"
407            + "</div></body></html>");
408        frame_d.close();
409    } else {
410        window.setTimeout("openDialogUrl('" + url + "')", 100);
411    }
412}
413
414function closeDialog(url) {
415    var w = window;
416    while (w.parent && (w.parent != w))
417        w = w.parent;
418    if (url)
419        w.location = url;
420    else
421        hide("dialog-container", w.document);
422    return false;
423}
424
425function getByID(n, d) {
426    if (!d) d = document;
427    if (d.getElementById)
428        return d.getElementById(n);
429    else if (d.all)
430        return d.all[n];
431}
432
433var canFormat = 0;
434if (document.selection ||
435    (typeof(document.createElement("textarea")["setSelectionRange"]) != "undefined"))
436    canFormat = 1;
437
438function getSelected(e) {
439    if (document.selection) {
440        e.focus();
441        var range = document.selection.createRange();
442        return range.text;
443    } else {
444        var length = e.textLength;
445        var start = e.selectionStart;
446        var end = e.selectionEnd;
447        if (end == 1 || end == 2 && length != undefined) end = length;
448        return e.value.substring(start, end);
449    }
450}
451
452function setSelection(e, v) {
453    if (document.selection) {
454        e.focus();
455        var range = document.selection.createRange();
456        range.text = v;
457    } else {
458        var scrollTop = e.scrollTop;
459        var length = e.textLength;
460        var start = e.selectionStart;
461        var end = e.selectionEnd;
462        if (end == 1 || end == 2 && length != undefined) end = length;
463        e.value = e.value.substring(0, start) + v + e.value.substr(end, length);
464        e.selectionStart = start + v.length;
465        e.selectionEnd = start + v.length;
466        e.scrollTop = scrollTop;
467    }
468    e.focus();
469}
470
471function formatStr(e, v) {
472    if (!canFormat) return;
473    var str = getSelected(e);
474    if (str) setSelection(e, '<' + v + '>' + str + '</' + v + '>');
475    return false;
476}
477
478function mtShortCuts(e) {
479    e = e || event;
480    var code;
481    if (e.keyCode) code = e.keyCode;
482    else if (e.which) code = e.which;
483    el = e.target || e.srcElement;
484    if (el.nodeType == 3) el = el.parentNode; // Safari bug
485    if (e.ctrlKey) {
486        switch(e.keyCode) {
487            case 66: // b
488            case 73: // i
489            case 85: // u
490            disableCtrlDefault(e);
491            if (code == '66') formatStr(el, 'strong');
492            if (code == '73') formatStr(el, 'em');
493            if (code == '85') formatStr(el, 'u');
494        }
495    }
496}
497
498function disableCtrlDefault(e) {
499    if(e.preventDefault) {
500        e.preventDefault();
501    } else {
502        e.returnValue = false;
503    }
504    return;
505}
506
507function insertLink(e, isMail) {
508    if (!canFormat) return;
509    var str = getSelected(e);
510    var link = '';
511    if (!isMail) {
512        if (str.match(/^https?:/)) {
513            link = str;
514        } else if (str.match(/^(\w+\.)+\w{2,5}\/?/)) {
515            link = 'http://' + str;
516        } else if (str.match(/ /)) {
517            link = 'http://';
518        } else {
519            link = 'http://' + str;
520        }
521    } else {
522        if (str.match(/@/)) {
523            link = str;
524        }
525    }
526    var my_link = prompt(isMail ? trans('Enter email address:') : trans('Enter URL:'), link);
527    if (my_link != null) {
528         if (str == '') str = my_link;
529         if (isMail) my_link = 'mailto:' + my_link;
530        setSelection(e, '<a href="' + my_link + '">' + str + '</a>');
531    }
532    return false;
533}
534
535function execFilter(f) {
536    var filter_col = f['filter-col'].options[f['filter-col'].selectedIndex].value;
537    var opts = f[filter_col+'-val'].options;
538    var filter_val = '';
539    if (opts) {
540        filter_val = opts[f[filter_col+'-val'].selectedIndex].value;
541    } else if (f[filter_col+'-val'].value) {
542        filter_val = f[filter_col+'-val'].value;
543    }
544    getByID('filter').value = filter_col;
545    getByID('filter_val').value = filter_val;
546    getByID('filter-form').submit();
547    return false;
548}
549
550function setFilterVal(value) {
551    var f = getByID('filter-select-form');
552    if (value == '') return;
553    var filter_col = f['filter-col'].options[f['filter-col'].selectedIndex].value;
554    var val_span = getByID("filter-text-val");
555    if (filter_col) {
556        var filter_fld = f[filter_col+'-val'];
557        if (filter_fld.options) {
558            for (var i = 0; i < filter_fld.options.length; i++) {
559                if (filter_fld.options[i].value == value) {
560                    value = filter_fld.options[i].text;
561                    // strip off any leading spacing found on category lists
562                    value = value.replace(/^(\xA0 )+/, '');
563                    filter_fld.selectedIndex = i;
564                    if (val_span)
565                        val_span.innerHTML = '<strong>' + value + '</strong>';
566                    break;
567                }
568            }
569        } else if (filter_fld.value) {
570            filter_fld.value = value;
571            if (val_span)
572                val_span.innerHTML = '<strong>' + value + '</strong>';
573        }
574    }
575}
576
577function toggleDisplayOptions() {
578    return toggleActive('display-options');
579}
580
581function toggleEntryDisplayOptions() {
582    return toggleActive('display-options-widget');
583}
584
585function toggleActive( id ) {
586    var div = DOM.getElement( id );
587    if ( !div )
588        return false;
589    if ( DOM.hasClassName( div, 'active' ) )
590        DOM.removeClassName( div, 'active' );
591    else
592        DOM.addClassName( div, 'active' );
593    return false;
594}
595
596function toggleHidden( id ) {
597    var div = DOM.getElement( id );
598    if ( !div )
599        return false;
600    if ( DOM.hasClassName( div, 'hidden' ) )
601        DOM.removeClassName( div, 'hidden' );
602    else
603        DOM.addClassName( div, 'hidden' );
604    return false;
605}
606
607function tabToggle(selectedTab, tabs) {
608    log.warn('tabToggle is deprecated. use mt tab delegate');
609    for (var i = 0; i < tabs.length; i++) {
610        var tabObject = getByID(tabs[i] + '-tab');
611        var contentObject = getByID(tabs[i] + '-panel');
612           
613        if (tabObject && contentObject) {
614            if (tabs[i] == selectedTab) {
615                DOM.addClassName( tabObject, 'selected-tab' );
616                DOM.addClassName( contentObject, 'selected-tab-panel' );
617            } else {
618                DOM.removeClassName( tabObject, 'selected-tab' );
619                DOM.removeClassName( contentObject, 'selected-tab-panel' );
620            }
621        }
622    }
623    return false;
624}
625
626function show(id, d, style) {
627    var el = getByID(id, d);
628    if (!el) return;
629    if ( DOM.hasClassName( el, "hidden" ) ) {
630        DOM.removeClassName ( el, "hidden");
631    } else {
632        el.style.display = style ? style : 'block';
633    }
634    /* hack */
635    if ( DOM.hasClassName( el, "autolayout-height-parent" ) )
636        DOM.setHeight( el, finiteInt( el.parentNode.clientHeight ) );
637}
638
639function hide(id, d) {
640    var el = getByID(id, d);
641    if (!el) return;
642    if ( !DOM.hasClassName( el, "hidden" ) ) {
643        DOM.addClassName ( el, "hidden");
644    } else {
645        el.style.display = 'none';
646    }
647}
648
649function showReply(id, d, style) {
650    var el = getByID(id, d);
651    if (!el) return;
652    el.style.visibility = style ? style : 'visible';
653}
654
655function hideReply(id, d) {
656    var el = getByID(id, d);
657    if (!el) return;
658    el.style.visibility = 'hidden';
659}
660
661function toggleSubPrefs(c) {
662    var div = TC.elementOrId((c.name || c.id)+"-prefs") || TC.elementOrId((c.name || c.id)+'_prefs');
663    if (div) {
664        if (c.type) {
665            var on = c.type == 'checkbox' ? c.checked : c.value != 0;
666            if (on) {
667                TC.removeClassName(div, "hidden");
668            } else {
669                TC.addClassName(div, "hidden");
670            }
671            // div.style.display = on ? "block" : "none";
672        } else {
673            var on = div.style.display && div.style.display != "none";
674            if (on) {
675                TC.addClassName(div, "hidden");
676            } else {
677                TC.removeClassName(div, "hidden");
678            }
679            // div.style.display = on ? "none" : "block";
680        }
681    }
682    return false;
683}
684
685function toggleAdvancedPrefs(evt, c) {
686    evt = evt || window.event;
687    var id;
688    var obj;
689    if (!c || (typeof c != 'string')) {
690        c = c || evt.target || evt.srcElement;
691        id = c.id || c.name;
692        obj = c;
693    } else {
694        id = c;
695    }
696    var div = getByID( id + '-advanced');
697    if (div) {
698        if (obj) {
699            var shiftKey = evt ? evt.shiftKey : undefined;
700                if (evt && shiftKey && obj.type == 'checkbox')
701                obj.checked = true;
702            var on = obj.type == 'checkbox' ? obj.checked : obj.value != 0;
703            if (on && shiftKey) {
704                if (div.style.display == "block")
705                    div.style.display = "none";
706                else
707                    div.style.display = "block";
708            } else {
709                div.style.display = "none";
710            }
711        } else {
712            if (div.style.display == "block")
713                div.style.display = "none";
714            else
715                div.style.display = "block";
716        }
717    }
718    return false;
719}
720
721function trans(str) {
722    if (Lexicon && Lexicon[str])
723        str = Lexicon[str];
724    if (arguments.length > 1)
725        for (var i = 1; i <= arguments.length; i++) {
726            /* This matches [_#] or [_#:comment] */
727            str = str.replace(new RegExp('\\[_' + i + '(?:\:[^\\]]+)?\\]', 'g'), arguments[i]);
728            var re = new RegExp('\\[quant,_' + i + ',(.+?)(?:,(.+?))?(?:\:[^\\]]+)?\\]');
729            var matches;
730            while (matches = str.match(re)) {
731                if (arguments[i] != 1)
732                    str = str.replace(re, arguments[i] + ' ' +
733                        ((typeof(matches[2]) != 'undefined') ? matches[2]
734                                                               : matches[1]
735                                                                 + 's'));
736                else
737                    str = str.replace(re, arguments[i] + ' ' + matches[1]);
738            }
739        }
740    return str;
741}
742
743function junkScoreNudge(amount, id, max) {
744    if (max == undefined) max = 10;
745    var fld = getByID(id);
746    score = fld.value;
747    score.replace(/\+/, '');
748    score = parseFloat(score) + amount;
749    if (isNaN(score)) score = amount;
750    if (score > max) score = max;
751    if (score < 0) score = 0;
752    fld.value = score;
753    return false;
754}
755
756var dirify_table = {
757    "\u00C0": 'A',    // A`
758    "\u00E0": 'a',    // a`
759    "\u00C1": 'A',    // A'
760    "\u00E1": 'a',    // a'
761    "\u00C2": 'A',    // A^
762    "\u00E2": 'a',    // a^
763    "\u0102": 'A',    // latin capital letter a with breve
764    "\u0103": 'a',    // latin small letter a with breve
765    "\u00C6": 'AE',   // latin capital letter AE
766    "\u00E6": 'ae',   // latin small letter ae
767    "\u00C5": 'A',    // latin capital letter a with ring above
768    "\u00E5": 'a',    // latin small letter a with ring above
769    "\u0100": 'A',    // latin capital letter a with macron
770    "\u0101": 'a',    // latin small letter a with macron
771    "\u0104": 'A',    // latin capital letter a with ogonek
772    "\u0105": 'a',    // latin small letter a with ogonek
773    "\u00C4": 'A',    // A:
774    "\u00E4": 'a',    // a:
775    "\u00C3": 'A',    // A~
776    "\u00E3": 'a',    // a~
777    "\u00C8": 'E',    // E`
778    "\u00E8": 'e',    // e`
779    "\u00C9": 'E',    // E'
780    "\u00E9": 'e',    // e'
781    "\u00CA": 'E',    // E^
782    "\u00EA": 'e',    // e^
783    "\u00CB": 'E',    // E:
784    "\u00EB": 'e',    // e:
785    "\u0112": 'E',    // latin capital letter e with macron
786    "\u0113": 'e',    // latin small letter e with macron
787    "\u0118": 'E',    // latin capital letter e with ogonek
788    "\u0119": 'e',    // latin small letter e with ogonek
789    "\u011A": 'E',    // latin capital letter e with caron
790    "\u011B": 'e',    // latin small letter e with caron
791    "\u0114": 'E',    // latin capital letter e with breve
792    "\u0115": 'e',    // latin small letter e with breve
793    "\u0116": 'E',    // latin capital letter e with dot above
794    "\u0117": 'e',    // latin small letter e with dot above
795    "\u00CC": 'I',    // I`
796    "\u00EC": 'i',    // i`
797    "\u00CD": 'I',    // I'
798    "\u00ED": 'i',    // i'
799    "\u00CE": 'I',    // I^
800    "\u00EE": 'i',    // i^
801    "\u00CF": 'I',    // I:
802    "\u00EF": 'i',    // i:
803    "\u012A": 'I',    // latin capital letter i with macron
804    "\u012B": 'i',    // latin small letter i with macron
805    "\u0128": 'I',    // latin capital letter i with tilde
806    "\u0129": 'i',    // latin small letter i with tilde
807    "\u012C": 'I',    // latin capital letter i with breve
808    "\u012D": 'i',    // latin small letter i with breve
809    "\u012E": 'I',    // latin capital letter i with ogonek
810    "\u012F": 'i',    // latin small letter i with ogonek
811    "\u0130": 'I',    // latin capital letter with dot above
812    "\u0131": 'i',    // latin small letter dotless i
813    "\u0132": 'IJ',   // latin capital ligature ij
814    "\u0133": 'ij',   // latin small ligature ij
815    "\u0134": 'J',    // latin capital letter j with circumflex
816    "\u0135": 'j',    // latin small letter j with circumflex
817    "\u0136": 'K',    // latin capital letter k with cedilla
818    "\u0137": 'k',    // latin small letter k with cedilla
819    "\u0138": 'k',    // latin small letter kra
820    "\u0141": 'L',    // latin capital letter l with stroke
821    "\u0142": 'l',    // latin small letter l with stroke
822    "\u013D": 'L',    // latin capital letter l with caron
823    "\u013E": 'l',    // latin small letter l with caron
824    "\u0139": 'L',    // latin capital letter l with acute
825    "\u013A": 'l',    // latin small letter l with acute
826    "\u013B": 'L',    // latin capital letter l with cedilla
827    "\u013C": 'l',    // latin small letter l with cedilla
828    "\u013F": 'l',    // latin capital letter l with middle dot
829    "\u0140": 'l',    // latin small letter l with middle dot
830    "\u00D2": 'O',    // O`
831    "\u00F2": 'o',    // o`
832    "\u00D3": 'O',    // O'
833    "\u00F3": 'o',    // o'
834    "\u00D4": 'O',    // O^
835    "\u00F4": 'o',    // o^
836    "\u00D6": 'O',    // O:
837    "\u00F6": 'o',    // o:
838    "\u00D5": 'O',    // O~
839    "\u00F5": 'o',    // o~
840    "\u00D8": 'O',    // O/
841    "\u00F8": 'o',    // o/
842    "\u014C": 'O',    // latin capital letter o with macron
843    "\u014D": 'o',    // latin small letter o with macron
844    "\u0150": 'O',    // latin capital letter o with double acute
845    "\u0151": 'o',    // latin small letter o with double acute
846    "\u014E": 'O',    // latin capital letter o with breve
847    "\u014F": 'o',    // latin small letter o with breve
848    "\u0152": 'OE',   // latin capital ligature oe
849    "\u0153": 'oe',   // latin small ligature oe
850    "\u0154": 'R',    // latin capital letter r with acute
851    "\u0155": 'r',    // latin small letter r with acute
852    "\u0158": 'R',    // latin capital letter r with caron
853    "\u0159": 'r',    // latin small letter r with caron
854    "\u0156": 'R',    // latin capital letter r with cedilla
855    "\u0157": 'r',    // latin small letter r with cedilla
856    "\u00D9": 'U',    // U`
857    "\u00F9": 'u',    // u`
858    "\u00DA": 'U',    // U'
859    "\u00FA": 'u',    // u'
860    "\u00DB": 'U',    // U^
861    "\u00FB": 'u',    // u^
862    "\u00DC": 'U',    // U:
863    "\u00FC": 'u',    // u:
864    "\u016A": 'U',    // latin capital letter u with macron
865    "\u016B": 'u',    // latin small letter u with macron
866    "\u016E": 'U',    // latin capital letter u with ring above
867    "\u016F": 'u',    // latin small letter u with ring above
868    "\u0170": 'U',    // latin capital letter u with double acute
869    "\u0171": 'u',    // latin small letter u with double acute
870    "\u016C": 'U',    // latin capital letter u with breve
871    "\u016D": 'u',    // latin small letter u with breve
872    "\u0168": 'U',    // latin capital letter u with tilde
873    "\u0169": 'u',    // latin small letter u with tilde
874    "\u0172": 'U',    // latin capital letter u with ogonek
875    "\u0173": 'u',    // latin small letter u with ogonek
876    "\u00C7": 'C',    // ,C
877    "\u00E7": 'c',    // ,c
878    "\u0106": 'C',    // latin capital letter c with acute
879    "\u0107": 'c',    // latin small letter c with acute
880    "\u010C": 'C',    // latin capital letter c with caron
881    "\u010D": 'c',    // latin small letter c with caron
882    "\u0108": 'C',    // latin capital letter c with circumflex
883    "\u0109": 'c',    // latin small letter c with circumflex
884    "\u010A": 'C',    // latin capital letter c with dot above
885    "\u010B": 'c',    // latin small letter c with dot above
886    "\u010E": 'D',    // latin capital letter d with caron
887    "\u010F": 'd',    // latin small letter d with caron
888    "\u0110": 'D',    // latin capital letter d with stroke
889    "\u0111": 'd',    // latin small letter d with stroke
890    "\u00D1": 'N',    // N~
891    "\u00F1": 'n',    // n~
892    "\u0143": 'N',    // latin capital letter n with acute
893    "\u0144": 'n',    // latin small letter n with acute
894    "\u0147": 'N',    // latin capital letter n with caron
895    "\u0148": 'n',    // latin small letter n with caron
896    "\u0145": 'N',    // latin capital letter n with cedilla
897    "\u0146": 'n',    // latin small letter n with cedilla
898    "\u0149": 'n',    // latin small letter n preceded by apostrophe
899    "\u014A": 'N',    // latin capital letter eng
900    "\u014B": 'n',    // latin small letter eng
901    "\u00DF": 'ss',   // double-s
902    "\u015A": 'S',    // latin capital letter s with acute
903    "\u015B": 's',    // latin small letter s with acute
904    "\u0160": 'S',    // latin capital letter s with caron
905    "\u0161": 's',    // latin small letter s with caron
906    "\u015E": 'S',    // latin capital letter s with cedilla
907    "\u015F": 's',    // latin small letter s with cedilla
908    "\u015C": 'S',    // latin capital letter s with circumflex
909    "\u015D": 's',    // latin small letter s with circumflex
910    "\u0218": 'S',    // latin capital letter s with comma below
911    "\u0219": 's',    // latin small letter s with comma below
912    "\u0164": 'T',    // latin capital letter t with caron
913    "\u0165": 't',    // latin small letter t with caron
914    "\u0162": 'T',    // latin capital letter t with cedilla
915    "\u0163": 't',    // latin small letter t with cedilla
916    "\u0166": 'T',    // latin capital letter t with stroke
917    "\u0167": 't',    // latin small letter t with stroke
918    "\u021A": 'T',    // latin capital letter t with comma below
919    "\u021B": 't',    // latin small letter t with comma below
920    "\u0192": 'f',    // latin small letter f with hook
921    "\u011C": 'G',    // latin capital letter g with circumflex
922    "\u011D": 'g',    // latin small letter g with circumflex
923    "\u011E": 'G',    // latin capital letter g with breve
924    "\u011F": 'g',    // latin small letter g with breve
925    "\u0120": 'G',    // latin capital letter g with dot above
926    "\u0121": 'g',    // latin small letter g with dot above
927    "\u0122": 'G',    // latin capital letter g with cedilla
928    "\u0123": 'g',    // latin small letter g with cedilla
929    "\u0124": 'H',    // latin capital letter h with circumflex
930    "\u0125": 'h',    // latin small letter h with circumflex
931    "\u0126": 'H',    // latin capital letter h with stroke
932    "\u0127": 'h',    // latin small letter h with stroke
933    "\u0174": 'W',    // latin capital letter w with circumflex
934    "\u0175": 'w',    // latin small letter w with circumflex
935    "\u00DD": 'Y',    // latin capital letter y with acute
936    "\u00FD": 'y',    // latin small letter y with acute
937    "\u0178": 'Y',    // latin capital letter y with diaeresis
938    "\u00FF": 'y',    // latin small letter y with diaeresis
939    "\u0176": 'Y',    // latin capital letter y with circumflex
940    "\u0177": 'y',    // latin small letter y with circumflex
941    "\u017D": 'Z',    // latin capital letter z with caron
942    "\u017E": 'z',    // latin small letter z with caron
943    "\u017B": 'Z',    // latin capital letter z with dot above
944    "\u017C": 'z',    // latin small letter z with dot above
945    "\u0179": 'Z',    // latin capital letter z with acute
946    "\u017A": 'z'     // latin small letter z with acute
947};
948
949function dirify (s) {
950    s = s.replace(/<[^>]+>/g, '');
951    for (var p in dirify_table)
952        if (s.indexOf(p) != -1)
953            s = s.replace(new RegExp(p, "g"), dirify_table[p]);
954    s = s.toLowerCase();
955    s = s.replace(/&[^;\s]+;/g, '');
956    s = s.replace(/[^-a-z0-9_ ]/g, '');
957    s = s.replace(/\s+/g, '_');
958    s = s.replace(/_+$/, '');
959    s = s.replace(/_+/g, '_');
960    return s;
961}
962
963function setElementValue(domID, newVal) {
964    getByID(domID).value = newVal;
965}
966
967/* pager and datasource */
968
969/***
970 * Datasource class
971 * A class for navigating and displaying data from an AJAX datasource.
972 * Methods:
973 *   constructor(el, datatype): Creates a new datasource using DOM
974 *     element 'el' as a container for the data (table rows typically)
975 *     and datatype is used to communicate the '_type' parameter to
976 *     the server.
977 *   setPager(pager): Sets a Pager class object which is refreshed
978 *     upon receiving new data.
979 *   search(string): Invokes a search on the datasource.
980 *   navigate(offset): Used to navigate to a particular offset within
981 *     the dataset.
982 */
983Datasource = new Class(Object, {
984    init: function(el, datatype) {
985        // this.id = id;
986        // this.document = doc || document;
987        this.element = TC.elementOrId(el);
988        this.searching = false;
989        this.navigating = false;
990        this.type = datatype;
991        this.onUpdate = null;
992    },
993    setPager: function(pager, pager2) {
994        this.pager = pager;
995        if (pager2) this.pager2 = pager2
996        if (pager) pager.datasource = this;
997        if (pager2) pager2.datasource = this;
998        if (pager) pager.render();
999        if (pager2) pager2.render();
1000    },
1001    search: function(str) {
1002        if (this.searching) return;
1003
1004        var doc = TC.getOwnerDocument(this.element);
1005        var args = doc.location.search;
1006        args = args.replace(/^\?/, '');
1007        args = args.replace(/&?offset=\d+/, '');
1008        args = 'search=' + escape(str) + (args ? '&' + args : '') + '&json=1';
1009        if (this.type) {
1010            args = args.replace(/&?_type=\w+/, '');
1011            args += '&_type=' + this.type;
1012        }
1013
1014        this.searching = true;
1015        if (this.pager)
1016            this.pager.render();
1017        if (this.pager2)
1018            this.pager2.render();
1019        var self = this;
1020        TC.Client.call({
1021            'load': function(c,r) { self.searched(r); },
1022            'error': function() { alert("Error during search."); self.searched(null); },
1023            'method': 'POST',
1024            'uri': ScriptURI,
1025            'arguments': args
1026        });
1027    },
1028    searched: function(c) {
1029        this.searching = false;
1030        if (c) {
1031            try {
1032                data = eval('(' + c + ')');
1033                this.update(data['html']);
1034                if (this.pager)
1035                    this.pager.setState({});
1036                if (this.pager2)
1037                    this.pager2.setState({});
1038            } catch (e) {
1039                alert("Error in response: " + e);
1040                if (this.pager)
1041                    this.pager.render();
1042                if (this.pager2)
1043                    this.pager2.render();
1044            }
1045        } else {
1046            if (this.pager)
1047                this.pager.render();
1048            if (this.pager2)
1049                this.pager2.render();
1050        }
1051    },
1052    update: function(html) {
1053        if (!this.element) return;
1054        this.element.innerHTML = html;
1055        this.updated();
1056    },
1057    updated: function() {
1058        if (this.onUpdate) this.onUpdate(this);
1059    },
1060    navigate: function(offset) {
1061        if (offset == null) return;
1062        if (this.navigating) return;
1063
1064        var doc = TC.getOwnerDocument(this.element);
1065        var args = doc.location.search;
1066        args = args.replace(/^\?/, '');
1067        //args = args.replace(/&?search=[^&]+/, '');
1068        //args = args.replace(/&?do_search=1/, '');
1069        args = args.replace(/&?offset=\d+/, '');
1070        args = 'offset=' + offset + (args ? '&' + args : '') + '&json=1';
1071        if (this.type) {
1072            args = args.replace(/&?_type=\w+/, '');
1073            args = args + '&_type=' + this.type;
1074        }
1075
1076        this.navigating = true;
1077        if (this.pager)
1078            this.pager.render();
1079        if (this.pager2)
1080            this.pager2.render();
1081        var self = this;
1082        TC.Client.call({
1083            'load': function(c,r) { self.navigated(r); },
1084            'error': function() { alert("Error in request."); self.navigated(null); },
1085            'method': 'POST',
1086            'uri': ScriptURI,
1087            'arguments': args
1088        });
1089        return false;
1090    },
1091    navigated: function(c) {
1092        var data;
1093        this.navigating = false;
1094        if (c) {
1095            try {
1096                data = eval('(' + c + ')');
1097                this.update(data['html']);
1098                if (this.pager)
1099                    this.pager.setState(data['pager']);
1100                if (this.pager2)
1101                    this.pager2.setState(data['pager']);
1102            } catch (e) {
1103                alert("Error in response: " + e);
1104                if (this.pager)
1105                    this.pager.render();
1106                if (this.pager2)
1107                    this.pager2.render();
1108            }
1109        } else {
1110            if (this.pager) this.pager.render();
1111            if (this.pager2) this.pager2.render();
1112        }
1113    }
1114});
1115//These two lines are to translate phrases in list_tags.tmpl
1116//trans("The tag '[_2]' already exists. Are you sure you want to merge '[_1]' with '[_2]'?");
1117//trans("The tag '[_2]' already exists. Are you sure you want to merge '[_1]' with '[_2]' across all weblogs?");
1118
1119/***
1120 * Pager class
1121 * Expects a 'state' object containing:
1122 *   offset: offset into listing (10, means first row displayed is 11)
1123 *   listTotal: total number of rows in dataset
1124 *   rows: number of rows being displayed
1125 *   chronological: boolean, whether listing is reverse-chronological
1126 *     or not.
1127 *  Methods:
1128 *    constructor(el): constructs using DOM element el as a container
1129 *    setDatasource(ds): used to assign a datasource object
1130 *    setState(state): used to update state settings
1131 *    previous: navigates datasource to previous page
1132 *    next: navigates datasource to next page
1133 *    first: navigates datasource to first page
1134 *    last: navigates datasource to last page
1135 *    previousOffset: calculates and returns offset for previous page
1136 *    nextOffset: calculates and returns offset for next page
1137 *    lastOffset: calculates and returns offset for 'last' page
1138 *    render: refreshes the pagination controls
1139 */
1140Pager = new Class(Object, {
1141    init: function(el) {
1142        this.element = TC.elementOrId(el);
1143        this.state = {};
1144    },
1145    setDatasource: function(ds) {
1146        this.datasource = ds;
1147        this.render();
1148    },
1149    setState: function(state) {
1150        this.state = state;
1151        this.render();
1152    },
1153    previous: function(e) {
1154        this.navigate(this.previousOffset());
1155        return TC.stopEvent(e || window.event);
1156    },
1157    navigate: function(offset) {
1158        if (offset == null) return;
1159        if (this.datasource)
1160            return this.datasource.navigate(offset);
1161        // traditional navigation...
1162        var doc = TC.getOwnerDocument(this.element);
1163        new_loc = doc.location.href;
1164        if (this.state.method == 'POST') {
1165            new_loc += '?' + this.state.return_args;
1166        }
1167        new_loc = new_loc.replace(/&?offset=\d+/, '');
1168        new_loc += '&offset=' + offset;
1169        window.location = new_loc;
1170        return false;
1171    },
1172    previousOffset: function() {
1173        if (this.state.offset > 0) {
1174            var offset = this.state.offset - this.state.limit;
1175            if (offset < 0)
1176                offset = 0;
1177            return offset;
1178        }
1179        return null;
1180    },
1181    nextOffset: function() {
1182        if (this.state.listTotal) {
1183            var listStart = (this.state.offset ? this.state.offset : 0) + 1;
1184            var offset = (this.state.offset ? this.state.offset : 0) + this.state.rows;
1185            if (offset >= this.state.listTotal) {
1186                offset = null;
1187            }
1188            return offset;
1189        }
1190        return null;
1191    },
1192    lastOffset: function() {
1193        var offset = 0;
1194        if (this.state.listTotal) {
1195            var listStart = (this.state.offset ? this.state.offset : 0) + 1;
1196            var listEnd = (this.state.offset ? this.state.offset : 0) + this.state.rows;
1197            if (listEnd >= this.state.listTotal) {
1198                offset = null;
1199            } else {
1200                offset = this.state.listTotal - this.state.rows;
1201                if (offset < listStart)
1202                    offset = null;
1203            }
1204            return offset;
1205        }
1206        return null;
1207    },
1208    next: function(e) {
1209        this.navigate(this.nextOffset());
1210        return TC.stopEvent(e || window.event);
1211    },
1212    first: function(e) {
1213        this.navigate(0);
1214        return TC.stopEvent(e || window.event);
1215    },
1216    last: function(e) {
1217        this.navigate(this.lastOffset());
1218        return TC.stopEvent(e || window.event);
1219    },
1220    render: function() {
1221        if (!this.element) return;
1222
1223        /*
1224        This long method is concerned with creating the elements of
1225        the pagination control. It refreshes the controls based on
1226        the 'state' member of the Pager object. This control is
1227        typically tied to a Datasource object. So the navigation
1228        links of the control will influence the Datasource.
1229        Likewise, upon navigating the Datasource, it will invoke
1230        the pager to refresh when the data has been updated.
1231
1232        pager.rows (number of rows shown)
1233        pager.listTotal (total number of rows in datasource)
1234        pager.offset (offset currently used)
1235        pager.chronological (boolean, whether the listing is chronological or not)
1236        */
1237        var html = '';
1238        /* TODO - this can all be replaced with a js template */
1239        if (this.datasource && this.datasource.navigating) {
1240            // TODO: change this to use a CSS class instead.
1241            html = "<div>" + trans('Loading...') + " <img src=\"" + StaticURI + "images/indicator.white.gif\" height=\"10\" width=\"10\" alt=\"...\" /></div>";
1242            this.element.innerHTML = html;
1243        } else if ((this.state.rows != null) && (this.state.rows > 0)) {
1244            this.element.innerHTML = '';
1245            var listStart = (this.state.offset ? this.state.offset : 0) + 1;
1246            var listEnd = (this.state.offset ? this.state.offset : 0) + this.state.rows;
1247
1248            var doc = TC.getOwnerDocument(this.element);
1249            var self = this;
1250            // pagination control structure
1251            if (this.state.offset > 0) {
1252                var link = doc.createElement('a');
1253                link.href = 'javascript:void(0)';
1254                link.onclick = function(e) { return self.first(e) };
1255                link.className = 'start';
1256                link.innerHTML = '<em>&lt;&lt;</em>&nbsp;';
1257                this.element.appendChild(link);
1258            } else {
1259                var txt = doc.createElement('span');
1260                txt.className = 'start-disabled';
1261                txt.innerHTML = '<em>&lt;&lt;</em>&nbsp;';
1262                this.element.appendChild(txt);
1263            }
1264            if (this.previousOffset() != null) {
1265                var link = doc.createElement('a');
1266                link.href = 'javascript:void(0)';
1267                link.onclick = function(e) { return self.previous(e) };
1268                link.className = 'to-start';
1269                link.innerHTML = '<em>&lt;</em>&nbsp;';
1270                this.element.appendChild(link);
1271            } else {
1272                var txt = doc.createElement('span');
1273                txt.className = 'to-start-disabled';
1274                txt.innerHTML = '<em>&lt;</em>&nbsp;';
1275                this.element.appendChild(txt);
1276            }
1277            var showing = doc.createElement('span');
1278            showing.className = 'current-rows';
1279            if (this.state.listTotal)
1280                showing.innerHTML = trans('[_1] &ndash; [_2] of [_3]', listStart, listEnd, this.state.listTotal);
1281            else
1282                showing.innerHTML = trans('[_1] &ndash; [_2]', listStart, listEnd);
1283            this.element.appendChild(showing);
1284            if (this.nextOffset() != null) {
1285                var link = doc.createElement('a');
1286                link.href = 'javascript:void(0)';
1287                link.onclick = function(e) { return self.next(e) };
1288                link.className = 'to-end';
1289                link.innerHTML = '&nbsp;<em>&gt;</em>';
1290                this.element.appendChild(link);
1291            } else {
1292                var txt = doc.createElement('span');
1293                txt.className = 'to-end-disabled';
1294                txt.innerHTML = '&nbsp;<em>&gt;</em>';
1295                this.element.appendChild(txt);
1296            }
1297            if (this.lastOffset() != null) {
1298                var link = doc.createElement('a');
1299                link.href = 'javascript:void(0)';
1300                link.onclick = function(e) { return self.last(e) };
1301                link.className = 'end';
1302                link.innerHTML = '&nbsp;<em>&gt;&gt;</em>';
1303                this.element.appendChild(link);
1304            } else {
1305                var txt = doc.createElement('span');
1306                txt.className = 'end-disabled';
1307                txt.innerHTML = '&nbsp;<em>&gt;&gt;</em>';
1308                this.element.appendChild(txt);
1309            }
1310        } else {
1311            this.element.innerHTML = '';
1312        }
1313    }
1314});
1315
1316
1317MT = {};
1318
1319
1320if ( window.App ) {
1321
1322App.singletonConstructor =
1323MT.App = new Class( App, {
1324 
1325
1326    NAMESPACE: "mt",
1327    changed: false,
1328    autoSaveDelay: 15000, /* ms */
1329
1330
1331    initComponents: function() {
1332        arguments.callee.applySuper( this, arguments );
1333        this.openFlyouts = [];
1334
1335        this.setDelegate( "navMenu", new this.constructor.NavMenu() );
1336
1337        this.initFormElements();
1338
1339        /* fix a display issue */
1340        var navEl = DOM.getElement( "content-nav" );
1341        var navConEl = DOM.getElement( "content-header-inner" );
1342        if ( navEl && navConEl )
1343            navEl.style.top = "-" + DOM.getStyle( navConEl, "height" );
1344
1345        if ( this.constructor.Resizer ) {
1346            this.setDelegate( "resizer", new this.constructor.Resizer( this.getIndirectMethod( "resizeComplete" ) ) );
1347            this.setDelegateListener( "eventMouseUp", "resizer" );
1348            this.setDelegateListener( "eventMouseMove", "resizer" );
1349        }
1350
1351        if ( this.constructor.DefaultValue )
1352            this.setDelegate( "defaultValue", new this.constructor.DefaultValue() );
1353       
1354        if ( this.constructor.TabContainer )
1355            this.setDelegate( "tabContainer", new this.constructor.TabContainer() );
1356           
1357        var forms = DOM.getElementsByTagAndAttribute( this.document, "form", "mt:auto-save" );
1358        if ( forms.length )
1359            window.onbeforeunload = this.getIndirectEventListener( "eventBeforeUnload" );
1360
1361        for ( var i = 0; i < forms.length; i++ ) {
1362            var autosave = truth( forms[ i ].getAttribute( "mt:auto-save" ) );
1363            if ( !autosave )
1364                continue;
1365
1366            this.form = forms[ i ];
1367
1368            var ad = forms[ i ].getAttribute( "mt:auto-save-delay" );
1369            var autoSaveDelay;
1370            if ( ad !== null ) {
1371                autoSaveDelay = parseInt( ad ) || 0;
1372                this.autoSaveDelay = autoSaveDelay;
1373            } else
1374                log.warn("auto-save-delay not defined on this form. Defaulting to "+this.autoSaveDelay);
1375
1376            log('using auto save delay: '+this.autoSaveDelay);
1377
1378            var es = Array.fromPseudo(
1379                forms[ i ].getElementsByTagName( "input" ),
1380                forms[ i ].getElementsByTagName( "textarea" )
1381            );
1382            for ( var j = 0; j < es.length; j++ ) {
1383                if ( es[ j ].getAttribute && es[ j ].getAttribute( "mt:watch-change" ) ) {
1384                    log('adding watcher to '+es[ j ].name);
1385                    DOM.addEventListener( es[ j ], "change", this.getIndirectEventListener( "setDirty" ) );
1386                }
1387                if ( autosave && es[ j ].nodeName == "TEXTAREA" ) {
1388                    /* don't attach to the editor textarea in this form */
1389                    if ( this.editor && es[ j ].id == this.editor.textarea.element.id )
1390                        continue;
1391                    DOM.addEventListener( es[ j ], "keydown", this.getIndirectEventListener( "setDirtyKeyDown" ) );
1392                }
1393            }
1394        }
1395
1396        if ( MT.App.dirty )
1397            this.changed = true;   
1398    },
1399
1400
1401    destroyObject: function() {
1402        this.autoSaveReq = null;
1403        this.autoSaveTimer = null;
1404        this.form = null;
1405        this.cpeList = null;
1406        arguments.callee.applySuper( this, arguments );
1407    },
1408
1409
1410    initFormElements: function() {
1411        var forms = document.getElementsByTagName( "form" );
1412        for( var i = 0; i < forms.length; i++ ) {
1413            forms[ i ].submitted = false;
1414            DOM.addEventListener( forms[ i ], "submit", this.getIndirectEventListener( "eventSubmit" ) );
1415            var tareas = forms[ i ].getElementsByTagName( "textarea" );
1416            var tabs = 0;
1417            for ( var j = 0; j < tareas.length; j++ ) {
1418                if ( ( tabs = tareas[ j ].getAttribute( "mt:allow-tabs" ) ) )
1419                    if ( truth ( tabs ) )
1420                        this.attachTabsToTextarea( tareas[ j ] );
1421
1422                if ( tareas[ j ].getAttribute( "mt:editor" ) == "codepress" ) {
1423                    if ( this.constructor.CodePress.isSupported() ) {
1424                        var ed = new this.constructor.CodePress( tareas[ j ] );
1425                        if ( ed ) {
1426                            if ( !this.cpeList )
1427                                this.cpeList = [];
1428                            this.cpeList.push( ed );
1429                        }
1430                    }
1431                }
1432            }
1433        }
1434    },
1435
1436
1437    eventSubmit: function( event ) {
1438        var form = DOM.getFirstAncestorByTagName( event.target, "form", true );
1439        if ( !form )
1440            return;
1441           
1442        if ( form.getAttribute( "mt:once" ) ) {
1443       
1444            if ( form.submitted )
1445                return event.stop();
1446       
1447            this.toggleSubmit( form, true );
1448        }
1449
1450        if ( this.cpeList )
1451            this.cpeList.forEach( function( cpe ) { cpe.onSubmit() } );
1452
1453        form.submitted = true;
1454    },
1455
1456
1457    eventBeforeUnload: function( event ) {
1458        if ( this.changed ) {
1459            if ( this.constructor.Editor )
1460                return event.returnValue = Editor.strings.unsavedChanges;
1461            else if ( window.Editor )
1462                return event.returnValue = window.Editor.strings.unsavedChanges;
1463        }
1464       
1465        return undefined;
1466    },
1467
1468
1469    toggleSubmit: function( form, disable ) {
1470        /* sane default */
1471        if ( !disable )
1472            disable = false;
1473        var elements = form.getElementsByTagName( "*" );
1474        for( var i = 0; i < elements.length; i++ ) {
1475            var element = elements[ i ];
1476            var tagName = element.tagName.toLowerCase();
1477            var type = element.getAttribute( "type" );
1478            type = type ? type.toLowerCase() : "";
1479            if ( tagName == "button" || 
1480                (tagName == "input" && (type == "button" || type == "submit" || type == "image")) )
1481                element.disabled = disable;
1482        }
1483    },
1484
1485
1486    closeFlyouts: function( target ) {
1487        var flyout;
1488        var es = Array.fromPseudo( this.openFlyouts );
1489        for ( var i = 0, len = es.length; i < len; i++ ) {
1490            if ( ( flyout = DOM.getElement( es[ i ] ) ) ) {
1491                if ( target && DOM.hasAncestor( target, flyout ) )
1492                    continue;
1493                DOM.addClassName( flyout, "hidden" );
1494                this.openFlyouts.remove( es[ i ] );
1495                showAllDropDown();
1496            }
1497        }
1498    },
1499
1500
1501    eventClick: function( event ) {
1502        var command = this.getMouseEventCommand( event );
1503
1504        switch( command ) {
1505           
1506            case "openSelectBlog":
1507                app.openDialog( '__mode=dialog_select_weblog&amp;select_favorites=1&return_args='
1508                    + escape( event.commandElement.getAttribute( "mt:href" ) ) );
1509                break;
1510
1511            case "goToLocation":
1512                this.gotoLocation( event.commandElement.getAttribute( "href" ) );
1513                break;
1514           
1515            case "autoSave":
1516                this.autoSave();
1517                break;
1518
1519            case "setModeCodepressOn":
1520                this.cpeList.forEach( function( cpe ) { cpe.toggleOn( true ); } );
1521                break;
1522
1523            case "setModeCodepressOff":
1524                this.cpeList.forEach( function( cpe ) { cpe.toggleOff( true ); } );
1525                break;
1526
1527            case "openFlyout":
1528                var name = event.commandElement.getAttribute( "mt:flyout" );
1529                var el = DOM.getElement( name );
1530                if ( !defined( el ) )
1531                    return;
1532
1533                this.closeFlyouts( event.target );
1534
1535                DOM.removeClassName( el, "hidden" );
1536                this.targetElement = event.target;
1537                this.applyAutolayouts( el );
1538                this.targetElement = null;
1539
1540                hideAllDropDown();
1541                showDropDown( el );
1542                this.openFlyouts.add( name );
1543
1544                break;
1545               
1546            case "closeFlyout":
1547                this.closeFlyouts();
1548               
1549                break;
1550
1551            default:
1552                this.closeFlyouts( event.target );
1553
1554                var form = DOM.getFirstAncestorByTagName( event.target, "form", true );
1555                if ( !form )
1556                    return;
1557
1558                var mode = event.target.getAttribute( "mt:mode" );
1559                if ( !mode && event.commandElement )
1560                    mode = event.commandElement.getAttribute( "mt:mode" );
1561
1562                if ( mode ) {
1563                    log('setting __mode in this form: '+mode);
1564                    var elements = form.getElementsByTagName( "input" );
1565                    for( var i = 0; i < elements.length; i++ ) {
1566                        if ( elements[ i ].name == "__mode" ) {
1567                            log('found __mode element');
1568                            elements[ i ].value = mode;
1569                            break;
1570                        }
1571                    }
1572                }
1573
1574                if ( command == "submit" ) {
1575                    event.stop();
1576                    var msg;
1577                    if ( event.commandElement && ( msg = event.commandElement.getAttribute( "mt:confirm-msg" ) ) )
1578                        if ( !confirm( msg ) )
1579                            return;
1580                    form.submit();
1581                }
1582
1583                return;
1584
1585        }
1586        return event.stop();
1587    },
1588   
1589
1590    /* from blog selector transient */
1591    gotoUrl: function( url ) {
1592        if ( url )
1593            this.gotoLocation( url );
1594    },
1595
1596
1597    toggleActive: function( id ) {
1598        log('toggleactive:'+id);
1599        var div = DOM.getElement( id );
1600        if ( DOM.hasClassName( div, 'active' ) )
1601            DOM.removeClassName( div, 'active' );
1602        else
1603            DOM.addClassName( div, 'active' );
1604    },
1605
1606
1607    openDialog: function( params ) {
1608        this.closeFlyouts();
1609        show("dialog-container");
1610        /* TODO remove this cruft */
1611        /*  handle escape key for closing modal dialog */
1612        DOM.addEventListener( document.body, "keypress", dialogKeyPress, true );
1613        openDialogUrl( ScriptURI + "?" + params );
1614        /* IE hack to get the dialog modal to reflow */
1615        if ( document.all && DOM.getElement( "dialog-container" ) ) {
1616            DOM.addClassName( "dialog-container", "hidden" );
1617            new Timer(function() {
1618                DOM.removeClassName( "dialog-container", "hidden" );
1619            }, 500, 1 );
1620        }
1621    },
1622
1623
1624    attachTabsToTextarea: function( element ) {
1625        DOM.addEventListener( element, "keypress", this.getIndirectMethod( "eventKeyPressAllowTabs" ) );
1626        DOM.addEventListener( element, "keydown", this.getIndirectMethod( "eventKeyDownAllowTabs" ) );
1627    },
1628
1629
1630    eventKeyPressAllowTabs: function( event ) {
1631        if ( event.keyCode == 9 )
1632            return event.stop();
1633    },
1634   
1635   
1636    eventKeyDownAllowTabs: function( event ) {
1637        if ( event.keyCode == 9 ) {
1638                    TC.setSelectionValue( ( event.target || event.srcElement ) , "\t" );
1639            return false;
1640        }
1641    },
1642
1643
1644    resizeComplete: function( target, xStart, yStart, x, y, width, height ) {
1645       
1646        switch ( target.id ) {
1647            case "textarea-enclosure":
1648                var es = [ "text", "text_cpe" ];
1649                for ( var i = 0; i < es.length; i++ ) {
1650                    es[ i ] = DOM.getElement( es[ i ] );
1651                    if ( es[ i ] )
1652                        DOM.setHeight( es[ i ], height );
1653                }
1654                break;
1655
1656            /* expand here */
1657        }
1658
1659    },
1660   
1661   
1662    autoSave: function() {
1663        var data = DOM.getFormData( this.form );
1664        data["_autosave"] = 1;
1665
1666        if ( this.cpeList )
1667            this.cpeList.forEach( function( cpe ) { cpe.autoSave( data ) } );
1668
1669        /* don't cancel a pending save */
1670        if ( defined( this.autoSaveReq ) )
1671            return;
1672       
1673        var areas = [
1674            DOM.getElement( "autosave-notification" ),
1675            DOM.getElement( "autosave-notification-top" ),
1676            DOM.getElement( "autosave-notification-bottom" )
1677        ];
1678        if ( areas )
1679            for ( var i = 0; i < areas.length; i++ )
1680                if ( areas[ i ] )
1681                    areas[ i ].innerHTML = Template.process( "autoSave", { saving: true } );
1682
1683        this.autoSaveReq = TC.Client.call({
1684            load: this.getIndirectMethod( "autoSaveComplete" ),
1685            error: this.getIndirectMethod( "autoSaveError" ),
1686            method: 'POST',
1687            uri: this.form.action,
1688            arguments: data
1689        });
1690    },
1691
1692
1693    autoSaveComplete: function( c, r ) {
1694        this.autoSaveTimer = this.autoSaveReq = undefined;
1695
1696        log('auto save complete '+r);
1697        if ( r != "true" )
1698            return log.error( "Error auto-saving post: "+r );
1699       
1700        var areas = [
1701            DOM.getElement( "autosave-notification" ),
1702            DOM.getElement( "autosave-notification-top" ),
1703            DOM.getElement( "autosave-notification-bottom" )
1704        ];
1705        var d = new Date();
1706        if ( areas )
1707            for ( var i = 0; i < areas.length; i++ )
1708                if ( areas[ i ] )
1709                    areas[ i ].innerHTML = Template.process( "autoSave", {
1710                        saving: false,
1711                        hh: d.getHours().toString().pad( 2, "0" ),
1712                        mm: d.getMinutes().toString().pad( 2, "0" ),
1713                        ss: d.getSeconds().toString().pad( 2, "0" )
1714                    } );
1715    },
1716
1717   
1718    autoSaveError: function( c, r ) {
1719        this.autoSaveTimer = this.autoSaveReq = undefined;
1720       
1721        log.error( "Error auto-saving post" );
1722        var areas = [
1723            DOM.getElement( "autosave-notification" ),
1724            DOM.getElement( "autosave-notification-top" ),
1725            DOM.getElement( "autosave-notification-bottom" )
1726        ];
1727        if ( areas )
1728            for ( var i = 0; i < areas.length; i++ )
1729                if ( areas[ i ] )
1730                    areas[ i ].innerHTML = ''
1731    },
1732
1733
1734    setDirtyKeyDown: function( event ) {
1735        if ( this.dirtyKeyDownTimer )
1736            this.dirtyKeyDownTimer.stop();
1737        this.dirtyKeyDownTimer = new Timer( this.getIndirectMethod( "setDirty" ), 5000, 1 );
1738    },
1739
1740
1741    setDirty: function( event ) {
1742        var autoSaveDelay = this.autoSaveDelay;
1743        if ( event && event.target ) {
1744            var form = DOM.getFirstAncestorByTagName( event.target, "form", true );
1745            if ( form ) {
1746                log('found dirty form: '+form);
1747                this.form = form;
1748                if ( autoSaveDelay = parseInt( form.getAttribute( "mt:auto-save-delay" ) ) || 0 ) {
1749                    this.autoSaveDelay = autoSaveDelay;
1750                    log('using auto save delay: '+this.autoSaveDelay);
1751                }
1752            }
1753        }
1754           
1755        this.changed = true;
1756        if ( autoSaveDelay < 1 )
1757            return;
1758
1759        if ( defined( this.autoSaveTimer ) )
1760            return this.autoSaveTimer.reset();
1761        this.autoSaveTimer = new Timer( this.getIndirectMethod( "autoSave" ), autoSaveDelay, 1 );
1762    },
1763
1764
1765    clearDirty: function( event ) {
1766        this.changed = false;
1767    },
1768
1769   
1770    insertCode: function( code ) {
1771        if ( this.cpeList )
1772            this.cpeList[ 0 ].insertCode( code );
1773        else if ( this.editor )
1774            this.editor.insertHTML( code );
1775        else {
1776            var txt = DOM.getElement( "text" );
1777            setSelection( txt, code );
1778            DOM.focus( txt );
1779        }
1780    }
1781   
1782
1783} );
1784
1785if ( window.Calendar ) {
1786
1787MT.App.Calendar = new Class( Calendar, {
1788
1789
1790    open: function( data, callback ) {
1791        if ( data.date && data.date.length )
1792            data.date = data.date.replace( /^(\S+).*/, "$1" );
1793        arguments.callee.applySuper( this, arguments );
1794       
1795        /* reset invalid dates to the current date */
1796        if ( !this.dateObject )
1797            this.dateObject = new Date();
1798    },
1799
1800
1801    eventClick: function() {
1802        arguments.callee.applySuper( this, arguments );
1803        if ( this.callback )
1804            this.callback( this.dateObject );
1805    }
1806
1807
1808} );
1809
1810}
1811
1812
1813
1814
1815MT.App.Resizer = new Class( Object, {
1816
1817
1818    dragging: false,
1819    element: null,
1820   
1821    xLock: false,
1822    yLock: false,
1823
1824    xStart: null,
1825    yStart: null,
1826
1827
1828    init: function( callback ) {
1829        if ( callback )
1830            this.callback = callback;
1831    },
1832
1833
1834    destroy: function() {
1835        this.callback = null;
1836    },
1837
1838
1839    eventMouseDown: function( event ) {
1840        this.dragging = true;
1841       
1842        this.reset();
1843       
1844        this.target = event.attributeElement.getAttribute( "mt:target" );
1845
1846        /* x or y locking */
1847        var lock = event.attributeElement.getAttribute( "mt:lock" );
1848        if ( lock ) {
1849            if ( lock == "x" || lock == "X" )
1850                this.xLock = true;
1851            else if ( lock == "y" || lock == "Y" )
1852                this.yLock = true;
1853        }
1854       
1855        /* clone the drag node */
1856        this.element = event.attributeElement.cloneNode( true );
1857        /* using the current mouse position, set the positon of the drag obj */
1858        var d = DOM.getAbsoluteCursorPosition( event );
1859        this.yStart = d.y;
1860        this.xStart = d.x;
1861       
1862        var dm = DOM.getAbsoluteDimensions( event.attributeElement );
1863        var adm = DOM.getAbsoluteDimensions( event.attributeElement );
1864       
1865        if ( !this.yLock )
1866            DOM.setTop( this.element,  d.y );
1867        else
1868            DOM.setTop( this.element, adm.absoluteTop );
1869
1870        if ( !this.xLock )
1871            DOM.setLeft( this.element,  d.x );
1872        else
1873            DOM.setLeft( this.element, adm.absoluteLeft );
1874
1875        DOM.setWidth( this.element, dm.offsetWidth );
1876        DOM.setHeight( this.element, dm.offsetHeight );
1877       
1878        var mask = DOM.getElement( "resize-mask" );
1879        mask.insertBefore( this.element, mask.firstChild );
1880        DOM.addClassName( this.element, "moving" );
1881        DOM.removeClassName( mask, "hidden" );
1882        /* TODO autolayout this */
1883        DOM.setHeight( mask, finiteInt( mask.parentNode.clientHeight ) );
1884
1885        return event.stop();
1886    },
1887
1888
1889    eventMouseMove: function( event ) {
1890        if ( !this.dragging )
1891            return;
1892       
1893        var d = DOM.getAbsoluteCursorPosition( event );
1894       
1895        if ( !this.yLock )
1896            DOM.setTop( this.element, d.y );
1897
1898        if ( !this.xLock )
1899            DOM.setLeft( this.element, d.x );
1900
1901        return event.stop();
1902    },
1903
1904
1905    eventMouseUp: function( event ) {
1906        if ( !this.dragging )
1907            return;
1908       
1909        this.dragging = false;
1910        var d = DOM.getAbsoluteCursorPosition( event );
1911           
1912        DOM.addClassName( "resize-mask", "hidden" );
1913       
1914        /* cleanup */
1915        if ( this.element && this.element.parentNode )
1916            this.element.parentNode.removeChild( this.element );
1917
1918        var target = DOM.getElement( this.target );
1919        if ( !target )
1920            return this.reset();
1921
1922        var targetDim = DOM.getDimensions( target );
1923       
1924        var height = d.y - targetDim.offsetTop;
1925        if ( !this.yLock ) {
1926            var hMin = target.getAttribute( "mt:min-height" );
1927            if ( hMin )
1928                if ( height < parseInt( hMin ) )
1929                    height = parseInt( hMin );
1930       
1931            var hMax = target.getAttribute( "mt:max-height" );
1932            if ( hMax )
1933                if ( height > parseInt( hMax ) )
1934                    height = parseInt( hMax );
1935
1936            log('new height: '+height);
1937        }
1938
1939        var width = d.x - targetDim.offsetLeft;
1940        if ( !this.xLock ) {
1941            var wMin = target.getAttribute( "mt:min-width" );
1942            if ( wMin )
1943                if ( width < parseInt( wMin ) )
1944                    width = parseInt( wMin );
1945       
1946            var wMax = target.getAttribute( "mt:max-width" );
1947            if ( wMax )
1948                if ( width > parseInt( wMax ) )
1949                    width = parseInt( wMax );
1950
1951            log('new width: '+width);
1952        }
1953       
1954        /* give the callback a chance to stop us from setting this height and width */
1955        if ( this.callback && ( this.callback( target, this.xStart, this.yStart, d.x, d.y, width, height ) ) )
1956            return this.reset();
1957       
1958        if ( !this.yLock )
1959            DOM.setHeight( target, height );
1960
1961        if ( !this.xLock )
1962            DOM.setWidth( target, width );
1963
1964        var hUpdate = target.getAttribute( "mt:update-field-height" );
1965        if ( hUpdate && ( hUpdate = DOM.getElement( hUpdate ) ) )
1966            hUpdate.value = height;
1967       
1968        var wUpdate = target.getAttribute( "mt:update-field-width" );
1969        if ( wUpdate && ( wUpdate = DOM.getElement( wUpdate ) ) )
1970            wUpdate.value = width;
1971
1972        this.reset();
1973    },
1974
1975
1976    reset: function() {
1977        /* remove left over drag obj, if any */
1978        if ( this.element && this.element.parentNode )
1979            this.element.parentNode.removeChild( this.element );
1980       
1981        this.xStart = this.yStart = this.element = this.target = null;
1982        this.xLock = this.yLock = false;
1983    }
1984
1985
1986} );
1987
1988
1989MT.App.DefaultValue = new Class( Object, {
1990
1991
1992    init: function() {
1993        var es = DOM.getElementsByAttributeAndValue( document, "mt:delegate", "default-value" );
1994        for ( var i = 0; i < es.length; i++ ) {
1995            var val = es[ i ].getAttribute( "mt:default" );
1996            if ( !val )
1997                continue;
1998           
1999            if ( es[ i ].value != val )
2000                DOM.removeClassName( es[ i ], "input-hint" );
2001        }
2002    },
2003
2004
2005    eventFocus: function( event ) {
2006        var element = event.attributeElement;
2007        var val = element.getAttribute( "mt:default" );
2008        if ( !val )
2009            return;
2010
2011        DOM.removeClassName( element, "input-hint" );
2012       
2013        if ( element.value == val )
2014            element.value = "";
2015    },
2016
2017
2018    eventBlur: function( event ) {
2019        var element = event.attributeElement;
2020        var val = element.getAttribute( "mt:default" );
2021        if ( !val )
2022            return;
2023       
2024        var opts = {};
2025        /* simple options for now */
2026        var opt = element.getAttribute( "mt:delegate-options" );
2027        if ( opt && opt == "-class" ) {
2028            opts.noclassChange = true;
2029        }
2030       
2031        if ( element.value != "" )
2032            return;
2033       
2034        element.value = val;
2035        if ( opts.noclassChange )
2036            return;
2037
2038        DOM.addClassName( element, "input-hint" );
2039    },
2040   
2041   
2042    /* hate on IE */
2043    eventFocusIn: function( event ) {
2044        return this.eventFocus( event );
2045    },
2046   
2047
2048    eventFocusOut: function( event ) {
2049        this.eventBlur( event );
2050    },
2051
2052
2053    eventSubmit: function( event ) {
2054        return event.stop();
2055    }
2056   
2057   
2058} );
2059
2060
2061MT.App.TabContainer = new Class( Object, {
2062
2063    init: function() {
2064        var es = DOM.getElementsByAttributeAndValue( document, "mt:delegate", "tab-container" );
2065        var t;
2066        for ( var i = 0; i < es.length; i++ ) {
2067            if ( t = es[ i ].getAttribute( "mt:selected-tab" ) ) {
2068                this.selectTab( es[ i ], t );
2069                continue;
2070            }
2071
2072            if ( t = es[ i ].getAttribute( "mt:persist-tab-cookie" ) ) {
2073                log( 'found persisted tab setting: '+t);
2074                t = Cookie.fetch( t );
2075                if ( t && t.value && t.value != "" ) {
2076                    log( 'cookie: '+t.value);
2077                    this.selectTab( es[ i ], t.value );
2078                }
2079            }
2080        }
2081    },
2082
2083
2084    eventClick: function( event ) {
2085        var command = app.getMouseEventCommand( event );
2086        if (!event.commandElement) return;
2087        var tab = event.commandElement.getAttribute( "mt:tab" );
2088        if ( tab && command != "selectTab" )
2089            this.selectTab( event.attributeElement, tab );
2090
2091        switch( command ) {
2092           
2093            case "setEditorContent":
2094                event.stop();
2095                app.setEditor( "content" );
2096                break;
2097
2098            case "setEditorExtended":
2099                event.stop();
2100                app.setEditor( "extended" );
2101                break;
2102           
2103            case "selectTab":
2104                if ( !tab )
2105                    tab = event.commandElement.getAttribute( "mt:select-tab" );
2106
2107                if ( tab ) {
2108                    this.selectTab( event.attributeElement, tab );
2109                    var cookie = event.attributeElement.getAttribute( "mt:persist-tab-cookie" );
2110                    if ( cookie ) {
2111                        var d = new Date();
2112                        d.setYear( d.getYear() + 1902 ); /* two years */
2113                        Cookie.bake( cookie, tab, undefined, undefined, d );
2114                    }
2115                }
2116               
2117                event.stop();
2118                break;
2119
2120        }
2121    },
2122
2123
2124    selectTab: function( element, name ) {
2125        log('select tab '+name);
2126        var es = DOM.getElementsByAttribute( element, "mt:tab" );
2127        for ( var i = 0; i < es.length; i++ ) {
2128            if ( es[ i ].getAttribute( "mt:tab" ) == name )
2129                DOM.addClassName( es[ i ], "selected-tab" );
2130            else
2131                DOM.removeClassName( es[ i ], "selected-tab" );
2132        }
2133
2134        /* look for tab contents elements matching 'name' */
2135        es = DOM.getElementsByAttribute( element, "mt:tab-content" );
2136        for ( var i = 0; i < es.length; i++ ) {
2137            /* then hide everything except the tab content we want to show */
2138            if ( es[ i ].getAttribute( "mt:tab-content" ) == name )
2139                DOM.removeClassName( es[ i ], "hidden" );
2140            else
2141                DOM.addClassName( es[ i ], "hidden" );
2142        }
2143    }
2144
2145
2146} );
2147
2148
2149MT.App.NavMenu = new Class( Object, {
2150
2151    opened: false,
2152    outTimer: null,
2153    inTimer: null,
2154    el: null,
2155    al: null,
2156
2157
2158    eventMouseOver: function( event ) {
2159        var el = DOM.getFirstAncestorByClassName( event.target, "nav-menu", true );
2160        if ( !el )
2161            return;
2162       
2163        /* if they moused in, but moved to a new menu, reset the in timer */
2164        if ( this.inTimer && this.el && this.el !== el )
2165            this.inTimer.stop();
2166       
2167        this.al = event.attributeElement;
2168        this.el = el;
2169
2170        if ( this.outTimer )
2171            this.outTimer.stop();
2172
2173        if ( this.al.getAttribute( "mt:is-opened" ) == "1" )
2174            return this.openMenu();
2175       
2176        var delay = event.attributeElement.getAttribute( "mt:nav-delayed-open" ); // ms
2177
2178        if ( delay ) {
2179            delay = parseInt( delay );
2180            /* no hover in? */
2181            if ( delay < 0 )
2182                return;
2183            if ( this.inTimer )
2184                this.inTimer.stop();
2185            this.inTimer = new Timer( this.getIndirectMethod( "openMenu" ), delay, 1 );
2186            return;
2187        } else
2188            this.openMenu();
2189    },
2190
2191
2192    eventMouseOut: function( event ) {
2193        var el = DOM.getFirstAncestorByClassName( event.target, "nav-menu", true );
2194        if ( !el && defined( event.relatedTarget ) )
2195            el = DOM.getFirstAncestorByClassName( event.relatedTarget, "nav-menu", true );
2196        if ( !el )
2197            return;
2198
2199        this.al = event.attributeElement;
2200        this.el = el;
2201
2202        var delay = event.attributeElement.getAttribute( "mt:nav-delayed-close" ); // ms
2203        if ( delay ) {
2204            delay = parseInt( delay );
2205            /* no hover out? */
2206            if ( delay < 0 )
2207                return;
2208            if ( this.outTimer )
2209                this.outTimer.stop();
2210            this.outTimer = new Timer( this.getIndirectMethod( "closeMenu" ), delay, 1 );
2211        } else
2212            this.closeMenu();
2213    },
2214
2215
2216    eventClick: function( event ) {
2217        var command = app.getMouseEventCommand( event );
2218        if ( !command ) {
2219            if ( this.inTimer )
2220                this.inTimer.stop();
2221            return;
2222        }
2223
2224        var el = DOM.getFirstAncestorByClassName( event.target, "nav-menu", true );
2225        if ( !el )
2226            return;
2227        this.al = event.attributeElement;
2228
2229        if ( command == "openMenu" ) {
2230            event.