function stop_event(event)
{
    if (event.preventDefault) {
        event.preventDefault();
        event.stopPropagation();
    }
    else {
        event.returnValue = false;
        event.cancelBubble = true;
    }
}

function add_to_bookmarks(title, url, fallback){
    if (document.all)
        window.external.AddFavorite(url, title);
    else if (window.sidebar)
        window.sidebar.addPanel(title, url, "")
    else
        alert(fallback);
}

function save_scroll() {
    var top = document.documentElement.scrollTop || document.body.scrollTop;
    document.cookie = 'scroll='+top+'; path=/; expires=' +
        new Date(new Date().getTime() + 7 * 1000 * 60 * 60 * 24).toGMTString();
}

function restore_scroll() {
    var top;
    top = document.cookie.match(/scroll=(\d+)/);
    if (top && top[1])
        window.scrollTo(0, top);
}

function make_search(engine)
{
    var q = encodeURIComponent(document.ff.q.value);
    q = q.replace(/%20/g, '+')
    if (!engine && document.cookie.match(/search=/)) {
        engine = document.cookie.match(/search=(\w+)/)[1];
    }

    var expires = new Date(new Date().getTime() + 7 * 1000 * 60 * 60 * 24).toGMTString();
    if (engine == 'yahoo') {
        document.cookie = 'search=yahoo; path=/; expires=' +expires;
        location.href = 'http://search.yahoo.com/search?p='+ q+ '&ei=UTF-8';
    }
    else if (engine == 'yandex') {
        document.cookie = 'search=yandex; path=/; expires=' +expires;
        location.href = 'http://www.yandex.ru/yandsearch?text='+ q;
    }
    else if (engine == 'wikipedia') {
        document.cookie = 'search=wikipedia; path=/; expires=' +expires;
        location.href = 'http://en.wikipedia.org/wiki/Special:Search?search='+ q;
    }
    else if (engine == 'tfd') {
        document.cookie = 'search=tfd; path=/; expires=' +expires;
        location.href = 'http://www.tfd.com/'+ q;
    }
    else if (engine == 'ask') {
        document.cookie = 'search=ask; path=/; expires=' +expires;
        location.href = 'http://www.ask.com/web?q='+ q;
    }
    else if (engine == 'live') {
        document.cookie = 'search=live; path=/; expires=' +expires;
        location.href = 'http://search.live.com/results.aspx?q='+ q;
    }
    else if (engine == 'youtube') {
        document.cookie = 'search=youtube; path=/; expires=' +expires;
        location.href = 'http://www.youtube.com/results?search_query='+ q;
    }
    else {
        document.cookie = 'search=google; path=/; expires=' +expires;
        location.href = 'http://www.google.com/'+ (top.google_id ? 'custom' : 'search')+ '?q='+ q+
            (top.google_id ? ('&client='+ top.google_id+
                '&sa=Google+Search&forid=1&ie=UTF-8&oe=UTF-8&cof=DIV%3A%23DDEEFF%3BGFNT%3A7777CC%3BAH%3Acenter%3B&hl=en') : '');
    }
    arrange_search_icons();
    return false;
}

var search_icons = {
    google: "Google",
    yandex: "Yandex",
    yahoo: "Yahoo!",
    wikipedia: "Wikipedia",
    tfd: "Dictionary"
}

function arrange_search_icons() {
    var engine;
    if (document.cookie && document.cookie.match(/search=(\w*)/))
        engine = document.cookie.match(/search=(\w*)/)[1];
    if (!engine || !$('search-' + engine))
        engine = 'google';

    var si = $('search-icons'), se = $('search-' + engine);
    if (si.firstChild.firstChild)
        si.firstChild.removeChild(si.firstChild.firstChild);
    var newChild = se.cloneNode(true);
    newChild.id = 'search-default';
    si.firstChild.insertBefore(newChild, null);
}

Function.prototype.bind_with_event = function() {
    var __method = this, args = $A(arguments), object = args.shift();
    args.unshift(null);
    return function(event) {
        args[0] = event;
        return __method.apply(object, args);
    }
}

