root/branches/release-41/mt-static/mt.js @ 2734

Revision 2734, 95.9 kB (checked in by bsmith, 17 months ago)

bugzid:80375 - if element is hidden no need to apply display:none

  • 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 (window.app) window.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
577
578function toggleDisable(id, state) {
579    var id = DOM.getElement( id );
580    if ((id.disabled && state) || !id)
581        return false;
582    else if (!id.disabled && !state)
583        return false;
584    if ( !id.disabled )
585        id.disabled="disabled";
586    else
587        id.disabled="";
588}
589
590function toggleDisplayOptions() {
591    return toggleActive('display-options');
592}
593
594function toggleEntryDisplayOptions() {
595    return toggleActive('display-options-widget');
596}
597
598function toggleActive( id ) {
599    var id = DOM.getElement( id );
600    if ( !id )
601        return false;
602    if ( DOM.hasClassName( id, 'active' ) )
603        DOM.removeClassName( id, 'active' );
604    else
605        DOM.addClassName( id, 'active' );
606    return false;
607}
608
609function toggleHidden( id ) {
610    var id = DOM.getElement( id );
611    if ( !id )
612        return false;
613    if ( DOM.hasClassName( id, 'hidden' ) )
614        DOM.removeClassName( id, 'hidden' );
615    else
616        DOM.addClassName( id, 'hidden' );
617    return false;
618}
619
620function toggle( id ) {
621    var id = DOM.getElement( id );
622    if ( !id )
623        return false;
624    if ( DOM.hasClassName( id, 'hidden' ) ) {
625        DOM.removeClassName( id, 'hidden' );
626        DOM.addClassName( id, 'active' );
627    } else {
628        DOM.removeClassName( id, 'active' );
629        DOM.addClassName( id, 'hidden' );
630    }
631    return false;
632}
633
634function toggleClass( id, c ) {
635    if ( !id )
636        return false;
637    if ( DOM.hasClassName( id, c ) ) {
638        DOM.removeClassName( id, c );
639    } else {
640        DOM.addClassName( id, c );
641    }
642    return false;
643}
644
645function tabToggle(selectedTab, tabs) {
646    log.warn('tabToggle is deprecated. use mt tab delegate');
647    for (var i = 0; i < tabs.length; i++) {
648        var tabObject = getByID(tabs[i] + '-tab');
649        var contentObject = getByID(tabs[i] + '-panel');
650           
651        if (tabObject && contentObject) {
652            if (tabs[i] == selectedTab) {
653                DOM.addClassName( tabObject, 'selected-tab' );
654                DOM.addClassName( contentObject, 'selected-tab-panel' );
655            } else {
656                DOM.removeClassName( tabObject, 'selected-tab' );
657                DOM.removeClassName( contentObject, 'selected-tab-panel' );
658            }
659        }
660    }
661    return false;
662}
663
664function show(id, d, style) {
665    var el = getByID(id, d);
666    if (!el) return;
667    if ( DOM.hasClassName( el, "hidden" ) ) {
668        DOM.removeClassName ( el, "hidden");
669    } else {
670        el.style.display = style ? style : 'block';
671    }
672    /* hack */
673    if ( DOM.hasClassName( el, "autolayout-height-parent" ) )
674        DOM.setHeight( el, finiteInt( el.parentNode.clientHeight ) );
675}
676
677function hide(id, d) {
678    var el = getByID(id, d);
679    if (!el) return;
680    if ( DOM.hasClassName( el, "hidden" ) ) return;
681    if ( !DOM.hasClassName( el, "hidden" ) ) {
682        DOM.addClassName ( el, "hidden");
683    } else {
684        el.style.display = 'none';
685    }
686    if ( window.app )
687        app.reflow();
688}
689
690function showReply(id, d, style) {
691    var el = getByID(id, d);
692    if (!el) return;
693    el.style.visibility = style ? style : 'visible';
694}
695
696function hideReply(id, d) {
697    var el = getByID(id, d);
698    if (!el) return;
699    el.style.visibility = 'hidden';
700}
701
702function toggleSubPrefs(c) {
703    var div = TC.elementOrId((c.name || c.id)+"-prefs") || TC.elementOrId((c.name || c.id)+'_prefs');
704    if (div) {
705        if (c.type) {
706            var on = c.type == 'checkbox' ? c.checked : c.value != 0;
707            if (on) {
708                TC.removeClassName(div, "hidden");
709            } else {
710                TC.addClassName(div, "hidden");
711            }
712            // div.style.display = on ? "block" : "none";
713        } else {
714            var on = div.style.display && div.style.display != "none";
715            if (on) {
716                TC.addClassName(div, "hidden");
717            } else {
718                TC.removeClassName(div, "hidden");
719            }
720            // div.style.display = on ? "none" : "block";
721        }
722    }
723    return false;
724}
725
726function toggleAdvancedPrefs(evt, c) {
727    evt = evt || window.event;
728    var id;
729    var obj;
730    if (!c || (typeof c != 'string')) {
731        c = c || evt.target || evt.srcElement;
732        id = c.id || c.name;
733        obj = c;
734    } else {
735        id = c;
736    }
737    var div = getByID( id + '-advanced');
738    if (div) {
739        if (obj) {
740            var shiftKey = evt ? evt.shiftKey : undefined;
741                if (evt && shiftKey && obj.type == 'checkbox')
742                obj.checked = true;
743            var on = obj.type == 'checkbox' ? obj.checked : obj.value != 0;
744            if (on && shiftKey) {
745                if (div.style.display == "block")
746                    div.style.display = "none";
747                else
748                    div.style.display = "block";
749            } else {
750                div.style.display = "none";
751            }
752        } else {
753            if (div.style.display == "block")
754                div.style.display = "none";
755            else
756                div.style.display = "block";
757        }
758    }
759    return false;
760}
761
762function trans(str) {
763    if (Lexicon && Lexicon[str])
764        str = Lexicon[str];
765    if (arguments.length > 1)
766        for (var i = 1; i <= arguments.length; i++) {
767            /* This matches [_#] or [_#:comment] */
768            str = str.replace(new RegExp('\\[_' + i + '(?:\:[^\\]]+)?\\]', 'g'), arguments[i]);
769            var re = new RegExp('\\[quant,_' + i + ',(.+?)(?:,(.+?))?(?:\:[^\\]]+)?\\]');
770            var matches;
771            while (matches = str.match(re)) {
772                if (arguments[i] != 1)
773                    str = str.replace(re, arguments[i] + ' ' +
774                        ((typeof(matches[2]) != 'undefined') ? matches[2]
775                                                               : matches[1]
776                                                                 + 's'));
777                else
778                    str = str.replace(re, arguments[i] + ' ' + matches[1]);
779            }
780        }
781    return str;
782}
783
784function junkScoreNudge(amount, id, max) {
785    if (max == undefined) max = 10;
786    var fld = getByID(id);
787    score = fld.value;
788    score.replace(/\+/, '');
789    score = parseFloat(score) + amount;
790    if (isNaN(score)) score = amount;
791    if (score > max) score = max;
792    if (score < 0) score = 0;
793    fld.value = score;
794    return false;
795}
796
797var dirify_table = {
798    "\u00C0": 'A',    // A`
799    "\u00E0": 'a',    // a`
800    "\u00C1": 'A',    // A'
801    "\u00E1": 'a',    // a'
802    "\u00C2": 'A',    // A^
803    "\u00E2": 'a',    // a^
804    "\u0102": 'A',    // latin capital letter a with breve
805    "\u0103": 'a',    // latin small letter a with breve
806    "\u00C6": 'AE',   // latin capital letter AE
807    "\u00E6": 'ae',   // latin small letter ae
808    "\u00C5": 'A',    // latin capital letter a with ring above
809    "\u00E5": 'a',    // latin small letter a with ring above
810    "\u0100": 'A',    // latin capital letter a with macron
811    "\u0101": 'a',    // latin small letter a with macron
812    "\u0104": 'A',    // latin capital letter a with ogonek
813    "\u0105": 'a',    // latin small letter a with ogonek
814    "\u00C4": 'A',    // A:
815    "\u00E4": 'a',    // a:
816    "\u00C3": 'A',    // A~
817    "\u00E3": 'a',    // a~
818    "\u00C8": 'E',    // E`
819    "\u00E8": 'e',    // e`
820    "\u00C9": 'E',    // E'
821    "\u00E9": 'e',    // e'
822    "\u00CA": 'E',    // E^
823    "\u00EA": 'e',    // e^
824    "\u00CB": 'E',    // E:
825    "\u00EB": 'e',    // e:
826    "\u0112": 'E',    // latin capital letter e with macron
827    "\u0113": 'e',    // latin small letter e with macron
828    "\u0118": 'E',    // latin capital letter e with ogonek
829    "\u0119": 'e',    // latin small letter e with ogonek
830    "\u011A": 'E',    // latin capital letter e with caron
831    "\u011B": 'e',    // latin small letter e with caron
832    "\u0114": 'E',    // latin capital letter e with breve
833    "\u0115": 'e',    // latin small letter e with breve
834    "\u0116": 'E',    // latin capital letter e with dot above
835    "\u0117": 'e',    // latin small letter e with dot above
836    "\u00CC": 'I',    // I`
837    "\u00EC": 'i',    // i`
838    "\u00CD": 'I',    // I'
839    "\u00ED": 'i',    // i'
840    "\u00CE": 'I',    // I^
841    "\u00EE": 'i',    // i^
842    "\u00CF": 'I',    // I:
843    "\u00EF": 'i',    // i:
844    "\u012A": 'I',    // latin capital letter i with macron
845    "\u012B": 'i',    // latin small letter i with macron
846    "\u0128": 'I',    // latin capital letter i with tilde
847    "\u0129": 'i',    // latin small letter i with tilde
848    "\u012C": 'I',    // latin capital letter i with breve
849    "\u012D": 'i',    // latin small letter i with breve
850    "\u012E": 'I',    // latin capital letter i with ogonek
851    "\u012F": 'i',    // latin small letter i with ogonek
852    "\u0130": 'I',    // latin capital letter with dot above
853    "\u0131": 'i',    // latin small letter dotless i
854    "\u0132": 'IJ',   // latin capital ligature ij
855    "\u0133": 'ij',   // latin small ligature ij
856    "\u0134": 'J',    // latin capital letter j with circumflex
857    "\u0135": 'j',    // latin small letter j with circumflex
858    "\u0136": 'K',    // latin capital letter k with cedilla
859    "\u0137": 'k',    // latin small letter k with cedilla
860    "\u0138": 'k',    // latin small letter kra
861    "\u0141": 'L',    // latin capital letter l with stroke
862    "\u0142": 'l',    // latin small letter l with stroke
863    "\u013D": 'L',    // latin capital letter l with caron
864    "\u013E": 'l',    // latin small letter l with caron
865    "\u0139": 'L',    // latin capital letter l with acute
866    "\u013A": 'l',    // latin small letter l with acute
867    "\u013B": 'L',    // latin capital letter l with cedilla
868    "\u013C": 'l',    // latin small letter l with cedilla
869    "\u013F": 'l',    // latin capital letter l with middle dot
870    "\u0140": 'l',    // latin small letter l with middle dot
871    "\u00D2": 'O',    // O`
872    "\u00F2": 'o',    // o`
873    "\u00D3": 'O',    // O'
874    "\u00F3": 'o',    // o'
875    "\u00D4": 'O',    // O^
876    "\u00F4": 'o',    // o^
877    "\u00D6": 'O',    // O:
878    "\u00F6": 'o',    // o:
879    "\u00D5": 'O',    // O~
880    "\u00F5": 'o',    // o~
881    "\u00D8": 'O',    // O/
882    "\u00F8": 'o',    // o/
883    "\u014C": 'O',    // latin capital letter o with macron
884    "\u014D": 'o',    // latin small letter o with macron
885    "\u0150": 'O',    // latin capital letter o with double acute
886    "\u0151": 'o',    // latin small letter o with double acute
887    "\u014E": 'O',    // latin capital letter o with breve
888    "\u014F": 'o',    // latin small letter o with breve
889    "\u0152": 'OE',   // latin capital ligature oe
890    "\u0153": 'oe',   // latin small ligature oe
891    "\u0154": 'R',    // latin capital letter r with acute
892    "\u0155": 'r',    // latin small letter r with acute
893    "\u0158": 'R',    // latin capital letter r with caron
894    "\u0159": 'r',    // latin small letter r with caron
895    "\u0156": 'R',    // latin capital letter r with cedilla
896    "\u0157": 'r',    // latin small letter r with cedilla
897    "\u00D9": 'U',    // U`
898    "\u00F9": 'u',    // u`
899    "\u00DA": 'U',    // U'
900    "\u00FA": 'u',    // u'
901    "\u00DB": 'U',    // U^
902    "\u00FB": 'u',    // u^
903    "\u00DC": 'U',    // U:
904    "\u00FC": 'u',    // u:
905    "\u016A": 'U',    // latin capital letter u with macron
906    "\u016B": 'u',    // latin small letter u with macron
907    "\u016E": 'U',    // latin capital letter u with ring above
908    "\u016F": 'u',    // latin small letter u with ring above
909    "\u0170": 'U',    // latin capital letter u with double acute
910    "\u0171": 'u',    // latin small letter u with double acute
911    "\u016C": 'U',    // latin capital letter u with breve
912    "\u016D": 'u',    // latin small letter u with breve
913    "\u0168": 'U',    // latin capital letter u with tilde
914    "\u0169": 'u',    // latin small letter u with tilde
915    "\u0172": 'U',    // latin capital letter u with ogonek
916    "\u0173": 'u',    // latin small letter u with ogonek
917    "\u00C7": 'C',    // ,C
918    "\u00E7": 'c',    // ,c
919    "\u0106": 'C',    // latin capital letter c with acute
920    "\u0107": 'c',    // latin small letter c with acute
921    "\u010C": 'C',    // latin capital letter c with caron
922    "\u010D": 'c',    // latin small letter c with caron
923    "\u0108": 'C',    // latin capital letter c with circumflex
924    "\u0109": 'c',    // latin small letter c with circumflex
925    "\u010A": 'C',    // latin capital letter c with dot above
926    "\u010B": 'c',    // latin small letter c with dot above
927    "\u010E": 'D',    // latin capital letter d with caron
928    "\u010F": 'd',    // latin small letter d with caron
929    "\u0110": 'D',    // latin capital letter d with stroke
930    "\u0111": 'd',    // latin small letter d with stroke
931    "\u00D1": 'N',    // N~
932    "\u00F1": 'n',    // n~
933    "\u0143": 'N',    // latin capital letter n with acute
934    "\u0144": 'n',    // latin small letter n with acute
935    "\u0147": 'N',    // latin capital letter n with caron
936    "\u0148": 'n',    // latin small letter n with caron
937    "\u0145": 'N',    // latin capital letter n with cedilla
938    "\u0146": 'n',    // latin small letter n with cedilla
939    "\u0149": 'n',    // latin small letter n preceded by apostrophe
940    "\u014A": 'N',    // latin capital letter eng
941    "\u014B": 'n',    // latin small letter eng
942    "\u00DF": 'ss',   // double-s
943    "\u015A": 'S',    // latin capital letter s with acute
944    "\u015B": 's',    // latin small letter s with acute
945    "\u0160": 'S',    // latin capital letter s with caron
946    "\u0161": 's',    // latin small letter s with caron
947    "\u015E": 'S',    // latin capital letter s with cedilla
948    "\u015F": 's',    // latin small letter s with cedilla
949    "\u015C": 'S',    // latin capital letter s with circumflex
950    "\u015D": 's',    // latin small letter s with circumflex
951    "\u0218": 'S',    // latin capital letter s with comma below
952    "\u0219": 's',    // latin small letter s with comma below
953    "\u0164": 'T',    // latin capital letter t with caron
954    "\u0165": 't',    // latin small letter t with caron
955    "\u0162": 'T',    // latin capital letter t with cedilla
956    "\u0163": 't',    // latin small letter t with cedilla
957    "\u0166": 'T',    // latin capital letter t with stroke
958    "\u0167": 't',    // latin small letter t with stroke
959    "\u021A": 'T',    // latin capital letter t with comma below
960    "\u021B": 't',    // latin small letter t with comma below
961    "\u0192": 'f',    // latin small letter f with hook
962    "\u011C": 'G',    // latin capital letter g with circumflex
963    "\u011D": 'g',    // latin small letter g with circumflex
964    "\u011E": 'G',    // latin capital letter g with breve
965    "\u011F": 'g',    // latin small letter g with breve
966    "\u0120": 'G',    // latin capital letter g with dot above
967    "\u0121": 'g',    // latin small letter g with dot above
968    "\u0122": 'G',    // latin capital letter g with cedilla
969    "\u0123": 'g',    // latin small letter g with cedilla
970    "\u0124": 'H',    // latin capital letter h with circumflex
971    "\u0125": 'h',    // latin small letter h with circumflex
972    "\u0126": 'H',    // latin capital letter h with stroke
973    "\u0127": 'h',    // latin small letter h with stroke
974    "\u0174": 'W',    // latin capital letter w with circumflex
975    "\u0175": 'w',    // latin small letter w with circumflex
976    "\u00DD": 'Y',    // latin capital letter y with acute
977    "\u00FD": 'y',    // latin small letter y with acute
978    "\u0178": 'Y',    // latin capital letter y with diaeresis
979    "\u00FF": 'y',    // latin small letter y with diaeresis
980    "\u0176": 'Y',    // latin capital letter y with circumflex
981    "\u0177": 'y',    // latin small letter y with circumflex
982    "\u017D": 'Z',    // latin capital letter z with caron
983    "\u017E": 'z',    // latin small letter z with caron
984    "\u017B": 'Z',    // latin capital letter z with dot above
985    "\u017C": 'z',    // latin small letter z with dot above
986    "\u0179": 'Z',    // latin capital letter z with acute
987    "\u017A": 'z'     // latin small letter z with acute
988};
989
990function dirify (s) {
991    s = s.replace(/<[^>]+>/g, '');
992    for (var p in dirify_table)
993        if (s.indexOf(p) != -1)
994            s = s.replace(new RegExp(p, "g"), dirify_table[p]);
995    s = s.toLowerCase();
996    s = s.replace(/&[^;\s]+;/g, '');
997    s = s.replace(/[^-a-z0-9_ ]/g, '');
998    s = s.replace(/\s+/g, '_');
999    s = s.replace(/_+$/, '');
1000    s = s.replace(/_+/g, '_');
1001    return s;
1002}
1003
1004function setElementValue(domID, newVal) {
1005    getByID(domID).value = newVal;
1006}
1007
1008/* pager and datasource */
1009
1010/***
1011 * Datasource class
1012 * A class for navigating and displaying data from an AJAX datasource.
1013 * Methods:
1014 *   constructor(el, datatype): Creates a new datasource using DOM
1015 *     element 'el' as a container for the data (table rows typically)
1016 *     and datatype is used to communicate the '_type' parameter to
1017 *     the server.
1018 *   setPager(pager): Sets a Pager class object which is refreshed
1019 *     upon receiving new data.
1020 *   search(string): Invokes a search on the datasource.
1021 *   navigate(offset): Used to navigate to a particular offset within
1022 *     the dataset.
1023 */
1024Datasource = new Class(Object, {
1025    init: function(el, datatype) {
1026        // this.id = id;
1027        // this.document = doc || document;
1028        this.element = TC.elementOrId(el);
1029        this.searching = false;
1030        this.navigating = false;
1031        this.type = datatype;
1032        this.onUpdate = null;
1033    },
1034    setPager: function(pager, pager2) {
1035        this.pager = pager;
1036        if (pager2) this.pager2 = pager2
1037        if (pager) pager.datasource = this;
1038        if (pager2) pager2.datasource = this;
1039        if (pager) pager.render();
1040        if (pager2) pager2.render();
1041    },
1042    search: function(str) {
1043        if (this.searching) return;
1044
1045        var doc = TC.getOwnerDocument(this.element);
1046        var args = doc.location.search;
1047        args = args.replace(/^\?/, '');
1048        args = args.replace(/&?offset=\d+/, '');
1049        args = 'search=' + escape(str) + (args ? '&' + args : '') + '&json=1';
1050        if (this.type) {
1051            args = args.replace(/&?_type=\w+/, '');
1052            args += '&_type=' + this.type;
1053        }
1054
1055        this.searching = true;
1056        if (this.pager)
1057            this.pager.render();
1058        if (this.pager2)
1059            this.pager2.render();
1060        var self = this;
1061        TC.Client.call({
1062            'load': function(c,r) { self.searched(r); },
1063            'error': function() { alert("Error during search."); self.searched(null); },
1064            'method': 'POST',
1065            'uri': ScriptURI,
1066            'arguments': args
1067        });
1068    },
1069    searched: function(c) {
1070        this.searching = false;
1071        if (c) {
1072            try {
1073                data = eval('(' + c + ')');
1074                this.update(data['html']);
1075                if (this.pager)
1076                    this.pager.setState({});
1077                if (this.pager2)
1078                    this.pager2.setState({});
1079            } catch (e) {
1080                alert("Error in response: " + e);
1081                if (this.pager)
1082                    this.pager.render();
1083                if (this.pager2)
1084                    this.pager2.render();
1085            }
1086        } else {
1087            if (this.pager)
1088                this.pager.render();
1089            if (this.pager2)
1090                this.pager2.render();
1091        }
1092    },
1093    update: function(html) {
1094        if (!this.element) return;
1095        this.element.innerHTML = html;
1096        this.updated();
1097    },
1098    updated: function() {
1099        if (this.onUpdate) this.onUpdate(this);
1100    },
1101    navigate: function(offset) {
1102        if (offset == null) return;
1103        if (this.navigating) return;
1104
1105        var doc = TC.getOwnerDocument(this.element);
1106        var args = doc.location.search;
1107        args = args.replace(/^\?/, '');
1108        //args = args.replace(/&?search=[^&]+/, '');
1109        //args = args.replace(/&?do_search=1/, '');
1110        args = args.replace(/&?offset=\d+/, '');
1111        args = 'offset=' + offset + (args ? '&' + args : '') + '&json=1';
1112        if (this.type) {
1113            args = args.replace(/&?_type=\w+/, '');
1114            args = args + '&_type=' + this.type;
1115        }
1116
1117        this.navigating = true;
1118        if (this.pager)
1119            this.pager.render();
1120        if (this.pager2)
1121            this.pager2.render();
1122        var self = this;
1123        TC.Client.call({
1124            'load': function(c,r) { self.navigated(r); },
1125            'error': function() { alert("Error in request."); self.navigated(null); },
1126            'method': 'POST',
1127            'uri': ScriptURI,
1128            'arguments': args
1129        });
1130        return false;
1131    },
1132    navigated: function(c) {
1133        var data;
1134        this.navigating = false;
1135        if (c) {
1136            try {
1137                data = eval('(' + c + ')');
1138                this.update(data['html']);
1139                if (this.pager)
1140                    this.pager.setState(data['pager']);
1141                if (this.pager2)
1142                    this.pager2.setState(data['pager']);
1143            } catch (e) {
1144                alert("Error in response: " + e);
1145                if (this.pager)
1146                    this.pager.render();
1147                if (this.pager2)
1148                    this.pager2.render();
1149            }
1150        } else {
1151            if (this.pager) this.pager.render();
1152            if (this.pager2) this.pager2.render();
1153        }
1154    }
1155});
1156//These two lines are to translate phrases in list_tags.tmpl
1157//trans("The tag '[_2]' already exists. Are you sure you want to merge '[_1]' with '[_2]'?");
1158//trans("The tag '[_2]' already exists. Are you sure you want to merge '[_1]' with '[_2]' across all weblogs?");
1159
1160/***
1161 * Pager class
1162 * Expects a 'state' object containing:
1163 *   offset: offset into listing (10, means first row displayed is 11)
1164 *   listTotal: total number of rows in dataset
1165 *   rows: number of rows being displayed
1166 *   chronological: boolean, whether listing is reverse-chronological
1167 *     or not.
1168 *  Methods:
1169 *    constructor(el): constructs using DOM element el as a container
1170 *    setDatasource(ds): used to assign a datasource object
1171 *    setState(state): used to update state settings
1172 *    previous: navigates datasource to previous page
1173 *    next: navigates datasource to next page
1174 *    first: navigates datasource to first page
1175 *    last: navigates datasource to last page
1176 *    previousOffset: calculates and returns offset for previous page
1177 *    nextOffset: calculates and returns offset for next page
1178 *    lastOffset: calculates and returns offset for 'last' page
1179 *    render: refreshes the pagination controls
1180 */
1181Pager = new Class(Object, {
1182    init: function(el) {
1183        this.element = TC.elementOrId(el);
1184        this.state = {};
1185    },
1186    setDatasource: function(ds) {
1187        this.datasource = ds;
1188        this.render();
1189    },
1190    setState: function(state) {
1191        this.state = state;
1192        this.render();
1193    },
1194    previous: function(e) {
1195        this.navigate(this.previousOffset());
1196        return TC.stopEvent(e || window.event);
1197    },
1198    navigate: function(offset) {
1199        if (offset == null) return;
1200        if (this.datasource)
1201            return this.datasource.navigate(offset);
1202        // traditional navigation...
1203        var doc = TC.getOwnerDocument(this.element);
1204        new_loc = doc.location.href;
1205        if (this.state.method == 'POST') {
1206            new_loc += '?' + this.state.return_args;
1207        }
1208        new_loc = new_loc.replace(/&?offset=\d+/, '');
1209        new_loc += '&offset=' + offset;
1210        window.location = new_loc;
1211        return false;
1212    },
1213    previousOffset: function() {
1214        if (this.state.offset > 0) {
1215            var offset = this.state.offset - this.state.limit;
1216            if (offset < 0)
1217                offset = 0;
1218            return offset;
1219        }
1220        return null;
1221    },
1222    nextOffset: function() {
1223        if (this.state.listTotal) {
1224            var listStart = (this.state.offset ? this.state.offset : 0) + 1;
1225            var offset = (this.state.offset ? this.state.offset : 0) + this.state.rows;
1226            if (offset >= this.state.listTotal) {
1227                offset = null;
1228            }
1229            return offset;
1230        }
1231        return null;
1232    },
1233    lastOffset: function() {
1234        var offset = 0;
1235        if (this.state.listTotal) {
1236            var listStart = (this.state.offset ? this.state.offset : 0) + 1;
1237            var listEnd = (this.state.offset ? this.state.offset : 0) + this.state.rows;
1238            if (listEnd >= this.state.listTotal) {
1239                offset = null;
1240            } else {
1241                offset = this.state.listTotal - this.state.rows;
1242                if (offset < listStart)
1243                    offset = null;
1244            }
1245            return offset;
1246        }
1247        return null;
1248    },
1249    next: function(e) {
1250        this.navigate(this.nextOffset());
1251        return TC.stopEvent(e || window.event);
1252    },
1253    first: function(e) {
1254        this.navigate(0);
1255        return TC.stopEvent(e || window.event);
1256    },
1257    last: function(e) {
1258        this.navigate(this.lastOffset());
1259        return TC.stopEvent(e || window.event);
1260    },
1261    render: function() {
1262        if (!this.element) return;
1263
1264        /*
1265        This long method is concerned with creating the elements of
1266        the pagination control. It refreshes the controls based on
1267        the 'state' member of the Pager object. This control is
1268        typically tied to a Datasource object. So the navigation
1269        links of the control will influence the Datasource.
1270        Likewise, upon navigating the Datasource, it will invoke
1271        the pager to refresh when the data has been updated.
1272
1273        pager.rows (number of rows shown)
1274        pager.listTotal (total number of rows in datasource)
1275        pager.offset (offset currently used)
1276        pager.chronological (boolean, whether the listing is chronological or not)
1277        */
1278        var html = '';
1279        /* TODO - this can all be replaced with a js template */
1280        if (this.datasource && this.datasource.navigating) {
1281            // TODO: change this to use a CSS class instead.
1282            html = "<div>" + trans('Loading...') + " <img src=\"" + StaticURI + "images/indicator.white.gif\" height=\"10\" width=\"10\" alt=\"...\" /></div>";
1283            this.element.innerHTML = html;
1284        } else if ((this.state.rows != null) && (this.state.rows > 0)) {
1285            this.element.innerHTML = '';
1286            var listStart = (this.state.offset ? this.state.offset : 0) + 1;
1287            var listEnd = (this.state.offset ? this.state.offset : 0) + this.state.rows;
1288
1289            var doc = TC.getOwnerDocument(this.element);
1290            var self = this;
1291            // pagination control structure
1292            if (this.state.offset > 0) {
1293                var link = doc.createElement('a');
1294                link.href = 'javascript:void(0)';
1295                link.onclick = function(e) { return self.first(e) };
1296                link.className = 'start';
1297                link.innerHTML = '<em>&lt;&lt;</em>&nbsp;';
1298                this.element.appendChild(link);
1299            } else {
1300                var txt = doc.createElement('span');
1301                txt.className = 'start-disabled';
1302                txt.innerHTML = '<em>&lt;&lt;</em>&nbsp;';
1303                this.element.appendChild(txt);
1304            }
1305            if (this.previousOffset() != null) {
1306                var link = doc.createElement('a');
1307                link.href = 'javascript:void(0)';
1308                link.onclick = function(e) { return self.previous(e) };
1309                link.className = 'to-start';
1310                link.innerHTML = '<em>&lt;</em>&nbsp;';
1311                this.element.appendChild(link);
1312            } else {
1313                var txt = doc.createElement('span');
1314                txt.className = 'to-start-disabled';
1315                txt.innerHTML = '<em>&lt;</em>&nbsp;';
1316                this.element.appendChild(txt);
1317            }
1318            var showing = doc.createElement('span');
1319            showing.className = 'current-rows';
1320            if (this.state.listTotal)
1321                showing.innerHTML = trans('[_1] &ndash; [_2] of [_3]', listStart, listEnd, this.state.listTotal);
1322            else
1323                showing.innerHTML = trans('[_1] &ndash; [_2]', listStart, listEnd);
1324            this.element.appendChild(showing);
1325            if (this.nextOffset() != null) {
1326                var link = doc.createElement('a');
1327                link.href = 'javascript:void(0)';
1328                link.onclick = function(e) { return self.next(e) };
1329                link.className = 'to-end';
1330                link.innerHTML = '&nbsp;<em>&gt;</em>';
1331                this.element.appendChild(link);
1332            } else {
1333                var txt = doc.createElement('span');
1334                txt.className = 'to-end-disabled';
1335                txt.innerHTML = '&nbsp;<em>&gt;</em>';
1336                this.element.appendChild(txt);
1337            }
1338            if (this.lastOffset() != null) {
1339                var link = doc.createElement('a');
1340                link.href = 'javascript:void(0)';
1341                link.onclick = function(e) { return self.last(e) };
1342                link.className = 'end';
1343                link.innerHTML = '&nbsp;<em>&gt;&gt;</em>';
1344                this.element.appendChild(link);
1345            } else {
1346                var txt = doc.createElement('span');
1347                txt.className = 'end-disabled';
1348                txt.innerHTML = '&nbsp;<em>&gt;&gt;</em>';
1349                this.element.appendChild(txt);
1350            }
1351        } else {
1352            this.element.innerHTML = '';
1353        }
1354    }
1355});
1356
1357
1358MT = {};
1359
1360
1361if ( window.App ) {
1362
1363App.singletonConstructor =
1364MT.App = new Class( App, {
1365 
1366
1367    NAMESPACE: "mt",
1368    changed: false,
1369    autoSaveDelay: 15000, /* ms */
1370
1371
1372    initComponents: function() {
1373        arguments.callee.applySuper( this, arguments );
1374        this.openFlyouts = [];
1375
1376        this.setDelegate( "navMenu", new this.constructor.NavMenu() );
1377
1378        this.initFormElements();
1379       
1380        if ( this.constructor.Resizer ) {
1381            this.setDelegate( "resizer", new this.constructor.Resizer( this.getIndirectMethod( "resizeComplete" ) ) );
1382            this.setDelegateListener( "eventMouseUp", "resizer" );
1383            this.setDelegateListener( "eventMouseMove", "resizer" );
1384        }
1385
1386        if ( this.constructor.DefaultValue )
1387            this.setDelegate( "defaultValue", new this.constructor.DefaultValue() );
1388       
1389        if ( this.constructor.TabContainer )
1390            this.setDelegate( "tabContainer", new this.constructor.TabContainer() );
1391           
1392        var forms = DOM.getElementsByTagAndAttribute( this.document, "form", "mt:auto-save" );
1393        if ( forms.length )
1394            window.onbeforeunload = this.getIndirectEventListener( "eventBeforeUnload" );
1395
1396        for ( var i = 0; i < forms.length; i++ ) {
1397            var autosave = truth( forms[ i ].getAttribute( "mt:auto-save" ) );
1398            if ( !autosave )
1399                continue;
1400
1401            this.form = forms[ i ];
1402
1403            var ad = forms[ i ].getAttribute( "mt:auto-save-delay" );
1404            var autoSaveDelay;
1405            if ( ad !== null ) {
1406                autoSaveDelay = parseInt( ad ) || 0;
1407                this.autoSaveDelay = autoSaveDelay;
1408            } else
1409                log.warn("auto-save-delay not defined on this form. Defaulting to "+this.autoSaveDelay);
1410
1411            log('using auto save delay: '+this.autoSaveDelay);
1412
1413            var es = Array.fromPseudo(
1414                forms[ i ].getElementsByTagName( "input" ),
1415                forms[ i ].getElementsByTagName( "textarea" )
1416            );
1417            for ( var j = 0; j < es.length; j++ ) {
1418                if ( es[ j ].getAttribute && es[ j ].getAttribute( "mt:watch-change" ) ) {
1419                    log('adding watcher to '+es[ j ].name);
1420                    DOM.addEventListener( es[ j ], "change", this.getIndirectEventListener( "setDirty" ) );
1421                }
1422                if ( autosave && es[ j ].nodeName == "TEXTAREA" ) {
1423                    /* don't attach to the editor textarea in this form */
1424                    if ( this.editor && es[ j ].id == this.editor.textarea.element.id )
1425                        continue;
1426                    DOM.addEventListener( es[ j ], "keydown", this.getIndirectEventListener( "setDirtyKeyDown" ) );
1427                }
1428            }
1429        }
1430
1431        if ( MT.App.dirty )
1432            this.changed = true;   
1433    },
1434
1435
1436    destroyObject: function() {
1437        this.autoSaveReq = null;
1438        this.autoSaveTimer = null;
1439        this.form = null;
1440        this.cpeList = null;
1441        arguments.callee.applySuper( this, arguments );
1442    },
1443
1444
1445    reflow: function() {
1446        arguments.callee.applySuper( this, arguments );
1447        /* fix a display issue */
1448        var navEl = DOM.getElement( "content-nav" );
1449        var navConEl = DOM.getElement( "content-header-inner" );
1450        if ( navEl && navConEl ) {
1451            var d = DOM.getAbsoluteDimensions( navConEl );
1452            navEl.style.top = "-" + (d.clientHeight - 13) + "px";
1453        }
1454    },
1455
1456
1457    initFormElements: function() {
1458        var forms = document.getElementsByTagName( "form" );
1459        for( var i = 0; i < forms.length; i++ ) {
1460            forms[ i ].submitted = false;
1461            DOM.addEventListener( forms[ i ], "submit", this.getIndirectEventListener( "eventSubmit" ) );
1462            var tareas = forms[ i ].getElementsByTagName( "textarea" );
1463            var tabs = 0;
1464            for ( var j = 0; j < tareas.length; j++ ) {
1465                if ( ( tabs = tareas[ j ].getAttribute( "mt:allow-tabs" ) ) )
1466                    if ( truth ( tabs ) )
1467                        this.attachTabsToTextarea( tareas[ j ] );
1468
1469                if ( tareas[ j ].getAttribute( "mt:editor" ) == "codepress" ) {
1470                    if ( this.constructor.CodePress.isSupported() ) {
1471                        var ed = new this.constructor.CodePress( tareas[ j ] );
1472                        if ( ed ) {
1473                            if ( !this.cpeList )
1474                                this.cpeList = [];
1475                            this.cpeList.push( ed );
1476                        }
1477                    }
1478                }
1479            }
1480        }
1481    },
1482
1483
1484    eventSubmit: function( event ) {
1485        var form = DOM.getFirstAncestorByTagName( event.target, "form", true );
1486        if ( !form )
1487            return;
1488           
1489        if ( form.getAttribute( "mt:once" ) ) {
1490       
1491            if ( form.submitted )
1492                return event.stop();
1493       
1494            this.toggleSubmit( form, true );
1495        }
1496
1497        if ( this.cpeList )
1498            this.cpeList.forEach( function( cpe ) { cpe.onSubmit() } );
1499
1500        form.submitted = true;
1501    },
1502
1503
1504    eventBeforeUnload: function( event ) {
1505        if ( this.changed ) {
1506            if ( this.constructor.Editor )
1507                return event.returnValue = Editor.strings.unsavedChanges;
1508            else if ( window.Editor )
1509                return event.returnValue = window.Editor.strings.unsavedChanges;
1510        }
1511       
1512        return undefined;
1513    },
1514
1515
1516    toggleSubmit: function( form, disable ) {
1517        /* sane default */
1518        if ( !disable )
1519            disable = false;
1520        var elements = form.getElementsByTagName( "*" );
1521        for( var i = 0; i < elements.length; i++ ) {
1522            var element = elements[ i ];
1523            var tagName = element.tagName.toLowerCase();
1524            var type = element.getAttribute( "type" );
1525            type = type ? type.toLowerCase() : "";
1526            if ( tagName == "button" || 
1527                (tagName == "input" && (type == "button" || type == "submit" || type == "image")) )
1528                element.disabled = disable;
1529        }
1530    },
1531
1532
1533    closeFlyouts: function( target ) {
1534        var flyout;
1535        var es = Array.fromPseudo( this.openFlyouts );
1536        for ( var i = 0, len = es.length; i < len; i++ ) {
1537            if ( ( flyout = DOM.getElement( es[ i ] ) ) ) {
1538                if ( target && DOM.hasAncestor( target, flyout ) )
1539                    continue;
1540                DOM.addClassName( flyout, "hidden" );
1541                this.openFlyouts.remove( es[ i ] );
1542                showAllDropDown();
1543            }
1544        }
1545    },
1546
1547
1548    eventClick: function( event ) {
1549        var command = this.getMouseEventCommand( event );
1550
1551        switch( command ) {
1552           
1553            case "openSelectBlog":
1554                app.openDialog( '__mode=dialog_select_weblog&amp;select_favorites=1&return_args='
1555                    + escape( event.commandElement.getAttribute( "mt:href" ) ) );
1556                break;
1557
1558            case "goToLocation":
1559                this.gotoLocation( event.commandElement.getAttribute( "href" ) );
1560                break;
1561           
1562            case "autoSave":
1563                this.autoSave();
1564                break;
1565
1566            case "setModeCodepressOn":
1567                this.cpeList.forEach( function( cpe ) { cpe.toggleOn( true ); } );
1568                break;
1569
1570            case "setModeCodepressOff":
1571                this.cpeList.forEach( function( cpe ) { cpe.toggleOff( true ); } );
1572                break;
1573
1574            case "openFlyout":
1575                var name = event.commandElement.getAttribute( "mt:flyout" );
1576                var el = DOM.getElement( name );
1577                if ( !defined( el ) )
1578                    return;
1579
1580                this.closeFlyouts( event.target );
1581
1582                DOM.removeClassName( el, "hidden" );
1583                this.targetElement = event.target;
1584                this.applyAutolayouts( el );
1585                this.targetElement = null;
1586
1587                hideAllDropDown();
1588                showDropDown( el );
1589                this.openFlyouts.add( name );
1590
1591                break;
1592               
1593            case "closeFlyout":
1594                this.closeFlyouts();
1595               
1596                break;
1597
1598            default:
1599                this.closeFlyouts( event.target );
1600
1601                var form = DOM.getFirstAncestorByTagName( event.target, "form", true );
1602                if ( !form )
1603                    return;
1604
1605                var mode = event.target.getAttribute( "mt:mode" );
1606                if ( !mode && event.commandElement )
1607                    mode = event.commandElement.getAttribute( "mt:mode" );
1608
1609                if ( mode ) {
1610                    log('setting __mode in this form: '+mode);
1611                    var elements = form.getElementsByTagName( "input" );
1612                    for( var i = 0; i < elements.length; i++ ) {
1613                        if ( elements[ i ].name == "__mode" ) {
1614                            log('found __mode element');
1615                            elements[ i ].value = mode;
1616                            break;
1617                        }
1618                    }
1619                }
1620
1621                if ( command == "submit" ) {
1622                    event.stop();
1623                    var msg;
1624                    if ( event.commandElement && ( msg = event.commandElement.getAttribute( "mt:confirm-msg" ) ) )
1625                        if ( !confirm( msg ) )
1626                            return;
1627                    form.submit();
1628                }
1629
1630                return;
1631
1632        }
1633        return event.stop();
1634    },
1635   
1636
1637    /* from blog selector transient */
1638    gotoUrl: function( url ) {
1639        if ( url )
1640            this.gotoLocation( url );
1641    },
1642
1643
1644    toggleActive: function( id ) {
1645        log('toggleactive:'+id);
1646        var div = DOM.getElement( id );
1647        if ( DOM.hasClassName( div, 'active' ) )
1648            DOM.removeClassName( div, 'active' );
1649        else
1650            DOM.addClassName( div, 'active' );
1651    },
1652
1653
1654    openDialog: function( params ) {
1655        this.closeFlyouts();
1656        show("dialog-container");
1657        /* TODO remove this cruft */
1658        /*  handle escape key for closing modal dialog */
1659        DOM.addEventListener( document.body, "keypress", dialogKeyPress, true );
1660        openDialogUrl( ScriptURI + "?" + params );
1661        /* IE hack to get the dialog modal to reflow */
1662        if ( document.all && DOM.getElement( "dialog-container" ) ) {
1663            DOM.addClassName( "dialog-container", "hidden" );
1664            new Timer(function() {
1665                DOM.removeClassName( "dialog-container", "hidden" );
1666            }, 500, 1 );
1667        }
1668    },
1669
1670
1671    attachTabsToTextarea: function( element ) {
1672        DOM.addEventListener( element, "keypress", this.getIndirectMethod( "eventKeyPressAllowTabs" ) );
1673        DOM.addEventListener( element, "keydown", this.getIndirectMethod( "eventKeyDownAllowTabs" ) );
1674    },
1675
1676
1677    eventKeyPressAllowTabs: function( event ) {
1678        if ( event.keyCode == 9 )
1679            return event.stop();
1680    },
1681   
1682   
1683    eventKeyDownAllowTabs: function( event ) {
1684        if ( event.keyCode == 9 ) {
1685                    TC.setSelectionValue( ( event.target || event.srcElement ) , "\t" );
1686            return false;
1687        }
1688    },
1689
1690
1691    resizeComplete: function( target, xStart, yStart, x, y, width, height ) {
1692       
1693        switch ( target.id ) {
1694            case "textarea-enclosure":
1695                var es = [ "text", "text_cpe" ];
1696                for ( var i = 0; i < es.length; i++ ) {
1697                    es[ i ] = DOM.getElement( es[ i ] );
1698                    if ( es[ i ] )
1699                        DOM.setHeight( es[ i ], height );
1700                }
1701                break;
1702
1703            /* expand here */
1704        }
1705
1706    },
1707   
1708   
1709    autoSave: function() {
1710        var data = DOM.getFormData( this.form );
1711        data["_autosave"] = 1;
1712
1713        if ( this.cpeList )
1714            this.cpeList.forEach( function( cpe ) { cpe.autoSave( data ) } );
1715
1716        /* don't cancel a pending save */
1717        if ( defined( this.autoSaveReq ) )
1718            return;
1719       
1720        var areas = [
1721            DOM.getElement( "autosave-notification" ),
1722            DOM.getElement( "autosave-notification-top" ),
1723            DOM.getElement( "autosave-notification-bottom" )
1724        ];
1725        if ( areas )
1726            for ( var i = 0; i < areas.length; i++ )
1727                if ( areas[ i ] )
1728                    areas[ i ].innerHTML = Template.process( "autoSave", { saving: true } );
1729
1730        this.autoSaveReq = TC.Client.call({
1731            load: this.getIndirectMethod( "autoSaveComplete" ),
1732            error: this.getIndirectMethod( "autoSaveError" ),
1733            method: 'POST',
1734            uri: this.form.action,
1735            arguments: data
1736        });
1737    },
1738
1739
1740    autoSaveComplete: function( c, r ) {
1741        this.autoSaveTimer = this.autoSaveReq = undefined;
1742
1743        log('auto save complete '+r);
1744        if ( r != "true" )
1745            return log.error( "Error auto-saving post: "+r );
1746       
1747        var areas = [
1748            DOM.getElement( "autosave-notification" ),
1749            DOM.getElement( "autosave-notification-top" ),
1750            DOM.getElement( "autosave-notification-bottom" )
1751        ];
1752        var d = new Date();
1753        if ( areas )
1754            for ( var i = 0; i < areas.length; i++ )
1755                if ( areas[ i ] )
1756                    areas[ i ].innerHTML = Template.process( "autoSave", {
1757                        saving: false,
1758                        hh: d.getHours().toString().pad( 2, "0" ),
1759                        mm: d.getMinutes().toString().pad( 2, "0" ),
1760                        ss: d.getSeconds().toString().pad( 2, "0" )
1761                    } );
1762    },
1763
1764   
1765    autoSaveError: function( c, r ) {
1766        this.autoSaveTimer = this.autoSaveReq = undefined;
1767       
1768        log.error( "Error auto-saving post" );
1769        var areas = [
1770            DOM.getElement( "autosave-notification" ),
1771            DOM.getElement( "autosave-notification-top" ),
1772            DOM.getElement( "autosave-notification-bottom" )
1773        ];
1774        if ( areas )
1775            for ( var i = 0; i < areas.length; i++ )
1776                if ( areas[ i ] )
1777                    areas[ i ].innerHTML = ''
1778    },
1779
1780
1781    setDirtyKeyDown: function( event ) {
1782        if ( this.dirtyKeyDownTimer )
1783            this.dirtyKeyDownTimer.stop();
1784        this.dirtyKeyDownTimer = new Timer( this.getIndirectMethod( "setDirty" ), 5000, 1 );
1785    },
1786
1787
1788    setDirty: function( event ) {
1789        var autoSaveDelay = this.autoSaveDelay;
1790        if ( event && event.target ) {
1791            var form = DOM.getFirstAncestorByTagName( event.target, "form", true );
1792            if ( form ) {
1793                log('found dirty form: '+form);
1794                this.form = form;
1795                if ( autoSaveDelay = parseInt( form.getAttribute( "mt:auto-save-delay" ) ) || 0 ) {
1796                    this.autoSaveDelay = autoSaveDelay;
1797                    log('using auto save delay: '+this.autoSaveDelay);
1798                }
1799            }
1800        }
1801           
1802        this.changed = true;
1803        if ( autoSaveDelay < 1 )
1804            return;
1805
1806        if ( defined( this.autoSaveTimer ) )
1807            return this.autoSaveTimer.reset();
1808        this.autoSaveTimer = new Timer( this.getIndirectMethod( "autoSave" ), autoSaveDelay, 1 );
1809    },
1810
1811
1812    clearDirty: function( event ) {
1813        this.changed = false;
1814    },
1815
1816   
1817    insertCode: function( code ) {
1818        if ( this.cpeList )
1819            this.cpeList[ 0 ].insertCode( code );
1820        else if ( this.editor )
1821            this.editor.insertHTML( code );
1822        else {
1823            var txt = DOM.getElement( "text" );
1824            setSelection( txt, code );
1825            DOM.focus( txt );
1826        }
1827    }
1828   
1829
1830} );
1831
1832if ( window.Calendar ) {
1833
1834MT.App.Calendar = new Class( Calendar, {
1835
1836
1837    open: function( data, callback ) {
1838        if ( data.date && data.date.length )
1839            data.date = data.date.replace( /^(\S+).*/, "$1" );
1840        arguments.callee.applySuper( this, arguments );
1841       
1842        /* reset invalid dates to the current date */
1843        if ( !this.dateObject )
1844            this.dateObject = new Date();
1845    },
1846
1847
1848    eventClick: function() {
1849        arguments.callee.applySuper( this, arguments );
1850        if ( this.callback )
1851            this.callback( this.dateObject );
1852    }
1853
1854
1855} );
1856
1857}
1858
1859
1860
1861
1862MT.App.Resizer = new Class( Object, {
1863
1864
1865    dragging: false,
1866    element: null,
1867   
1868    xLock: false,
1869    yLock: false,
1870
1871    xStart: null,
1872    yStart: null,
1873
1874
1875    init: function( callback ) {
1876        if ( callback )
1877            this.callback = callback;
1878    },
1879
1880
1881    destroy: function() {
1882        this.callback = null;
1883    },
1884
1885
1886    eventMouseDown: function( event ) {
1887        this.dragging = true;
1888       
1889        this.reset();
1890       
1891        this.target = event.attributeElement.getAttribute( "mt:target" );
1892
1893        /* x or y locking */
1894        var lock = event.attributeElement.getAttribute( "mt:lock" );
1895        if ( lock ) {
1896            if ( lock == "x" || lock == "X" )
1897                this.xLock = true;
1898            else if ( lock == "y" || lock == "Y" )
1899                this.yLock = true;
1900        }
1901       
1902        /* clone the drag node */
1903        this.element = event.attributeElement.cloneNode( true );
1904        /* using the current mouse position, set the positon of the drag obj */
1905        var d = DOM.getAbsoluteCursorPosition( event );
1906        this.yStart = d.y;
1907        this.xStart = d.x;
1908       
1909        var dm = DOM.getAbsoluteDimensions( event.attributeElement );
1910        var adm = DOM.getAbsoluteDimensions( event.attributeElement );
1911       
1912        if ( !this.yLock )
1913            DOM.setTop( this.element,  d.y );
1914        else
1915            DOM.setTop( this.element, adm.absoluteTop );
1916
1917        if ( !this.xLock )
1918            DOM.setLeft( this.element,  d.x );
1919        else
1920            DOM.setLeft( this.element, adm.absoluteLeft );
1921
1922        DOM.setWidth( this.element, dm.offsetWidth );
1923        DOM.setHeight( this.element, dm.offsetHeight );
1924       
1925        var mask = DOM.getElement( "resize-mask" );
1926        mask.insertBefore( this.element, mask.firstChild );
1927        DOM.addClassName( this.element, "moving" );
1928        DOM.removeClassName( mask, "hidden" );
1929        /* TODO autolayout this */
1930        DOM.setHeight( mask, finiteInt( mask.parentNode.clientHeight ) );
1931
1932        return event.stop();
1933    },
1934
1935
1936    eventMouseMove: function( event ) {
1937        if ( !this.dragging )
1938            return;
1939       
1940        var d = DOM.getAbsoluteCursorPosition( event );
1941       
1942        if ( !this.yLock )
1943            DOM.setTop( this.element, d.y );
1944
1945        if ( !this.xLock )
1946            DOM.setLeft( this.element, d.x );
1947
1948        return event.stop();
1949    },
1950
1951
1952    eventMouseUp: function( event ) {
1953        if ( !this.dragging )
1954            return;
1955       
1956        this.dragging = false;
1957        var d = DOM.getAbsoluteCursorPosition( event );
1958           
1959        DOM.addClassName( "resize-mask", "hidden" );
1960       
1961        /* cleanup */
1962        if ( this.element && this.element.parentNode )
1963            this.element.parentNode.removeChild( this.element );
1964
1965        var target = DOM.getElement( this.target );
1966        if ( !target )
1967            return this.reset();
1968
1969        var targetDim = DOM.getDimensions( target );
1970       
1971        var height = d.y - targetDim.offsetTop;
1972        if ( !this.yLock ) {
1973            var hMin = target.getAttribute( "mt:min-height" );
1974            if ( hMin )
1975                if ( height < parseInt( hMin ) )
1976                    height = parseInt( hMin );
1977       
1978            var hMax = target.getAttribute( "mt:max-height" );
1979            if ( hMax )
1980                if ( height > parseInt( hMax ) )
1981                    height = parseInt( hMax );
1982
1983            log('new height: '+height);
1984        }
1985
1986        var width = d.x - targetDim.offsetLeft;
1987        if ( !this.xLock ) {
1988            var wMin = target.getAttribute( "mt:min-width" );
1989            if ( wMin )
1990                if ( width < parseInt( wMin ) )
1991                    width = parseInt( wMin );
1992       
1993            var wMax = target.getAttribute( "mt:max-width" );
1994            if ( wMax )
1995                if ( width > parseInt( wMax ) )
1996                    width = parseInt( wMax );
1997
1998            log('new width: '+width);
1999        }
2000       
2001        /* give the callback a chance to stop us from setting this height and width */
2002        if ( this.callback && ( this.callback( target, this.xStart, this.yStart, d.x, d.y, width, height ) ) )
2003            return this.reset();
2004       
2005        if ( !this.yLock )
2006            DOM.setHeight( target, height );
2007
2008        if ( !this.xLock )
2009            DOM.setWidth( target, width );
2010
2011        var hUpdate = target.getAttribute( "mt:update-field-height" );
2012        if ( hUpdate && ( hUpdate = DOM.getElement( hUpdate ) ) )
2013            hUpdate.value = height;
2014       
2015        var wUpdate = target.getAttribute( "mt:update-field-width" );
2016        if ( wUpdate && ( wUpdate = DOM.getElement( wUpdate ) ) )
2017            wUpdate.value = width;
2018
2019        this.reset();
2020    },
2021
2022
2023    reset: function() {
2024        /* remove left over drag obj, if any */
2025        if ( this.element && this.element.parentNode )
2026            this.element.parentNode.removeChild( this.element );
2027       
2028        this.xStart = this.yStart = this.element = this.target = null;
2029        this.xLock = this.yLock = false;
2030    }
2031
2032
2033} );
2034
2035
2036MT.App.DefaultValue = new Class( Object, {
2037
2038
2039    init: function() {
2040        var es = DOM.getElementsByAttributeAndValue( document, "mt:delegate", "default-value" );
2041        for ( var i = 0; i < es.length; i++ ) {
2042            var val = es[ i ].getAttribute( "mt:default" );
2043            if ( !val )
2044                continue;
2045           
2046            if ( es[ i ].value != val )
2047                DOM.removeClassName( es[ i ], "input-hint" );
2048        }
2049    },
2050
2051
2052    eventFocus: function( event ) {
2053        var element = event.attributeElement;
2054        var val = element.getAttribute( "mt:default" );
2055        if ( !val )
2056            return;
2057
2058        DOM.removeClassName( element, "input-hint" );
2059       
2060        if ( element.value == val )
2061            element.value = "";
2062    },
2063
2064
2065    eventBlur: function( event ) {
2066        var element = event.attributeElement;
2067        var val = element.getAttribute( "mt:default" );
2068        if ( !val )
2069            return;
2070       
2071        var opts = {};
2072        /* simple options for now */
2073        var opt = element.getAttribute( "mt:delegate-options" );
2074        if ( opt && opt == "-class" ) {
2075            opts.noclassChange = true;
2076        }
2077       
2078        if ( element.value != "" )
2079            return;
2080       
2081        element.value = val;
2082        if ( opts.noclassChange )
2083            return;
2084
2085        DOM.addClassName( element, "input-hint" );
2086    },
2087   
2088   
2089    /* hate on IE */
2090    eventFocusIn: function( event ) {
2091        return this.eventFocus( event );
2092    },
2093   
2094
2095    eventFocusOut: function( event ) {
2096        this.eventBlur( event );
2097    },
2098
2099
2100    eventSubmit: function( event ) {
2101        return event.stop();
2102    }
2103   
2104   
2105} );
2106
2107
2108MT.App.TabContainer = new Class( Object, {
2109
2110    init: function() {
2111        var es = DOM.getElementsByAttributeAndValue( document, "mt:delegate", "tab-container" );
2112        var t;
2113        for ( var i = 0; i < es.length; i++ ) {
2114            if ( t = es[ i ].getAttribute( "mt:selected-tab" ) ) {
2115                this.selectTab( es[ i ], t );
2116                continue;
2117            }
2118
2119            if ( t = es[ i ].getAttribute( "mt:persist-tab-cookie" ) ) {
2120                log( 'found persisted tab setting: '+t);
2121                t = Cookie.fetch( t );
2122                if ( t && t.value && t.value != "" ) {
2123                    log( 'cookie: '+t.value);
2124                    this.selectTab( es[ i ], t.value );
2125                }
2126            }
2127        }
2128    },
2129
2130
2131    eventClick: function( event ) {
2132        var command = app.getMouseEventCommand( event );
2133        if (!event.commandElement) return;
2134        var tab = event.commandElement.getAttribute( "mt:tab" );
2135        if ( tab && command != "selectTab" )
2136            this.selectTab( event.attributeElement, tab );
2137
2138        switch( command ) {
2139           
2140            case "setEditorContent":
2141                event.stop();
2142                app.setEditor( "content" );
2143                break;
2144
2145            case "setEditorExtended":
2146                event.stop();
2147                app.setEditor( "extended" );
2148                break;
2149           
2150            case "selectTab":
2151                if ( !tab )
2152                    tab = event.commandElement.getAttribute( "mt:select-tab" );
2153
2154                if ( tab ) {
2155                    this.selectTab( event.attributeElement, tab );
2156                    var cookie = event.attributeElement.getAttribute( "mt:persist-tab-cookie" );
2157                    if ( cookie ) {
2158                        var d = new Date();
2159                        d.setYear( d.getYear() + 1902 ); /* two years */
2160                        Cookie.bake( cookie, tab, undefined, undefined, d );
2161                    }
2162                }
2163               
2164                event.stop();
2165                break;
2166
2167        }
2168    },
2169
2170
2171    selectTab: function( element, name ) {
2172        log('select tab '+name);
2173        var es = DOM.getElementsByAttribute( element, "mt:tab" );
2174        for ( var i = 0; i < es.length; i++ ) {
2175            if ( es[ i ].getAttribute( "mt:tab" ) == name )
2176                DOM.addClassName( es[ i ], "selected-tab" );
2177            else
2178                DOM.removeClassName( es[ i ], "selected-tab" );
2179        }
2180
2181        /* look for tab contents elements matching 'name' */
2182        es = DOM.getElementsByAttribute( element, "mt:tab-content" );
2183        for ( var i = 0; i < es.length; i++ ) {
2184            /* then hide everything except the tab content we want to show */
2185            if ( es[ i ].getAttribute( "mt:tab-content" ) == name )
2186                DOM.removeClassName( es[ i ], "hidden" );
2187            else
2188                DOM.addClassName( es[ i ], "hidden" );
2189        }
2190    }
2191
2192
2193} );
2194
2195
2196MT.App.NavMenu = new Class( Object, {
2197
2198    opened: false,
2199    outTimer: null,
2200    inTimer: null,
2201    el: null,
2202    al: null,
2203
2204
2205    eventMouseOver: function( event ) {
2206        var el = DOM.getFirstAncestorByClassName( event.target, "nav-menu", true );
2207        if ( !el )
2208            return;
2209       
2210        /* if they moused in, but moved to a new menu, reset the in timer */
2211        if ( this.inTimer && this.el && this.el !== el )
2212            this.inTimer.stop();
2213       
2214        this.al = event.attributeElement;
2215        this.el = el;
2216
2217        if ( this.outTimer )
2218            this.outTimer.stop();
2219
2220        if ( this.al.getAttribute( "mt:is-opened" ) == "1" )
2221            return this.openMenu();
2222       
2223        var delay = event.attributeElement.getAttribute( "mt:nav-delayed-open" ); // ms
2224
2225        if ( delay ) {
2226            delay = parseInt( delay );
2227            /* no hover in? */
2228            if ( delay < 0 )
2229                return;
2230            if ( this.inTimer )
2231                this.inTimer.stop();
2232            this.inTimer = newÂ