root/branches/release-32/mt-static/mt.js @ 1581

Revision 1581, 95.4 kB (checked in by bsmith, 20 months ago)

bugzid:70071 - Module Caching Options frontend work

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