function getOffset(node) {
    var top = 0, left = 0;
    do {
        top += node.offsetTop || 0;
        left += node.offsetLeft || 0;
        node = node.offsetParent;
    } while (node);
    return [left, top];
}


var Drag = {
    initialize: function() {
        document.body.onmousemove = this.move.bind_with_event(this);
        document.body.onmouseup = this.stop.bind_with_event(this);
        this.placeholder = $('placeholder');
    },

    start: function(event, element) {
        if (event.which && event.which == 1 || !event.which && event.button && event.button == 1) {
            this.latch = element.parentNode;
            this.oid = element.parentNode.id.replace(/^oid_/, '');
            this.windowHeight = window.innerHeight || document.documentElement.clientHeight ||
                document.body.clientHeight;
            var offset = getOffset(this.latch);

            // including scroll
            var pointerX = event.pageX || (event.clientX +
                (document.documentElement.scrollLeft || document.body.scrollLeft));
            var pointerY = event.pageY || (event.clientY +
                (document.documentElement.scrollTop || document.body.scrollTop));

            this.deltaX = pointerX - offset[0];
            this.deltaY = pointerY - offset[1];

            this.pointerX = pointerX;
            this.pointerY = pointerY;

//            this.scrollTolerance = this.latch.offsetHeight > 50 ? this.latch.offsetHeight : 50;
            this.scrollTolerance = 100;
            var content = $('draggable-content');
            this.contentEndY = getOffset(content)[1] + content.offsetHeight;

            stop_event(event);
        }
    },

    move: function(event) {
        event = event || window.event;
        
        var pointerX, pointerY;
        if (this.latch) {
            // including scroll
            pointerX = event.pageX || (event.clientX +
                (document.documentElement.scrollLeft || document.body.scrollLeft));
            pointerY = event.pageY || (event.clientY +
                (document.documentElement.scrollTop || document.body.scrollTop));
        }

        if (!this.dragging && this.latch) {
            // add some tolerance for click
            if (Math.abs(this.pointerX - pointerX) < 5 &&
                Math.abs(this.pointerY - pointerY) < 5)
                return;
            this.placeholder.style.width = this.latch.style.width = this.latch.offsetWidth + 'px';
            this.placeholder.style.height = this.latch.style.height = this.latch.offsetHeight + 'px';

            // save these to determine if actually relocated
            this.previousSibling = this.latch.previousSibling;
            this.nextSibling = this.latch.nextSibling;
            this.parentNode = this.latch.parentNode;

            this.latch.parentNode.replaceChild(this.placeholder, this.latch);
            this.latch.style.position = 'absolute';
            document.body.appendChild(this.latch);
            this.cacheULs();
            this.dragging = this.latch;
        }

        if (this.dragging) { // follow the first "if" so position is assigned at the same time

            this.latch.style.top = (pointerY - this.deltaY) + 'px';
            this.latch.style.left = (pointerX - this.deltaX) + 'px';

            // locate the target and relocate if necessary
            var td = null;
            for (var i = 0; i < this.ul.length; i++) {
                if (pointerX >= this.ul[i][0] && pointerX <= this.ul[i][1]) {
                    td = this.ul[i];
                    break;
                }
            }
            if (this.dragging.tagName == 'LI' && td) {
                // find the target UL
                var ul = null, td = td[3];
                for (var j = 0; j < td.length; j++) {
                    if (pointerY >= td[j][0] && pointerY <= td[j][1]) {
                        ul = td[j][2];
                        break;
                    }
                }
                if (ul && ul.firstChild)
                    this.relocate(pointerY, ul, ul.firstChild.nextSibling);
            }
            else if (td) {
                this.relocate(pointerY, td[2], td[2].firstChild);
            }

            this.oldClientY = event.clientY;
            this.scrollPage();
            stop_event(event);
        }
    },

    didRelocate: function(node) {
        if (this.previousSibling && node.previousSibling != this.previousSibling)
            return true;
        if (this.nextSibling && node.nextSibling != this.nextSibling)
            return true;
        if (this.parentNode != node.parentNode)
            return true;
        return false;
    },

    cacheULs: function() {
        // cache UL coordinates by columns
        var p = $('draggable-content'), td = p.getElementsByTagName('TD');
        this.ul = [];
        for (var i = 0; i < td.length; i++) {
            var l = [], node = td[i], left = 0;
            do { left += node.offsetLeft || 0; node = node.offsetParent; } while (node);
            this.ul.push([left, left + td[i].offsetWidth, td[i], l]);
            var ul = td[i].getElementsByTagName('UL');
            for (var j = 0; j < ul.length; j++) {
                var node = ul[j], top = 0;
                do { top += node.offsetTop || 0; node = node.offsetParent; } while (node);
                l.push([top, top + ul[j].offsetHeight, ul[j]]);
            }
        }
    },

    relocate: function(pointerY, target, startWith) {
        if (this.dragging) {

            var i = startWith;
            if (i) {
                var top = (getOffset(i))[1];
                for (; i; i = i.nextSibling) {
                    top += i.offsetHeight;
                    if (pointerY < top)
                        break;
                }
            }

            var recache = target != this.placeholder.parentNode;
            if (i) {
                if ((!i.previousSibling || i.previousSibling != this.placeholder) &&
                    pointerY < top - i.offsetHeight + this.placeholder.offsetHeight &&
                    pointerY < top - i.offsetHeight/2)
                    target.insertBefore(this.placeholder, i);
                else if (i.nextSibling && i.nextSibling != this.placeholder)
                    target.insertBefore(this.placeholder, i.nextSibling);
                else if (!i.nextSibling)
                    target.appendChild(this.placeholder);
            }
            else
                target.appendChild(this.placeholder);

            if (recache)
                this.cacheULs();
        }
    },

    stop: function(event) {
        if (this.dragging) {
            var dragging = this.dragging, scope = this.dragging.tagName;

            // null these so there's no further relocations onmousemove
            this.latch = null;
            this.dragging = null;
            dragging.style.position = 'static';
            dragging.style.width = 'auto';
            dragging.style.height = 'auto';
            this.placeholder.parentNode.replaceChild(dragging, this.placeholder);

            if (this.didRelocate(dragging)) {
                if (scope == 'LI') {
                    var newTag =
                        dragging.parentNode.firstChild.firstChild.firstChild.firstChild.nodeValue;
                    var before = dragging.nextSibling;
                    if (!before)
                        before = find_next_li(dragging.parentNode);

                    save_relocate("/bookmarks/action_relocate_uri",
                        "newTag=" +encodeURIComponent(newTag) +"&oid=" +this.oid, before);
                }
                else {
                    var tag = dragging.firstChild.firstChild.firstChild.firstChild.nodeValue;
                    var before = find_next_li(dragging);
                    var pagebreak = isTopUL(dragging);
                    var pagebottom = isBottomUL(dragging);
                    save_relocate("/bookmarks/action_relocate_tag", "tag=" +encodeURIComponent(tag) +
                        (pagebottom ? "&pagebottom=y" : '') +(pagebreak ? "&pagebreak=y" : ""), before);
                }
            }
        }
        this.latch = null;
    },

    scrollPage: function() {
        var delta = this.scrollTolerance - this.windowHeight + this.oldClientY;
        if (this.latch && delta > 0) {
            var offset = parseInt(this.latch.style.top.replace('px', ''));
            if (offset > this.contentEndY)
                return; // do not scroll past bottom of content
            delta = Math.floor(delta/10)
            this.latch.style.top = (offset + delta) + 'px';
            window.scrollBy(0, delta);
            window.setTimeout('Drag.scrollPage()', 50);
            return;
        }
        delta = this.oldClientY - this.scrollTolerance;
        if (this.latch && delta < 0) {
            var offset = parseInt(this.latch.style.top.replace('px', ''));
            if (!window.pageYOffset && !document.documentElement.scrollTop && !document.body.scrollTop)
                return; // already at the top of the page
            delta = Math.floor(delta/10)
            this.latch.style.top = (offset + delta) + 'px'
            window.scrollBy(0, delta);
            window.setTimeout('Drag.scrollPage()', 50);
        }
    }
}

function save_relocate(url, pars, before) {
    async_request(url, pars + (before ? "&before_oid=" +before.id.replace(/^oid_/, '') : ''),
        function(transport) { transport.responseText.evalScripts(); });
}

function find_next_li(currentUL) {
    var td = currentUL.parentNode, ul = currentUL.nextSibling;
    while (td) {
        while (ul) {
            if (ul.childNodes[1])
                return ul.childNodes[1];
            ul = ul.nextSibling;
        }
        td = td.nextSibling;
        if (td)
            ul = td.firstChild;
    }
    return false;
}

function isTopUL(currentUL) {
    var ul = currentUL.previousSibling;
    while (ul) {
        if (ul.childNodes[1])
            return false;
        ul = ul.previousSibling;
    }
    return true;
}

function isBottomUL(currentUL) {
    var ul = currentUL.nextSibling;
    while (ul) {
        if (ul.childNodes[1])
            return false;
        ul = ul.nextSibling;
    }
    return true;
}

function async_request(url, pars, onComplete)
{
    new Ajax.Request(url,
        { method: 'post', parameters: pars, onComplete: onComplete,
          onFailure: reportError, evalScripts: true });
}

function sync_request(url, pars)
{
    var t = new Ajax.Request(url,
        { method: 'post', parameters: pars, asynchronous: false, evalScripts: true });
    if (t.responseIsFailure())
        reportError(t);
    else
        return t.transport.responseText;
    return '';
}

function isLeftButton(event)
{
    return ((event.which && event.which == 1) || (event.button && event.button == 1));
}

function reportError(request)
{
alert("There was a problem communicating with the server. Your changes may be lost.");
}

function goto_href(href)
{
    if (href && href != '')
        location.href = href;
    else
        window.history.back();
}

function add_uri(href)
{
    var f = document.f;
    if (!f.label.value || f.label.value.match(/^\s+$/)) {
alert("Enter the label for this bookmark.");
        f.label.focus();
        return;
    }
    if (!f.href.value || f.href.value.match(/^\s+$/)) {
alert("Enter the link for this bookmark.");
        f.href.focus();
        return;
    }
    if (!f.tags.value || f.tags.value.match(/^[\s,]+$/)) {
alert("Enter one or more comma separated tags.");
        f.tags.focus();
        return;
    }
    f.submit_button.disabled = true;
    sync_request('/bookmarks/action_edit_href', 'href=' +encodeURIComponent(f.href.value) +
        '&oids=' +encodeURIComponent(f.oids.value) +
        '&label=' +encodeURIComponent(f.label.value) +
        '&tags=' +encodeURIComponent(f.tags.value)).evalScripts();
    f.submit_button.disabled = false;
    goto_href(href);
}

function delete_uri(href)
{
    var f = document.f;
    f.submit_button.disabled = true;
    sync_request('/bookmarks/action_delete_href', 'oids=' +encodeURIComponent(f.oids.value)).evalScripts();
    f.submit_button.disabled = false;
    goto_href(href);
}

function change_tag(href)
{
    var f = document.f;
    if (!f.tag.value || f.tag.value.match(/^\s+$/)) {
alert("Enter the new tag.");
        f.tag.focus();
        return;
    }
    f.submit_button.disabled = true;
    sync_request('/bookmarks/action_edit_tag', 'oldTag=' +encodeURIComponent(f.oldTag.value) +
        '&tag=' +encodeURIComponent(f.tag.value)).evalScripts();
    f.submit_button.disabled = false;
    goto_href(href);
}

function change_uid()
{
    var f = document.f;
    if (!f.newUid.value || f.newUid.value.match(/^\s+$/)) {
alert("Enter a user name");
        f.uid.focus();
        return;
    }
    if (f.newPwd1.value != f.newPwd.value) {
alert("Provided passwords do not match.");
        f.newPwd.focus();
        return;
    }
    f.submit_button.disabled = true;
    sync_request('/bookmarks/action_edit_uid', 'newUid=' +encodeURIComponent(f.newUid.value) +
        '&newPwd=' +encodeURIComponent(f.newPwd.value) +
        '&email=' +encodeURIComponent(f.email.value)).evalScripts();
    f.submit_button.disabled = false;
}

function reset_pwd(k, href)
{
    var f = document.f;
    if (!f.newPwd.value || f.newPwd.value.match(/^\s+$/)) {
alert("Enter a password");
        f.newPwd.focus();
        return;
    }
    if (f.newPwd1.value != f.newPwd.value) {
alert("Provided passwords do not match.");
        f.newPwd.focus();
        return;
    }
    f.submit_button.disabled = true;
    sync_request('/bookmarks/action_reset_pwd', 'k=' +encodeURIComponent(k) +
        '&newPwd=' +encodeURIComponent(f.newPwd.value)).evalScripts();
    f.submit_button.disabled = false;
    goto_href(href);
}

function register()
{
    var f = document.f;
    if (!f.uid.value || f.uid.value.match(/^\s+$/)) {
alert("Enter a user name");
        f.uid.focus();
        return;
    }
    if (!f.pwd.value || f.pwd.value.match(/^\s+$/)) {
alert("Enter a password");
        f.pwd.focus();
        return;
    }
    if (f.pwd1.value != f.pwd.value) {
alert("Provided passwords do not match.");
        f.pwd.focus();
        return;
    }
    f.submit_button.disabled = true;
    sync_request('/bookmarks/action_register', 'uid=' +encodeURIComponent(f.uid.value) +'&pwd=' +
        encodeURIComponent(f.pwd.value) +'&email=' +encodeURIComponent(f.email.value)).evalScripts();
    f.uid.select();
    f.uid.focus();
    f.submit_button.disabled = false;
}

function remind()
{
    var f = document.f;
    if (!f.email.value || f.email.value.match(/^\s+$/)) {
        f.email.focus();
        return;
    }
    f.submit_button.disabled = true;
    sync_request('/bookmarks/action_remind', 'email=' +encodeURIComponent(f.email.value)).evalScripts();
    f.email.select();
    f.email.focus();
    f.submit_button.disabled = false;
}

function login(href)
{
    var f = document.f;
    if (!f.uid.value || f.uid.value.match(/^\s+$/)) {
        f.uid.focus();
        return;
    }
    if (!f.pwd.value || f.pwd.value.match(/^\s+$/)) {
        f.pwd.focus();
        return;
    }
    f.submit_button.disabled = true;
    sync_request('/bookmarks/action_authenticate', 'uid=' +encodeURIComponent(f.uid.value) +
        '&pwd=' +encodeURIComponent(f.pwd.value) +
        '&keep=' +(f.keep.checked ? 'y' : '') +
        (href ? '&goto=' +encodeURIComponent(href) : '')).evalScripts();
    f.submit_button.disabled = false;
}

function logout() {
    var uids_left = sync_request('/bookmarks/action_logout');
    if (!uids_left.match(/uids_left=\d/)) {
        document.cookie = 'sid=; domain=.bkmks.com; path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT';
        document.cookie = 'uid=; domain=.bkmks.com; path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT';
        document.cookie = 'search=; domain=.bkmks.com; path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT';
    }
    location.href = '/bookmarks';
}

function get_feedback() {
    var f = document.f;
    if (!f.message.value || f.message.value.match(/^\s+$/)) {
alert("Enter your message.");
        f.message.focus();
        return;
    }
    f.submit_button.disabled = true;
    sync_request('/bookmarks/action_feedback', 'email=' +encodeURIComponent(f.email.value) +
        '&subject=' +encodeURIComponent(f.subject.value) +
        '&message=' +encodeURIComponent(f.message.value)).evalScripts();
    f.submit_button.disabled = false;
}

function authorize_as(a)
{
    sync_request('/bookmarks/action_authorize_as', 'uid=' +
        encodeURIComponent(a.firstChild.nodeValue)).evalScripts();
}

function edit_uri(event, element)
{
    if (Drag.dragging || !isLeftButton(event))
        return;
    location.href = '/bookmarks/form_edit_href?oid=' +element.parentNode.id.replace(/^oid_/, '');
}

function edit_tag(event, h1)
{
    if (Drag.dragging || !isLeftButton(event))
        return;
    location.href = '/bookmarks/form-edit-tag?tag='+ encodeURIComponent(h1.firstChild.nodeValue);
}

function add_tag(a)
{
    var f = document.f;
    if (f.tags.value == '')
        f.tags.value = a.firstChild.nodeValue;
    else {
        var str = f.tags.value+ ', '+ a.firstChild.nodeValue;
        if (str.length > 200)
alert("Sorry, too many tags.");
        else
            f.tags.value = str;
    }
    return false;
}

function layout(n)
{
    sync_request('/bookmarks/action_layout', 'columns=' +n);
    location.href=location.href;
}

function show_favicons(show)
{
    sync_request('/bookmarks/action_favicons', 'what=' +(show ? 'show' : 'none'));
    location.href=location.href;
}

function use_default_font(yes)
{
    sync_request('/bookmarks/action_default_font', 'use_default_font=' +(yes ? 'Y' : ''));
    location.href=location.href;
}

function open_in_new_window(yes)
{
    sync_request('/bookmarks/action_open_in_new_window', 'open_in_new_window=' +(yes ? 'Y' : ''));
    location.href=location.href;
}

function adjust_submenu(event, element)
{
    var target = event.srcElement || event.target;
    if (target != element)
        return;
    for (var sub = element.nextSibling; sub; sub = sub.nextSibling)
        if (sub.tagName == 'UL')
            break;
    sub.style.left = (event.clientX - getOffset(element)[0] + 5) + 'px';
}

var visible_menues = [];

function toggle_menu(event, node)
{
    var menu = node;
    for (menu = node; menu; menu = menu.nextSibling)
        if (menu.tagName == 'UL')
            break;
    var offset = getOffset(node);
    menu.style.left = offset[0] +'px';
/*    menu.style.top = node.parentNode.parentNode.offsetHeight +'px';*/
    menu.style.display = menu.style.display != 'block' ? 'block' : 'none';
    if (menu.style.display == 'block' && visible_menues[-1] != menu)
        visible_menues.push(menu);
    stop_event(event);
}

function hide_menues()
{
    for (var i = 0; i < visible_menues.length; i++)
        visible_menues[i].style.display = 'none';
    visible_menues = [];
}

function stop_menu_event(event)
{
    var target = event.srcElement || event.target;
    if (target.parentNode.tagName == 'UL' || target.tagName == 'UL')
        stop_event(event);
}

/*  -------------------------------------------------------------------------------------------
 *  following are ajax related portions of
 *
 *  Prototype JavaScript framework, version 1.5.0_rc0
 *  (c) 2005 Sam Stephenson <sam@conio.net>
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://prototype.conio.net/
 *
 */

var Prototype = {
  Version: '1.5.0_rc0',
  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',

  emptyFunction: function() {},
  K: function(x) {return x}
}

Object.extend = function(destination, source) {
  for (var property in source) {
    destination[property] = source[property];
  }
  return destination;
}

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}

var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
}

var $break    = new Object();
var $continue = new Object();

Object.extend(Array.prototype, {
    _each: function(iterator) {
        for (var i = 0; i < this.length; i++)
            iterator(this[i]);
    }
});

var Enumerable = {
  each: function(iterator) {
    var index = 0;
    try {
      this._each(function(value) {
        try {
          iterator(value, index++);
        } catch (e) {
          if (e != $continue) throw e;
        }
      });
    } catch (e) {
      if (e != $break) throw e;
    }
  },
  include: function(object) {
    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },
  collect: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      results.push(iterator(value, index));
    });
    return results;
  }
}

Object.extend(Enumerable, {
    map: Enumerable.collect
});

Object.extend(Array.prototype, Enumerable);

var $A = Array.from = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0; i < iterable.length; i++)
      results.push(iterable[i]);
    return results;
  }
}

Object.extend(String.prototype, {

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script) });
  }
});

var Ajax = {
  getTransport: function() {
    try { return new XMLHttpRequest(); } catch (e) {}
    try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {}
    try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {}
    return false;
  },

  activeRequestCount: 0
}

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responderToAdd) {
    if (!this.include(responderToAdd))
      this.responders.push(responderToAdd);
  },

  unregister: function(responderToRemove) {
    this.responders = this.responders.without(responderToRemove);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (responder[callback] && typeof responder[callback] == 'function') {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) {}
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate: function() {
    Ajax.activeRequestCount++;
  },

  onComplete: function() {
    Ajax.activeRequestCount--;
  }
});

Ajax.Base = function() {};
Ajax.Base.prototype = {
  setOptions: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      parameters:   ''
    }
    Object.extend(this.options, options || {});
  },

  responseIsSuccess: function() {
    return this.transport.status == undefined
        || this.transport.status == 0
        || (this.transport.status >= 200 && this.transport.status < 300);
  },

  responseIsFailure: function() {
    return !this.responseIsSuccess();
  }
}

Ajax.Request = Class.create();
Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(url, options) {
    this.transport = Ajax.getTransport();
    this.setOptions(options);
    this.request(url);
  },

  request: function(url) {
    var parameters = this.options.parameters || '';
    if (parameters.length > 0) parameters += '&_=';

    try {
      this.url = url;
      if (this.options.method == 'get' && parameters.length > 0)
        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;

      Ajax.Responders.dispatch('onCreate', this, this.transport);

      this.transport.open(this.options.method, this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) {
        this.transport.onreadystatechange = this.onStateChange.bind(this);
        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
      }

      this.setRequestHeaders();

      var body = this.options.postBody ? this.options.postBody : parameters;
      this.transport.send(this.options.method == 'post' ? body : null);

    } catch (e) {
      this.dispatchException(e);
    }
  },

  setRequestHeaders: function() {
    var requestHeaders =
      ['X-Requested-With', 'XMLHttpRequest',
       'X-Prototype-Version', Prototype.Version,
       'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];

    if (this.options.method == 'post') {
      requestHeaders.push('Content-type', this.options.contentType);

      /* Force "Connection: close" for Mozilla browsers to work around
       * a bug where XMLHttpReqeuest sends an incorrect Content-length
       * header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType)
        requestHeaders.push('Connection', 'close');
    }

    if (this.options.requestHeaders)
      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);

    for (var i = 0; i < requestHeaders.length; i += 2)
      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState != 1)
      this.respondToReadyState(this.transport.readyState);
  },

  header: function(name) {
    try {
      return this.transport.getResponseHeader(name);
    } catch (e) {}
  },

  evalJSON: function() {
    try {
      return eval('(' + this.header('X-JSON') + ')');
    } catch (e) {}
  },

  evalResponse: function() {
    try {
      return eval(this.transport.responseText);
    } catch (e) {
      this.dispatchException(e);
    }
  },

  respondToReadyState: function(readyState) {
    var event = Ajax.Request.Events[readyState];
    var transport = this.transport, json = this.evalJSON();

    if (event == 'Complete') {
      try {
        (this.options['on' + this.transport.status]
         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(transport, json);
      } catch (e) {
        this.dispatchException(e);
      }

      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
        this.evalResponse();
    }

    try {
      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
      Ajax.Responders.dispatch('on' + event, this, transport, json);
    } catch (e) {
      this.dispatchException(e);
    }

    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
    if (event == 'Complete')
      this.transport.onreadystatechange = Prototype.emptyFunction;
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

function $(element) {
    return typeof element == 'string' ? document.getElementById(element) : element;
}


