// prototype.js
/*  Prototype JavaScript framework, version 1.5.0
 *  (c) 2005-2007 Sam Stephenson
 *
 *  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',
  BrowserFeatures: {
    XPath: !!document.evaluate
  },

  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
  emptyFunction: function() {},
  K: function(x) { return x }
}

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

var Abstract = new Object();

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

Object.extend(Object, {
  inspect: function(object) {
    try {
      if (object === undefined) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : object.toString();
    } catch (e) {
      if (e instanceof RangeError) return '...';
      throw e;
    }
  },

  keys: function(object) {
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
  },

  values: function(object) {
    var values = [];
    for (var property in object)
      values.push(object[property]);
    return values;
  },

  clone: function(object) {
    return Object.extend({}, object);
  }
});

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

Function.prototype.bindAsEventListener = function(object) {
  var __method = this, args = $A(arguments), object = args.shift();
  return function(event) {
    return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments)));
  }
}

Object.extend(Number.prototype, {
  toColorPart: function() {
    var digits = this.toString(16);
    if (this < 16) return '0' + digits;
    return digits;
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator) {
    $R(0, this, true).each(iterator);
    return this;
  }
});

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }

    return returnValue;
  }
}

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  stop: function() {
    if (!this.timer) return;
    clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.callback(this);
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
}
String.interpret = function(value){
  return value == null ? '' : String(value);
}

Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

    while (source.length > 0) {
      if (match = source.match(pattern)) {
        result += source.slice(0, match.index);
        result += String.interpret(replacement(match));
        source  = source.slice(match.index + match[0].length);
      } else {
        result += source, source = '';
      }
    }
    return result;
  },

  sub: function(pattern, replacement, count) {
    replacement = this.gsub.prepareReplacement(replacement);
    count = count === undefined ? 1 : count;

    return this.gsub(pattern, function(match) {
      if (--count < 0) return match[0];
      return replacement(match);
    });
  },

  scan: function(pattern, iterator) {
    this.gsub(pattern, iterator);
    return this;
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = truncation === undefined ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : this;
  },

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

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  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) });
  },

  escapeHTML: function() {
    var div = document.createElement('div');
    var text = document.createTextNode(this);
    div.appendChild(text);
    return div.innerHTML;
  },

  unescapeHTML: function() {
    var div = document.createElement('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      $A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) :
      div.childNodes[0].nodeValue) : '';
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return {};

    return match[1].split(separator || '&').inject({}, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var name = decodeURIComponent(pair[0]);
        var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;

        if (hash[name] !== undefined) {
          if (hash[name].constructor != Array)
            hash[name] = [hash[name]];
          if (value) hash[name].push(value);
        }
        else hash[name] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function(){
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.replace(/\\/g, '\\\\');
    if (useDoubleQuotes)
      return '"' + escapedString.replace(/"/g, '\\"') + '"';
    else
      return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (typeof replacement == 'function') return replacement;
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match) };
}

String.prototype.parseQuery = String.prototype.toQueryParams;

var Template = Class.create();
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
Template.prototype = {
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern  = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    return this.template.gsub(this.pattern, function(match) {
      var before = match[1];
      if (before == '\\') return match[2];
      return before + String.interpret(object[match[3]]);
    });
  }
}

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

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;
    }
    return this;
  },

  eachSlice: function(number, iterator) {
    var index = -number, slices = [], array = this.toArray();
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.map(iterator);
  },

  all: function(iterator) {
    var result = true;
    this.each(function(value, index) {
      result = result && !!(iterator || Prototype.K)(value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator) {
    var result = false;
    this.each(function(value, index) {
      if (result = !!(iterator || Prototype.K)(value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      results.push((iterator || Prototype.K)(value, index));
    });
    return results;
  },

  detect: function(iterator) {
    var result;
    this.each(function(value, index) {
      if (iterator(value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (iterator(value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(pattern, iterator) {
    var results = [];
    this.each(function(value, index) {
      var stringValue = value.toString();
      if (stringValue.match(pattern))
        results.push((iterator || Prototype.K)(value, index));
    })
    return results;
  },

  include: function(object) {
    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = fillWith === undefined ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator) {
    this.each(function(value, index) {
      memo = iterator(memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (result == undefined || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (result == undefined || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator) {
    var trues = [], falses = [];
    this.each(function(value, index) {
      ((iterator || Prototype.K)(value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value, index) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator(value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator) {
    return this.map(function(value, index) {
      return {value: value, criteria: iterator(value, index)};
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (typeof args.last() == 'function')
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
}

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray
});
var $A = Array.from = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0, length = iterable.length; i < length; i++)
      results.push(iterable[i]);
    return results;
  }
}

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse)
  Array.prototype._reverse = Array.prototype.reverse;

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

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(value && value.constructor == Array ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  indexOf: function(object) {
    for (var i = 0, length = this.length; i < length; i++)
      if (this[i] == object) return i;
    return -1;
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  reduce: function() {
    return this.length > 1 ? this : this[0];
  },

  uniq: function() {
    return this.inject([], function(array, value) {
      return array.include(value) ? array : array.concat([value]);
    });
  },

  clone: function() {
    return [].concat(this);
  },

  size: function() {
    return this.length;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  }
});

Array.prototype.toArray = Array.prototype.clone;

function $w(string){
  string = string.strip();
  return string ? string.split(/\s+/) : [];
}

if(window.opera){
  Array.prototype.concat = function(){
    var array = [];
    for(var i = 0, length = this.length; i < length; i++) array.push(this[i]);
    for(var i = 0, length = arguments.length; i < length; i++) {
      if(arguments[i].constructor == Array) {
        for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  }
}
var Hash = function(obj) {
  Object.extend(this, obj || {});
};

Object.extend(Hash, {
  toQueryString: function(obj) {
    var parts = [];

	  this.prototype._each.call(obj, function(pair) {
      if (!pair.key) return;

      if (pair.value && pair.value.constructor == Array) {
        var values = pair.value.compact();
        if (values.length < 2) pair.value = values.reduce();
        else {
        	key = encodeURIComponent(pair.key);
          values.each(function(value) {
            value = value != undefined ? encodeURIComponent(value) : '';
            parts.push(key + '=' + encodeURIComponent(value));
          });
          return;
        }
      }
      if (pair.value == undefined) pair[1] = '';
      parts.push(pair.map(encodeURIComponent).join('='));
	  });

    return parts.join('&');
  }
});

Object.extend(Hash.prototype, Enumerable);
Object.extend(Hash.prototype, {
  _each: function(iterator) {
    for (var key in this) {
      var value = this[key];
      if (value && value == Hash.prototype[key]) continue;

      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },

  keys: function() {
    return this.pluck('key');
  },

  values: function() {
    return this.pluck('value');
  },

  merge: function(hash) {
    return $H(hash).inject(this, function(mergedHash, pair) {
      mergedHash[pair.key] = pair.value;
      return mergedHash;
    });
  },

  remove: function() {
    var result;
    for(var i = 0, length = arguments.length; i < length; i++) {
      var value = this[arguments[i]];
      if (value !== undefined){
        if (result === undefined) result = value;
        else {
          if (result.constructor != Array) result = [result];
          result.push(value)
        }
      }
      delete this[arguments[i]];
    }
    return result;
  },

  toQueryString: function() {
    return Hash.toQueryString(this);
  },

  inspect: function() {
    return '#<Hash:{' + this.map(function(pair) {
      return pair.map(Object.inspect).join(': ');
    }).join(', ') + '}>';
  }
});

function $H(object) {
  if (object && object.constructor == Hash) return object;
  return new Hash(object);
};
ObjectRange = Class.create();
Object.extend(ObjectRange.prototype, Enumerable);
Object.extend(ObjectRange.prototype, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    while (this.include(value)) {
      iterator(value);
      value = value.succ();
    }
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
}

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new XMLHttpRequest()},
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
    ) || false;
  },

  activeRequestCount: 0
}

Ajax.Responders = {
  responders: [],

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

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

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

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (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',
      encoding:     'UTF-8',
      parameters:   ''
    }
    Object.extend(this.options, options || {});

    this.options.method = this.options.method.toLowerCase();
    if (typeof this.options.parameters == 'string')
      this.options.parameters = this.options.parameters.toQueryParams();
  }
}

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

Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  _complete: false,

  initialize: function(url, options) {
    this.transport = Ajax.getTransport();
    this.setOptions(options);
    this.request(url);
  },

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = this.options.parameters;

    if (!['get', 'post'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';
    }

    params = Hash.toQueryString(params);
    if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='

    // when GET, append parameters to URL
    if (this.method == 'get' && params)
      this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;

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

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

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

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      var body = this.method == 'post' ? (this.options.postBody || params) : null;

      this.transport.send(body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

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

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

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

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (typeof extras.push == 'function')
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);
  },

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

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

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

      if ((this.getHeader('Content-type') || 'text/javascript').strip().
        match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
          this.evalResponse();
    }

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

    if (state == 'Complete') {
      // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

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

  evalJSON: function() {
    try {
      var json = this.getHeader('X-JSON');
      return json ? eval('(' + json + ')') : null;
    } catch (e) { return null }
  },

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

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

Ajax.Updater = Class.create();

Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
  initialize: function(container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    }

    this.transport = Ajax.getTransport();
    this.setOptions(options);

    var onComplete = this.options.onComplete || Prototype.emptyFunction;
    this.options.onComplete = (function(transport, param) {
      this.updateContent();
      onComplete(transport, param);
    }).bind(this);

    this.request(url);
  },

  updateContent: function() {
    var receiver = this.container[this.success() ? 'success' : 'failure'];
    var response = this.transport.responseText;

    if (!this.options.evalScripts) response = response.stripScripts();

    if (receiver = $(receiver)) {
      if (this.options.insertion)
        new this.options.insertion(receiver, response);
      else
        receiver.update(response);
    }

    if (this.success()) {
      if (this.onComplete)
        setTimeout(this.onComplete.bind(this), 10);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create();
Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(container, url, options) {
    this.setOptions(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = {};
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(request) {
    if (this.options.decay) {
      this.decay = (request.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = request.responseText;
    }
    this.timer = setTimeout(this.onTimerEvent.bind(this),
      this.decay * this.frequency * 1000);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (typeof element == 'string')
    element = document.getElementById(element);
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(query.snapshotItem(i));
    return results;
  };
}

document.getElementsByClassName = function(className, parentElement) {
  if (Prototype.BrowserFeatures.XPath) {
    var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
    return document._getElementsByXPath(q, parentElement);
  } else {
    var children = ($(parentElement) || document.body).getElementsByTagName('*');
    var elements = [], child;
    for (var i = 0, length = children.length; i < length; i++) {
      child = children[i];
      if (Element.hasClassName(child, className))
        elements.push(Element.extend(child));
    }
    return elements;
  }
};

/*--------------------------------------------------------------------------*/

if (!window.Element)
  var Element = new Object();

Element.extend = function(element) {
  if (!element || _nativeExtensions || element.nodeType == 3) return element;

  if (!element._extended && element.tagName && element != window) {
    var methods = Object.clone(Element.Methods), cache = Element.extend.cache;

    if (element.tagName == 'FORM')
      Object.extend(methods, Form.Methods);
    if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
      Object.extend(methods, Form.Element.Methods);

    Object.extend(methods, Element.Methods.Simulated);

    for (var property in methods) {
      var value = methods[property];
      if (typeof value == 'function' && !(property in element))
        element[property] = cache.findOrStore(value);
    }
  }

  element._extended = true;
  return element;
};

Element.extend.cache = {
  findOrStore: function(value) {
    return this[value] = this[value] || function() {
      return value.apply(null, [this].concat($A(arguments)));
    }
  }
};

Element.Methods = {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function(element) {
    element = $(element);
    Element[Element.visible(element) ? 'hide' : 'show'](element);
    return element;
  },

  hide: function(element) {
    $(element).style.display = 'none';
    return element;
  },

  show: function(element) {
    $(element).style.display = '';
    return element;
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
    return element;
  },

  update: function(element, html) {
    html = typeof html == 'undefined' ? '' : html.toString();
    $(element).innerHTML = html.stripScripts();
    setTimeout(function() {html.evalScripts()}, 10);
    return element;
  },

  replace: function(element, html) {
    element = $(element);
    html = typeof html == 'undefined' ? '' : html.toString();
    if (element.outerHTML) {
      element.outerHTML = html.stripScripts();
    } else {
      var range = element.ownerDocument.createRange();
      range.selectNodeContents(element);
      element.parentNode.replaceChild(
        range.createContextualFragment(html.stripScripts()), element);
    }
    setTimeout(function() {html.evalScripts()}, 10);
    return element;
  },

  inspect: function(element) {
    element = $(element);
    var result = '<' + element.tagName.toLowerCase();
    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
      var property = pair.first(), attribute = pair.last();
      var value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property) {
    element = $(element);
    var elements = [];
    while (element = element[property])
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
    return elements;
  },

  ancestors: function(element) {
    return $(element).recursivelyCollect('parentNode');
  },

  descendants: function(element) {
    return $A($(element).getElementsByTagName('*'));
  },

  immediateDescendants: function(element) {
    if (!(element = $(element).firstChild)) return [];
    while (element && element.nodeType != 1) element = element.nextSibling;
    if (element) return [element].concat($(element).nextSiblings());
    return [];
  },

  previousSiblings: function(element) {
    return $(element).recursivelyCollect('previousSibling');
  },

  nextSiblings: function(element) {
    return $(element).recursivelyCollect('nextSibling');
  },

  siblings: function(element) {
    element = $(element);
    return element.previousSiblings().reverse().concat(element.nextSiblings());
  },

  match: function(element, selector) {
    if (typeof selector == 'string')
      selector = new Selector(selector);
    return selector.match($(element));
  },

  up: function(element, expression, index) {
    return Selector.findElement($(element).ancestors(), expression, index);
  },

  down: function(element, expression, index) {
    return Selector.findElement($(element).descendants(), expression, index);
  },

  previous: function(element, expression, index) {
    return Selector.findElement($(element).previousSiblings(), expression, index);
  },

  next: function(element, expression, index) {
    return Selector.findElement($(element).nextSiblings(), expression, index);
  },

  getElementsBySelector: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element, args);
  },

  getElementsByClassName: function(element, className) {
    return document.getElementsByClassName(className, element);
  },

  readAttribute: function(element, name) {
    element = $(element);
    if (document.all && !window.opera) {
      var t = Element._attributeTranslations;
      if (t.values[name]) return t.values[name](element, name);
      if (t.names[name])  name = t.names[name];
      var attribute = element.attributes[name];
      if(attribute) return attribute.nodeValue;
    }
    return element.getAttribute(name);
  },

  getHeight: function(element) {
    return $(element).getDimensions().height;
  },

  getWidth: function(element) {
    return $(element).getDimensions().width;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    var elementClassName = element.className;
    if (elementClassName.length == 0) return false;
    if (elementClassName == className ||
        elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
      return true;
    return false;
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    Element.classNames(element).add(className);
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    Element.classNames(element).remove(className);
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
    return element;
  },

  observe: function() {
    Event.observe.apply(Event, arguments);
    return $A(arguments).first();
  },

  stopObserving: function() {
    Event.stopObserving.apply(Event, arguments);
    return $A(arguments).first();
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    var node = element.firstChild;
    while (node) {
      var nextNode = node.nextSibling;
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        element.removeChild(node);
      node = nextNode;
    }
    return element;
  },

  empty: function(element) {
    return $(element).innerHTML.match(/^\s*$/);
  },

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);
    while (element = element.parentNode)
      if (element == ancestor) return true;
    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = Position.cumulativeOffset(element);
    window.scrollTo(pos[0], pos[1]);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    if (['float','cssFloat'].include(style))
      style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat');
    style = style.camelize();
    var value = element.style[style];
    if (!value) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        var css = document.defaultView.getComputedStyle(element, null);
        value = css ? css[style] : null;
      } else if (element.currentStyle) {
        value = element.currentStyle[style];
      }
    }

    if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
      value = element['offset'+style.capitalize()] + 'px';

    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
      if (Element.getStyle(element, 'position') == 'static') value = 'auto';
    if(style == 'opacity') {
      if(value) return parseFloat(value);
      if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if(value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }
    return value == 'auto' ? null : value;
  },

  setStyle: function(element, style) {
    element = $(element);
    for (var name in style) {
      var value = style[name];
      if(name == 'opacity') {
        if (value == 1) {
          value = (/Gecko/.test(navigator.userAgent) &&
            !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;
          if(/MSIE/.test(navigator.userAgent) && !window.opera)
            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
        } else if(value === '') {
          if(/MSIE/.test(navigator.userAgent) && !window.opera)
            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'');
        } else {
          if(value < 0.00001) value = 0;
          if(/MSIE/.test(navigator.userAgent) && !window.opera)
            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
              'alpha(opacity='+value*100+')';
        }
      } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';
      element.style[name.camelize()] = value;
    }
    return element;
  },

  getDimensions: function(element) {
    element = $(element);
    var display = $(element).getStyle('display');
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (window.opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
    return element;
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return element;
    element._overflow = element.style.overflow || 'auto';
    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
      element.style.overflow = 'hidden';
    return element;
  },

  undoClipping: function(element) {
    element = $(element);
    if (!element._overflow) return element;
    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
    element._overflow = null;
    return element;
  }
};

Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});

Element._attributeTranslations = {};

Element._attributeTranslations.names = {
  colspan:   "colSpan",
  rowspan:   "rowSpan",
  valign:    "vAlign",
  datetime:  "dateTime",
  accesskey: "accessKey",
  tabindex:  "tabIndex",
  enctype:   "encType",
  maxlength: "maxLength",
  readonly:  "readOnly",
  longdesc:  "longDesc"
};

Element._attributeTranslations.values = {
  _getAttr: function(element, attribute) {
    return element.getAttribute(attribute, 2);
  },

  _flag: function(element, attribute) {
    return $(element).hasAttribute(attribute) ? attribute : null;
  },

  style: function(element) {
    return element.style.cssText.toLowerCase();
  },

  title: function(element) {
    var node = element.getAttributeNode('title');
    return node.specified ? node.nodeValue : null;
  }
};

Object.extend(Element._attributeTranslations.values, {
  href: Element._attributeTranslations.values._getAttr,
  src:  Element._attributeTranslations.values._getAttr,
  disabled: Element._attributeTranslations.values._flag,
  checked:  Element._attributeTranslations.values._flag,
  readonly: Element._attributeTranslations.values._flag,
  multiple: Element._attributeTranslations.values._flag
});

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    var t = Element._attributeTranslations;
    attribute = t.names[attribute] || attribute;
    return $(element).getAttributeNode(attribute).specified;
  }
};

// IE is missing .innerHTML support for TABLE-related elements
if (document.all && !window.opera){
  Element.Methods.update = function(element, html) {
    element = $(element);
    html = typeof html == 'undefined' ? '' : html.toString();
    var tagName = element.tagName.toUpperCase();
    if (['THEAD','TBODY','TR','TD'].include(tagName)) {
      var div = document.createElement('div');
      switch (tagName) {
        case 'THEAD':
        case 'TBODY':
          div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
          depth = 2;
          break;
        case 'TR':
          div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
          depth = 3;
          break;
        case 'TD':
          div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
          depth = 4;
      }
      $A(element.childNodes).each(function(node){
        element.removeChild(node)
      });
      depth.times(function(){ div = div.firstChild });

      $A(div.childNodes).each(
        function(node){ element.appendChild(node) });
    } else {
      element.innerHTML = html.stripScripts();
    }
    setTimeout(function() {html.evalScripts()}, 10);
    return element;
  }
};

Object.extend(Element, Element.Methods);

var _nativeExtensions = false;

if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
  ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
    var className = 'HTML' + tag + 'Element';
    if(window[className]) return;
    var klass = window[className] = {};
    klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
  });

Element.addMethods = function(methods) {
  Object.extend(Element.Methods, methods || {});

  function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    var cache = Element.extend.cache;
    for (var property in methods) {
      var value = methods[property];
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = cache.findOrStore(value);
    }
  }

  if (typeof HTMLElement != 'undefined') {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
    copy(Form.Methods, HTMLFormElement.prototype);
    [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
      copy(Form.Element.Methods, klass.prototype);
    });
    _nativeExtensions = true;
  }
}

var Toggle = new Object();
Toggle.display = Element.toggle;

/*--------------------------------------------------------------------------*/

Abstract.Insertion = function(adjacency) {
  this.adjacency = adjacency;
}

Abstract.Insertion.prototype = {
  initialize: function(element, content) {
    this.element = $(element);
    this.content = content.stripScripts();

    if (this.adjacency && this.element.insertAdjacentHTML) {
      try {
        this.element.insertAdjacentHTML(this.adjacency, this.content);
      } catch (e) {
        var tagName = this.element.tagName.toUpperCase();
        if (['TBODY', 'TR'].include(tagName)) {
          this.insertContent(this.contentFromAnonymousTable());
        } else {
          throw e;
        }
      }
    } else {
      this.range = this.element.ownerDocument.createRange();
      if (this.initializeRange) this.initializeRange();
      this.insertContent([this.range.createContextualFragment(this.content)]);
    }

    setTimeout(function() {content.evalScripts()}, 10);
  },

  contentFromAnonymousTable: function() {
    var div = document.createElement('div');
    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
    return $A(div.childNodes[0].childNodes[0].childNodes);
  }
}

var Insertion = new Object();

Insertion.Before = Class.create();
Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
  initializeRange: function() {
    this.range.setStartBefore(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment, this.element);
    }).bind(this));
  }
});

Insertion.Top = Class.create();
Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(true);
  },

  insertContent: function(fragments) {
    fragments.reverse(false).each((function(fragment) {
      this.element.insertBefore(fragment, this.element.firstChild);
    }).bind(this));
  }
});

Insertion.Bottom = Class.create();
Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.appendChild(fragment);
    }).bind(this));
  }
});

Insertion.After = Class.create();
Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
  initializeRange: function() {
    this.range.setStartAfter(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment,
        this.element.nextSibling);
    }).bind(this));
  }
});

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set($A(this).concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set($A(this).without(classNameToRemove).join(' '));
  },

  toString: function() {
    return $A(this).join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);
var Selector = Class.create();
Selector.prototype = {
  initialize: function(expression) {
    this.params = {classNames: []};
    this.expression = expression.toString().strip();
    this.parseExpression();
    this.compileMatcher();
  },

  parseExpression: function() {
    function abort(message) { throw 'Parse error in selector: ' + message; }

    if (this.expression == '')  abort('empty expression');

    var params = this.params, expr = this.expression, match, modifier, clause, rest;
    while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
      params.attributes = params.attributes || [];
      params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
      expr = match[1];
    }

    if (expr == '*') return this.params.wildcard = true;

    while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
      modifier = match[1], clause = match[2], rest = match[3];
      switch (modifier) {
        case '#':       params.id = clause; break;
        case '.':       params.classNames.push(clause); break;
        case '':
        case undefined: params.tagName = clause.toUpperCase(); break;
        default:        abort(expr.inspect());
      }
      expr = rest;
    }

    if (expr.length > 0) abort(expr.inspect());
  },

  buildMatchExpression: function() {
    var params = this.params, conditions = [], clause;

    if (params.wildcard)
      conditions.push('true');
    if (clause = params.id)
      conditions.push('element.readAttribute("id") == ' + clause.inspect());
    if (clause = params.tagName)
      conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
    if ((clause = params.classNames).length > 0)
      for (var i = 0, length = clause.length; i < length; i++)
        conditions.push('Element.hasClassName(element,' + clause[i].inspect() + ')');
    if (clause = params.attributes) {
      clause.each(function(attribute) {
        var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
        var splitValueBy = function(delimiter) {
          return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
        }

        switch (attribute.operator) {
          case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break;
          case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
          case '|=':      conditions.push(
                            splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
                          ); break;
          case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break;
          case '':
          case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
          default:        throw 'Unknown operator ' + attribute.operator + ' in selector';
        }
      });
    }

    return conditions.join(' && ');
  },

  compileMatcher: function() {
    this.match = new Function('element', 'if (!element.tagName) return false; \
      element = $(element); \
      return ' + this.buildMatchExpression());
  },

  findElements: function(scope) {
    var element;

    if (element = $(this.params.id))
      if (this.match(element))
        if (!scope || Element.childOf(element, scope))
          return [element];

    scope = (scope || document).getElementsByTagName(this.params.tagName || '*');

    var results = [];
    for (var i = 0, length = scope.length; i < length; i++)
      if (this.match(element = scope[i]))
        results.push(Element.extend(element));

    return results;
  },

  toString: function() {
    return this.expression;
  }
}

Object.extend(Selector, {
  matchElements: function(elements, expression) {
    var selector = new Selector(expression);
    return elements.select(selector.match.bind(selector)).map(Element.extend);
  },

  findElement: function(elements, expression, index) {
    if (typeof expression == 'number') index = expression, expression = false;
    return Selector.matchElements(elements, expression || '*')[index || 0];
  },

  findChildElements: function(element, expressions) {
    return expressions.map(function(expression) {
      return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
        var selector = new Selector(expr);
        return results.inject([], function(elements, result) {
          return elements.concat(selector.findElements(result || element));
        });
      });
    }).flatten();
  }
});

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}
var Form = {
  reset: function(form) {
    $(form).reset();
    return form;
  },

  serializeElements: function(elements, getHash) {
    var data = elements.inject({}, function(result, element) {
      if (!element.disabled && element.name) {
        var key = element.name, value = $(element).getValue();
        if (value != undefined) {
          if (result[key]) {
            if (result[key].constructor != Array) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return getHash ? data : Hash.toQueryString(data);
  }
};

Form.Methods = {
  serialize: function(form, getHash) {
    return Form.serializeElements(Form.getElements(form), getHash);
  },

  getElements: function(form) {
    return $A($(form).getElementsByTagName('*')).inject([],
      function(elements, child) {
        if (Form.Element.Serializers[child.tagName.toLowerCase()])
          elements.push(Element.extend(child));
        return elements;
      }
    );
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name) return $A(inputs).map(Element.extend);

    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) || (name && input.name != name))
        continue;
      matchingInputs.push(Element.extend(input));
    }

    return matchingInputs;
  },

  disable: function(form) {
    form = $(form);
    form.getElements().each(function(element) {
      element.blur();
      element.disabled = 'true';
    });
    return form;
  },

  enable: function(form) {
    form = $(form);
    form.getElements().each(function(element) {
      element.disabled = '';
    });
    return form;
  },

  findFirstElement: function(form) {
    return $(form).getElements().find(function(element) {
      return element.type != 'hidden' && !element.disabled &&
        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    form.findFirstElement().activate();
    return form;
  }
}

Object.extend(Form, Form.Methods);

/*--------------------------------------------------------------------------*/

Form.Element = {
  focus: function(element) {
    $(element).focus();
    return element;
  },

  select: function(element) {
    $(element).select();
    return element;
  }
}

Form.Element.Methods = {
  serialize: function(element) {
    element = $(element);
    if (!element.disabled && element.name) {
      var value = element.getValue();
      if (value != undefined) {
        var pair = {};
        pair[element.name] = value;
        return Hash.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](element);
  },

  clear: function(element) {
    $(element).value = '';
    return element;
  },

  present: function(element) {
    return $(element).value != '';
  },

  activate: function(element) {
    element = $(element);
    element.focus();
    if (element.select && ( element.tagName.toLowerCase() != 'input' ||
      !['button', 'reset', 'submit'].include(element.type) ) )
      element.select();
    return element;
  },

  disable: function(element) {
    element = $(element);
    element.disabled = true;
    return element;
  },

  enable: function(element) {
    element = $(element);
    element.blur();
    element.disabled = false;
    return element;
  }
}

Object.extend(Form.Element, Form.Element.Methods);
var Field = Form.Element;
var $F = Form.Element.getValue;

/*--------------------------------------------------------------------------*/

Form.Element.Serializers = {
  input: function(element) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element);
      default:
        return Form.Element.Serializers.textarea(element);
    }
  },

  inputSelector: function(element) {
    return element.checked ? element.value : null;
  },

  textarea: function(element) {
    return element.value;
  },

  select: function(element) {
    return this[element.type == 'select-one' ?
      'selectOne' : 'selectMany'](element);
  },

  selectOne: function(element) {
    var index = element.selectedIndex;
    return index >= 0 ? this.optionValue(element.options[index]) : null;
  },

  selectMany: function(element) {
    var values, length = element.length;
    if (!length) return null;

    for (var i = 0, values = []; i < length; i++) {
      var opt = element.options[i];
      if (opt.selected) values.push(this.optionValue(opt));
    }
    return values;
  },

  optionValue: function(opt) {
    // extend element because hasAttribute may not be native
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
}

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = function() {}
Abstract.TimedObserver.prototype = {
  initialize: function(element, frequency, callback) {
    this.frequency = frequency;
    this.element   = $(element);
    this.callback  = callback;

    this.lastValue = this.getValue();
    this.registerCallback();
  },

  registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  onTimerEvent: function() {
    var value = this.getValue();
    var changed = ('string' == typeof this.lastValue && 'string' == typeof value
      ? this.lastValue != value : String(this.lastValue) != String(value));
    if (changed) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
}

Form.Element.Observer = Class.create();
Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create();
Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = function() {}
Abstract.EventObserver.prototype = {
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback.bind(this));
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        default:
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
}

Form.Element.EventObserver = Class.create();
Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create();
Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) {
  var Event = new Object();
}

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,

  element: function(event) {
    return event.target || event.srcElement;
  },

  isLeftClick: function(event) {
    return (((event.which) && (event.which == 1)) ||
            ((event.button) && (event.button == 1)));
  },

  pointerX: function(event) {
    return event.pageX || (event.clientX +
      (document.documentElement.scrollLeft || document.body.scrollLeft));
  },

  pointerY: function(event) {
    return event.pageY || (event.clientY +
      (document.documentElement.scrollTop || document.body.scrollTop));
  },

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

  // find the first node with the given tagName, starting from the
  // node the event was triggered on; traverses the DOM upwards
  findElement: function(event, tagName) {
    var element = Event.element(event);
    while (element.parentNode && (!element.tagName ||
        (element.tagName.toUpperCase() != tagName.toUpperCase())))
      element = element.parentNode;
    return element;
  },

  observers: false,

  _observeAndCache: function(element, name, observer, useCapture) {
    if (!this.observers) this.observers = [];
    if (element.addEventListener) {
      this.observers.push([element, name, observer, useCapture]);
      element.addEventListener(name, observer, useCapture);
    } else if (element.attachEvent) {
      this.observers.push([element, name, observer, useCapture]);
      element.attachEvent('on' + name, observer);
    }
  },

  unloadCache: function() {
    if (!Event.observers) return;
    for (var i = 0, length = Event.observers.length; i < length; i++) {
      Event.stopObserving.apply(this, Event.observers[i]);
      Event.observers[i][0] = null;
    }
    Event.observers = false;
  },

  observe: function(element, name, observer, useCapture) {
    element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.attachEvent))
      name = 'keydown';

    Event._observeAndCache(element, name, observer, useCapture);
  },

  stopObserving: function(element, name, observer, useCapture) {
    element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.detachEvent))
      name = 'keydown';

    if (element.removeEventListener) {
      element.removeEventListener(name, observer, useCapture);
    } else if (element.detachEvent) {
      try {
        element.detachEvent('on' + name, observer);
      } catch (e) {}
    }
  }
});

/* prevent memory leaks in IE */
if (navigator.appVersion.match(/\bMSIE\b/))
  Event.observe(window, 'unload', Event.unloadCache, false);
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  realOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return [valueL, valueT];
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return [valueL, valueT];
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if(element.tagName=='BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return [valueL, valueT];
  },

  offsetParent: function(element) {
    if (element.offsetParent) return element.offsetParent;
    if (element == document.body) return element;

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return element;

    return document.body;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = this.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = this.realOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = this.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  page: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent==document.body)
        if (Element.getStyle(element,'position')=='absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!window.opera || element.tagName=='BODY') {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return [valueL, valueT];
  },

  clone: function(source, target) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || {})

    // find page position of source
    source = $(source);
    var p = Position.page(source);

    // find coordinate system to use
    target = $(target);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(target,'position') == 'absolute') {
      parent = Position.offsetParent(target);
      delta = Position.page(parent);
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  },

  absolutize: function(element) {
    element = $(element);
    if (element.style.position == 'absolute') return;
    Position.prepare();

    var offsets = Position.positionedOffset(element);
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.width  = width + 'px';
    element.style.height = height + 'px';
  },

  relativize: function(element) {
    element = $(element);
    if (element.style.position == 'relative') return;
    Position.prepare();

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
  }
}

// Safari returns margins on body which is incorrect if the child is absolutely
// positioned.  For performance reasons, redefine Position.cumulativeOffset for
// KHTML/WebKit only.
if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  Position.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return [valueL, valueT];
  }
}

Element.addMethods();

document.observe = function(){

}
;

// prototype-browser.js
Prototype.Browser = {
  IE:     !!(window.attachEvent && !window.opera),
  Opera:  !!window.opera,
  WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
  Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
  MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
}

Prototype.Browser.asString = (function() {
  for (key in Prototype.Browser) {
    if (Prototype.Browser[key])
      return key;
  }
})();

// json.js
function JSON() {
    var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        s = {
            'array': function (x) {
                var a = ['['], b, f, i, l = x.length, v;
                for (i = 0; i < l; i += 1) {
                    v = x[i];
                    f = s[typeof v];
                    if (f) {
                        v = f(v);
                        if (typeof v == 'string') {
                            if (b) {
                                a[a.length] = ',';
                            }
                            a[a.length] = v;
                            b = true;
                        }
                    }
                }
                a[a.length] = ']';
                return a.join('');
            },
            'boolean': function (x) {
                return String(x);
            },
            'null': function (x) {
                return "null";
            },
            'number': function (x) {
                return isFinite(x) ? String(x) : 'null';
            },
            'object': function (x) {
                if (x) {
                    if (x instanceof Array) {
                        return s.array(x);
                    }
                    var a = ['{'], b, f, i, v;
                    for (i in x) {
                        v = x[i];
                        f = s[typeof v];
                        if (f) {
                            v = f(v);
                            if (typeof v == 'string') {
                                if (b) {
                                    a[a.length] = ',';
                                }
                                a.push(s.string(i), ':', v);
                                b = true;
                            }
                        }
                    }
                    a[a.length] = '}';
                    return a.join('');
                }
                return 'null';
            },
            'string': function (x) {
                if (/["\\\x00-\x1f]/.test(x)) {
                    x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                        var c = m[b];
                        if (c) {
                            return c;
                        }
                        c = b.charCodeAt();
                        return '\\u00' +
                            Math.floor(c / 16).toString(16) +
                            (c % 16).toString(16);
                    });
                }
                return '"' + x + '"';
            }
        };

	JSON.toJSON = function(v) {
		var f = isNaN(v) ? s[typeof v] : s['number'];
		if (f) return f(v);
	};
	
	JSON.parseJSON = function(v, safe) {
		if (safe === undefined) safe = JSON.parseJSON.safe;
		if (safe && !/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(v))
			return undefined;
		return eval('('+v+')');
	};
	
	JSON.parseJSON.safe = false;
};
JSON();
;

// effects.js
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// See scriptaculous.js for full license.  

// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';  
  if(this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if(this.slice(0,1) == '#') {  
      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if(this.length==7) color = this.toLowerCase();  
    }  
  }  
  return(color.length==7 ? color : (arguments[0] || this));  
}

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
}

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
}

Element.setContentZoom = function(element, percent) {
  element = $(element);  
  Element.setStyle(element, {fontSize: (percent/100) + 'em'});   
  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
}

Element.getOpacity = function(element){  
  var opacity;
  if (opacity = Element.getStyle(element, 'opacity'))  
    return parseFloat(opacity);  
  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))  
    if(opacity[1]) return parseFloat(opacity[1]) / 100;  
  return 1.0;  
}

Element.setOpacity = function(element, value){  
  element= $(element);  
  if (value == 1){
    Element.setStyle(element, { opacity: 
      (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 
      0.999999 : null });
    if(/MSIE/.test(navigator.userAgent))  
      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
  } else {  
    if(value < 0.00001) value = 0;  
    Element.setStyle(element, {opacity: value});
    if(/MSIE/.test(navigator.userAgent))  
     Element.setStyle(element, 
       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
                 'alpha(opacity='+value*100+')' });  
  }
}  
 
Element.getInlineOpacity = function(element){  
  return $(element).style.opacity || '';
}  

Element.childrenWithClassName = function(element, className, findFirst) {
  var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
  var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { 
    return (c.className && c.className.match(classNameRegExp));
  });
  if(!results) results = [];
  return results;
}

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

Array.prototype.call = function() {
  var args = arguments;
  this.each(function(f){ f.apply(this, args) });
}

/*--------------------------------------------------------------------------*/

var Effect = {
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if(child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            Builder.node('span',{style: tagifyStyle},
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if(((typeof element == 'object') || 
        (typeof element == 'function')) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || {});
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || {});
    Effect[element.visible() ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

var Effect2 = Effect; // deprecated

/* ------------- transitions ------------- */

Effect.Transitions = {}

Effect.Transitions.linear = function(pos) {
  return pos;
}
Effect.Transitions.sinoidal = function(pos) {
  return (-Math.cos(pos*Math.PI)/2) + 0.5;
}
Effect.Transitions.reverse  = function(pos) {
  return 1-pos;
}
Effect.Transitions.flicker = function(pos) {
  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
}
Effect.Transitions.wobble = function(pos) {
  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
}
Effect.Transitions.pulse = function(pos) {
  return (Math.floor(pos*10) % 2 == 0 ? 
    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
}
Effect.Transitions.none = function(pos) {
  return 0;
}
Effect.Transitions.full = function(pos) {
  return 1;
}

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create();
Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
  initialize: function() {
    this.effects  = [];
    this.interval = null;
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = (typeof effect.options.queue == 'string') ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if(!this.interval) 
      this.interval = setInterval(this.loop.bind(this), 40);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if(this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    this.effects.invoke('loop', timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if(typeof queueName != 'string') return queueName;
    
    if(!this.instances[queueName])
      this.instances[queueName] = new Effect.ScopedQueue();
      
    return this.instances[queueName];
  }
}
Effect.Queue = Effect.Queues.get('global');

Effect.DefaultOptions = {
  transition: Effect.Transitions.sinoidal,
  duration:   1.0,   // seconds
  fps:        25.0,  // max. 25fps due to Effect.Queue implementation
  sync:       false, // true for combining
  from:       0.0,
  to:         1.0,
  delay:      0.0,
  queue:      'parallel'
}

Effect.Base = function() {};
Effect.Base.prototype = {
  position: null,
  start: function(options) {
    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn + (this.options.duration*1000);
    this.event('beforeStart');
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if(timePos >= this.startOn) {
      if(timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if(this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
      var frame = Math.round(pos * this.options.fps * this.options.duration);
      if(frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  render: function(pos) {
    if(this.state == 'idle') {
      this.state = 'running';
      this.event('beforeSetup');
      if(this.setup) this.setup();
      this.event('afterSetup');
    }
    if(this.state == 'running') {
      if(this.options.transition) pos = this.options.transition(pos);
      pos *= (this.options.to-this.options.from);
      pos += this.options.from;
      this.position = pos;
      this.event('beforeUpdate');
      if(this.update) this.update(pos);
      this.event('afterUpdate');
    }
  },
  cancel: function() {
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if(this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
}

Effect.Parallel = Class.create();
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if(effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Opacity = Class.create();
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    // make this work on IE on elements without 'layout'
    if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || {});
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create();
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Bug in Opera: Opera returns the "real" position of a static element or
    // relative element that does not have top/left explicitly set.
    // ==> Always set top and left for position relative elements in your stylesheets 
    // (to 0 if you do not need them) 
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if(this.options.mode == 'absolute') {
      // absolute movement, so we need to calc deltaX and deltaY
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: this.options.x  * position + this.originalLeft + 'px',
      top:  this.options.y  * position + this.originalTop  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element, 
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
};

Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
  initialize: function(element, percent) {
    this.element = $(element)
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || {});
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');
    
    this.originalStyle = {};
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%'].each( function(fontSizeType) {
      if(fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));
    
    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    
    this.dims = null;
    if(this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if(/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if(!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if(this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = {};
    if(this.options.scaleX) d.width = width + 'px';
    if(this.options.scaleY) d.height = height + 'px';
    if(this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if(this.elementPositioning == 'absolute') {
        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if(this.options.scaleY) d.top = -topd + 'px';
        if(this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create();
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if(this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = {
      backgroundImage: this.element.getStyle('background-image') };
    this.element.setStyle({backgroundImage: 'none'});
    if(!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if(!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    this.start(arguments[1] || {});
  },
  setup: function() {
    Position.prepare();
    var offsets = Position.cumulativeOffset(this.element);
    if(this.options.offset) offsets[1] += this.options.offset;
    var max = window.innerHeight ? 
      window.height - window.innerHeight :
      document.body.scrollHeight - 
        (document.documentElement.clientHeight ? 
          document.documentElement.clientHeight : document.body.clientHeight);
    this.scrollStart = Position.deltaY;
    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
  },
  update: function(position) {
    Position.prepare();
    window.scrollTo(Position.deltaX, 
      this.scrollStart + (position*this.delta));
  }
});

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
  from: element.getOpacity() || 1.0,
  to:   0.0,
  afterFinishInternal: function(effect) { 
    if(effect.options.to!=0) return;
    effect.element.hide();
    effect.element.setStyle({opacity: oldOpacity}); 
  }}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from);
    effect.element.show(); 
  }}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200, 
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
     Object.extend({ duration: 1.0, 
      beforeSetupInternal: function(effect) {
        effect.effects[0].element.setStyle({position: 'absolute'}); },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide();
         effect.effects[0].element.setStyle(oldStyle); }
     }, arguments[1] || {})
   );
}

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0, 
    Object.extend({ scaleContent: false, 
      scaleX: false, 
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide();
        effect.element.undoClipping();
      } 
    }, arguments[1] || {})
  );
}

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, 
    Object.extend({ scaleContent: false, 
      scaleX: false,
      scaleFrom: 0,
      scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
      restoreAfterFinish: true,
      afterSetup: function(effect) {
        effect.element.makeClipping();
        effect.element.setStyle({height: '0px'});
        effect.element.show(); 
      },  
      afterFinishInternal: function(effect) {
        effect.element.undoClipping();
      }
    }, arguments[1] || {})
  );
}

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, { 
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, { 
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) { 
          effect.element.makePositioned();
          effect.element.makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide();
          effect.element.undoClipping();
          effect.element.undoPositioned();
          effect.element.setStyle({opacity: oldOpacity});
        }
      })
    }
  });
}

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned(); 
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide();
          effect.effects[0].element.undoPositioned();
          effect.effects[0].element.setStyle(oldStyle);
        } 
      }, arguments[1] || {}));
}

Effect.Shake = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element, 
      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
        effect.element.undoPositioned();
        effect.element.setStyle(oldStyle);
  }}) }}) }}) }}) }}) }});
}

Effect.SlideDown = function(element) {
  element = $(element);
  element.cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = $(element.firstChild).getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false, 
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.firstChild.makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping();
      effect.element.setStyle({height: '0px'});
      effect.element.show(); },
    afterUpdateInternal: function(effect) {
      effect.element.firstChild.setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping(); 
      // IE will crash if child is undoPositioned first
      if(/MSIE/.test(navigator.userAgent)){
        effect.element.undoPositioned();
        effect.element.firstChild.undoPositioned();
      }else{
        effect.element.firstChild.undoPositioned();
        effect.element.undoPositioned();
      }
      effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || {})
  );
}
  
Effect.SlideUp = function(element) {
  element = $(element);
  element.cleanWhitespace();
  var oldInnerBottom = $(element.firstChild).getStyle('bottom');
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false, 
    scaleX: false, 
    scaleMode: 'box',
    scaleFrom: 100,
    restoreAfterFinish: true,
    beforeStartInternal: function(effect) {
      effect.element.makePositioned();
      effect.element.firstChild.makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping();
      effect.element.show(); },  
    afterUpdateInternal: function(effect) {
      effect.element.firstChild.setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); },
    afterFinishInternal: function(effect) {
      effect.element.hide();
      effect.element.undoClipping();
      effect.element.firstChild.undoPositioned();
      effect.element.undoPositioned();
      effect.element.setStyle({bottom: oldInnerBottom}); }
   }, arguments[1] || {})
  );
}

// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, 
    { restoreAfterFinish: true,
      beforeSetup: function(effect) {
        effect.element.makeClipping(effect.element); },  
      afterFinishInternal: function(effect) {
        effect.element.hide(effect.element); 
        effect.element.undoClipping(effect.element); }
  });
}

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();    
  var initialMoveX, initialMoveY;
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0; 
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }
  
  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01, 
    beforeSetup: function(effect) {
      effect.element.hide();
      effect.element.makeClipping();
      effect.element.makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'});
               effect.effects[0].element.show(); 
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping();
               effect.effects[0].element.undoPositioned();
               effect.effects[0].element.setStyle(oldStyle); 
             }
           }, options)
      )
    }
  });
}

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':  
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }
  
  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({            
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned();
           effect.effects[0].element.makeClipping(); },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide();
           effect.effects[0].element.undoClipping();
           effect.effects[0].element.undoPositioned();
           effect.effects[0].element.setStyle(oldStyle); }
       }, options)
  );
}

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || {};
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({  duration: 3.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
}

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  Element.makeClipping(element);
  return new Effect.Scale(element, 5, Object.extend({   
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, { 
      scaleContent: false, 
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide();
        effect.element.undoClipping(); 
        effect.element.setStyle(oldStyle);
      } });
  }}, arguments[1] || {}));
};

['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( 
  function(f) { Element.Methods[f] = Element[f]; }
);

Element.Methods.visualEffect = function(element, effect, options) {
  s = effect.gsub(/_/, '-').camelize();
  effect_class = s.charAt(0).toUpperCase() + s.substring(1);
  new Effect[effect_class](element, options);
  return $(element);
};

Element.addMethods();

;

// dragdrop.js
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
// 
// See scriptaculous.js for full license.

/*--------------------------------------------------------------------------*/

var Droppables = {
  drops: [],

  remove: function(element) {
    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  },

  add: function(element) {
    element = $(element);
    var options = Object.extend({
      greedy:     true,
      hoverclass: null,
      tree:       false
    }, arguments[1] || {});

    // cache containers
    if(options.containment) {
      options._containers = [];
      var containment = options.containment;
      if((typeof containment == 'object') && 
        (containment.constructor == Array)) {
        containment.each( function(c) { options._containers.push($(c)) });
      } else {
        options._containers.push($(containment));
      }
    }
    
    if(options.accept) options.accept = [options.accept].flatten();

    Element.makePositioned(element); // fix IE
    options.element = element;

    this.drops.push(options);
  },
  
  findDeepestChild: function(drops) {
    deepest = drops[0];
      
    for (i = 1; i < drops.length; ++i)
      if (Element.isParent(drops[i].element, deepest.element))
        deepest = drops[i];
    
    return deepest;
  },

  isContained: function(element, drop) {
    var containmentNode;
    if(drop.tree) {
      containmentNode = element.treeNode; 
    } else {
      containmentNode = element.parentNode;
    }
    return drop._containers.detect(function(c) { return containmentNode == c });
  },
  
  isAffected: function(point, element, drop) {
    return (
      (drop.element!=element) &&
      ((!drop._containers) ||
        this.isContained(element, drop)) &&
      ((!drop.accept) ||
        (Element.classNames(element).detect( 
          function(v) { return drop.accept.include(v) } ) )) &&
      Position.within(drop.element, point[0], point[1]) );
  },

  deactivate: function(drop) {
    if(drop.hoverclass)
      Element.removeClassName(drop.element, drop.hoverclass);
    this.last_active = null;
  },

  activate: function(drop) {
    if(drop.hoverclass)
      Element.addClassName(drop.element, drop.hoverclass);
    this.last_active = drop;
  },

  show: function(point, element) {
    if(!this.drops.length) return;
    var affected = [];
    
    if(this.last_active) this.deactivate(this.last_active);
    this.drops.each( function(drop) {
      if(Droppables.isAffected(point, element, drop))
        affected.push(drop);
    });
        
    if(affected.length>0) {
      drop = Droppables.findDeepestChild(affected);
      Position.within(drop.element, point[0], point[1]);
      if(drop.onHover)
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
      
      Droppables.activate(drop);
    }
  },

  fire: function(event, element) {
    if(!this.last_active) return;
    Position.prepare();

    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
      if (this.last_active.onDrop) 
        this.last_active.onDrop(element, this.last_active.element, event);
  },

  reset: function() {
    if(this.last_active)
      this.deactivate(this.last_active);
  }
}

var Draggables = {
  drags: [],
  observers: [],
  
  register: function(draggable) {
    if(this.drags.length == 0) {
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
      
      Event.observe(document, "mouseup", this.eventMouseUp);
      Event.observe(document, "mousemove", this.eventMouseMove);
      Event.observe(document, "keypress", this.eventKeypress);
    }
    this.drags.push(draggable);
  },
  
  unregister: function(draggable) {
    this.drags = this.drags.reject(function(d) { return d==draggable });
    if(this.drags.length == 0) {
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
      Event.stopObserving(document, "keypress", this.eventKeypress);
    }
  },
  
  activate: function(draggable) {
    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
    this.activeDraggable = draggable;
  },
  
  deactivate: function() {
    this.activeDraggable = null;
  },
  
  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    // Mozilla-based browsers fire successive mousemove events with
    // the same coordinates, prevent needless redrawing (moz bug?)
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
    this._lastPointer = pointer;
    this.activeDraggable.updateDrag(event, pointer);
  },
  
  endDrag: function(event) {
    if(!this.activeDraggable) return;
    this._lastPointer = null;
    this.activeDraggable.endDrag(event);
    this.activeDraggable = null;
  },
  
  keyPress: function(event) {
    if(this.activeDraggable)
      this.activeDraggable.keyPress(event);
  },
  
  addObserver: function(observer) {
    this.observers.push(observer);
    this._cacheObserverCallbacks();
  },
  
  removeObserver: function(element) {  // element instead of observer fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },
  
  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
    if(this[eventName+'Count'] > 0)
      this.observers.each( function(o) {
        if(o[eventName]) o[eventName](eventName, draggable, event);
      });
  },
  
  _cacheObserverCallbacks: function() {
    ['onStart','onEnd','onDrag'].each( function(eventName) {
      Draggables[eventName+'Count'] = Draggables.observers.select(
        function(o) { return o[eventName]; }
      ).length;
    });
  }
}

/*--------------------------------------------------------------------------*/

var Draggable = Class.create();
Draggable.prototype = {
  initialize: function(element) {
    var options = Object.extend({
      handle: false,
      starteffect: function(element) {
        element._opacity = Element.getOpacity(element); 
        new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
      },
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
      },
      endeffect: function(element) {
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity}); 
      },
      zindex: 1000,
      revert: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] }
    }, arguments[1] || {});

    this.element = $(element);
    
    if(options.handle && (typeof options.handle == 'string')) {
      var h = Element.childrenWithClassName(this.element, options.handle, true);
      if(h.length>0) this.handle = h[0];
    }
    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;
    
    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
      options.scroll = $(options.scroll);

    Element.makePositioned(this.element); // fix IE    

    this.delta    = this.currentDelta();
    this.options  = options;
    this.dragging = false;   

    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);
    
    Draggables.register(this);
  },
  
  destroy: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    Draggables.unregister(this);
  },
  
  currentDelta: function() {
    return([
      parseInt(Element.getStyle(this.element,'left') || '0'),
      parseInt(Element.getStyle(this.element,'top') || '0')]);
  },
  
  initDrag: function(event) {
    if(Event.isLeftClick(event)) {    
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if(src.tagName && (
        src.tagName=='INPUT' ||
        src.tagName=='SELECT' ||
        src.tagName=='OPTION' ||
        src.tagName=='BUTTON' ||
        src.tagName=='TEXTAREA')) return;
        
      if(this.element._revert) {
        this.element._revert.cancel();
        this.element._revert = null;
      }
      
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var pos     = Position.cumulativeOffset(this.element);
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
      
      Draggables.activate(this);
      Event.stop(event);
    }
  },
  
  startDrag: function(event) {
    this.dragging = true;
    
    if(this.options.zindex) {
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
      this.element.style.zIndex = this.options.zindex;
    }
    
    if(this.options.ghosting) {
      this._clone = this.element.cloneNode(true);
      Position.absolutize(this.element);
      this.element.parentNode.insertBefore(this._clone, this.element);
    }
    
    if(this.options.scroll) {
      if (this.options.scroll == window) {
        var where = this._getWindowScroll(this.options.scroll);
        this.originalScrollLeft = where.left;
        this.originalScrollTop = where.top;
      } else {
        this.originalScrollLeft = this.options.scroll.scrollLeft;
        this.originalScrollTop = this.options.scroll.scrollTop;
      }
    }
    
    Draggables.notify('onStart', this, event);
    if(this.options.starteffect) this.options.starteffect(this.element);
  },
  
  updateDrag: function(event, pointer) {
    if(!this.dragging) this.startDrag(event);
    Position.prepare();
    Droppables.show(pointer, this.element);
    Draggables.notify('onDrag', this, event);
    this.draw(pointer);
    if(this.options.change) this.options.change(this);
    
    if(this.options.scroll) {
      this.stopScrolling();
      
      var p;
      if (this.options.scroll == window) {
        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
      } else {
        p = Position.page(this.options.scroll);
        p[0] += this.options.scroll.scrollLeft;
        p[1] += this.options.scroll.scrollTop;
        p.push(p[0]+this.options.scroll.offsetWidth);
        p.push(p[1]+this.options.scroll.offsetHeight);
      }
      var speed = [0,0];
      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
      this.startScrolling(speed);
    }
    
    // fix AppleWebKit rendering
    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
    
    Event.stop(event);
  },
  
  finishDrag: function(event, success) {
    this.dragging = false;

    if(this.options.ghosting) {
      Position.relativize(this.element);
      Element.remove(this._clone);
      this._clone = null;
    }

    if(success) Droppables.fire(event, this.element);
    Draggables.notify('onEnd', this, event);

    var revert = this.options.revert;
    if(revert && typeof revert == 'function') revert = revert(this.element);
    
    var d = this.currentDelta();
    if(revert && this.options.reverteffect) {
      this.options.reverteffect(this.element, 
        d[1]-this.delta[1], d[0]-this.delta[0]);
    } else {
      this.delta = d;
    }

    if(this.options.zindex)
      this.element.style.zIndex = this.originalZ;

    if(this.options.endeffect) 
      this.options.endeffect(this.element);

    Draggables.deactivate(this);
    Droppables.reset();
  },
  
  keyPress: function(event) {
    if(event.keyCode!=Event.KEY_ESC) return;
    this.finishDrag(event, false);
    Event.stop(event);
  },
  
  endDrag: function(event) {
    if(!this.dragging) return;
    this.stopScrolling();
    this.finishDrag(event, true);
    Event.stop(event);
  },
  
  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];
    
    if(this.options.scroll && (this.options.scroll != window)) {
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
    }
    
    var p = [0,1].map(function(i){ 
      return (point[i]-pos[i]-this.offset[i]) 
    }.bind(this));
    
    if(this.options.snap) {
      if(typeof this.options.snap == 'function') {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(this.options.snap instanceof Array) {
        p = p.map( function(v, i) {
          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
      } else {
        p = p.map( function(v) {
          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
      }
    }}
    
    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = p[0] + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = p[1] + "px";
    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },
  
  stopScrolling: function() {
    if(this.scrollInterval) {
      clearInterval(this.scrollInterval);
      this.scrollInterval = null;
      Draggables._lastScrollPointer = null;
    }
  },
  
  startScrolling: function(speed) {
    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
    this.lastScrolled = new Date();
    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  },
  
  scroll: function() {
    var current = new Date();
    var delta = current - this.lastScrolled;
    this.lastScrolled = current;
    if(this.options.scroll == window) {
      with (this._getWindowScroll(this.options.scroll)) {
        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
          var d = delta / 1000;
          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
        }
      }
    } else {
      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
    }
    
    Position.prepare();
    Droppables.show(Draggables._lastPointer, this.element);
    Draggables.notify('onDrag', this);
    Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
    Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
    Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
    if (Draggables._lastScrollPointer[0] < 0)
      Draggables._lastScrollPointer[0] = 0;
    if (Draggables._lastScrollPointer[1] < 0)
      Draggables._lastScrollPointer[1] = 0;
    this.draw(Draggables._lastScrollPointer);
    
    if(this.options.change) this.options.change(this);
  },
  
  _getWindowScroll: function(w) {
    var T, L, W, H;
    with (w.document) {
      if (w.document.documentElement && documentElement.scrollTop) {
        T = documentElement.scrollTop;
        L = documentElement.scrollLeft;
      } else if (w.document.body) {
        T = body.scrollTop;
        L = body.scrollLeft;
      }
      if (w.innerWidth) {
        W = w.innerWidth;
        H = w.innerHeight;
      } else if (w.document.documentElement && documentElement.clientWidth) {
        W = documentElement.clientWidth;
        H = documentElement.clientHeight;
      } else {
        W = body.offsetWidth;
        H = body.offsetHeight
      }
    }
    return { top: T, left: L, width: W, height: H };
  }
}

/*--------------------------------------------------------------------------*/

var SortableObserver = Class.create();
SortableObserver.prototype = {
  initialize: function(element, observer) {
    this.element   = $(element);
    this.observer  = observer;
    this.lastValue = Sortable.serialize(this.element);
  },
  
  onStart: function() {
    this.lastValue = Sortable.serialize(this.element);
  },
  
  onEnd: function() {
    Sortable.unmark();
    if(this.lastValue != Sortable.serialize(this.element))
      this.observer(this.element)
  }
}

var Sortable = {
  sortables: {},
  
  _findRootElement: function(element) {
    while (element.tagName != "BODY") {  
      if(element.id && Sortable.sortables[element.id]) return element;
      element = element.parentNode;
    }
  },

  options: function(element) {
    element = Sortable._findRootElement($(element));
    if(!element) return;
    return Sortable.sortables[element.id];
  },
  
  destroy: function(element){
    var s = Sortable.options(element);
    
    if(s) {
      Draggables.removeObserver(s.element);
      s.droppables.each(function(d){ Droppables.remove(d) });
      s.draggables.invoke('destroy');
      
      delete Sortable.sortables[s.element.id];
    }
  },

  create: function(element) {
    element = $(element);
    var options = Object.extend({ 
      element:     element,
      tag:         'li',       // assumes li children, override with tag: 'tagname'
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', // one of 'vertical', 'horizontal'
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
      containment: element,    // also takes array of elements (or id's); or false
      handle:      false,      // or a CSS class
      only:        false,
      hoverclass:  null,
      ghosting:    false,
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      /^[^_]*_(.*)$/,
      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || {});

    // clear any old sortable with same element
    this.destroy(element);

    // build options for the draggables
    var options_for_draggable = {
      revert:      true,
      scroll:      options.scroll,
      scrollSpeed: options.scrollSpeed,
      scrollSensitivity: options.scrollSensitivity,
      ghosting:    options.ghosting,
      constraint:  options.constraint,
      handle:      options.handle };

    if(options.starteffect)
      options_for_draggable.starteffect = options.starteffect;

    if(options.reverteffect)
      options_for_draggable.reverteffect = options.reverteffect;
    else
      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
        element.style.top  = 0;
        element.style.left = 0;
      };

    if(options.endeffect)
      options_for_draggable.endeffect = options.endeffect;

    if(options.zindex)
      options_for_draggable.zindex = options.zindex;

    // build options for the droppables  
    var options_for_droppable = {
      overlap:     options.overlap,
      containment: options.containment,
      tree:        options.tree,
      hoverclass:  options.hoverclass,
      onHover:     Sortable.onHover
      //greedy:      !options.dropOnEmpty
    }
    
    var options_for_tree = {
      onHover:      Sortable.onEmptyHover,
      overlap:      options.overlap,
      containment:  options.containment,
      hoverclass:   options.hoverclass
    }

    // fix for gecko engine
    Element.cleanWhitespace(element); 

    options.draggables = [];
    options.droppables = [];

    // drop on empty handling
    if(options.dropOnEmpty || options.tree) {
      Droppables.add(element, options_for_tree);
      options.droppables.push(element);
    }

    (this.findElements(element, options) || []).each( function(e) {
      // handles are per-draggable
      var handle = options.handle ? 
        Element.childrenWithClassName(e, options.handle)[0] : e;    
      options.draggables.push(
        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
      Droppables.add(e, options_for_droppable);
      if(options.tree) e.treeNode = element;
      options.droppables.push(e);      
    });
    
    if(options.tree) {
      (Sortable.findTreeElements(element, options) || []).each( function(e) {
        Droppables.add(e, options_for_tree);
        e.treeNode = element;
        options.droppables.push(e);
      });
    }

    // keep reference
    this.sortables[element.id] = options;

    // for onupdate
    Draggables.addObserver(new SortableObserver(element, options.onUpdate));

  },

  // return all suitable-for-sortable elements in a guaranteed order
  findElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.tag);
  },
  
  findTreeElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.treeTag);
  },

  onHover: function(element, dropon, overlap) {
    if(Element.isParent(dropon, element)) return;

    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
      return;
    } else if(overlap>0.5) {
      Sortable.mark(dropon, 'before');
      if(dropon.previousSibling != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, dropon);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    } else {
      Sortable.mark(dropon, 'after');
      var nextElement = dropon.nextSibling || null;
      if(nextElement != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, nextElement);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    }
  },
  
  onEmptyHover: function(element, dropon, overlap) {
    var oldParentNode = element.parentNode;
    var droponOptions = Sortable.options(dropon);
        
    if(!Element.isParent(dropon, element)) {
      var index;
      
      var children = Sortable.findElements(dropon, {tag: droponOptions.tag});
      var child = null;
            
      if(children) {
        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
        
        for (index = 0; index < children.length; index += 1) {
          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
            offset -= Element.offsetSize (children[index], droponOptions.overlap);
          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
            child = index + 1 < children.length ? children[index + 1] : null;
            break;
          } else {
            child = children[index];
            break;
          }
        }
      }
      
      dropon.insertBefore(element, child);
      
      Sortable.options(oldParentNode).onChange(element);
      droponOptions.onChange(element);
    }
  },

  unmark: function() {
    if(Sortable._marker) Element.hide(Sortable._marker);
  },

  mark: function(dropon, position) {
    // mark on ghosting only
    var sortable = Sortable.options(dropon.parentNode);
    if(sortable && !sortable.ghosting) return; 

    if(!Sortable._marker) {
      Sortable._marker = $('dropmarker') || document.createElement('DIV');
      Element.hide(Sortable._marker);
      Element.addClassName(Sortable._marker, 'dropmarker');
      Sortable._marker.style.position = 'absolute';
      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
    }    
    var offsets = Position.cumulativeOffset(dropon);
    Sortable._marker.style.left = offsets[0] + 'px';
    Sortable._marker.style.top = offsets[1] + 'px';
    
    if(position=='after')
      if(sortable.overlap == 'horizontal') 
        Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
      else
        Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
    
    Element.show(Sortable._marker);
  },
  
  _tree: function(element, options, parent) {
    var children = Sortable.findElements(element, options) || [];
  
    for (var i = 0; i < children.length; ++i) {
      var match = children[i].id.match(options.format);

      if (!match) continue;
      
      var child = {
        id: encodeURIComponent(match ? match[1] : null),
        element: element,
        parent: parent,
        children: new Array,
        position: parent.children.length,
        container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
      }
      
      /* Get the element containing the children and recurse over it */
      if (child.container)
        this._tree(child.container, options, child)
      
      parent.children.push (child);
    }

    return parent; 
  },

  /* Finds the first element of the given tag type within a parent element.
    Used for finding the first LI[ST] within a L[IST]I[TEM].*/
  _findChildrenElement: function (element, containerTag) {
    if (element && element.hasChildNodes)
      for (var i = 0; i < element.childNodes.length; ++i)
        if (element.childNodes[i].tagName == containerTag)
          return element.childNodes[i];
  
    return null;
  },

  tree: function(element) {
    element = $(element);
    var sortableOptions = this.options(element);
    var options = Object.extend({
      tag: sortableOptions.tag,
      treeTag: sortableOptions.treeTag,
      only: sortableOptions.only,
      name: element.id,
      format: sortableOptions.format
    }, arguments[1] || {});
    
    var root = {
      id: null,
      parent: null,
      children: new Array,
      container: element,
      position: 0
    }
    
    return Sortable._tree (element, options, root);
  },

  /* Construct a [i] index for a particular node */
  _constructIndex: function(node) {
    var index = '';
    do {
      if (node.id) index = '[' + node.position + ']' + index;
    } while ((node = node.parent) != null);
    return index;
  },

  sequence: function(element) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[1] || {});
    
    return $(this.findElements(element, options) || []).map( function(item) {
      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
    });
  },

  setSequence: function(element, new_sequence) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[2] || {});
    
    var nodeMap = {};
    this.findElements(element, options).each( function(n) {
        if (n.id.match(options.format))
            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
        n.parentNode.removeChild(n);
    });
   
    new_sequence.each(function(ident) {
      var n = nodeMap[ident];
      if (n) {
        n[1].appendChild(n[0]);
        delete nodeMap[ident];
      }
    });
  },
  
  serialize: function(element) {
    element = $(element);
    var options = Object.extend(Sortable.options(element), arguments[1] || {});
    var name = encodeURIComponent(
      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
    
    if (options.tree) {
      return Sortable.tree(element, arguments[1]).children.map( function (item) {
        return [name + Sortable._constructIndex(item) + "=" + 
                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
      }).flatten().join('&');
    } else {
      return Sortable.sequence(element, arguments[1]).map( function(item) {
        return name + "[]=" + encodeURIComponent(item);
      }).join('&');
    }
  }
}

/* Returns true if child is contained within element */
Element.isParent = function(child, element) {
  if (!child.parentNode || child == element) return false;

  if (child.parentNode == element) return true;

  return Element.isParent(child.parentNode, element);
}

Element.findChildren = function(element, only, recursive, tagName) {    
  if(!element.hasChildNodes()) return null;
  tagName = tagName.toUpperCase();
  if(only) only = [only].flatten();
  var elements = [];
  $A(element.childNodes).each( function(e) {
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
        elements.push(e);
    if(recursive) {
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
      if(grandchildren) elements.push(grandchildren);
    }
  });

  return (elements.length>0 ? elements.flatten() : []);
}

Element.offsetSize = function (element, type) {
  if (type == 'vertical' || type == 'height')
    return element.offsetHeight;
  else
    return element.offsetWidth;
}

;

// controls.js
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
// 
// See scriptaculous.js for full license.

// Autocompleter.Base handles all the autocompletion functionality 
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least, 
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method 
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most 
// useful when one of the tokens is \n (a newline), as it 
// allows smart autocompletion after linebreaks.

var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
  baseInitialize: function(element, update, options) {
    this.element     = $(element); 
    this.update      = $(update);  
    this.hasFocus    = false; 
    this.changed     = false; 
    this.active      = false; 
    this.index       = 0;     
    this.entryCount  = 0;

    if (this.setOptions)
      this.setOptions(options);
    else
      this.options = options || {};

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow || 
    function(element, update){ 
      if(!update.style.position || update.style.position=='absolute') {
        update.style.position = 'absolute';
        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
      }
      Effect.Appear(update,{duration:0.15});
    };
    this.options.onHide = this.options.onHide || 
    function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if (typeof(this.options.tokens) == 'string') 
      this.options.tokens = new Array(this.options.tokens);

    this.observer = null;
    
    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);

    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix && 
      (navigator.appVersion.indexOf('MSIE')>0) &&
      (navigator.userAgent.indexOf('Opera')<0) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update, 
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },
  
  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix);
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },

  hide: function() {
    this.stopIndicator();
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    if(this.iefix) Element.hide(this.iefix);
  },

  startIndicator: function() {
    if(this.options.indicator) Element.show(this.options.indicator);
  },

  stopIndicator: function() {
    if(this.options.indicator) Element.hide(this.options.indicator);
  },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
         return;
      }
     else 
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
         (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer = 
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },

  activate: function() {
    this.changed = false;
    this.hasFocus = true;
    this.getUpdatedChoices();
  },

  onHover: function(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex) 
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },
  
  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },
  
  onBlur: function(event) {
    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;     
  }, 
  
  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ? 
          Element.addClassName(this.getEntry(i),"selected") : 
          Element.removeClassName(this.getEntry(i),"selected");
        
      if(this.hasFocus) { 
        this.show();
        this.active = true;
      }
    } else {
      this.active = false;
      this.hide();
    }
  },
  
  markPrevious: function() {
    if(this.index > 0) this.index--
      else this.index = this.entryCount-1;
  },
  
  markNext: function() {
    if(this.index < this.entryCount-1) this.index++
      else this.index = 0;
  },
  
  getEntry: function(index) {
    return this.update.firstChild.childNodes[index];
  },
  
  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },
  
  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
    
    var lastTokenPos = this.findLastToken();
    if (lastTokenPos != -1) {
      var newValue = this.element.value.substr(0, lastTokenPos + 1);
      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value;
    } else {
      this.element.value = value;
    }
    this.element.focus();
    
    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.firstChild);

      if(this.update.firstChild && this.update.firstChild.childNodes) {
        this.entryCount = 
          this.update.firstChild.childNodes.length;
        for (var i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } else { 
        this.entryCount = 0;
      }

      this.stopIndicator();

      this.index = 0;
      this.render();
    }
  },

  addObservers: function(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;   
    if(this.getToken().length>=this.options.minChars) {
      this.startIndicator();
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
  },

  getToken: function() {
    var tokenPos = this.findLastToken();
    if (tokenPos != -1)
      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
    else
      var ret = this.element.value;

    return /\n/.test(ret) ? '' : ret;
  },

  findLastToken: function() {
    var lastTokenPos = -1;

    for (var i=0; i<this.options.tokens.length; i++) {
      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
      if (thisTokenPos > lastTokenPos)
        lastTokenPos = thisTokenPos;
    }
    return lastTokenPos;
  }
}

Ajax.Autocompleter = Class.create();
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    entry = encodeURIComponent(this.options.paramName) + '=' + 
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams) 
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }

});

// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
//                    text only at the beginning of strings in the 
//                    autocomplete array. Defaults to true, which will
//                    match text at the beginning of any *word* in the
//                    strings in the autocomplete array. If you want to
//                    search anywhere in the string, additionally set
//                    the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
//                   a partial match (unlike minChars, which defines
//                   how many characters are required to do any match
//                   at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
//                 Defaults to true.
//
// It's possible to pass in a custom function as the 'selector' 
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.

Autocompleter.Local = Class.create();
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
  },

  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
  },

  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      partialChars: 2,
      ignoreCase: true,
      fullSearch: false,
      selector: function(instance) {
        var ret       = []; // Beginning matches
        var partial   = []; // Inside matches
        var entry     = instance.getToken();
        var count     = 0;

        for (var i = 0; i < instance.options.array.length &&  
          ret.length < instance.options.choices ; i++) { 

          var elem = instance.options.array[i];
          var foundPos = instance.options.ignoreCase ? 
            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
            elem.indexOf(entry);

          while (foundPos != -1) {
            if (foundPos == 0 && elem.length != entry.length) { 
              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
                elem.substr(entry.length) + "</li>");
              break;
            } else if (entry.length >= instance.options.partialChars && 
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
                  foundPos + entry.length) + "</li>");
                break;
              }
            }

            foundPos = instance.options.ignoreCase ? 
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
              elem.indexOf(entry, foundPos + 1);

          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
        return "<ul>" + ret.join('') + "</ul>";
      }
    }, options || {});
  }
});

// AJAX in-place editor
//
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor

// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
  setTimeout(function() {
    Field.activate(field);
  }, 1);
}

Ajax.InPlaceEditor = Class.create();
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
Ajax.InPlaceEditor.prototype = {
  initialize: function(element, url, options) {
    this.url = url;
    this.element = $(element);

    this.options = Object.extend({
      okButton: true,
      okText: "ok",
      cancelLink: true,
      cancelText: "cancel",
      savingText: "Saving...",
      clickToEditText: "Click to edit",
      okText: "ok",
      rows: 1,
      onComplete: function(transport, element) {
        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
      },
      onFailure: function(transport) {
        alert("Error communicating with the server: " + transport.responseText.stripTags());
      },
      callback: function(form) {
        return Form.serialize(form);
      },
      handleLineBreaks: true,
      loadingText: 'Loading...',
      savingClassName: 'inplaceeditor-saving',
      loadingClassName: 'inplaceeditor-loading',
      formClassName: 'inplaceeditor-form',
      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
      highlightendcolor: "#FFFFFF",
      externalControl: null,
      submitOnBlur: false,
      ajaxOptions: {},
      evalScripts: false
    }, options || {});

    if(!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + "-inplaceeditor";
      if ($(this.options.formId)) {
        // there's already a form with that name, don't specify an id
        this.options.formId = null;
      }
    }
    
    if (this.options.externalControl) {
      this.options.externalControl = $(this.options.externalControl);
    }
    
    this.originalBackground = Element.getStyle(this.element, 'background-color');
    if (!this.originalBackground) {
      this.originalBackground = "transparent";
    }
    
    this.element.title = this.options.clickToEditText;
    
    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
    Event.observe(this.element, 'click', this.onclickListener);
    Event.observe(this.element, 'mouseover', this.mouseoverListener);
    Event.observe(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.observe(this.options.externalControl, 'click', this.onclickListener);
      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  },
  enterEditMode: function(evt) {
    if (this.saving) return;
    if (this.editing) return;
    this.editing = true;
    this.onEnterEditMode();
    if (this.options.externalControl) {
      Element.hide(this.options.externalControl);
    }
    Element.hide(this.element);
    this.createForm();
    this.element.parentNode.insertBefore(this.form, this.element);
    Field.scrollFreeActivate(this.editField);
    // stop the event to avoid a page refresh in Safari
    if (evt) {
      Event.stop(evt);
    }
    return false;
  },
  createForm: function() {
    this.form = document.createElement("form");
    this.form.id = this.options.formId;
    Element.addClassName(this.form, this.options.formClassName)
    this.form.onsubmit = this.onSubmit.bind(this);

    this.createEditField();

    if (this.options.textarea) {
      var br = document.createElement("br");
      this.form.appendChild(br);
    }

    if (this.options.okButton) {
      okButton = document.createElement("input");
      okButton.type = "submit";
      okButton.value = this.options.okText;
      okButton.className = 'editor_ok_button';
      this.form.appendChild(okButton);
    }

    if (this.options.cancelLink) {
      cancelLink = document.createElement("a");
      cancelLink.href = "#";
      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
      cancelLink.onclick = this.onclickCancel.bind(this);
      cancelLink.className = 'editor_cancel';      
      this.form.appendChild(cancelLink);
    }
  },
  hasHTMLLineBreaks: function(string) {
    if (!this.options.handleLineBreaks) return false;
    return string.match(/<br/i) || string.match(/<p>/i);
  },
  convertHTMLLineBreaks: function(string) {
    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
  },
  createEditField: function() {
    var text;
    if(this.options.loadTextURL) {
      text = this.options.loadingText;
    } else {
      text = this.getText();
    }

    var obj = this;
    
    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
      this.options.textarea = false;
      var textField = document.createElement("input");
      textField.obj = this;
      textField.type = "text";
      textField.name = "value";
      textField.value = text;
      textField.style.backgroundColor = this.options.highlightcolor;
      textField.className = 'editor_field';
      var size = this.options.size || this.options.cols || 0;
      if (size != 0) textField.size = size;
      if (this.options.submitOnBlur)
        textField.onblur = this.onSubmit.bind(this);
      this.editField = textField;
    } else {
      this.options.textarea = true;
      var textArea = document.createElement("textarea");
      textArea.obj = this;
      textArea.name = "value";
      textArea.value = this.convertHTMLLineBreaks(text);
      textArea.rows = this.options.rows;
      textArea.cols = this.options.cols || 40;
      textArea.className = 'editor_field';      
      if (this.options.submitOnBlur)
        textArea.onblur = this.onSubmit.bind(this);
      this.editField = textArea;
    }
    
    if(this.options.loadTextURL) {
      this.loadExternalText();
    }
    this.form.appendChild(this.editField);
  },
  getText: function() {
    return this.element.innerHTML;
  },
  loadExternalText: function() {
    Element.addClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = true;
    new Ajax.Request(
      this.options.loadTextURL,
      Object.extend({
        asynchronous: true,
        onComplete: this.onLoadedExternalText.bind(this)
      }, this.options.ajaxOptions)
    );
  },
  onLoadedExternalText: function(transport) {
    Element.removeClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = false;
    this.editField.value = transport.responseText.stripTags();
  },
  onclickCancel: function() {
    this.onComplete();
    this.leaveEditMode();
    return false;
  },
  onFailure: function(transport) {
    this.options.onFailure(transport);
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
      this.oldInnerHTML = null;
    }
    return false;
  },
  onSubmit: function() {
    // onLoading resets these so we need to save them away for the Ajax call
    var form = this.form;
    var value = this.editField.value;
    
    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
    // to be displayed indefinitely
    this.onLoading();
    
    if (this.options.evalScripts) {
      new Ajax.Request(
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this),
          asynchronous:true, 
          evalScripts:true
        }, this.options.ajaxOptions));
    } else  {
      new Ajax.Updater(
        { success: this.element,
          // don't update on failure (this could be an option)
          failure: null }, 
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this)
        }, this.options.ajaxOptions));
    }
    // stop the event to avoid a page refresh in Safari
    if (arguments.length > 1) {
      Event.stop(arguments[0]);
    }
    return false;
  },
  onLoading: function() {
    this.saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  showSaving: function() {
    this.oldInnerHTML = this.element.innerHTML;
    this.element.innerHTML = this.options.savingText;
    Element.addClassName(this.element, this.options.savingClassName);
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
  },
  removeForm: function() {
    if(this.form) {
      if (this.form.parentNode) Element.remove(this.form);
      this.form = null;
    }
  },
  enterHover: function() {
    if (this.saving) return;
    this.element.style.backgroundColor = this.options.highlightcolor;
    if (this.effect) {
      this.effect.cancel();
    }
    Element.addClassName(this.element, this.options.hoverClassName)
  },
  leaveHover: function() {
    if (this.options.backgroundColor) {
      this.element.style.backgroundColor = this.oldBackground;
    }
    Element.removeClassName(this.element, this.options.hoverClassName)
    if (this.saving) return;
    this.effect = new Effect.Highlight(this.element, {
      startcolor: this.options.highlightcolor,
      endcolor: this.options.highlightendcolor,
      restorecolor: this.originalBackground
    });
  },
  leaveEditMode: function() {
    Element.removeClassName(this.element, this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
    if (this.options.externalControl) {
      Element.show(this.options.externalControl);
    }
    this.editing = false;
    this.saving = false;
    this.oldInnerHTML = null;
    this.onLeaveEditMode();
  },
  onComplete: function(transport) {
    this.leaveEditMode();
    this.options.onComplete.bind(this)(transport, this.element);
  },
  onEnterEditMode: function() {},
  onLeaveEditMode: function() {},
  dispose: function() {
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
    }
    this.leaveEditMode();
    Event.stopObserving(this.element, 'click', this.onclickListener);
    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  }
};

Ajax.InPlaceCollectionEditor = Class.create();
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
  createEditField: function() {
    if (!this.cached_selectTag) {
      var selectTag = document.createElement("select");
      var collection = this.options.collection || [];
      var optionTag;
      collection.each(function(e,i) {
        optionTag = document.createElement("option");
        optionTag.value = (e instanceof Array) ? e[0] : e;
        if(this.options.value==optionTag.value) optionTag.selected = true;
        optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
        selectTag.appendChild(optionTag);
      }.bind(this));
      this.cached_selectTag = selectTag;
    }

    this.editField = this.cached_selectTag;
    if(this.options.loadTextURL) this.loadExternalText();
    this.form.appendChild(this.editField);
    this.options.callback = function(form, value) {
      return "value=" + encodeURIComponent(value);
    }
  }
});

// Delayed observer, like Form.Element.Observer, 
// but waits for delay after last key input
// Ideal for live-search fields

Form.Element.DelayedObserver = Class.create();
Form.Element.DelayedObserver.prototype = {
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element); 
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
};
;

// slider.js
// Copyright (c) 2005 Marty Haught, Thomas Fuchs 
//
// See http://script.aculo.us for more info
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

if(!Control) var Control = {};
Control.Slider = Class.create();

// options:
//  axis: 'vertical', or 'horizontal' (default)
//
// callbacks:
//  onChange(value)
//  onSlide(value)
Control.Slider.prototype = {
  initialize: function(handle, track, options) {
    var slider = this;
    
    if(handle instanceof Array) {
      this.handles = handle.collect( function(e) { return $(e) });
    } else {
      this.handles = [$(handle)];
    }
    
    this.track   = $(track);
    this.options = options || {};

    this.axis      = this.options.axis || 'horizontal';
    this.increment = this.options.increment || 1;
    this.step      = parseInt(this.options.step || '1');
    this.range     = this.options.range || $R(0,1);
    
    this.value     = 0; // assure backwards compat
    this.values    = this.handles.map( function() { return 0 });
    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
    this.options.startSpan = $(this.options.startSpan || null);
    this.options.endSpan   = $(this.options.endSpan || null);

    this.restricted = this.options.restricted || false;

    this.maximum   = this.options.maximum || this.range.end;
    this.minimum   = this.options.minimum || this.range.start;

    // Will be used to align the handle onto the track, if necessary
    this.alignX = parseInt(this.options.alignX || '0');
    this.alignY = parseInt(this.options.alignY || '0');
    
    //
    // Michael Neumann: 
    // added because otherwise the slider doesn't work when
    // visibility=hidden during initialize.
    //

    if (this.options.trackLength)
      this.trackLength = this.options.trackLength;
    else
      this.trackLength = this.maximumOffset() - this.minimumOffset();

    if (this.options.handleLength)
      this.handleLength = this.options.handleLength;
    else
      this.handleLength = this.isVertical() ? this.handles[0].offsetHeight : this.handles[0].offsetWidth;

    this.active   = false;
    this.dragging = false;
    this.disabled = false;

    if(this.options.disabled) this.setDisabled();

    // Allowed values array
    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
    if(this.allowedValues) {
      this.minimum = this.allowedValues.min();
      this.maximum = this.allowedValues.max();
    }

    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
    this.eventMouseMove = this.update.bindAsEventListener(this);

    // Initialize handles in reverse (make sure first handle is active)
    this.handles.each( function(h,i) {
      i = slider.handles.length-1-i;
      slider.setValue(parseFloat(
        (slider.options.sliderValue instanceof Array ? 
          slider.options.sliderValue[i] : slider.options.sliderValue) || 
         slider.range.start), i);
      Element.makePositioned(h); // fix IE
      Event.observe(h, "mousedown", slider.eventMouseDown);
    });
    
    Event.observe(this.track, "mousedown", this.eventMouseDown);
    Event.observe(document, "mouseup", this.eventMouseUp);
    Event.observe(document, "mousemove", this.eventMouseMove);
    
    this.initialized = true;
  },
  dispose: function() {
    var slider = this;    
    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
    Event.stopObserving(document, "mouseup", this.eventMouseUp);
    Event.stopObserving(document, "mousemove", this.eventMouseMove);
    this.handles.each( function(h) {
      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
    });
  },
  setDisabled: function(){
    this.disabled = true;
  },
  setEnabled: function(){
    this.disabled = false;
  },  
  getNearestValue: function(value){
    if(this.allowedValues){
      if(value >= this.allowedValues.max()) return(this.allowedValues.max());
      if(value <= this.allowedValues.min()) return(this.allowedValues.min());
      
      var offset = Math.abs(this.allowedValues[0] - value);
      var newValue = this.allowedValues[0];
      this.allowedValues.each( function(v) {
        var currentOffset = Math.abs(v - value);
        if(currentOffset <= offset){
          newValue = v;
          offset = currentOffset;
        } 
      });
      return newValue;
    }
    if(value > this.range.end) return this.range.end;
    if(value < this.range.start) return this.range.start;
    return value;
  },
  setValue: function(sliderValue, handleIdx){
    if(!this.active) {
      this.activeHandle    = this.handles[handleIdx];
      this.activeHandleIdx = handleIdx;
      this.updateStyles();
    }
    handleIdx = handleIdx || this.activeHandleIdx || 0;
    if(this.initialized && this.restricted) {
      if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
        sliderValue = this.values[handleIdx-1];
      if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
        sliderValue = this.values[handleIdx+1];
    }
    sliderValue = this.getNearestValue(sliderValue);
    this.values[handleIdx] = sliderValue;
    this.value = this.values[0]; // assure backwards compat
    
    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
      this.translateToPx(sliderValue);
    
    this.drawSpans();
    if(!this.dragging || !this.event) this.updateFinished();
  },
  setValueBy: function(delta, handleIdx) {
    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
      handleIdx || this.activeHandleIdx || 0);
  },
  translateToPx: function(value) {
    return Math.round(
      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
      (value - this.range.start)) + "px";
  },
  translateToValue: function(offset) {
    return ((offset/(this.trackLength-this.handleLength) * 
      (this.range.end-this.range.start)) + this.range.start);
  },
  getRange: function(range) {
    var v = this.values.sortBy(Prototype.K); 
    range = range || 0;
    return $R(v[range],v[range+1]);
  },
  minimumOffset: function(){
    return(this.isVertical() ? this.alignY : this.alignX);
  },
  maximumOffset: function(){
    return(this.isVertical() ?
      this.track.offsetHeight - this.alignY : this.track.offsetWidth - this.alignX);
  },  
  isVertical:  function(){
    return (this.axis == 'vertical');
  },
  drawSpans: function() {
    var slider = this;
    if(this.spans)
      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
    if(this.options.startSpan)
      this.setSpan(this.options.startSpan,
        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
    if(this.options.endSpan)
      this.setSpan(this.options.endSpan, 
        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
  },
  setSpan: function(span, range) {
    if(this.isVertical()) {
      span.style.top = this.translateToPx(range.start);
      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
    } else {
      span.style.left = this.translateToPx(range.start);
      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
    }
  },
  updateStyles: function() {
    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
    Element.addClassName(this.activeHandle, 'selected');
  },
  startDrag: function(event) {
    if(Event.isLeftClick(event)) {
      if(!this.disabled){
        this.active = true;
        
        var handle = Event.element(event);
        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
        if(handle==this.track) {
          var offsets  = Position.cumulativeOffset(this.track); 
          this.event = event;
          this.setValue(this.translateToValue( 
           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
          ));
          var offsets  = Position.cumulativeOffset(this.activeHandle);
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        } else {
          // find the handle (prevents issues with Safari)
          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
            handle = handle.parentNode;
        
          this.activeHandle    = handle;
          this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
          this.updateStyles();
        
          var offsets  = Position.cumulativeOffset(this.activeHandle);
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        }
      }
      Event.stop(event);
    }
  },
  update: function(event) {
   if(this.active) {
      if(!this.dragging) this.dragging = true;
      this.draw(event);
      // fix AppleWebKit rendering
      if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
      Event.stop(event);
   }
  },
  draw: function(event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = Position.cumulativeOffset(this.track);
    pointer[0] -= this.offsetX + offsets[0];
    pointer[1] -= this.offsetY + offsets[1];
    this.event = event;
    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
    if(this.initialized && this.options.onSlide)
      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
  },
  endDrag: function(event) {
    if(this.active && this.dragging) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },  
  finishDrag: function(event, success) {
    this.active = false;
    this.dragging = false;
    this.updateFinished();
  },
  updateFinished: function() {
    if(this.initialized && this.options.onChange) 
      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
    this.event = null;
  }
}
;

// builder.js
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// See scriptaculous.js for full license.

var Builder = {
  NODEMAP: {
    AREA: 'map',
    CAPTION: 'table',
    COL: 'table',
    COLGROUP: 'table',
    LEGEND: 'fieldset',
    OPTGROUP: 'select',
    OPTION: 'select',
    PARAM: 'object',
    TBODY: 'table',
    TD: 'table',
    TFOOT: 'table',
    TH: 'table',
    THEAD: 'table',
    TR: 'table'
  },
  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
  //       due to a Firefox bug
  node: function(elementName) {
    elementName = elementName.toUpperCase();
    
    // try innerHTML approach
    var parentTag = this.NODEMAP[elementName] || 'div';
    var parentElement = document.createElement(parentTag);
    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
    } catch(e) {}
    var element = parentElement.firstChild || null;
      
    // see if browser added wrapping tags
    if(element && (element.tagName != elementName))
      element = element.getElementsByTagName(elementName)[0];
    
    // fallback to createElement approach
    if(!element) element = document.createElement(elementName);
    
    // abort if nothing could be created
    if(!element) return;

    // attributes (or text)
    if(arguments[1])
      if(this._isStringOrNumber(arguments[1]) ||
        (arguments[1] instanceof Array)) {
          this._children(element, arguments[1]);
        } else {
          var attrs = this._attributes(arguments[1]);
          if(attrs.length) {
            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
              parentElement.innerHTML = "<" +elementName + " " +
                attrs + "></" + elementName + ">";
            } catch(e) {}
            element = parentElement.firstChild || null;
            // workaround firefox 1.0.X bug
            if(!element) {
              element = document.createElement(elementName);
              for(attr in arguments[1]) 
                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
            }
            if(element.tagName != elementName)
              element = parentElement.getElementsByTagName(elementName)[0];
            }
        } 

    // text, or array of children
    if(arguments[2])
      this._children(element, arguments[2]);

     return element;
  },
  _text: function(text) {
     return document.createTextNode(text);
  },
  _attributes: function(attributes) {
    var attrs = [];
    for(attribute in attributes)
      attrs.push((attribute=='className' ? 'class' : attribute) +
        '="' + attributes[attribute].toString().escapeHTML() + '"');
    return attrs.join(" ");
  },
  _children: function(element, children) {
    if(typeof children=='object') { // array can hold nodes and text
      children.flatten().each( function(e) {
        if(typeof e=='object')
          element.appendChild(e)
        else
          if(Builder._isStringOrNumber(e))
            element.appendChild(Builder._text(e));
      });
    } else
      if(Builder._isStringOrNumber(children)) 
         element.appendChild(Builder._text(children));
  },
  _isStringOrNumber: function(param) {
    return(typeof param=='string' || typeof param=='number');
  }
}
;

// node-compat.js
// IE doesn't define the Node type constants
if (!this.Node) {
  Node = {
    ELEMENT_NODE:            1,
    TEXT_NODE:               3,
    DOCUMENT_NODE:           9,
    COMMENT_NODE:            8,
    DOCUMENT_FRAGMENT_NODE: 11,
    ATTRIBUTE_NODE:          2
  }
};

// colorpicker.js

/* 
   colorPicker for script.aculo.us, version 0.9
   REQUIRES prototype.js, yahoo.color.js and script.aculo.us
   written by Matthias Platzer AT knallgrau.at
   for a detailled documentation go to http://www.knallgrau.at/code/colorpicker 
 */

if(!Control) var Control = {};
Control.colorPickers = [];
Control.ColorPicker = Class.create();
Control.ColorPicker.activeColorPicker;
Control.ColorPicker.CONTROL;
/**
 * ColorPicker Control allows you to open a little inline popUp HSV color chooser.
 * This control is bound to an input field, that holds a hex value.
 */
Control.ColorPicker.prototype = {
  initialize : function(field, options) {
    var colorPicker = this;
    Control.colorPickers.push(colorPicker);
    this.field = $(field);
    this.fieldName = this.field.name || this.field.id;
    this.options = Object.extend({
       IMAGE_BASE : "img/"
    }, options || {});
    this.swatch = $(this.options.swatch) || this.field;
    this.rgb = {};
    this.hsv = {};
    this.isOpen = false;

    // create control (popUp) if not already existing
    // all colorPickers on a page share the same control (popUp)
    if (!Control.ColorPicker.CONTROL) {
      Control.ColorPicker.CONTROL = {};
      if (!$("colorpicker")) {
        var control = Builder.node('div', {id: 'colorpicker'});
        control.innerHTML = 
          '<div id="colorpicker-div">' + (
            // apply png fix for ie 5.5 and 6.0
            (/MSIE ((6)|(5\.5))/gi.test(navigator.userAgent) && /windows/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) ?
              '<img id="colorpicker-bg" src="' + this.options.IMAGE_BASE + 'blank.gif" style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + this.options.IMAGE_BASE + 'pickerbg.png\', sizingMethod=\'scale\')" alt="">' :
              '<img id="colorpicker-bg" src="' + this.options.IMAGE_BASE + 'pickerbg.png" alt="">'
             ) +
          '<div id="colorpicker-bg-overlay" style="z-index: 1002;"></div>' +
          '<div id="colorpicker-selector"><img src="' + this.options.IMAGE_BASE + 'select.gif" width="11" height="11" alt="" /></div></div>' +
          '<div id="colorpicker-hue-container"><img src="' + this.options.IMAGE_BASE + 'hue.png" id="colorpicker-hue-bg-img"><div id="colorpicker-hue-slider"><div id="colorpicker-hue-thumb"><img src="' + this.options.IMAGE_BASE + 'hline.png"></div></div></div>' + 
          '<div id="colorpicker-footer"><table cellpadding="0" border="0"><tr><td><span id="colorpicker-value">#<input type="text" onclick="this.select()" id="colorpicker-value-input" name="colorpicker-value" value=""></input></span></td><td><button id="colorpicker-cancelbutton">Cancel</button></td><td><button id="colorpicker-okbutton">OK</button></td></tr></table></div>'
        document.body.appendChild(control);
      }
      Control.ColorPicker.CONTROL = {
        popUp : $("colorpicker"),
        pickerArea : $('colorpicker-div'),
        selector : $('colorpicker-selector'),
        okButton : $("colorpicker-okbutton"),
        cancelButton: $('colorpicker-cancelbutton'),
        value : $("colorpicker-value"),
        input : $("colorpicker-value-input"),
        picker : new Draggable($('colorpicker-selector'), {
          snap: function(x, y) {
            return [
              Math.min(Math.max(x, 0), Control.ColorPicker.activeColorPicker.control.pickerArea.offsetWidth), 
              Math.min(Math.max(y, 0), Control.ColorPicker.activeColorPicker.control.pickerArea.offsetHeight)
            ];
          },
          zindex: 1009,
          change: function(draggable) {
            var pos = draggable.currentDelta();
            Control.ColorPicker.activeColorPicker.update(pos[0], pos[1]);
          }
        }),
        hueSlider: new Control.Slider('colorpicker-hue-thumb', 'colorpicker-hue-slider', {
          axis: 'vertical',
          onChange: function(v) {
            Control.ColorPicker.activeColorPicker.updateHue(v);
          }
        })
      };
      Element.hide($("colorpicker"));
    }
    this.control = Control.ColorPicker.CONTROL;

    // bind event listener to properties, so we can use them savely with Event[observe|stopObserving]
    this.toggleOnClickListener = this.toggle.bindAsEventListener(this);
    this.updateOnChangeListener = this.updateFromFieldValue.bindAsEventListener(this);
    this.closeOnClickOkListener = this.close.bindAsEventListener(this);
    this.closeOnClickCancelListener = this.cancel.bindAsEventListener(this);
    this.updateOnClickPickerListener = this.updateSelector.bindAsEventListener(this);

    Event.observe(this.swatch, "click", this.toggleOnClickListener);
    Event.observe(this.field, "change", this.updateOnChangeListener);
    Event.observe(this.control.input, "change", this.updateOnChangeListener);

    this.updateSwatch();
  },
  toggle : function(event) {
    this[(this.isOpen) ? "close" : "open"](event);
    Event.stop(event);    
  },
  open : function(event) {
    Control.colorPickers.each(function(colorPicker) {
      colorPicker.close();
    });
    Control.ColorPicker.activeColorPicker = this;
    this.isOpen = true;
    Element.show(this.control.popUp);
    if (this.options.getPopUpPosition) {
       var pos = this.options.getPopUpPosition.bind(this)(event);
    } else {
      var pos = Position.cumulativeOffset(this.swatch || this.field);
      pos[0] = (pos[0] + (this.swatch || this.field).offsetWidth + 10);
    }
    this.control.popUp.style.left = (pos[0]) + "px";
    this.control.popUp.style.top = (pos[1]) + "px";
    this.updateFromFieldValue();
    this.fieldValueOnOpen = this.field.value;
    Event.observe(this.control.okButton, "click", this.closeOnClickOkListener);
    Event.observe(this.control.cancelButton, "click", this.closeOnClickCancelListener)
    Event.observe(this.control.pickerArea, "mousedown", this.updateOnClickPickerListener);
    if (this.options.onOpen) this.options.onOpen.bind(this)(event);
  },
  close : function(event) {
    this.isOpen = false;
    Element.hide(this.control.popUp);
    Event.stopObserving(this.control.okButton, "click", this.closeOnClickOkListener);
    Event.stopObserving(this.control.cancelButton, "click", this.closeOnClickCancelListener);
    Event.stopObserving(this.control.pickerArea, "mousedown", this.updateOnClickPickerListener);
    if (this.options.onClose) this.options.onClose.bind(this)();
    if (Control.ColorPicker.activeColorPicker == this) Control.ColorPicker.activeColorPicker = null;
  },
  cancel: function(event) {
    this.isCancel = true;
    this.close(event);
    this.field.value = this.fieldValueOnOpen;
    this.updateSwatch()
    this.isCancel = false;
  },
  updateHue : function(v) {
    var h = (this.control.pickerArea.offsetHeight - v * 100) / this.control.pickerArea.offsetHeight;
    if (h == 1) h = 0;
    var rgb = YAHOO.util.Color.hsv2rgb( h, 1, 1 );
    if (!YAHOO.util.Color.isValidRGB(rgb)) return;
    this.control.pickerArea.style.backgroundColor = "rgb(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")";
    this.update();
  },
  updateFromFieldValue : function(event) {
    if (!this.isOpen) return;
    var field = (event && Event.findElement(event, "input")) || this.field;
    var rgb = YAHOO.util.Color.hex2rgb( field.value );
    if (!YAHOO.util.Color.isValidRGB(rgb)) return;
    var hsv = YAHOO.util.Color.rgb2hsv( rgb[0], rgb[1], rgb[2] );
    this.control.selector.style.left = Math.round(hsv[1] * this.control.pickerArea.offsetWidth) + "px";
    this.control.selector.style.top = Math.round((1 - hsv[2]) * this.control.pickerArea.offsetWidth) + "px";
    this.control.hueSlider.setValue((1 - hsv[0]));
  },
  updateSelector : function(event) {
    var xPos = Event.pointerX(event);
    var yPos = Event.pointerY(event);
    var pos = Position.cumulativeOffset($("colorpicker-bg"));
    this.control.selector.style.left = (xPos - pos[0] - 6) + "px";
    this.control.selector.style.top = (yPos - pos[1] - 6) + "px";
    this.update((xPos - pos[0]), (yPos - pos[1]));
    this.control.picker.initDrag(event);
  },
  updateSwatch : function() {
    var rgb = YAHOO.util.Color.hex2rgb( this.field.value );
    if (!YAHOO.util.Color.isValidRGB(rgb)) return;
    this.swatch.style.backgroundColor = "rgb(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")";
    var hsv = YAHOO.util.Color.rgb2hsv( rgb[0], rgb[1], rgb[2] );
    this.swatch.style.color = (hsv[2] > 0.65) ? "#000000" : "#FFFFFF";
  },
  update : function(x, y) {
    if (!x) x = this.control.picker.currentDelta()[0];
    if (!y) y = this.control.picker.currentDelta()[1];

    var h = (this.control.pickerArea.offsetHeight - this.control.hueSlider.value * 100) / this.control.pickerArea.offsetHeight;
    if (h == 1) { h = 0; };
    this.hsv = {
      hue: 1 - this.control.hueSlider.value,
      saturation: x / this.control.pickerArea.offsetWidth,
      brightness: (this.control.pickerArea.offsetHeight - y) / this.control.pickerArea.offsetHeight
    };
    var rgb = YAHOO.util.Color.hsv2rgb( this.hsv.hue, this.hsv.saturation, this.hsv.brightness );
    this.rgb = {
      red: rgb[0],
      green: rgb[1],
      blue: rgb[2]
    };
    this.field.value = YAHOO.util.Color.rgb2hex(rgb[0], rgb[1], rgb[2]);
    this.control.input.value = this.field.value;
    this.updateSwatch();
    if (this.options.onUpdate) this.options.onUpdate.bind(this)(this.field.value);
  }
}
;

// yahoo.color.js
/* Copyright (c) 2006 Yahoo! Inc. All rights reserved. */

var YAHOO = function() { return {util: {}} } ();
YAHOO.util.Color = new function() {
  
  // Adapted from http://www.easyrgb.com/math.html
  // hsv values = 0 - 1
  // rgb values 0 - 255
  this.hsv2rgb = function (h, s, v) {
    var r, g, b;
    if ( s == 0 ) {
      r = v * 255;
      g = v * 255;
      b = v * 255;
    } else {

      // h must be < 1
      var var_h = h * 6;
      if ( var_h == 6 ) {
        var_h = 0;
      }

      //Or ... var_i = floor( var_h )
      var var_i = Math.floor( var_h );
      var var_1 = v * ( 1 - s );
      var var_2 = v * ( 1 - s * ( var_h - var_i ) );
      var var_3 = v * ( 1 - s * ( 1 - ( var_h - var_i ) ) );

      if ( var_i == 0 ) { 
        var_r = v; 
        var_g = var_3; 
        var_b = var_1;
      } else if ( var_i == 1 ) { 
        var_r = var_2;
        var_g = v;
        var_b = var_1;
      } else if ( var_i == 2 ) {
        var_r = var_1;
        var_g = v;
        var_b = var_3
      } else if ( var_i == 3 ) {
        var_r = var_1;
        var_g = var_2;
        var_b = v;
      } else if ( var_i == 4 ) {
        var_r = var_3;
        var_g = var_1;
        var_b = v;
      } else { 
        var_r = v;
        var_g = var_1;
        var_b = var_2
      }

      r = var_r * 255          //rgb results = 0 ÷ 255
      g = var_g * 255
      b = var_b * 255

      }
    return [Math.round(r), Math.round(g), Math.round(b)];
  };

  // added by Matthias Platzer AT knallgrau.at 
  this.rgb2hsv = function (r, g, b) {
      var r = ( r / 255 );                   //RGB values = 0 ÷ 255
      var g = ( g / 255 );
      var b = ( b / 255 );

      var min = Math.min( r, g, b );    //Min. value of RGB
      var max = Math.max( r, g, b );    //Max. value of RGB
      deltaMax = max - min;             //Delta RGB value

      var v = max;
      var s, h;
      var deltaRed, deltaGreen, deltaBlue;

      if ( deltaMax == 0 )                     //This is a gray, no chroma...
      {
         h = 0;                               //HSV results = 0 ÷ 1
         s = 0;
      }
      else                                    //Chromatic data...
      {
         s = deltaMax / max;

         deltaRed = ( ( ( max - r ) / 6 ) + ( deltaMax / 2 ) ) / deltaMax;
         deltaGreen = ( ( ( max - g ) / 6 ) + ( deltaMax / 2 ) ) / deltaMax;
         deltaBlue = ( ( ( max - b ) / 6 ) + ( deltaMax / 2 ) ) / deltaMax;

         if      ( r == max ) h = deltaBlue - deltaGreen;
         else if ( g == max ) h = ( 1 / 3 ) + deltaRed - deltaBlue;
         else if ( b == max ) h = ( 2 / 3 ) + deltaGreen - deltaRed;

         if ( h < 0 ) h += 1;
         if ( h > 1 ) h -= 1;
      }

      return [h, s, v];
  }

  this.rgb2hex = function (r,g,b) {
    return this.toHex(r) + this.toHex(g) + this.toHex(b);
  };

  this.hexchars = "0123456789ABCDEF";

  this.toHex = function(n) {
    n = n || 0;
    n = parseInt(n, 10);
    if (isNaN(n)) n = 0;
    n = Math.round(Math.min(Math.max(0, n), 255));

    return this.hexchars.charAt((n - n % 16) / 16) + this.hexchars.charAt(n % 16);
  };

  this.toDec = function(hexchar) {
    return this.hexchars.indexOf(hexchar.toUpperCase());
  };

  this.hex2rgb = function(str) { 
    var rgb = [];
    rgb[0] = (this.toDec(str.substr(0, 1)) * 16) + 
            this.toDec(str.substr(1, 1));
    rgb[1] = (this.toDec(str.substr(2, 1)) * 16) + 
            this.toDec(str.substr(3, 1));
    rgb[2] = (this.toDec(str.substr(4, 1)) * 16) + 
            this.toDec(str.substr(5, 1));
    // gLogger.debug("hex2rgb: " + str + ", " + rgb.toString());
    return rgb;
  };

  this.isValidRGB = function(a) { 
    if ((!a[0] && a[0] !=0) || isNaN(a[0]) || a[0] < 0 || a[0] > 255) return false;
    if ((!a[1] && a[1] !=0) || isNaN(a[1]) || a[1] < 0 || a[1] > 255) return false;
    if ((!a[2] && a[2] !=0) || isNaN(a[2]) || a[2] < 0 || a[2] > 255) return false;

    return true;
  };
}

;

// knallgrau.scriptaculous.js

// Various extensions for prototype.js and scriptaculous
// by knallgrau.at

// note: there are still some fixes left in prototype.js concerning element-border-width

Element.BLOCK_LEVEL = ["address","blockquote","center","dl","dir","div","fieldset",
	             "form","h1-6","hr","isindex","menu","noframes","noscript",
	             "ol","p","pre","table","ul","center","dir","menu","noframes","isindex"];

Element.isBlockLevel = function(element) {
  return element.isBlockLevel = (
    Element.getStyle(element, "display") == "block" || 
    element.isBlockLevel == true || 
    Element.BLOCK_LEVEL.indexOf(element.nodeName.toLowerCase()) != -1
  ); 
}

Element.show = function() {
  for (var i = 0; i < arguments.length; i++) {
    var element = $(arguments[i]);
//knallgrau: use Element.isBlockLevel
    element.style.display = Element.isBlockLevel(element) ? 'block' : '';
  }
}

Element.getDimensions = function(element) {
  element = $(element);
  if (Element.getStyle(element, 'display') != 'none')
    return {width: element.offsetWidth, height: element.offsetHeight};

  // All *Width and *Height properties give 0 on elements with display none,
  // so enable the element temporarily
  var els = element.style;
  var originalVisibility = els.visibility;
  var originalPosition = els.position;
  els.visibility = 'hidden';
  els.position = 'absolute';
//knallgrau: use Element.isBlockLevel
  els.display = Element.isBlockLevel(element) ? 'block' : '';
  var originalWidth = element.clientWidth;
  var originalHeight = element.clientHeight;
  els.display = 'none';
  els.position = originalPosition;
  els.visibility = originalVisibility;
  return {width: originalWidth, height: originalHeight};
}

Position.getVisibleWidth = function() {
  return (window.opera) ? 
    document.body.clientWidth || document.documentElement.clientWidth || window.innerWidth
    : document.documentElement.clientWidth || window.innerWidth || document.body.clientWidth;
}

Position.getVisibleHeight = function() {
  return (window.opera) ? 
     document.body.clientHeight || document.documentElement.clientHeight || window.innerHeight
     : document.documentElement.clientHeight || window.innerHeight || document.body.clientHeight;
}

Ajax.InPlaceEditor.prototype.convertHTMLLineBreaks = function(string) {
   string = string.replace(/<br>\n/gi, "\n");
   string = string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
   return string;
}

// Cookie Functions
function ClientCookie() {
	if (document.cookie.length) { this.cookies = ' ' + document.cookie; }
}

ClientCookie.prototype = {
   setValue: function (key, value) {
      document.cookie = key + "=" + escape(value);
   },

   getValue: function (key) {
      if (this.cookies) {
         var start = this.cookies.indexOf(' ' + key + '=');
         if (start == -1) { return null; }
         var end = this.cookies.indexOf(";", start);
         if (end == -1) { end = this.cookies.length; }
         end -= start;
         var cookie = this.cookies.substr(start,end);
         return unescape(cookie.substr(cookie.indexOf('=') + 1, cookie.length - cookie.indexOf('=') + 1));
      }
      else { return null; }
   }
}
var Cookie = new ClientCookie();


/*
 * code taken from http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
 * InPlaceEditor extension that adds a 'click to edit' text when the field is 
 * empty.
 */
Ajax.InPlaceEditor.prototype.__initialize = Ajax.InPlaceEditor.prototype.initialize;
Ajax.InPlaceEditor.prototype.__getText = Ajax.InPlaceEditor.prototype.getText;
Ajax.InPlaceEditor.prototype.__onComplete = Ajax.InPlaceEditor.prototype.onComplete;
Ajax.InPlaceEditor.prototype = Object.extend(Ajax.InPlaceEditor.prototype, {

    initialize: function(element, url, options){
        this.__initialize(element,url,options)
        this.setOptions(options);
        this._checkEmpty();
    },

    setOptions: function(options){
        this.options = Object.extend(Object.extend(this.options,{
            emptyText: 'click to edit...',
            emptyClassName: 'inplaceeditor-empty'
        }),options||{});
    },

    _checkEmpty: function(){
        if( this.element.innerHTML.length == 0 ){
            this.element.appendChild(
                Builder.node('span',{className:this.options.emptyClassName},this.options.emptyText));
        }
    },

    getText: function(){
        $A(document.getElementsByClassName(this.options.emptyClassName,this.element)).each(function(child){
            this.element.removeChild(child);
        }.bind(this));
        return this.__getText();
    },

    onComplete: function(transport){
        this._checkEmpty();
        this.__onComplete(transport);
    }
});
;

// memoize.js

Function.prototype.memoize = function(options) {
  var fn = this
  var cache = {}
  return function() {
    var dependencyFlags = options.dependencies.apply(this, arguments)
    var cachedValue = cache[dependencyFlags]
    if (cachedValue) {
      return cachedValue
    }
    return cache[dependencyFlags] = fn.apply(this, arguments)
  }
}

Function.prototype.localMemoize = function(options) {
  var fn = this
  return function() {
    var dependencyFlags = options.dependencies.apply(this, arguments)
    var cachedValue = this.cache[dependencyFlags]
    if (cachedValue) {
      return cachedValue
    }
    return this.cache[dependencyFlags] = fn.apply(this, arguments)
  }
}

;

// utilities.js
var __debug = true
var tmeasures = {}
var __afterShow = [];

function processAfterShow() {
  var o = Object.extend({}, arguments[0] || {}) 
  __afterShow.call(o)
  __afterShow = []
}

function afterShow(call) {
  __afterShow.push(
    function() { 
      var o = Object.extend({}, arguments[0] || {}) 
      call(o);
    }
  )
}

var javaScript = this
var __changes = 0;

function changeCounter() {
  return __changes++;
}

function tstart(id) {
  if (!__debug) {
    return
  }
  var now = new Date()
  tmeasures[id] = now
}

function measureTime(call, times) {
  if (!times) {
    times = 10
  }
  tstart("call");
  (times).times(function() { call() })
  tstop("call")
}

tstart("initialization");
tstart("parsing");

function tstop(id) {
  if (!__debug) {
    return
  }
  var now = new Date()
  d("[" + id + "] " + (now - tmeasures[id]) + "ms")
}


var axInitList = []

function getPageSize(){
	
	var xScroll, yScroll, pageWidth,pageHeight,windowWidth,windowHeight
	
	if (window.innerHeight && window.scrollMaxY) {	
		xScroll = document.body.scrollWidth;
		yScroll = window.innerHeight + window.scrollMaxY;
	} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
		xScroll = document.body.scrollWidth;
		yScroll = document.body.scrollHeight;
	} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
		xScroll = document.body.offsetWidth;
		yScroll = document.body.offsetHeight;
	}
	
	var windowWidth, windowHeight;
	if (self.innerHeight) {	// all except Explorer
		windowWidth = self.innerWidth;
		windowHeight = self.innerHeight;
	} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
		windowWidth = document.documentElement.clientWidth;
		windowHeight = document.documentElement.clientHeight;
	} else if (document.body) { // other Explorers
		windowWidth = document.body.clientWidth;
		windowHeight = document.body.clientHeight;
	}	
	
	// for small pages with total height less then height of the viewport
	if(yScroll < windowHeight){
		pageHeight = windowHeight;
	} else { 
		pageHeight = yScroll;
	}

	// for small pages with total width less then width of the viewport
	if(xScroll < windowWidth){	
		pageWidth = windowWidth;
	} else {
		pageWidth = xScroll;
	}

	arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight) 
	return arrayPageSize;
}
function getPageScroll(){

	var yScroll;

	if (self.pageYOffset) {
		yScroll = self.pageYOffset;
	} else if (document.documentElement && document.documentElement.scrollTop){	 // Explorer 6 Strict
		yScroll = document.documentElement.scrollTop;
	} else if (document.body) {// all other Explorers
		yScroll = document.body.scrollTop;
	}

	var arrayPageScroll = new Array('',yScroll) 
	return arrayPageScroll;
}

function pageWidth(){ return getPageSize()[0] }
function pageHeight(){ return getPageSize()[1] }
function windowWidth() { return getPageSize()[2] }
function windowHeight() { return getPageSize()[3] }
function yScroll() { return getPageScroll()[1] }

function deepCopyArray(src) {
  var dest = []
  $A(src).each(function(v) { dest.push(v) })
  return dest
}

function checkNull(v) { return (v ? v : "") }
function mergeHashArray(hashArray) {
  var r = {}
  hashArray.each(function (hash) { 
    var h = $H(hash) 
    h.keys().each(function(key) {
      r[key] = h[key]
    })
  })
  return r
}

function appendChildren(node, childrenList) {
  childrenList.each(function(c) {node.appendChild(c)})
}

function makeGramaticallyCorrectTimeString(c, f, m, r) {
  v = Math.round(c)
  return v + " " + f + (v > 1 ? m : "") + " " + r
}

var seconds = 1000;
var minutes = seconds * 60;
var hours =  minutes * 60;
var days =  hours * 24;
var years = days * 365.2425;

function makeNiceDate(ms) {
  var now = new Date()
  var d = now - ms;
  var dateTn;
  if (d < 0) {
    dateTn = translateText({path: ".date", id: "just-now"})
  }
  else {
    $A(["year", "day", "hour", "minute", "second"]).each(function(time) {
      var dateMs = javaScript[time+"s"]
      if (d >= dateMs) {
        var num = Math.round(d / dateMs)
        dateTn = translate({
          path: "",
          id: "date",
          args: [
            num,
            Builder.node('span', [
              translateText({path: ".date", id: (time + (num > 1 ? "s" : ""))})
            ]).innerHTML
          ],
          node: Builder.node('span'),
          setText: function(n, t) {
            n.innerHTML = t
          }
        })
        throw $break
      }
    })
    if (!dateTn) dateTn = translateText({path: ".date", id: "just-now"})
  }
  return dateTn
}

function frontPadding(s, c, p) {
  if (s.length >= c) {
    return s
  }
  return p.substr(s.length) + s
}

function leadingZeros(s, c) {
  return frontPadding(String(s), c, "00000000000000000".substr(1, c))
}

function makeFormattedSmallTime(ms) {
  var ms = Math.round(ms)
  var m = Math.floor(ms / minutes)
  var ms = ms - (m * minutes)
  var s = Math.floor(ms / seconds)
  return leadingZeros(m, 2) + ":" + leadingZeros(s, 2)
}

String.prototype.startsWith = function(p) {
  return this.substr(0, p.length) == p
}

String.prototype.pluralize = function() {
  if (this.endsWith("s")) { return this.substr(0) }
  return this.endsWith("y") ? this.substr(0, this.length - 1) + "ies" : 
      this + "s"
}

String.prototype.capitalize = function() {
  if (this.length < 1) {
    return ""
  } else {
    return this.substr(0,1).toUpperCase() + this.substr(1)
  }
}

String.prototype.singularize = function() {
  if (!this.endsWith("s")) { return this.substr(0) }
  return this.endsWith("ies") ? this.substr(0, this.length - 3) + "y": 
      this.substr(0, this.length - 1) 
}

String.prototype.repeat = function(i) { 
  var r = ""
  var instance = this;
  (i).times(function() { r += instance })
  return r
}

function startsWith(s, p) {
  var ss = String(s)
  return ss.substr(0, p.length) == p
}

String.prototype.endsWith = function(s) {
  return this.substr(this.length-s.length) == s
}

function startIdle(transparent) {
  endIdle()
  // var s = $('idle-spinner')
  // if (s)
  // {
  //   if (transparent) {
  //     s.style.backgroundColor = "transparent"
  //   }
  //   s.style.marginTop = yScroll() + "px"
  //   s.style.display = "";
  // }
}

function endIdle() {
  var s = $('idle-spinner')
  if (s)
  {
    s.style.backgroundColor = null
    s.style.display = "none"
  }
}

function normalizePath(p) {
  return p.replace(/\/+/g, "/")
}

function hashMerge(hash1, hash2) {
  var r = {}
  $H(hash1).each(function(p) {
    r[p.key] = p.value
  })
  $H(hash2).each(function(p) {
    r[p.key] = p.value
  })
  return r;
}

function hashMergeNonNull(hash1, hash2) {
  var r = {}
  $H(hash1).each(function(p) {
    if (p.value != undefined) {
      r[p.key] = p.value
    }
  })
  $H(hash2).each(function(p) {
    if (p.value != undefined) {
      r[p.key] = p.value
    }
  })
  return r;
}

function hash_merge(hash1, hash2) {
  h = hash1
  h.changed = changeCounter()
  return $H(hash2).inject(h, function(mergedHash, pair) {
    mergedHash[pair.key] = pair.value
    return mergedHash;
  });
}

function randomNumber() {
  return Math.random()
}

var __autoResize = []

function resizeHandler() {
  __autoResize.each(function(i) { i[1]() })
}

function autoSize(id, call) {
  __autoResize = __autoResize.select(function(i) { return i[0] != id })
  __autoResize.push([id, call])
  call()
}

function displayString(s) {
  return s == undefined ? "null" : s;
}

function itemPermalink(o) {
  var pname = o.pretty_name
  if (o.item_path) {
    var fullPath = o.item_path
  }
  else {
    var c = o.current_path || itemModel.currentPath()
    var fullPath = 
        pname ? c + (c.charAt(c.length - 1) == "/" ? "" : "/") + pname : c      
  }
  var sl = serviceLink({service: o.service, user: o.user})
  if (fullPath == "/") {
    return sl + fullPath
  }
  return encodeURI(sl + fullPath.gsub(/^\//, "/-"));
}

function serviceAsDomain(s) {
  s = s.gsub(/@core/, "")
  if (s == "main") { s = "sites" }
  if (s == "sites") { return "mysites.com" }
  return "my" + s.split(/es$/)[0] + ".es"
}

function isGeneric(sn) { return true }

function serviceLinkServer() {
  var o = Object.extend({
    service: itemModel.controllerName(),
    controllerName: null,
    ipMode: __no_dns
  }, arguments[0] || {}) 
  r = "http://"
  if (o.ipMode) {
    r += itemModel.serverName()
  } else {
    var ncsl = o.noCrossSiteLink 
    r += ncsl ? itemModel.serverName() : urlUserName(o.user) + "." + 
        serviceAsDomain(o.service);
  }
  return r
}

function fqServiceName(s) {
  if (s.indexOf("@") == -1) { s += "@core" }
  return s
}

function urlUserName(un) {
  if (un == "guest") { return "www" }
  return un
}

sl = {}

function serviceLink() {
  var o = Object.extend({
    service: itemModel.controllerName(),
    user: itemModel.user(),
    controllerName: null,
    action: null,
    noCrossSiteLink: false,
    ipMode:  __no_dns
  }, arguments[0] || {})
  r = serviceLinkServer(o)
  if (o.ipMode) {
    r += "/u=" + urlUserName(o.user) + "/" + fqServiceName(o.service)
  } else {
    var ncsl = o.noCrossSiteLink 
    if (ncsl) {
      r += "/" + fqServiceName(o.service)
    }
  }
  r += (o.action ? "/" + o.action : "")
  return r
}


function serviceLinkImages(name) {
  return serviceLinkServer() + "/images/" + name
}

function relativeLink(l) {
  return serviceLink() + l
}

var __e = null

function handleOnClickException(e) {
  __e = e
  var m =  "onlick Exception: " + e + (e.stack ? "\nstack: " + e.stack : "")
  displayError("Something went wrong, please try again later", m)
}

AfterAppearHandling = {
  __afterAppear: [],
  afterAppear: function(call) {
    this.__afterAppear.push(
      function() { 
        var o = Object.extend({}, arguments[0] || {}) 
        call(o);
      }
    )
  },
  afterAppearCallbacks: function(options) {
    this.__afterAppear.call(options);
  }
}

function methods(o) {
  var ms = []
  for (m in o) {
    if (typeof o[m] == "function")  {
      ms.push(m)
    }
  }
  return ms
}

var __r = null

function handleJsonResponse(response) {
  __r = response
  var o = Object.extend({
    onSuccess: function() { },
    onError:   function(error) { displayError(error.message, error.details) }
  }, arguments[1] || {}) 
  endIdle()
  try {
    if (/^success=/.test(response)) {
      var success;
      eval(response);
      o.onSuccess(success)
    } else {
      var error;
      eval(response);
      o.onError(error)
    }
  } catch (e) {
    var ed = e
    if (e.number && e.description) {
      ed = "e.number = " + e.number + ", e.description = '" + e.description + "'"
    }
    var m =  "JSON processing exception: " + ed + "\nstack: " + formatStackTrace(e)
    displayError("JSON processing failed", m)
  }
}


function handle(e) {
  __e = e
}

function initialize() {
  axInitList.each(function(i) {
    catchExceptionsForInitialize(i)()
  })
  endIdle()
}

function catchExceptionsForInitialize(handler) {
  return function() {
    try {
      handler()
    } catch (e) {
      endIdle()
      var m =  "initialize Exception: " + e.message + "\nstack:" + formatStackTrace(e)
      displayError("Initialization of component failed", m)
      throw e;
    }
    return false
  }
}

function localizeURL(url) {
  if (__no_dns) return url;
  var host = window.location.hostname
  if (window.location.hostname.match(/^([^.]+)\.([^.]+)\.([^.]+)$/)) {
    host = window.location.hostname.replace(/^([^.]*\.)/, "")          
  }
  // FIXME: Doesn't work with custom services!
  return url.gsub(/([^.]+)\.my([^.]+)\.es\/([^\s]+)/, function(m) {
    var user    = m[1]
    var service = m[2]
    var path    = m[3]
    // Don't "fix" URLs that are already fixed
    var localService = (path.match(/^(.*)es@core/) ? "" : 
        (service + "es@core/"))
    return user + "." + host + "/" + localService + path 
  })
}
 
__parameters = null
function parameters() {
  if (__parameters) { return __parameters }
  params = window.location.href.split("?")[1]
  __parameters = {}
  if (params) { 
    __parameters = params.split("&").inject({}, function(h, vv) { 
      kv = vv.split("=")
      h[kv[0]] = kv[1]
      return h 
    })
  }
  return __parameters
}

function loadService(o) {
  var options = Object.extend({
    user: "core",
    service: null,
    afterLoad: function() {}
  }, o || {})
  var dm = new DisplayModel({
    user:options.user,
    service: "services",
    path: "/" + options.service
  })
  dm.observeOnce({update: function() {
    var id    = "service-" + options.user + "-" + options.service
    var cssId = id + "-css"
    var jsId  = id + "-js"
    var css   = "";
    var js    = "";
    dm.getModel().each(function(i) {
      if (i.name.match(/\.css$/)) {
        css += i.content + "\n"
      }
      else if (i.name.match(/\.js/)) {
        js  += i.content + "\n"
      }
    })
    // JS
    if ($(jsId)) $(jsId).remove()
    e = Builder.node('script', {id: jsId, type: 'text/javascript'})
    document.body.appendChild(e)
    if (Prototype.Browser.IE) {
      e.text = js;
    }
    else {
      e.innerHTML = js;      
    }
    // CSS
    if ($(cssId)) $(cssId).remove()
    e = Builder.node('style', {id: cssId, type: 'text/css'})
    $(document.documentElement).down('head').appendChild(e)
    if (Prototype.Browser.IE) {
      // URLs like core.mythem.es/get_file?id=XXX don't work in IE so
      // we have to replace them by URLs on the same server.
      css = localizeURL(css, "g")
      var ss = $A(document.styleSheets).find(function(s) { return s.id == cssId })
      ss.cssText = css
    }
    else {
      e.innerHTML = css;        
    }
    options.afterLoad()
  }})
}

function serverLog(m) {
  AjaxRequestMixin.ajaxRequest({action: "client_log", 
      onSuccess: function() {} }, {m: m})
}

function assertAndLog(description, condition) {
  if (condition) { return }
  serverLog("Assert failed: #{description}")
}

var Utilities = {}
// Based on http://pastie.org/253058
var getStackTrace = (function() {
  var parse = {
    Gecko: function(e) {
      return e.stack.replace(/^.*?\n/,'').
        replace(/(?:\n@:0)?\s+$/m,'').
        replace(/^\(/gm,'{anonymous}(').
        split("\n");          
    },
    Opera: function(e) {
      var lines = e.message.split("\n")
      var ANON = '{anonymous}'
      var lineRE = /Line\s+(\d+).*?in\s+(http\S+)(?:.*?in\s+function\s+(\S+))?/i
      var i,j,len;
      for (i=4,j=0,len=lines.length; i<len; i+=2) {
        if (lineRE.test(lines[i])) {
          lines[j++] = (RegExp.$3 ?
            RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 :
            ANON + RegExp.$2 + ':' + RegExp.$1) +
            ' -- ' + lines[i+1].replace(/^\s+/,'');
        }
      }
      lines.splice(j,lines.length-j);
      return lines;          
    },
    Other: function() {
      var curr  = arguments.callee.caller
      var FUNC  = 'function'
      var ANON = "{anonymous}"
      var fnRE  = /function\s*([\w\-$]+)?\s*\(/i
      var stack = []
      var j=0
      var fn,args,i;
      while (curr) {
        fn    = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
        args  = stack.slice.call(curr.arguments);
        i     = args.length;
        while (i--) {
          switch (typeof args[i]) {
            case 'string'  : args[i] = '"'+args[i].replace(/"/g,'\\"')+'"'; break;
            case 'function': args[i] = FUNC; break;
          }
        }
        stack[j++] = fn + '(' + args.join() + ')';
        curr = curr.caller;
      }
      return stack;
    }
  }
  var mode = Prototype.Browser.asString
  if (!$A(["Gecko", "Opera"]).include(mode)) mode = "Other"
  if (mode == "Other") {
    return parse[mode]
  }
  else {
    return function(e) {
      if (e) {
        return parse[mode](e)
      }
      else {
        try {(0)()}
        catch (e) { return parse[mode](e) }
      }
    }
  }
})();

function formatStackTrace(e) {
  return getStackTrace(e).join("\n")
}
;

// icons.js
var iconsColor = "turquoise"
function iconPath(dim, name) {
  return iconPathForColor(iconsColor, dim, name)
}

function iconPathForColor(color, dim, name) {
  return "/images/icons/" + color  + "/" + dim + "x" + dim + "/" + name + 
      ".gif"
}
;

// nullobject.js
NullObject = Class.create();
NullObject.prototype = {
  initialize: function(clazz) {
    for (var property in clazz.prototype) {
      this[property] = function() { return false; }
    }
  }
}
;

// mstemplate.js
MSTemplate = Class.create();
MSTemplate.prototype = {
  initialize: function() {
    this.templateFields = ["nodeValue", "href", "src", "value", "id", 
        "onclick", "onmouseover", "onmouseout", "alt", "title"]
    this.options = Object.extend({
      create: function() {
      }
    }, arguments[0] || {})
  },
  makeNodeAndReferences: function(options) {
    if (!this.templateNode) {
      // IE seems to merge adjacent text nodes on cloneNode. So if we don't do a
      // cloneNode here, the DOM will look differently when we use the processor
      // than when the processor and reference functions were created! BUT, somehow
      // this messes up stuff in Firefox!
      this.templateNode = this.options.create()
      if (Prototype.Browser.IE) {
        this.templateNode = this.templateNode.cloneNode(true)
      }
      this.processor = this.compilePlaceholders(this.templateNode).processor
    }
    var n = this.templateNode.cloneNode(true)
    var references = this.processor(n, options)
    return {node: n, references: references};
  },
  makeAccessors: function() {
    this.templateNode = this.options.create()
    // See makeNodeAndReferences for explanation!
    if (Prototype.Browser.IE) {
      this.templateNode = this.templateNode.cloneNode(true)
    }
    var c = this.compilePlaceholders(this.templateNode)
    var instance = this
    return {
      processor: function(o) {
        c.processor(instance.templateNode, o)
      },
      references: c.getReferences(this.templateNode)
    }
  },
  makeNode: function(options) {
    return this.makeNodeAndReferences(options).node;
  },
  compilePlaceholders: function(e) {
    var instance = this;
    var processFunction = ""
    var references = "{\n"
    var rCount = 0;
    function noBugsHit(c, name) {
      // IE saves the value of the src attrubute into the href attribute, too. But
      // href is a read-only attribute so assigning a value results in an error!
      if (c.tagName == "IMG" && name == "href") {
        return false;
      }
      return true;
    }
    function processObject(path, e) {
      var i = 0;
      function checkProperty(p, name) {
        var basePath = path + ".childNodes[" + i + "]"
        if (startsWith(p, "template:")) {
          if (startsWith(p, "template:reference_as ")) {
           var k = p.substr("template:reference_as ".length)
           if (rCount > 0) {
             references += ",\n"
           }
           rCount += 1
           references += "    " + k + ": " + basePath
           processFunction += "  " + basePath + "." + name  + " = null;\n"
          } else {
            var k = "";
            var fieldName = "";
            if (startsWith(p, "template:value_of ")) {
              k = p.substr("template:value_of ".length)
              fieldName = name
            } else if (startsWith(p, "template:assign ")) {
              var e = p.substr("template:assign ".length)
              var ci = e.indexOf(",")
              k = e.substr(ci + 1)
              fieldName = e.substr(0, ci)
            } 
            processFunction += "  " + path + ".childNodes[" + i  + "]." + 
                fieldName + " = displayString(o." + k + ");\n";
          }
        }
      }
      for (i = 0; i < e.childNodes.length; i++) {
        var c = e.childNodes[i]
        if (c.childNodes != undefined) {
          processObject(path + ".childNodes[" + i + "]", c)
        } 
        instance.templateFields.each(function(name) {
          if (c[name] != undefined && noBugsHit(c, name)) {
            checkProperty(unescape(c[name]), name);
          }
        })
        //c += 1;
      }
      return 0;
    }
    processObject("n", e)
    references += "\n  }\n"
    processFunction += "  return " + references
    this.pf = new Function("n", "o", processFunction)
    this.rf = new Function("n", "return " + references)
    return {
      processor: this.pf, 
      getReferences: this.rf
    };
  }
}
;

// easyhtml.js
function makeButton() {
  var options = Object.extend({
    name: '',
    onclick: function() {}
  }, arguments[0])
  var i = Builder.node('input', {
    type: 'submit',
    value: options.name
  })
  i.onclick = options.onclick
  return i;
}

function TN(s) { return document.createTextNode(s) }
function EMPTY() { return TN("") }
function BR() { return Builder.node('br') }
function NBSP() { return document.createTextNode('\u00a0') }

IFrameAccessor = Class.create();
IFrameAccessor.prototype = {
  initialize: function(iframeElement) {
    this.iframe = iframeElement
  },
  content: function() {
    var cd = this.iframe.contentDocument

    if (!cd) {
      // IE6.0
      if (this.iframe.contentWindow) {
        cd = this.iframe.contentWindow.document
      }
    }
    return cd
  }
}

function highlightItem(item) {
  var options = Object.extend({
    onclick: function() {}
  }, arguments[1] || {})
  var oldcolor = item.style.backgroundColor
  item.onmouseover = function() {
    item.style.backgroundColor = '#ffff99'
  }
  item.onmouseout = function() {
    item.style.backgroundColor = oldcolor
  }
  item.onclick = function() {
    item.style.backgroundColor = oldcolor
    options.onclick()
  }
}

function writeImageActionLink() {
  if (!itemModel.canWrite()) {
    return TN("")
  } 
  return imageActionLink(arguments[0] || {})
}

function catchExceptionsForOnClickHandler(handler) {
  return function() {
    try {
      handler()
    } catch (e) {
      handleOnClickException(e)
    }
    return false
  }
}

function catchAndDisplayExceptions(handler) {
  return function() {
    try {
      handler()
    } catch (e) {
      var m =  "Exception: " + e + "\nstack: " + formatStackTrace(e)
      displayError("Something went wrong...", m)
    }
    return false
  }
}


function handleOnClickHandler(handler) {
  if (typeof handler != 'function') {
    return handler
  }
  return catchExceptionsForOnClickHandler(handler)
}


function actionLink() {
  var o = arguments[0] || {}
  var options = Object.extend({
    href: '#',
    title: '',
    onclick: o.href != undefined ? null : function() { return false; },
    className: '',
    text: null,
    target: ""
  }, o)
  var a = Builder.node('a', {
    href: options.href,
    className: options.className,
    target: options.target
  })
  a.onclick = handleOnClickHandler(options.onclick)
  if (options.text) {
    a.appendChild(TN(options.text))
  }
  return a;
}

function imageActionLink() {
  var o = arguments[0] || {}
  var options = Object.extend({
    href: '#',
    imageName: '',
    fullImageName: null,
    title: '',
    onclick: o.href != undefined ? null : function() { return false; },
    className: '',
    text: null,
    target: ""
  }, o)
  var imagePath = (options.fullImageName ? options.fullImageName : 
      "/images/" + options.imageName)
  var a = Builder.node('a', {
      href: options.href,
      className: options.className,
      target: options.target
    }, [
    Builder.node('img', { 
      border: "0", 
      align: "absmiddle",
      src: imagePath,
      title: options.title,
      alt: options.title
    })
  ])
  a.onclick = handleOnClickHandler(options.onclick)
  if (options.text) {
    appendChildren(a, [TN(" "), translateText({revert: options.text})])
  }
  return a;
}

function htmlNode(w) {
  return w && w.htmlNode && typeof w.htmlNode == 'function' ? w.htmlNode() : w;
}

function table() {
  var i = 0;
  var defaultOptions = {};
  if (arguments[0].flatten == undefined) {
    i = 1;
    defaultOptions = arguments[0]
  }
  var tableOptions = Object.extend({
    cellpadding: "0",
    cellspacing: "0",
    border: "0",
    width: "100%"
  }, defaultOptions)

  var tbody = Builder.node('tbody')
  var table = Builder.node('table', tableOptions, [tbody])
  for (; i < arguments.length; i++) {
    var rd = arguments[i];
    var tr = null;
    var trOptions = {};
    if (rd.rowData != undefined) {
      trOptions = rd.rowOptions
      rd = rd.rowData;
    }
    tbody.appendChild(tr = Builder.node('tr', trOptions))
    rd.each(function(c) {
      var cd = c;
      var tdOptions = {};
      if (cd.columnData != undefined || cd.columnOptions != undefined) {
        tdOptions = cd.columnOptions
        cd = cd.columnData;
      }
      tr.appendChild(Builder.node('td', tdOptions, cd != 0 ? [ htmlNode(cd) ] : null))
    })
  }
  return table;
}

function appendChildren(e, list) {
  list.each(function(l) { 
    e.appendChild(htmlNode(l)) 
  })
}

function removeAllChildren(n) {
  $A(n.childNodes).each(function(c) { n.removeChild(c) })
}

function replaceChild(n, c) {
  if (n.childNodes.length > 0) {
    n.removeChild(n.firstChild)
  }
  n.appendChild(htmlNode(c))
}

Array.prototype.remove = function(v) {
  return this.reject(function(a) { return a == v })
}

function documentRoot() {
  return $('root')
}

function documentStyle() {
  return document.body.style
}

function setBackgroundColor(c) {
  documentStyle().backgroundColor = "#aaa"
}


function span(cn, content) {
  return Builder.node('span', {className: cn}, [content])
}

function cell(options, content) {
  var co = (options == undefined || typeof options == "string") ? 
    { className: options || ""} : 
    options;

  return {
    columnOptions: co,
    columnData: content
  }
}

function roundBox() {
  var o = Object.extend({
    node: null,
    className: ""
  }, arguments[0] || {})
  return table({className: o.className + " round-box"}, 
      [cell('top-left'), cell('top'), cell('top-right')],
      [cell('main-left'), cell('content', htmlNode(o.node)), cell('main-right')],
      [cell('bottom-left'), cell('bottom'), cell('bottom-right')]
  )
}

function selectionBox(values) {
  var options = Object.extend({
    fieldName: "select",
    currentlySelected: ""
  }, arguments[1] || {})
  var count = 0
  var selectedIndex = 0
  var optionsDivs = values.map(function (o) {
    var h = $H(o) 
    var key = h.keys()[0]
    var p = {value: key}
    if (key == options.currentlySelected) {
      selectedIndex = count
    }
    count++
    return Builder.node('option', p, [TN(h[key])])
  })
  var s=Builder.node('select', {
    name: options.fieldName,
    size: 1
  }, optionsDivs)
  s.selectedIndex = selectedIndex
  return s
}

function modelCheckbox(m, property) {
  var cb  = Builder.node('input', {type: 'checkbox'})
  function isEnabled() {
    return m[property] == "true"
  }
  function set() {
    cb.checked = isEnabled()
  }
  cb.onclick = function() {
    m[property] = String(!isEnabled())
    set()
  }
  set()
  return cb
}

function modelInput(m, property) {
  var options = Object.extend({
    name: '',
    onclick: function() {}
  }, arguments[2])
  options.value = m[property]
  options.type = "text"
  var input = Builder.node('input', options)
  input.onblur = function() {
    m[property] = input.value
  }
  return input
}

function modelSelectionBox(values, m, property) {
  var options = Object.extend({
    afterChange: function() {}
  }, arguments[3])
  var sb = selectionBox(values, {currentlySelected: m[property]})
  sb.onchange = function() {
    m[property] = sb.value
    options.afterChange()
  }
  return sb
}

function easyLocalFields() {
  return $A(arguments).map(function(a) {
    return [a.display, a.id, a.id,
      Object.extend({generic: true}, a.options || {})]
  })
}

function easyLocalFileType() {
  var o = Object.extend({
    display: "File",
    id: "file",
    type: "file"
  }, arguments[0] || {})
  return function() {
    return [o.display, o.id, "",  {type: o.type}]
  }
}

var __ww = null

function displayError(message, details) {
  if (!is_developer) { return }
  serverLog(message + "\nDetails:" + details)
  var ww = new WidgetWindow({
    contentWidget: new ErrorWidget({
      message: message, 
      details: details
    }),
    title: "Ooops!",
    classSuffix: "-error"
  })
  ww.show()
  __ww = ww
  return ww
}

function getLeft(e) {
  return getXY(e, "offsetLeft");
}

function getTop(e) {
  return getXY(e, "offsetTop");
}

function getXY(e, direction) {
  var x = 0;
  while(e) {
    x += e[direction];
    e = e.offsetParent;
  }
  return x;
}
;

// themes.js
function setTheme(l) {
  $('theme').classNames().set(l)
}

function currentTheme() {
  return $('theme').className
}

// scope?
;

// translations.core.js
$MSH = function(i){
return $H(i);
}

var translations = {};

// English
Object.extend(translations, {
  ".english .userpage .edit": "Edit",
  ".english .userpage .manage": "Manage",
  ".english .userpage .my-profile": "My profile",
  ".english .about-me": "About me",
  ".english .recent-widget .more-info-for-user": "More info on user",
  ".english .registration .welcome": "Welcome",
  ".english .userpage .manage-items": "Manage items",
  ".english .userpage .site-settings": "Site settings",
  ".english .action-widget .add-folder": "Add folder",
  ".english .action-widget .add-item": "Add %1",
  ".english .action-widget .empty-trash": "Empty trash",
  ".english .action-widget .no-permission": "You cannot add items here",
  ".english .actions": "Actions",
  ".english .add-as-buddy": "Add %1 as buddy?",
  ".english .add-buddy-widget .add-as-buddy": "add as buddy",
  ".english .added-by": "Added %1 by %2", 
  ".english .badge .join-now": "join now!",
  ".english .badge .login-or-register": "Login or Register",
  ".english .badge .no-message": "This user hasn't updated his status yet",
  ".english .badge .read .message": "Message",
  ".english .badge .read .send-message": "send message",
  ".english .badge .read .send-message-button": "Send",
  ".english .badge .write-message": "Click here to write a status message",
  ".english .buddies": "Buddies",
  ".english .buddies-widget .add-button": "Add",
  ".english .buddies-widget .back": "Back to My Buddies",
  ".english .buddies-widget .input": "enter username here",
  ".english .buddy": "Buddy",
  ".english .cancel": "Cancel",
  ".english .comments .send-comment": "Comment!",
  ".english .comments .window-title": "Comments for %1",
  ".english .comments .write-comment": "Write comment",
  ".english .comments": "Comments",
  ".english .conversation-view .delete-msg": "Really delete this message?",
  ".english .creation .fields .description": "Description",
  ".english .creation .fields .tags .example": "(Example: me, holiday, greece)",
  ".english .creation .fields .tags": "Tags",
  ".english .creation .new .folder": "Add a new folder",
  ".english .creation .new": "Add a new %1",
  ".english .creation .permission .allbuddies": "All my buddies",
  ".english .creation .permission .everyone": "Everyone",
  ".english .creation .permission .onlyme": "Only me",
  ".english .creation .permission": "Who Can View this %1?",
  ".english .date .day":     "day",
  ".english .date .days":    "days",
  ".english .date .hour":    "hour",
  ".english .date .hours":   "hours",
  ".english .date .just-now": "just now",
  ".english .date .minute":  "minute",
  ".english .date .minutes": "minutes",
  ".english .date .second":  "second",
  ".english .date .seconds": "seconds",
  ".english .date .year":    "year",
  ".english .date .years":   "years",
  ".english .date": "%1 %2 ago",
  ".english .dbw .blog .l1": " added an update %1",
  ".english .dbw .l1": " added a %1 in <a style='color:black;' href='%2'>%3</a> %4",
  ".english .dbw .l2": "<a href='%1' style='font-weight:bolder;color:black;'>%2</a> in <a href='%3' style='font-weight:bolder;color:black;'>%4</a>",
  ".english .desktop-widget .dbl-select": "Double click on item to select",
  ".english .desktop-widget .double-click": "double click",
  ".english .desktop-widget .open-windows-on": "Open windows on",
  ".english .desktop-widget .rm-bg": "Click here to remove the wallpaper",
  ".english .desktop-widget .select-image": "Select image",
  ".english .desktop-widget .set-desktop-bg": "Set desktop background",
  ".english .desktop-widget .single-click": "single click",
  ".english .display": "Change View:", 
  ".english .everyone": "Everyone",
  ".english .explorer .listview .added_at": "Time Added",
  ".english .explorer .listview .name": "Name",
  ".english .explorer .listview .rating": "Rating",
  ".english .explorer .listview .type": "Type",
  ".english .explorer .sort-by": "Sort by",
  ".english .explorer .thumbsview .added_at": "Time Added",
  ".english .explorer .thumbsview .name": "Name",
  ".english .explorer .thumbsview .rating": "Rating",
  ".english .explorer .thumbsview .type": "Type",
  ".english .explorer .tooltip .details": "details:",
  ".english .info .content": "MySites supports <strong>double clicking</strong> just like your computer. Go ahead and try!",
  ".english .info .title": "Info",
  ".english .inline-edit .cancel": "Cancel",
  ".english .inline-edit .save": "Save",
  ".english .invitation-widget .1-selected": "1 contact selected",
  ".english .invitation-widget .back": "Back",
  ".english .invitation-widget .continue": "Continue",
  ".english .invitation-widget .de/select-all": "Select/Deselect all",
  ".english .invitation-widget .get-ab": "Get address book",
  ".english .invitation-widget .invite-btn": "Invite",
  ".english .invitation-widget .invited-x": "Invited %1 (<%2>)",
  ".english .invitation-widget .loading-contacts": "Loading contacts ...",
  ".english .invitation-widget .n-selected": "%1 contacts selected",
  ".english .invitation-widget .password": "Password",
  ".english .invitation-widget .sent-1": "Sent 1 invite",
  ".english .invitation-widget .sent-all": "Sent all invites",
  ".english .invitation-widget .sent-n": "Sent %1 invites",
  ".english .invitation-widget .service": "Service",
  ".english .invitation-widget .username": "Username/Email",
  ".english .invitation-widget .write-msg": "Write a personal message",
  ".english .invitation-widget .wrong-data": "Username or password wrong!",
  ".english .iteminfo .actions-title": "Actions",
  ".english .iteminfo .added-by": "Added by",
  ".english .iteminfo .click-to-add-tags": "Click here to add tags",
  ".english .iteminfo .delete-action": "Delete",
  ".english .iteminfo .edit-action": "Edit",
  ".english .iteminfo .info-title": "Info",
  ".english .iteminfo .name": "Name",
  ".english .iteminfo .open-action": "Open this %1",
  ".english .iteminfo .privacy": "Privacy",
  ".english .iteminfo .rate": "Rate this",
  ".english .iteminfo .report-action": "Report this %1",
  ".english .iteminfo .restore-action": "Restore",
  ".english .iteminfo .share-title": "Share",
  ".english .iteminfo .tags": "Tags",
  ".english .keyword-search .clear": "clear",
  ".english .keyword-search .go": "go",
  ".english .keyword-search .listview .count": "Count",
  ".english .keyword-search .listview .tag": "Keyword",
  ".english .link": "Link",
  ".english .login": "login",
  ".english .logout": "logout",
  ".english .Master::InvalidUserOrPassword": "Password invalid",
  ".english .Master::PasswordEmpty": "Password empty",
  ".english .Master::PasswordTooShort": "Password should have at least 6 characters",
  ".english .me": "Me",
  ".english .messages-widget .conv-title": "Conversation with %1",
  ".english .messages-widget .date": "Date",
  ".english .messages-widget .delete-conversation": "Really delete this conversation?",
  ".english .messages-widget .message": "Message",
  ".english .messages-widget .no-messages": "No messages",
  ".english .messages-widget .private": "private",
  ".english .messages-widget .sender": "Sender",
  ".english .messages-widget .write-all-users": "Write all users",
  ".english .messages-widget .write-message": "Write message",
  ".english .min-rating": "Min Rating:", 
  ".english .mlw .back": "Back to My Messages",
  ".english .mlw .date": "Date",
  ".english .mlw .from": "From",
  ".english .mlw .message": "Message",
  ".english .mlw .write": "Write Message",
  ".english .navi-tree-widget .back": "return to",
  ".english .navi-tree-widget .navigation": "Navigation",
  ".english .navi-tree-widget .services": "Services",
  ".english .navi-tree-widget .show-navigation": "Back to Navigation",
  ".english .navi-tree-widget .show-services": "Switch to Service List",
  ".english .navi-tree-widget .trash": "Trash",
  ".english .no-explorer-items": "There are no %1/folders at the moment.<BR>Would you like to add a <a href='#' onclick='%3'>%2</a> or a <a href='#' onclick='%5'>%4</a>?", 
  ".english .no-explorer-items-no-folder": "There are no %1 at the moment.<BR>Would you like to add a <a href='#' onclick='%3'>%2</a>?", 
  ".english .ok": "OK",
  ".english .password": "password: ",
  ".english .PasswordsDontMatch": "Passwords don't match!",
  ".english .permalink": "permalink",
  ".english .permission": "Permission",
  ".english .pictures .large-files": "Large files might take longer to appear in your item list",
  ".english .privacy": "Privacy:", 
  ".english .profiles .birthday": "Your birthday",
  ".english .profiles .cancel-change-pwd-button": "Cancel",
  ".english .profiles .change-password": "Change Password", 
  ".english .profiles .change-picture": "Change Picture",
  ".english .profiles .change-pwd-button": "Change Password",
  ".english .profiles .city": "Your city",
  ".english .profiles .country": "Your country",
  ".english .profiles .email": "Your email",
  ".english .profiles .full-name": "Full name",
  ".english .profiles .new-password": "New password",
  ".english .profiles .new-password-confirmation": "New password confirmation",
  ".english .profiles .old-password": "Old password",
  ".english .profiles .privacy": "MySites respects your privacy.<br />Unlike other websites, all the information you add in your profile is optional.<br />You decide what data you are comfortable to share.",
  ".english .profiles .save": "Save", 
  ".english .profiles .upload-picture": "Upload new profile picture",
  ".english .registration .captcha": "Captcha",
  ".english .registration .captcha-expl": "Enter the text from above",
  ".english .registration .email": "Email",
  ".english .registration .errors .accept-terms": "You must accept the terms!",
  ".english .registration .errors .EmailAddressAlreadyUsed": "Email address already used!",
  ".english .registration .errors .FullNameIncomplete": "Full name incomplete!",
  ".english .registration .errors .FullNameTooShort": "Full name too short!",
  ".english .registration .errors .InvalidCaptcha": "Invalid captcha!",
  ".english .registration .errors .InvalidEmailAddress": "Invalid email address!",
  ".english .registration .errors .InvalidUsername": "Username invalid, allowed characters are a-z, 0-9, and -",
  ".english .registration .errors .PasswordsDontMatch": "Passwords don't match!",
  ".english .registration .errors .PasswordTooShort": "Password should have a least 6 characters!",
  ".english .registration .errors .UsernameAlreadyTaken": "Username already taken!",
  ".english .registration .errors .UsernameTooShort": "Username too short!",
  ".english .registration .full-name": "Full name",
  ".english .registration .help .email": "Enter a valid email, so we can contact you or send you notifications.",
  ".english .registration .help .full_name": "Please enter your full name here (e.g. Robert Smith)",
  ".english .registration .help .password": "Select a password. Make sure you don't forget it!",
  ".english .registration .help .password_confirmation": "Please retype your password to confirm it is correct.",
  ".english .registration .help .start": "Let's create an account! Very soon, you'll be able to start using the site. Start by picking a username!",
  ".english .registration .help .username": "People will go to http://yourname.mysites.com to see what you let them see, so make sure you pick a nice name! Also, make sure it's 4 characters or more, and only use a-z 0-9.",
  ".english .registration .info": "Let's start using MySites!<br/><br/>Once you are done filling your information, click the MySites logo or the <q>home</q><img src=\"%1\" style=\"%2\"/> icon on top to go to your desktop!",
  ".english .registration .join": "%1 invites you to join MySites",
  ".english .registration .login": "Login",
  ".english .registration .logout-first": "You have to logout before you can create a new account!",
  ".english .registration .new-captcha": "Click for new captcha.",
  ".english .registration .password": "Password",
  ".english .registration .password_confirmation": "Retype password",
  ".english .registration .register": "Register",
  ".english .registration .repeat-password": "Repeat Password",
  ".english .registration .terms": "I accept the <a href=\"%1\" target=\"_blank\">terms of use</a>",
  ".english .registration .thanks": "Thanks for registering! Please login to proceed.",
  ".english .registration .username": "Username",
  ".english .related-links": "Related Links", 
  ".english .save": "Save",
  ".english .search .input": "Search (beta)",
  ".english .selected-items": "Selected Items", 
  ".english .selection .zero-items-selected": "0 items selected",
  ".english .service-list-widget .add-remove-services": "Add/Remove services",
  ".english .service-list-widget .back-to-services": "Back to My Services",
  ".english .service-list-widget .click-to-add": "Other services. Click to add.",
  ".english .service-list-widget .click-to-remove": "Your services. Click to remove.",
  ".english .service-list-widget .latest": "Latest %1",
  ".english .service-list-widget .reset": "Reset",
  ".english .share .bookmark-item": "Bookmark item!",
  ".english .share .copied-to-clipboard": "Copied to Clipboard",
  ".english .share .download": "Download",
  ".english .share .send-to-mobile": "Send to mobile",
  ".english .share .share-item": "Share this %1",
  ".english .share .share-url": "Share URL",
  ".english .share": "Share",
  ".english .start-page .preferences .number-of-items": "Number of items per row",
  ".english .start-page .preferences": "Preferences",
  ".english .successfully-saved": "Successfully saved!",
  ".english .successfully-saved-password": "Successfully saved password!",
  ".english .tags": "Tags:", 
  ".english .texteditor .edit-text": "Edit text",
  ".english .texteditor .link": "create a link",
  ".english .texteditor .picture": "insert a picture",
  ".english .texteditor .redo": "Redo",
  ".english .texteditor .text-color": "text color",
  ".english .texteditor .undo": "Undo",
  ".english .title": "Title",
  ".english .title-bar .bookmark": "Bookmark",
  ".english .title-bar .language": "language",
  ".english .title-bar .logout": "logout",
  ".english .title-bar .share": "Share",
  ".english .title-bar .subscribe": "Subscribe",
  ".english .title-bar .theme": "theme",
  ".english .trash": "Trash", 
  ".english .um .message": "Message",
  ".english .um .reply-button": "Send",
  ".english .um .reply-text": "Write a reply here",
  ".english .um .to": "To",
  ".english .um .write-title": "Write message",
  ".english .username": "username: ",
  ".english .verify-delete": "Are you sure you want to delete \"%1\"?",
  ".english .whats-hot": "What's Hot",
  ".english .whats-new": "What's New",
  ".english .widget-window .close": "close",


".english .frontpage .slide1": "Get your own desktop, with everything you need for your content",
".english .frontpage .slide2": "Have lots to share? Multi-Upload all your files in on go!",
".english .frontpage .slide3": "Play music and video with the MySites player, share it with your friends",
".english .frontpage .window1": "Gamers: Upload your Lan party photos, your frag movies, screenshots, configs and share them with your team and more!<br/><br/>Students: Save and share documents, presentations and group work.<br/><br/>Artists: Save your portfolio. Play and share your designs and music with your community! ",
".english .frontpage .window2": "MySites is a single place for all your content. View, upload, share photos, music, video, and any kind of document with the people you want.<br/><br/>Our uploader is among the best online! You can upload hundreds of files in one go, with a progress bar to know how much time is left.<br/><br/>MySites looks and feels like your desktop computer. You\'ll learn how to use it in about as much time as it takes to read this window!<br/><br/>Got private photos? Holiday albums, school work? You can set the privacy on anything you upload on MySites. ",
".english .frontpage .window3": "Are you (and your friends) on Facebook?<br/><br/>Good! With our application, you\'ll be able to take the content you\'ve already uploaded on MySites, and share it on Facebook.<br/><br/>Post content on your friends\' walls, or your own, post music, photos, or anything you want!",
 ".english .frontpage .window4": "A long time ago, in a cold and remote place, lived a French exchange student.<br/><br/>As an exchange student, he made new friends, he would often party, and when he thought of home, he would want to write about his thoughts and share them with his peers. In school, he would always forget his USB memory or send himself attachments by email. And at home, he’d play Counterstrike with his team and lament the fact that it was difficult to set up a decent website for them.<br/><br/>Back in those days, “Web 2.0? and Social Networking sites were the new black. New sites came every day, promising to solve his problems. But none did. More and more spam (sorry, invites) asked him to register to silly websites with stupid names, just so that he could do one thing.<br/><br/>For every YourTube and MyFace, there would be another link, another login. Another set of friends to invite, another interface.<br/><br/>Different functionality, no interoperability. No localizations, no centralization.<br/><br/>This burden became too much for him.<br/><br/>The Finnish winter can be long and dark, but forges the will. In his room, he started sketching for ways to put his content online for others in a sensible way.<br/><br/>As he consumed the equivalent of a small forest’s worth in paper, he realized one pattern. That everything online could be mastered through a single interface, with the same tools. Just like on your computer. But online! All you would need to create is ways to manage that data differently.<br/><br/>With this idea in mind, he went to look for brave and bold men who would be crazy enough to build it. He found experts in Ruby on Rails and scaling, who believed in his vision of recycling, optimization and openness.<br/><br/>And so, their two year journey to create a new way for people to “be” online began.<br/><br/>As he was still a student, the task seemed unfeasable.<br/>He begged from his dad “Why do you want to spend money rather than have a job and make some?!”.<br/>He received support from the Finnish government to build his company.<br/>He ate large quantities of pasta (the Finnish equivalent of Ramen noodles).<br/>He found a nice domain name.<br/>And eventually, after extensive bootstrapping, he found private investors to help. <br/>",
".english .frontpage .window5": "MySites is a real platform, similar to Google App Engine or Windows Live, which can support any type of web service, in any language, and on any device.<br/><br/>We put a strong emphasis on technology, in order to deliver the best experience we can to our users.<br/><br/>We use a single login to connect you through many different domains and services (similarly to Passport or Facebook Connect).<br/><br/>Our multiple upload is amongst the best and fastest on the web.<br/><br/>We have created a new use for Ajax, by putting a strong focus on client-side processing, which helps both lower latency and for the users and improve our server performance.<br/><br/>MySites features a fully custom built IDE, which enables our team to create new Internet services at record speeds, by recycling code and interlinking our existing services.<br/><br/>We will fully open our tools, together with a read/write API very soon. Please feel free to contact us if you are interested!<br/><br/>MySites is designed to run on any type of device. During the coming months, we will deploy more services for all types of devices and OSes.<br/><br/>", 
".english .frontpage .people-think": "What people think",
".english .frontpage .hot": "What\'s hot",
".english .frontpage .more-reviews": "more reviews...",
".english .frontpage .read-more": "read more...",
".english .frontpage .why-use": "Why Use MySites",
".english .frontpage .features": "Features",
".english .frontpage .fb-app": "Facebook App (Coming soon!)",
".english .frontpage .about": "About MySites",
".english .frontpage .developer": "Developers",
".english .frontpage .box1": "MySites can make your life easier! Are you a gamer, a student, an artist?", 
".english .frontpage .box2": "Only one login, desktop-like interface, mass upload, easy sharing, privacy, audio+video players", 
".english .frontpage .box3": "Show your MySites content on Facebook! Share and play music, videos and more.", 
".english .frontpage .box4": "MySites is a European startup created 3 years ago in Finland. We are funded, and gather top programing talent.", 
".english .frontpage .box5": "We use client-side processing, built our own IDE, cluster, and replaced Rails. API is coming soon. Find out more!", 
".english .frontpage .media": "Media &amp; Press",
".english .frontpage .legal": "Legal Information",
".english .frontpage .business": "Businesses",
".english .frontpage .close": "Close",
".english .frontpage .recent-blog": "Recent blog posts",
".english .frontpage .recent-items": "Recent items",
".english .frontpage .front-info": "MySites is a place to upload all your content and share it with the rest of the world.",

".english .recent-widget .posted": "posted",
".english .recent-widget .by": "by",
".english .frontpage .recent": "Recent",
".english .registration .register-info": "Register-Info",
".english .registration .register-info-text": "Register now, it\'s FREE! Get 10Gb of space to share with the people you want. ",
".english .frontpage .connect-to-us": "Connect to us",

 ".english .title-bar .my sites": "MySites",
".english .userpage .loading-please-wait": "(Loading content)",
".english .userpage .latest": "Recent",
".english .userpage .subscribe": "Subscribe",
".english .userpage .my-serv": "My content",
".english .userpage .buddies-serv": "My buddies",
".english .userpage .commu-serv": "My communities",
".english .userpage .me": "Me",
".english .userpage .popular": "Popular",
".english .userpage .recent": "Recent",
".english .userpage .public": "Public",
".english .userpage .userpage": "website",
".english .userpage .favorites": "Favorites",
".english .userpage .about": "About",
".english .userpage .sort-by": "Sort by",
".english .recent-widget .todownload": "Download",
".english .recent-widget .toembed": "Embed",
".english .recent-widget .tomobile": "Mobile code",
".english .recent-widget .sendmsg": "Send a message",
".english .recent-widget .addbudd": "Add to my buddies",
 
 ".english .cloud .save_desc": "MySites give you reliable storage online for all your content.<br/><br/>Upload up to 10Gb of your music, photos, videos, files and documents.<br/><br/>Select who can see your content: only you, your friends or everyone.<br/><br/>We have a multiple file uploader, which allows your to upload hundreds of files in a few clicks!",
".english .cloud .save_txt": "save",
".english .cloud .play_desc": "Once your content is online, you can play it directly from there.<br/><br/>Not just your content, but anyone else\'s,  anywhere and at any time.<br/><br/>You can even use your mobile, your console, your desktop, anything that has a browser!",
".english .cloud .play_txt": "play",
".english .cloud .combine_desc": "Show all your online content in one place.<br/><br/>Already have content on YouTube, Twitter, Flickr, Delicious, Friendfeed or others? No problem!<br/><br/>You can show them all on MySites.",
".english .cloud .combine_txt": "combine",
".english .cloud .share_desc": "Once your content is on MySites, it's easy to share.<br/><br/>Share with the people you want, using our permission system.<br/>Send it on Facebook, Twitter.<br/>Save it on Delicious, StumbleUpon, Digg, Reddit and more.<br/>Embed it to your blog and forum.<br/>Link it for direct download on your site.<br/>Share it with mobile QR codes.<br/>",
".english .cloud .share_txt": "share",
".english .cloud .social_desc": "Comment, tag, share any content.<br/><br/>Find the latest musics from your friends, or the most popular ones.",
".english .cloud .social_txt": "social",
".english .cloud .blog_desc": "You can embed or make direct links to any content  hosted on MySites, so you don\'t have to worry about storage for your site.",
".english .cloud .blog_txt": "blog",
".english .cloud .mobile_desc": "(COMING SOON!)<br/><br/>MySites can be used on your mobile too!<br/><br/>Take all your content with you anywhere, including from other websites, to show it to others.<br/><br/>Browse the content from your friends and communities on the go.<br/><br/>Upload content directly from your mobile.<br/><br/>Share content using mobile QR codes.",
".english .cloud .mobile_txt": "mobile",
".english .cloud .community_desc": "(COMING SOON!)<br/><br/>Create a site where you and others can save content together.<br/><br/>You have a hobby you share with your friends? Share its content easily with them!",
".english .cloud .community_txt": "community",
".english .cloud .privacy_desc": "You own your content.<br/><br/>You can decide with whom to share it, or where to share it.<br/><br/>You can remove your content at any time, we won\'t keep copies.<br/><br/>we\'re just here to help you save and share your things more easily.",
".english .cloud .privacy_txt": "privacy", 

".english .frontpage .more-reviews": "more-reviews",
  ".english .nm-sgpw .Why Register?": "Why Register?",
  ".english .nm-sgpw .Join MySites now!": "Join MySites now!",
  ".english .nm-sgpw .Become a partner": "Become a partner",
  ".english .nm-sgpw .Partner Sites": "Partner Sites",
  ".english .nm-sgpw .The easiest way to share files with your friends": "The easiest way to share files with your friends", 

 ".english .registration .captcha": "Captcha",
  ".english .registration .captcha-expl": "Enter the text from above",
  ".english .registration .email": "Email",
  ".english .registration .errors .accept-terms": "You must accept the terms!",
  ".english .registration .errors .EmailAddressAlreadyUsed": "Email address already used!",
  ".english .registration .errors .FullNameIncomplete": "Full name incomplete!",
  ".english .registration .errors .FullNameTooShort": "Full name too short!",
  ".english .registration .errors .InvalidCaptcha": "Invalid captcha!",
  ".english .registration .errors .InvalidEmailAddress": "Invalid email address!",
  ".english .registration .errors .InvalidUsername": "Username invalid, allowed characters are a-z, 0-9, and -",
  ".english .registration .errors .PasswordsDontMatch": "Passwords don't match!",
  ".english .registration .errors .PasswordTooShort": "Password should have a least 6 characters!",
  ".english .registration .errors .UsernameAlreadyTaken": "Username already taken!",
  ".english .registration .errors .UsernameTooShort": "Username too short!",
  ".english .registration .full-name": "Full name",
  ".english .registration .help .email": "Enter a valid email, so we can contact you or send you notifications.",
  ".english .registration .help .full_name": "Please enter your full name here (e.g. Robert Smith)",
  ".english .registration .help .password": "Select a password. Make sure you don't forget it!",
  ".english .registration .help .password_confirmation": "Please retype your password to confirm it is correct.",
  ".english .registration .help .start": "Let's create an account! Very soon, you'll be able to start using the site. Start by picking a username!",
  ".english .registration .help .username": "People will go to http://yourname.mysites.com to see what you let them see, so make sure you pick a nice name! Also, make sure it's 4 characters or more, and only use a-z 0-9.",
  ".english .registration .info": "Let's start using MySites!<br/><br/>Once you are done filling your information, click the MySites logo or the <q>home</q><img src=\"%1\" style=\"%2\"/> icon on top to go to your desktop!",
  ".english .registration .join": "%1 invites you to join MySites",
  ".english .registration .login": "Login",
  ".english .registration .logout-first": "You have to logout before you can create a new account!",
  ".english .registration .new-captcha": "Click for new captcha.",
  ".english .registration .password": "Password",
  ".english .registration .password_confirmation": "Retype password",
  ".english .registration .register": "Register",
  ".english .registration .repeat-password": "Repeat Password",
  ".english .registration .terms": "I accept the <a href=\"%1\" target=\"_blank\">terms of use</a>",
  ".english .registration .thanks": "Thanks for registering! Please login to proceed.",
  ".english .registration .username": "Username",

".english .date .day":     "day",
".english .date .days":    "days",
".english .date .hour":    "hour",
".english .date .hours":   "hours",
".english .date .just-now": "just now",
".english .date .minute":  "minute",
".english .date .minutes": "minutes",
".english .date .second":  "second",
".english .date .seconds": "seconds",
".english .date .year":    "year",
".english .date .years":   "years",
".english .date": "%1 %2 ago",


".english .nm-supw .Share": "Share",
".english .nm-supw .Comments": "Comments",
".english .nm-supw .Share this": "Share this", 

".english .nm-sew .change": "change",
".english .nm-sew .This file is public.": "This file is public.",
".english .nm-sew .<br/>Anyone can find the file if they come to your site.<br/><br/>Use the following links to share it:": "<br/>Anyone can find the file if they come to your site.<br/><br/>Use the following links to share it:",
".english .nm-sew .added": "added",
".english .nm-sew .Forum code": "Forum code",
".english .nm-sew .Original file": "Original file",
".english .nm-sew .This page": "This page",
".english .nm-sew .Share this album": "Share this album",
".english .nm-sew .What is this?": "What is this?",
".english .nm-sew .mobile QR-Code": "mobile QR-Code",


".english .nm-ac .Sort by": "Sort by",
".english .nm-ac .newest": "newest",
".english .nm-ac .oldest": "oldest",
".english .nm-ac .a-z": "a-z",
".english .nm-ac .z-a": "z-a",
".english .nm-ac .Show": "Show",
".english .nm-ac .All File Types": "All File Types",
".english .nm-ac .Pictures Only": "Pictures Only",
".english .nm-ac .Videos Only": "Videos Only",
".english .nm-ac .Music Only": "Music Only",
".english .nm-spw .previous": "previous",
".english .nm-spw .items": "items",
".english .nm-spw .next": "next",
".english .nm-spw .Pages": "Pages",

".english .nm-sia .Publish this item to your Facebook-Account.": "Publish this item to your Facebook-Account.",
".english .nm-sia .Publish to your wall": "Publish to your wall",
".english .nm-sia .Share this": "Share this",
".english .nm-sia .Download this item to your Harddrive": "Download this item to your Harddrive",
".english .nm-sia .Save to your computer": "Save to your computer",
".english .nm-sia .Save": "Save",
".english .nm-sia .Welcome": "Welcome", 

".english .nm-sprow .Full name:": "Full name:",
".english .nm-sprow .Short description:": "Short description:",
".english .nm-sprow .Web:": "Web:",
".english .nm-sprow .Country:": "Country:",
".english .nm-sprow .City:": "City:",

".english .nm-supw .User": "User",
".english .nm-supw .albums": "albums",
".english .nm-supw .RSS": "RSS",
".english .nm-supw .Navigation": "Navigation",
".english .nm-supw .recent public files": "recent public files",

".english .nm-sfpw .My Site": "My Site",
".english .nm-sfpw .My Albums": "My Albums",
".english .nm-sfpw .Navigation": "Navigation",
".english .nm-sfpw .Recent public files": "Recent public files",
".english .nm-sfpw .Navigation": "Navigation",
".english .nm-sfpw .Add files to MySites.com": "Add files to MySites.com",
".english .nm-sfpw .Latest Additions": "Latest Additions",
".english .nm-sia .Home": "Home", 

".english .nm-supw .Edit my profile": "Edit my profile",
".english .nm-supw .Edit Profile": "Edit Profile",
".english .nm-sfw .Full name:": "Full name:",
".english .nm-sfw .Picture:": "Picture:",
".english .nm-sfw .Language:": "Language:",
".english .nm-sfw .Short description:": "Short description:",
".english .nm-sfw .Web:": "Web:",
".english .nm-sfw .Country:": "Country:",
".english .nm-sfw .City:": "City:", 


 
 
".english": "Dummy entry that comes last to avoid syntax errors when sorting"
})
// German
Object.extend(translations, {
  ".german .about-me": "Über mich",
  ".german .action-widget .add-folder": "Ordner hinzufügen",
  ".german .action-widget .add-item": "%1 hinzufügen",
  ".german .action-widget .empty-trash": "Papierkorb leeren",
  ".german .action-widget .no-permission": "Sie können hier keine Items hinzufügen",
  ".german .actions": "Aktionen",
  ".german .add-as-buddy": "%1 als Buddy hinzufügen?",
  ".german .add-buddy-widget .add-as-buddy": "als Buddy hinzufügen",
  ".german .added-by": "Hinzugefügt %1 by %2", 
  ".german .badge .join-now": "beitreten",
  ".german .badge .login-or-register": "Einloggen oder registieren",
  ".german .badge .no-message": "Dieser Benutzer hat seinen Status noch nicht aktualisiert",
  ".german .badge .read .message": "Nachricht",
  ".german .badge .read .send-message": "Nachricht schicken",
  ".german .badge .read .send-message-button": "Abschicken",
  ".german .badge .write-message": "Hier klicken um eine Status-Nachricht zu schreiben",
  ".german .buddies": "Buddies",
  ".german .buddies-widget .add-button": "Hinzufügen",
  ".german .buddies-widget .back": "Zurück zu My Buddies",
  ".german .buddies-widget .input": "Benutzernamen eingeben",
  ".german .buddy": "Buddy",
  ".german .cancel": "Abbrechen",
  ".german .comments .send-comment": "Kommentieren!",
  ".german .comments .window-title": "Kommentare für %1",
  ".german .comments .write-comment": "Kommentar schreiben",
  ".german .comments": "Kommentare",
  ".german .conversation-view .delete-msg": "Diese Nachricht wirklich löschen?",
  ".german .creation .fields .description": "Beschreibung",
  ".german .creation .fields .tags .example": "(Beispiel: ich, Urlaub, Griechenland)",
  ".german .creation .fields .tags": "Tags",
  ".german .creation .new .folder": "Ordner hinzufügen",
  ".german .creation .new": "%1 hinzufügen",
  ".german .creation .permission .allbuddies": "Alle meine Buddies",
  ".german .creation .permission .everyone": "Alle",
  ".german .creation .permission .onlyme": "Nur ich",
  ".german .creation .permission": "Wer kann dieses %1 sehen?",
  ".german .date .day": "Tag",
  ".german .date .days": "Tagen",
  ".german .date .hour": "Stunde",
  ".german .date .hours": "Stunden",
  ".german .date .just-now": "gerade eben",
  ".german .date .minute": "Minute",
  ".german .date .minutes": "Minuten",
  ".german .date .second": "Sekunde",
  ".german .date .seconds": "Sekunden",
  ".german .date .year": "Jahr",
  ".german .date .years": "Jahren",
  ".german .date": "vor %1 %2",
  ".german .dbw .blog .l1": " hat ein Update %1 hinzugefügt",
  ".german .dbw .l1": " hat ein %1 in <a style='color:black;' href='%2'>%3</a> %4 hinzugefügt",
  ".german .dbw .l2": "<a href='%1' style='font-weight:bolder;color:black;'>%2</a> in <a href='%3' style='font-weight:bolder;color:black;'>%4</a>",
  ".german .desktop-widget .dbl-select": "Mit Doppelklick Bild auswählen",
  ".german .desktop-widget .double-click": "Doppel-Klick",
  ".german .desktop-widget .open-windows-on": "Fenster öffnen mittels",
  ".german .desktop-widget .rm-bg": "Hier klicken um den Desktophintergrund zu entfernen",
  ".german .desktop-widget .select-image": "Bild auswählen",
  ".german .desktop-widget .set-desktop-bg": "Desktophintergrund ändern",
  ".german .desktop-widget .single-click": "einfachem Klick",
  ".german .display": "Anzeige:", 
  ".german .everyone": "Alle",
  ".german .explorer .listview .added_at": "hinzugefügt am",
  ".german .explorer .listview .name": "Name",
  ".german .explorer .listview .rating": "Rating",
  ".german .explorer .listview .type": "Typ",
  ".german .explorer .sort-by": "Sortieren nach",
  ".german .explorer .thumbsview .added_at": "hinzugefügt am",
  ".german .explorer .thumbsview .name": "Name",
  ".german .explorer .thumbsview .rating": "Rating",
  ".german .explorer .thumbsview .type": "Typ",
  ".german .explorer .tooltip .details": "Details:",
  ".german .info .content": "MySites unterstützt <strong>Doppelklicken</strong> genau wie auf ihrem Computer. Probieren sie es doch einfach aus!",
  ".german .info .title": "Info",
  ".german .inline-edit .cancel": "Abbrechen",
  ".german .inline-edit .save": "Speichern",
  ".german .invitation-widget .1-selected": "1 Kontakt ausgewählt",
  ".german .invitation-widget .back": "Zurück",
  ".german .invitation-widget .continue": "Weiter",
  ".german .invitation-widget .de/select-all": "Alle aus/abwählen",
  ".german .invitation-widget .get-ab": "Adressbuch laden",
  ".german .invitation-widget .invite-btn": "Einladen",
  ".german .invitation-widget .invited-x": "%1 (<%2>) eingeladen",
  ".german .invitation-widget .loading-contacts": "Lade Kontakte ...",
  ".german .invitation-widget .n-selected": "%1 Kontakte ausgewählt",
  ".german .invitation-widget .password": "Passwort",
  ".german .invitation-widget .sent-1": "1 Einladung verschickt",
  ".german .invitation-widget .sent-all": "Alle Einladungen verschickt",
  ".german .invitation-widget .sent-n": "%1 Einladungen verschickt",
  ".german .invitation-widget .service": "Service",
  ".german .invitation-widget .username": "Benutzername/Email",
  ".german .invitation-widget .write-msg": "Persönliche Nachricht schreiben",
  ".german .invitation-widget .wrong-data": "Benutzername oder Passwort falsch!",
  ".german .iteminfo .actions-title": "Aktionen",
  ".german .iteminfo .added-by": "Hinzugefügt von",
  ".german .iteminfo .click-to-add-tags": "Hier klicken um Tags hinzuzufügen",
  ".german .iteminfo .delete-action": "Löschen",
  ".german .iteminfo .edit-action": "Editieren",
  ".german .iteminfo .info-title": "Info",
  ".german .iteminfo .name": "Name",
  ".german .iteminfo .open-action": "Dieses %1 öffnen",
  ".german .iteminfo .privacy": "Privatsphäre",
  ".german .iteminfo .rate": "Bewerten",
  ".german .iteminfo .report-action": "Diese %1 melden",
  ".german .iteminfo .restore-action": "Wiederherstellen",
  ".german .iteminfo .share-title": "Share",
  ".german .iteminfo .tags": "Tags",
  ".german .keyword-search .clear": "löschen",
  ".german .keyword-search .go": "Auf gehts",
  ".german .keyword-search .listview .count": "Zähler",
  ".german .keyword-search .listview .tag": "Keywort",
  ".german .link": "Link",
  ".german .login": "einloggen",
  ".german .logout": "ausloggen",
  ".german .Master::InvalidUserOrPassword": "Passwort ungültig",
  ".german .Master::PasswordEmpty": "Passwortfeld leer",
  ".german .Master::PasswordTooShort": "Das Passwort muss aus mind. 6 Zeichen bestehen",
  ".german .me": "Ich",
  ".german .messages-widget .conv-title": "Unterhaltung mit %1",
  ".german .messages-widget .date": "Datum",
  ".german .messages-widget .delete-conversation": "Diese Unterhaltung wirklich löschen?",
  ".german .messages-widget .message": "Nachricht",
  ".german .messages-widget .no-messages": "Keine Nachrichten",
  ".german .messages-widget .private": "privat",
  ".german .messages-widget .sender": "Absender",
  ".german .messages-widget .write-all-users": "Allen Benutzern schreiben",
  ".german .messages-widget .write-message": "Nachricht schreiben",
  ".german .min-rating": "Min Rating:", 
  ".german .mlw .back": "Zurück zu My Messages",
  ".german .mlw .date": "Datum",
  ".german .mlw .from": "Von",
  ".german .mlw .message": "Nachricht",
  ".german .mlw .write": "Nachricht schreiben",
  ".german .navi-tree-widget .back": "zurück nach",
  ".german .navi-tree-widget .navigation": "Navigation",
  ".german .navi-tree-widget .services": "Services",
  ".german .navi-tree-widget .show-navigation": "Zurück zur Navigation",
  ".german .navi-tree-widget .show-services": "Zur Service-Liste wechseln",
  ".german .navi-tree-widget .trash": "Papierkorb",
  ".german .no-explorer-items": "Es gibt %1/Ordner.<BR>Möchten Sie einen hinzufügen <a href='#' onclick='%3'>%2</a> or a <a href='#' onclick='%5'>%4</a>?", 
  ".german .no-explorer-items-no-folder": "Es gibt %1 Items.<BR>Möchten Sie ein neues hinzufügen <a href='#' onclick='%3'>%2</a>?", 
  ".german .ok": "OK",
  ".german .password": "Passwort: ",
  ".german .PasswordsDontMatch": "Passwörter stimmen nicht überein!",
  ".german .permalink": "stabiler Link",
  ".german .permission": "Erlaubnis",
  ".german .pictures .large-files": "Große Dateien können länger brauchen um in der Liste der Items zu erscheinen",
  ".german .privacy": "Datenschutz:", 
  ".german .profiles .birthday": "Geburtstag",
  ".german .profiles .cancel-change-pwd-button": "Abbrechen",
  ".german .profiles .change-password": "Passwort ändern",
  ".german .profiles .change-picture": "Bild ändern",
  ".german .profiles .change-pwd-button": "Passwort ändern",
  ".german .profiles .city": "Stadt",
  ".german .profiles .country": "Land",
  ".german .profiles .email": "Emailadresse",
  ".german .profiles .full-name": "vollständiger Name",
  ".german .profiles .new-password": "Neues Passwort",
  ".german .profiles .new-password-confirmation": "Neues Passwort (Wiederholung)",
  ".german .profiles .old-password": "Altes Passwort",
  ".german .profiles .privacy": "MySites respektiert ihr Privatsphäre.<br />Im Gegensatz zu anderen Webseiten ist jegliche Information die sie zu ihrem Profil hinzufügen optional.<br />Sie entscheiden welche Informationen sie veröffentlichen wollen.",
  ".german .profiles .save": "Speichern",
  ".german .profiles .upload-picture": "Neues Profil-Bild hochladen",
  ".german .registration .captcha": "Captcha",
  ".german .registration .captcha-expl": "Text von oben eingeben",
  ".german .registration .email": "Email",
  ".german .registration .errors .accept-terms": "Sie müssen die Nutzungsbedingungen akzeptieren!",
  ".german .registration .errors .EmailAddressAlreadyUsed": "Emailadresee schon in Benutzung!",
  ".german .registration .errors .FullNameIncomplete": "vollständiger Name unvollständig",
  ".german .registration .errors .FullNameTooShort": "Vollständiger Name zu kurz!",
  ".german .registration .errors .InvalidCaptcha": "Ungültiges Captcha!",
  ".german .registration .errors .InvalidEmailAddress": "Ungültige Emailadresse!",
  ".german .registration .errors .InvalidUsername": "Benutzername ungültig. Erlaubt sind die Zeichen a-z, 0-9, und -",
  ".german .registration .errors .PasswordsDontMatch": "Passwörter stimmen nicht überein!",
  ".german .registration .errors .PasswordTooShort": "Passwort muss aus mindestens 6 Zeichen bestehen!",
  ".german .registration .errors .UsernameAlreadyTaken": "Benutzername schon in Verwendung!",
  ".german .registration .errors .UsernameTooShort": "Benutzername zu kurz!",
  ".german .registration .full-name": "vollständiger Name",
  ".german .registration .help .email": "Bitte eine gültige Emailadresse eingeben, damit wir dich kontaktieren und Benachrichtigungen schicken können.",
  ".german .registration .help .full_name": "Bitte vollständigen Namen eingeben (z.B. Robert Schmitt)",
  ".german .registration .help .password": "Ein Passwort auswählen. Bitte nicht vergessen!",
  ".german .registration .help .password_confirmation": "Bitte das Passwort noch einmal eingeben um sicher zu stellen, dass es korrekt ist.",
  ".german .registration .help .start": "Lege eine Konto an! Schon sehr bald wirst du die Seite verwenden können. Wähle bitte zuerst einen Benutzernamen aus!",
  ".german .registration .help .username": "Andere Leute werden auf http://benutzername.mysites.com gehen um zu sehen was du ihnen erlaubst zu sehen. Deshalb stelle sicher, dass du einen guten Benutzernamen auswählst. Achte ausserdem darauf, dass er mindestens vier Zeichen lang ist und nur aus a-z und 0-9 besteht (auch keine Großbuchstaben oder Umlaute).",
  ".german .registration .info": "Fangen wir an MySites zu benutzen!<br/><br/>Sobald du deine Informationen eingetragen hast kannst du auf das MySites logo oder den <q>home</q><img src=\"%1\" style=\"%2\"/> Button in der Kopfzeile der Seite klicken um zu deinem Dektop zu kommen!",
  ".german .registration .join": "%1 lädt dich zu MySites ein",
  ".german .registration .login": "Einloggen",
  ".german .registration .logout-first": "Bitte ausloggen vor dem Account anlegen!",
  ".german .registration .new-captcha": "Für neues Captcha hier klicken",
  ".german .registration .password": "Passwort",
  ".german .registration .password_confirmation": "Passwort wiederholen",
  ".german .registration .register": "Registrieren",
  ".german .registration .repeat-password": "Passwort wiederholen",
  ".german .registration .terms": "Ich akzeptiere die <a href=\"%1\" target=\"_blank\">Nutzungsbedingungen</a>",
  ".german .registration .thanks": "Danke für ihre Registrierung! Zum fortfahren bitte einloggen.",
  ".german .registration .username": "Benutzername",
  ".german .related-links": "Ähnliche Links",   
  ".german .save": "Speichern",
  ".german .search .input": "Suche (beta)",
  ".german .selected-items": "Ausgewählte Items", 
  ".german .selection .zero-items-selected": "0 Gegenstände ausgewählt",
  ".german .service-list-widget .add-remove-services": "Services hinzufügen/entfernen",
  ".german .service-list-widget .back-to-services": "Zurück zu My Services",
  ".german .service-list-widget .click-to-add": "Übrige Services. Klicken zum hinzufügen.",
  ".german .service-list-widget .click-to-remove": "Ihre Services. Klicken zum entfernen.",
  ".german .service-list-widget .latest": "Neuesten %1",
  ".german .service-list-widget .reset": "Zurücksetzen",
  ".german .share .bookmark-item": "Item as Lesezeichen setzen!",
  ".german .share .copied-to-clipboard": "In's Clipboard kopiert",
  ".german .share .download": "Herunterladen",
  ".german .share .send-to-mobile": "An Handy senden",
  ".german .share .share-item": "Dieses %1 anderen mitteilen",
  ".german .share .share-url": "Share URL",
  ".german .share": "Share",
  ".german .start-page .preferences .number-of-items": "Anzahl der Kästen pro Zeile",
  ".german .start-page .preferences": "Einstellungen",
  ".german .successfully-saved": "Erfolgreich abgespeichert",
  ".german .successfully-saved-password": "Passwort gespeichert",
  ".german .tags": "Tags:", 
  ".german .texteditor .edit-text": "Text editieren",
  ".german .texteditor .link": "Link erzeugen",
  ".german .texteditor .picture": "Bild einfügen",
  ".german .texteditor .redo": "Wiederholen",
  ".german .texteditor .text-color": "Text Farbe",
  ".german .texteditor .undo": "Rückgängig",
  ".german .title": "Titel",
  ".german .title-bar .bookmark": "Bookmark",
  ".german .title-bar .language": "Sprache",
  ".german .title-bar .logout": "Ausloggen",
  ".german .title-bar .share": "Share",
  ".german .title-bar .subscribe": "Abonnieren",
  ".german .title-bar .theme": "Thema",
  ".german .trash": "Abfalleimer", 
  ".german .um .message": "Nachricht",
  ".german .um .reply-button": "Abschicken",
  ".german .um .reply-text": "Antwort schreiben",
  ".german .um .to": "An",
  ".german .um .write-title": "Nachricht schreiben",
  ".german .username": "Benutzername: ",
  ".german .verify-delete": "Wirklich \"%1\" löschen?",
  ".german .whats-hot": "Angesagt",
  ".german .whats-new": "Neu",
  ".german .widget-window .close": "Schliessen",
 
 ".german .frontpage .slide1": "Dein eigener Desktop, mit allem was du für deine Inhalte benötigst",
".german .frontpage .slide2": "Hast du viele Dateien, die du teilen möchtest? Lade alles in einem Durchgang hoch.",
".german .frontpage .slide3": "Spiele Musik und Videos mit dem MySites-VideoPlayer, und zeige es deinen Freunden",
".german .frontpage .window1": "Gamer: Ladet eure Lan-Party Fotos, eure frag movies, Screenshots, Configs hoch und teile sie mit deinem Team!<br/><br/>Studenten: Speichert und veröffentlicht eure Dokumente, Präsentationen und Gruppenarbeiten.<br/><br/>Künstler: Speichere dein Portfolio. Spiele und veröffentliche deine Designs und Musik für deine Community",
".german .frontpage .window2": "MySites ist ein Ort für alle deine Online-Aktivitäten. Spiele, Uploade, veröffentliche Fotos, Music, Video und jedes andere Dokument mit den Menschen, die du selber wählst.<br/><br/>Unser Uploader ist einer der besten online! Du kannst hunderte von Dateien in einem Durchgang hochladen und siehst deren Status durch eine Fortschrittsanzeige.<br/><br/>MySites ist einfach zu bedienen! Du wirst in etwa so lange brauchen MySites zu beherrschen, wie es dauert dieses Fenster zu lesen!<br/><br/>Hast du private Fotos? Ferien-Fotoalben, Hausaufgaben? Mit MySites kannst du festlegen wer deine Dateien sehen darf!",
".german .frontpage .window3": "Sind du und deine Freunde bei Facebook?<br/><br/>Gut! Mit unserer Applikation kannst du die Inhalte, die du auf MySites hast, auf Facebook anzeigen und mit deinen Freunden teilen<br/><br/> ",
".german .frontpage .people-think": "Was andere sagen",
".german .frontpage .hot": "What\'s hot",
".german .frontpage .more-reviews": "mehr Zitate...",
".german .frontpage .read-more": "mehr lesen...",
".german .frontpage .why-use": "Warum MySites?",
".german .frontpage .features": "Funktionen",
".german .frontpage .fb-app": "Facebook App (Bald verfügbar!)",
".german .frontpage .about": "Über MySites",
".german .frontpage .developer": "Entwickler",
".german .frontpage .box1": "MySites vereinfacht dein Leben! Bist du Student, Gamer oder Künstler?", 
".german .frontpage .box2": "Nur ein Login, Desktop-Interface, Multi-Upload, einfaches veröffentlichen, Sicherheit und Privatsphäre, Audio+Video Player", 
".german .frontpage .box3": "Zeige deine MySites Inhalte auf Facebook. ", 
".german .frontpage .box4": "Erfahre mehr über uns: Finde heraus, wer hinter MySites steckt und was wir machen.", 
".german .frontpage .box5": "MySites verwendet führende Cloud-computing und Ajax-Technologien. Finde heraus, wie! ", 
".german .frontpage .media": "Medien &amp; Presse",
".german .frontpage .legal": "Rechtliche Informationen",
".german .frontpage .business": "Unternehmen",

".german .frontpage .close": "Schliessen",
".german .frontpage .recent-blog": "Letzte Blogeinträge",
".german .frontpage .recent-items": "Letzte Items",  

".german .frontpage .front-info": "MySites is a place to upload all your content and share it with the rest of the world.",


".german .recent-widget .posted": "gepostet",
".german .recent-widget .by": "von",
".german .frontpage .recent": "Letzte",
".german .registration .register-info": "Registrierungs-Info",
".german .registration .register-info-text": "Register now, it\'s FREE! Get 10Gb of space to share with the people you want. ",
".german .frontpage .connect-to-us": "Folge Uns",

 
".german .title-bar .my sites": "MySites",
".german .userpage .loading-please-wait": "(Loading content)",
".german .userpage .latest": "(Recent)",
".german .userpage .subscribe": "(Subscribe)",
".german .userpage .my-serv": "(My content)",
".german .userpage .buddies-serv": "(My buddies)",
".german .userpage .commu-serv": "(My communities)",
".german .userpage .me": "(Me)",
".german .userpage .popular": "(Popular)",
".german .userpage .recent": "(Recent)",
".german .userpage .public": "(Public)",
".german .userpage .userpage": "(Website)",
".german .userpage .favorites": "(Favorites)",
".german .recent-widget .todownload": "(Download)",
".german .recent-widget .toembed": "(Embed)",
".german .recent-widget .tomobile": "(Mobile code)",
".german .recent-widget .sendmsg": "(Send a message)",
".german .recent-widget .addbudd": "(Add to my buddies)",
 
".german .cloud .save_desc": "MySites give you reliable storage online for all your content.<br/><br/>Upload up to 10Gb of your music, photos, videos, files and documents.<br/><br/>Select who can see your content: only you, your friends or everyone.<br/><br/>We have a multiple file uploader, which allows your to upload hundreds of files in a few clicks!",
".german .cloud .save_txt": "save",
".german .cloud .play_desc": "Once your content is online, you can play it directly from there.<br/><br/>Not just your content, but anyone else\'s,  anywhere and at any time.<br/><br/>You can even use your mobile, your console, your desktop, anything that has a browser!",
".german .cloud .play_txt": "play",
".german .cloud .combine_desc": "Show all your online content in one place.<br/><br/>Already have content on YouTube, Twitter, Flickr, Delicious, Friendfeed or others? No problem!<br/><br/>You can show them all on MySites.",
".german .cloud .combine_txt": "combine",
".german .cloud .share_desc": "Once your content is on MySites, it's easy to share.<br/><br/>Share with the people you want, using our permission system.<br/>Send it on Facebook, Twitter.<br/>Save it on Delicious, StumbleUpon, Digg, Reddit and more.<br/>Embed it to your blog and forum.<br/>Link it for direct download on your site.<br/>Share it with mobile QR codes.<br/>",
".german .cloud .share_txt": "share",
".german .cloud .social_desc": "Comment, tag, share any content.<br/><br/>Find the latest musics from your friends, or the most popular ones.",
".german .cloud .social_txt": "social",
".german .cloud .blog_desc": "You can embed or make direct links to any content  hosted on MySites, so you don\'t have to worry about storage for your site.",
".german .cloud .blog_txt": "blog",
".german .cloud .mobile_desc": "(COMING SOON!)<br/><br/>MySites can be used on your mobile too!<br/><br/>Take all your content with you anywhere, including from other websites, to show it to others.<br/><br/>Browse the content from your friends and communities on the go.<br/><br/>Upload content directly from your mobile.<br/><br/>Share content using mobile QR codes.",
".german .cloud .mobile_txt": "mobile",
".german .cloud .community_desc": "(COMING SOON!)<br/><br/>Create a site where you and others can save content together.<br/><br/>You have a hobby you share with your friends? Share its content easily with them!",
".german .cloud .community_txt": "community",
".german .cloud .privacy_desc": "You own your content.<br/><br/>You can decide with whom to share it, or where to share it.<br/><br/>You can remove your content at any time, we won\'t keep copies.<br/><br/>we\'re just here to help you save and share your things more easily.",
".german .cloud .privacy_txt": "privacy", 
  
".german": "Dummy entry that comes last to avoid syntax errors when sorting"
})
 
 
 // Finnish
Object.extend(translations, {
  ".finnish .about-me": "Jotain minusta",
  ".finnish .action-widget .add-folder": "Lisää kansio",
  ".finnish .action-widget .add-item": "Lisää %1",
  ".finnish .action-widget .empty-trash": "Tyhjennä Roskakori",
  ".finnish .action-widget .no-permission": "Et voi lisätä kohteita tähän",
  ".finnish .actions": "Toiminnot",
  ".finnish .add-as-buddy": "Lisää %1 kaveriksi?",
  ".finnish .add-buddy-widget .add-as-buddy": "Lisää kaveriksi",
  ".finnish .added-by": "Lisännyt %1 %2", 
  ".finnish .badge .join-now": "Liity nyt!",
  ".finnish .badge .login-or-register": "Kirjaudu sisään tai rekisteröidy",
  ".finnish .badge .no-message": "Tämä käyttäjä ei ole päivittänyt tilaansa vielä",
  ".finnish .badge .read .message": "Viesti",
  ".finnish .badge .read .send-message": "Lähetä viesti",
  ".finnish .badge .read .send-message-button": "Lähetä",
  ".finnish .badge .write-message": "Päivitä tilasi",
  ".finnish .buddies": "Kaverit",
  ".finnish .buddies-widget .add-button": "Lisää",
  ".finnish .buddies-widget .back": "Takaisin kaverilistaan",
  ".finnish .buddies-widget .input": "Kirjoita käyttäjätunnus tähän",
  ".finnish .buddy": "Kaveri",
  ".finnish .cancel": "Peruuta",
  ".finnish .comments .send-comment": "Kommentoi!",
  ".finnish .comments .window-title": "Kommentteja %1",
  ".finnish .comments .write-comment": "Kirjoita kommentti",
  ".finnish .comments": "Kommentoi tätä",
  ".finnish .conversation-view .delete-msg": "Oletko varma että haluat poistaa tämän viestin?",
  ".finnish .creation .fields .description": "Kuvaus",
  ".finnish .creation .fields .tags .example": "(Esimerkki: minä, loma, kreikka)",
  ".finnish .creation .fields .tags": "Tunnisteet",
  ".finnish .creation .new .folder": "Lisää uusi kansio",
  ".finnish .creation .new": "Lisää uusi %1",
  ".finnish .creation .permission .allbuddies": "Kaikki kaverini",
  ".finnish .creation .permission .everyone": "Kaikki",
  ".finnish .creation .permission .onlyme": "Vain minä",
  ".finnish .creation .permission": "Kuka voi nähdä tämän %1?",
  ".finnish .date .day":     "päivä",
  ".finnish .date .days":    "päivää",
  ".finnish .date .hour":    "tunti",
  ".finnish .date .hours":   "tuntia",
  ".finnish .date .just-now": "tällä hetkellä",
  ".finnish .date .minute":  "minuutti",
  ".finnish .date .minutes": "minuuttia",
  ".finnish .date .second":  "sekunti",
  ".finnish .date .seconds": "sekuntia",
  ".finnish .date .year":    "vuosi",
  ".finnish .date .years":   "vuotta",
  ".finnish .date": "%1 %2 sitten",
  ".finnish .dbw .blog .l1": " lisäsi päivityksen %1",
  ".finnish .dbw .l1": " Lisätty kohteeseen %1 <a style='color:black;' href='%2'>%3</a> %4",
  ".finnish .dbw .l2": "<a href='%1' style='font-weight:bolder;color:black;'>%2</a> in <a href='%3' style='font-weight:bolder;color:black;'>%4</a>",
  ".finnish .desktop-widget .dbl-select": "Tuplaklikkaa valitaksesi kohde",
  ".finnish .desktop-widget .double-click": "Tuplaklikkaus",
  ".finnish .desktop-widget .open-windows-on": "Avaa kuvakkeet",
  ".finnish .desktop-widget .rm-bg": "Klikkaa tästä poistaaksesi taustakuva",
  ".finnish .desktop-widget .select-image": "Valitse kuva",
  ".finnish .desktop-widget .set-desktop-bg": "Aseta työpöydän taustakuva",
  ".finnish .desktop-widget .single-click": "kerta klikkaus",
  ".finnish .display": "Vaihda näkymää:", 
  ".finnish .everyone": "Kaikki",
  ".finnish .explorer .listview .added_at": "Aika milloin lisätty",
  ".finnish .explorer .listview .name": "Nimi",
  ".finnish .explorer .listview .rating": "Äänet",
  ".finnish .explorer .listview .type": "Tyyli",
  ".finnish .explorer .sort-by": "Järjestä",
  ".finnish .explorer .thumbsview .added_at": "Aika",
  ".finnish .explorer .thumbsview .name": "Nimi",
  ".finnish .explorer .thumbsview .rating": "Äänet",
  ".finnish .explorer .thumbsview .type": "Tyyli",
  ".finnish .explorer .tooltip .details": "yksityiskohdat:",
  ".finnish .info .content": "MySites tukee <strong>tuplaklikkausta</strong> kuten tietokoneesi. Kokeile!",
  ".finnish .info .title": "Info",
  ".finnish .inline-edit .cancel": "Peruuta",
  ".finnish .inline-edit .save": "Tallenna",
  ".finnish .invitation-widget .1-selected": "1 yhteystieto valittu",
  ".finnish .invitation-widget .back": "Takaisin",
  ".finnish .invitation-widget .continue": "Jatka",
  ".finnish .invitation-widget .de/select-all": "Valitse kaikki/Poista valinta kaikista",
  ".finnish .invitation-widget .get-ab": "Nouda yhteystiedot",
  ".finnish .invitation-widget .invite-btn": "Kutsu",
  ".finnish .invitation-widget .invited-x": "Kutsuttu %1 (<%2>)",
  ".finnish .invitation-widget .loading-contacts": "Ladataan yhteystietoja ...",
  ".finnish .invitation-widget .n-selected": "%1 yhteystietoa valittu",
  ".finnish .invitation-widget .password": "Salasana",
  ".finnish .invitation-widget .sent-1": "Lähetä 1 kutsu",
  ".finnish .invitation-widget .sent-all": "Lähetä kaikki kutsut",
  ".finnish .invitation-widget .sent-n": "Lähetä %1 kutsua",
  ".finnish .invitation-widget .service": "Palvelu",
  ".finnish .invitation-widget .username": "Käyttäjänimi/Sähköposti",
  ".finnish .invitation-widget .write-msg": "Kirjoita henkilökohtainen viesti",
  ".finnish .invitation-widget .wrong-data": "Käyttäjänimi tai salasana on väärä!",
  ".finnish .iteminfo .actions-title": "Toiminnot",
  ".finnish .iteminfo .added-by": "Lisännyt",
  ".finnish .iteminfo .click-to-add-tags": "Lisää tunniste",
  ".finnish .iteminfo .delete-action": "Poista",
  ".finnish .iteminfo .edit-action": "Muokkaa",
  ".finnish .iteminfo .info-title": "Info",
  ".finnish .iteminfo .name": "Nimi",
  ".finnish .iteminfo .open-action": "Avaa %1",
  ".finnish .iteminfo .privacy": "Yksityisyys",
  ".finnish .iteminfo .rate": "Arvioi",
  ".finnish .iteminfo .report-action": "Raportoi %1",
  ".finnish .iteminfo .restore-action": "Palauta",
  ".finnish .iteminfo .share-title": "Jaa",
  ".finnish .iteminfo .tags": "Tunnisteet",
  ".finnish .keyword-search .clear": "tyhjennä",
  ".finnish .keyword-search .go": "mene",
  ".finnish .keyword-search .listview .count": "Tulos",
  ".finnish .keyword-search .listview .tag": "avainsana",
  ".finnish .link": "Linkki",
  ".finnish .login": "kirjaudu sisään",
  ".finnish .logout": "kirjaudu ulos",
  ".finnish .Master::InvalidUserOrPassword": "epäkelpo salasana",
  ".finnish .Master::PasswordEmpty": "tyhjä salasana",
  ".finnish .Master::PasswordTooShort": "Salasanassa tulee olla vähintään 6 merkkiä",
  ".finnish .me": "Minä",
  ".finnish .messages-widget .conv-title": "Keskustelee käyttäjän %1 kanssa",
  ".finnish .messages-widget .date": "Päivämäärä",
  ".finnish .messages-widget .delete-conversation": "Haluatko varmasti poistaa tämän keskustelun?",
  ".finnish .messages-widget .message": "Viesti",
  ".finnish .messages-widget .no-messages": "Ei viestejä",
  ".finnish .messages-widget .private": "yksityinen",
  ".finnish .messages-widget .sender": "Lähettäjä",
  ".finnish .messages-widget .write-all-users": "Kirjoita kaikille käyttäjille",
  ".finnish .messages-widget .write-message": "Kirjoita viesti",
  ".finnish .min-rating": "Vähintään ääntä:", 
  ".finnish .mlw .back": "Takaisin viesteihin",
  ".finnish .mlw .date": "Päivämäärä",
  ".finnish .mlw .from": "Keneltä",
  ".finnish .mlw .message": "Viesti",
  ".finnish .mlw .write": "Kirjoita viesti",
  ".finnish .navi-tree-widget .back": "palaa",
  ".finnish .navi-tree-widget .navigation": "Navigointi",
  ".finnish .navi-tree-widget .services": "Palvelut",
  ".finnish .navi-tree-widget .show-navigation": "Takaisin navigointiin",
  ".finnish .navi-tree-widget .show-services": "Vaihda palvelulistaan",
  ".finnish .navi-tree-widget .trash": "Roskakori",
  ".finnish .no-explorer-items": "Sinulla ei ole %1/kansioita tällä hetkellä.<BR>Haluatko lisätä <a href='#' onclick='%3'>%2</a> or a <a href='#' onclick='%5'>%4</a>?", 
  ".finnish .no-explorer-items-no-folder": "Sinulla ei ole %1 tällä hetkellä.<BR>Haluatko lisätä <a href='#' onclick='%3'>%2</a>?", 
  ".finnish .ok": "OK",
  ".finnish .password": "salasana: ",
  ".finnish .PasswordsDontMatch": "Salasana ei täsmää!",
  ".finnish .permalink": "linkki",
  ".finnish .permission": "Käyttöoikeus",
  ".finnish .pictures .large-files": "Suurilla tiedostoilla voi kestää kauemmin ilmestyä tiedostolistaasi",
  ".finnish .privacy": "Yksityisyys:", 
  ".finnish .profiles .birthday": "Syntymäpäiväsi",
  ".finnish .profiles .cancel-change-pwd-button": "Peruuta",
  ".finnish .profiles .change-password": "Vaihda salasana", 
  ".finnish .profiles .change-picture": "Vaihda kuva",
  ".finnish .profiles .change-pwd-button": "Vaihda salasana",
  ".finnish .profiles .city": "Kaupunki",
  ".finnish .profiles .country": "Maa",
  ".finnish .profiles .email": "Sähköposti",
  ".finnish .profiles .full-name": "Koko nimi",
  ".finnish .profiles .new-password": "Uusi salasana",
  ".finnish .profiles .new-password-confirmation": "Varmista uusi salasana",
  ".finnish .profiles .old-password": "Vanha salasana",
  ".finnish .profiles .privacy": "MySites arvostaa yksityisyyttäsi.<br />Toisin kuin muut sivustot, kaikki lisäämäsi tiedot ovat valinnaisia.<br />Sinä päätät mitä tietoja haluat jakaa.",
  ".finnish .profiles .save": "Tallenna", 
  ".finnish .profiles .upload-picture": "Lataa uusi profiilikuva",
  ".finnish .registration .captcha": "Captcha",
  ".finnish .registration .captcha-expl": "Kirjoita yllä oleva teksti",
  ".finnish .registration .email": "Sähköposti",
  ".finnish .registration .errors .accept-terms": "Sinun on hyväksyttävä käyttäjäehdot!",
  ".finnish .registration .errors .EmailAddressAlreadyUsed": "Sähköpostiosoite on jo käytössä!",
  ".finnish .registration .errors .FullNameIncomplete": "Kirjoita koko nimesi!",
  ".finnish .registration .errors .FullNameTooShort": "Koko nimi on liian lyhyt!",
  ".finnish .registration .errors .InvalidCaptcha": "Invalid captcha!",
  ".finnish .registration .errors .InvalidEmailAddress": "Epäkelpo sähköpostiosoite!",
  ".finnish .registration .errors .InvalidUsername": "Epäkelpo käyttäjätunnus, sallitut merkit ovat a-z, 0-9, ja -",
  ".finnish .registration .errors .PasswordsDontMatch": "Salasana ei täsmää!",
  ".finnish .registration .errors .PasswordTooShort": "Salasanassa tulee olla vähintään 6 merkkiä!",
  ".finnish .registration .errors .UsernameAlreadyTaken": "Käyttäjätunnus on jo käytössä!",
  ".finnish .registration .errors .UsernameTooShort": "Käyttäjätunnus on liian lyhyt !",
  ".finnish .registration .full-name": "Koko nimi",
  ".finnish .registration .help .email": "Anna toimiva sähköpostiosoite, että voimme ottaa sinuun yhteyttä tai lähettää tiedotteita tarvittaessa.",
  ".finnish .registration .help .full_name": "Kirjoita koko nimesi (esim. Matti Tainio)",
  ".finnish .registration .help .password": "Valitse salasana. Varmista että muistat salasanasi!",
  ".finnish .registration .help .password_confirmation": "Syötä uudelleen salasanasi.",
  ".finnish .registration .help .start": "Luodaan käyttäjätilisi! Hyvin pian, olet valmis käyttämään sivustoa. Aloita valitsemalla käyttäjätunnus!",
  ".finnish .registration .help .username": "Ihmiset menevät osoitteeseen http://yourname.mysites.com nähdäkseen mitä haluat heille näyttää, joten valitse hyvä nimi! Huomioi myös että nimen on oltava vähintään 4 merkin pituinen, ja käytä vain merkkejä a-z 0-9.",
  ".finnish .registration .info": "Aloitetaan MySites-sivuston käyttäminen!<br/><br/>Kun olet täyttänyt tietosi, klikkaa MySites-logoa tai <q>home</q><img src=\"%1\" style=\"%2\"/> kuvaketta siirtyäksesi työpöydällesi!",
  ".finnish .registration .join": "%1 kutsuu sinut käyttämään MySites-sivustoa",
  ".finnish .registration .login": "Kirjaudu sisään",
  ".finnish .registration .logout-first": "Sinun on kirjauduttava ulos ennen kuin voit luoda uuden käyttäjätilin!",
  ".finnish .registration .new-captcha": "Click for new captcha.",
  ".finnish .registration .password": "Salasana",
  ".finnish .registration .password_confirmation": "Kirjoita salasana uudelleen",
  ".finnish .registration .register": "Rekisteröidy",
  ".finnish .registration .repeat-password": "Kirjoita salasana uudelleen",
  ".finnish .registration .terms": "Hyväksyn <a href=\"%1\" target=\"_blank\">käyttäjäehdot</a>",
  ".finnish .registration .thanks": "Kiitos rekisteröitymisestä! Kirjaudu sisään jatkaaksesi.",
  ".finnish .registration .username": "Käyttäjänimi",
  ".finnish .related-links": "Aiheeseen liittyviä linkkejä", 
  ".finnish .save": "Tallenna",
  ".finnish .search .input": "Haku (beta)",
  ".finnish .selected-items": "Valitut kohteet", 
  ".finnish .selection .zero-items-selected": "0 kohdetta valittu",
  ".finnish .service-list-widget .add-remove-services": "Lisää/poista palvelu",
  ".finnish .service-list-widget .back-to-services": "Takaisin palveluihin",
  ".finnish .service-list-widget .click-to-add": "Muut palvelut. Klikkaa lisätäksesi.",
  ".finnish .service-list-widget .click-to-remove": "Omat palvelut. Klikkaa poistaaksesi.",
  ".finnish .service-list-widget .latest": "Viimeisin %1",
  ".finnish .service-list-widget .reset": "Nollaa",
  ".finnish .share .bookmark-item": "Lisää kohde kirjanmerkkeihin!",
  ".finnish .share .copied-to-clipboard": "Kopioitu leikepöydälle",
  ".finnish .share .download": "Lataa",
  ".finnish .share .send-to-mobile": "Lähetä kännykkään",
  ".finnish .share .share-item": "Jaa tämä %1",
  ".finnish .share .share-url": "Jaa osoite",
  ".finnish .share": "Jaa",
  ".finnish .start-page .preferences .number-of-items": "Kohteiden lukumäärä rivillä",
  ".finnish .start-page .preferences": "Asetukset",
  ".finnish .successfully-saved": "Tallennus onnistui!",
  ".finnish .successfully-saved-password": "Salasanan tallennus onnistui!",
  ".finnish .tags": "Merkinnät:", 
  ".finnish .texteditor .edit-text": "Muokkaa tekstiä",
  ".finnish .texteditor .link": "luo linkki",
  ".finnish .texteditor .picture": "lisää kuva",
  ".finnish .texteditor .redo": "Tee uudelleen",
  ".finnish .texteditor .text-color": "tekstin väri",
  ".finnish .texteditor .undo": "Kumoa",
  ".finnish .title": "Otsikko",
  ".finnish .title-bar .bookmark": "Kirjanmerkki",
  ".finnish .title-bar .language": "kieli",
  ".finnish .title-bar .logout": "kirjaudu ulos",
  ".finnish .title-bar .share": "Jaa",
  ".finnish .title-bar .subscribe": "Tilaa",
  ".finnish .title-bar .theme": "teema",
  ".finnish .trash": "Roskakori", 
  ".finnish .um .message": "Viesti",
  ".finnish .um .reply-button": "Lähetä",
  ".finnish .um .reply-text": "Kirjoita vastaus tähän",
  ".finnish .um .to": "Kenelle",
  ".finnish .um .write-title": "Kirjoita viesti",
  ".finnish .username": "käyttäjätunnus: ",
  ".finnish .verify-delete": "Haluatko varmasti poistaa \"%1\"?",
  ".finnish .whats-hot": "Kuuminta hottia",
  ".finnish .whats-new": "Uusimmat",
  ".finnish .widget-window .close": "sulje",


".finnish .frontpage .slide1": "Valitse oma työpöytäsi, juuri sillä sisällöllä mitä itse haluat",
".finnish .frontpage .slide2": "Onko sinulla paljon tiedostoja jaettavana? Voit ladata ne kaikki kerralla!",
".finnish .frontpage .slide3": "Soita musiikkia ja videoita MySites-soittimella,   voit myös jakaa ne kavereidesi kanssa",
".finnish .frontpage .window1": "Gamers: Upload your Lan party photos, your frag movies, screenshots, configs and share them with your team and more!<br/><br/>Students: Save and share documents, presentations and group work.<br/><br/>Artists: Save your portfolio. Play and share your designs and music with your community! ",
".finnish .frontpage .window2": "MySites is a single place for all your content. View, upload, share photos, music, video, and any kind of document with the people you want.<br/><br/>Our uploader is among the best online! You can upload hundreds of files in one go, with a progress bar to know how much time is left.<br/><br/>MySites looks and feels like your desktop computer. You\'ll learn how to use it in about as much time as it takes to read this window!<br/><br/>Got private photos? Holiday albums, school work? You can set the privacy on anything you upload on MySites. ",
".finnish .frontpage .window3": "Are you (and your friends) on Facebook?<br/><br/>Good! With our application, you\'ll be able to take the content you\'ve already uploaded on MySites, and share it on Facebook.<br/><br/>Post content on your friends\' walls, or your own, post music, photos, or anything you want!",
 ".finnish .frontpage .window4": "A long time ago, in a cold and remote place, lived a French exchange student.<br/><br/>As an exchange student, he made new friends, he would often party, and when he thought of home, he would want to write about his thoughts and share them with his peers. In school, he would always forget his USB memory or send himself attachments by email. And at home, hed play Counterstrike with his team and lament the fact that it was difficult to set up a decent website for them.<br/><br/>Back in those days, Web 2.0? and Social Networking sites were the new black. New sites came every day, promising to solve his problems. But none did. More and more spam (sorry, invites) asked him to register to silly websites with stupid names, just so that he could do one thing.<br/><br/>For every YourTube and MyFace, there would be another link, another login. Another set of friends to invite, another interface.<br/><br/>Different functionality, no interoperability. No localizations, no centralization.<br/><br/>This burden became too much for him.<br/><br/>The Finnish winter can be long and dark, but forges the will. In his room, he started sketching for ways to put his content online for others in a sensible way.<br/><br/>As he consumed the equivalent of a small forests worth in paper, he realized one pattern. That everything online could be mastered through a single interface, with the same tools. Just like on your computer. But online! All you would need to create is ways to manage that data differently.<br/><br/>With this idea in mind, he went to look for brave and bold men who would be crazy enough to build it. He found experts in Ruby on Rails and scaling, who believed in his vision of recycling, optimization and openness.<br/><br/>And so, their two year journey to create a new way for people to be online began.<br/><br/>As he was still a student, the task seemed unfeasable.<br/>He begged from his dad Why do you want to spend money rather than have a job and make some?!.<br/>He received support from the Finnish government to build his company.<br/>He ate large quantities of pasta (the Finnish equivalent of Ramen noodles).<br/>He found a nice domain name.<br/>And eventually, after extensive bootstrapping, he found private investors to help. <br/>",
".finnish .frontpage .window5": "MySites is a real platform, similar to Google App Engine or Windows Live, which can support any type of web service, in any language, and on any device.<br/><br/>We put a strong emphasis on technology, in order to deliver the best experience we can to our users.<br/><br/>We use a single login to connect you through many different domains and services (similarly to Passport or Facebook Connect).<br/><br/>Our multiple upload is amongst the best and fastest on the web.<br/><br/>We have created a new use for Ajax, by putting a strong focus on client-side processing, which helps both lower latency and for the users and improve our server performance.<br/><br/>MySites features a fully custom built IDE, which enables our team to create new Internet services at record speeds, by recycling code and interlinking our existing services.<br/><br/>We will fully open our tools, together with a read/write API very soon. Please feel free to contact us if you are interested!<br/><br/>MySites is designed to run on any type of device. During the coming months, we will deploy more services for all types of devices and OSes.<br/><br/>", 
".finnish .frontpage .people-think": "Mitä mieltä ihmiset ovat",
".finnish .frontpage .hot": "Mikä on kuuminta",
".finnish .frontpage .more-reviews": "enemmän arvosteluja...",
".finnish .frontpage .read-more": "lue lisää...",
".finnish .frontpage .why-use": "Miksi käyttää MySites-sivustoa",
".finnish .frontpage .features": "Ominaisuudet",
".finnish .frontpage .fb-app": "Facebook Sovellus (Tulossa pian!)",
".finnish .frontpage .about": "Yleisesti MySites-sivustosta",
".finnish .frontpage .developer": "Kehittäjät",
".finnish .frontpage .box1": "MySites voi helpottaa elämääsi! Oletko pelaaja, opiskelija, taiteilija?", 
".finnish .frontpage .box2": "Yhdellä sisäänkirjautumisella, tietokoneen työpöydän kaltainen käyttöliittymä, ison tiedosto määrän lataaminen, helppo jakaminen, yksityisyys, ääni+video soittimet", 
".finnish .frontpage .box3": "Näytä MySites-sisältösi Facebook:ssa! Soita ja jaa musiikkia, videoita ja paljon muuta.", 
".finnish .frontpage .box4": "MySites on Eurooppalainen 3 vuotta sitten Suomessa aloitettu sivusto. Meillä on rahoitus ja olemme keränneet parhaat alan osaajat.", 
".finnish .frontpage .box5": "Käytämme client-side prosessointia, rakensimme oman IDE:n, cluster, and replaced Rails. API is coming soon. Find out more!", 
".finnish .frontpage .media": "Media &amp; Lehdistö",
".finnish .frontpage .legal": "Lainmukainen informaatio",
".finnish .frontpage .business": "Yritykset",
".finnish .frontpage .close": "Sulje",
".finnish .frontpage .recent-blog": "Tuoreimmat blogi kirjoitukset",
".finnish .frontpage .recent-items": "Tuoreimmat kohteet",


 ".finnish .title-bar .my sites": "MySites",
".finnish .userpage .loading-please-wait": "(Ladataan sisältöä)",
".finnish .userpage .latest": "(Tuorein)",
".finnish .userpage .subscribe": "(Tilaa)",
".finnish .userpage .my-serv": "(Oma sisältö)",
".finnish .userpage .buddies-serv": "(Kaverit)",
".finnish .userpage .commu-serv": "(Omat yhteisöt)",
".finnish .userpage .me": "(Minä)",
".finnish .userpage .popular": "(Suosittu)",
".finnish .userpage .recent": "(Tuorein)",
".finnish .userpage .public": "(Yleinen)",
".finnish .userpage .userpage": "(Sivusto)",
".finnish .userpage .favorites": "(Suosikit)",
".finnish .recent-widget .todownload": "(Lataa)",
".finnish .recent-widget .toembed": "(Liitetty)",
".finnish .recent-widget .tomobile": "(Kännykkä-koodi)",
".finnish .recent-widget .sendmsg": "(Lähetä viesti)",
".finnish .recent-widget .addbudd": "(Lisää kavereihini)",
 
 ".finnish .cloud .save_desc": "MySites tarjoaa netissä luotettavaa säilytystilaa kaikille tiedostoillesi.<br/><br/>Lataa 10 gigatavun edestä, kuvia, videoita, tiedostoja ja dokumentteja.<br/><br/>Valitse kuka pääsee näkemään sisällön: vain sinä, kaverisi tai kaikki.<br/><br/>Meillä voit ladata satoja tiedostoja kerralla, vain muutamalla klikkauksella!",
".finnish .cloud .save_txt": "tallenna",
".finnish .cloud .play_desc": "Kun tiedostosi on ladattu sivustollemme, voit suorittaa ne suoraan sieltä.<br/><br/>Voit myös avata ja soittaa muiden tiedostoja, missä vain ja milloin vain<br/><br/>Voit käyttää mitä tahansa laitetta missä on nettiselain, kännykkää, pelikonsolia, tietokonetta jne!",
".finnish .cloud .play_txt": "soita",
".finnish .cloud .combine_desc": "Jaa kaikki tiedostosi samassa paikassa.<br/><br/>Onko sinulla jo sisältöä Youtube:ssa, Twitter:ssa, Flickr:ssa, Delicious:ssa, Friendfeed:ssa tai jossain muualla? Ei hätää!<br/><br/>Voit jakaa ne kaikki MySites-sivustolla.",
".finnish .cloud .combine_txt": "yhdistä",
".finnish .cloud .share_desc": "Kun tiedostosi on MySites-sivustolla, niitä on helppo jakaa.<br/><br/>jaa niille ihmisille kenelle haluat.<br/>Lähetä Facebook:iin, Twitter:iin.<br/>Tallenna Delicious:iin, StumbleUpon:iin, Digg:iin, Reddit:iin ja moneen muuhun.<br/>Liitä blogiin ja keskustelualueille.<br/>Linkitä suoraan sivustollesi.<br/>Jaa mobiililaitteisiin QR-koodeilla.<br/>",
".finnish .cloud .share_txt": "jaa",
".finnish .cloud .social_desc": "Kommentoi, merkitse, jaa mikä tahansa tiedosto.<br/><br/>Etsi uusimmat musiikki kappaleet kavereiltasi, tai kaikkein suosituimmat.",
".finnish .cloud .social_txt": "sosiaalisuus",
".finnish .cloud .blog_desc": "Voit liittää tai linkittää mitä tahansa MySites-sivustolle lataamaasi sisältöä, joten sinun ei tarvitse miettiä oman sivustosi tila kapasiteettia.",
".finnish .cloud .blog_txt": "blogi",
".finnish .cloud .mobile_desc": "(TULOSSA PIAN!)<br/><br/>MySites-sivustoa voi käyttää myös kännykässä!<br/><br/>Ota kaikki tiedostosi mukaan minne vain, ja voit esitellä niitä kaikille.<br/><br/>Selaa kavereidesi tiedostoja tai omiasi kun olet matkalla.<br/><br/>Lataa tiedostoja suoraan kännykästäsi.<br/><br/>Jaa tiedostoja käyttäen mobiileja QR-koodeja.",
".finnish .cloud .mobile_txt": "kännykkä",
".finnish .cloud .community_desc": "(TULOSSA PIAN!)<br/><br/>Luo sivusto jossa voitte jakaa tiedostoja kavereiden kanssa.<br/><br/>Onko sinulla ja kavereillasi yhteinen harrastus? Jaa harrastukseen liittyviä tiedostoja helposti heidän kanssaan!",
".finnish .cloud .community_txt": "yhteisö",
".finnish .cloud .privacy_desc": "Sinä omistat sisältösi.<br/><br/>Sinä voit päättää kenelle ja missä tiedostosi haluat jakaa.<br/><br/>Voit poistaa tiedostosi koska vain, me emme pidä niistä kopioita.<br/><br/>me olemme täällä vain helpottaaksemme tiedostoidesi tallennusta ja jakamista.",
".finnish .cloud .privacy_txt": "yksityisyys", 
 
 
".finnish": "Dummy entry that comes last to avoid syntax errors when sorting"
})
 
// The rest (for now)
Object.extend(translations, {
  ".croatian .keyword-search .listview .count": "Broj",
  ".croatian .keyword-search .listview .tag": "Kljuèna rijeè",
  ".croatian .explorer .listview .name": "Ime",
  ".croatian .explorer .listview .type": "Tip",
  ".croatian .explorer .listview .rating": "Ocjena",
  ".croatian .explorer .listview .added_at": "Vrijeme dodavanja",
  ".croatian .explorer .thumbsview .name": "Ime",
  ".croatian .explorer .thumbsview .type": "Tip",
  ".croatian .explorer .thumbsview .rating": "Ocjena",
  ".croatian .explorer .thumbsview .added_at": "Vrijeme dodavanja",
  ".croatian .logout": "Izlogirajte se",
  ".croatian .login": "Logirajte se",
  ".croatian .username": "Korisnièko ime: ",
  ".croatian .password": "Lozinka: ",
  ".croatian .keyword-search .go": "Idi",
  ".croatian .keyword-search .clear": "Izbriši",
  ".croatian .selection .zero-items-selected": "0 stavki oznaèeno",
  ".croatian .title-bar .logout": "Izlogirajte se",
  ".croatian .title-bar .language": "Logirajte se",
  ".croatian .title-bar .theme": "Tema",
  ".croatian .explorer .tooltip .details": "detalji:",
  ".croatian .added-by": "Dodano %1 po %2", 
  ".croatian .privacy": "Privatnost:", 
  ".croatian .tags": "Kljuène rijeèi:", 
  ".croatian .display": "Prikaz:", 
  ".croatian .selected-items": "Oznaèene stavke", 
  ".croatian .permalink": "Link",
  ".croatian .no-explorer-items": "Trenutno ne postoje%1/Datoteke.<BR>Želite li dodati <a href='#' onclick='%3'>%2</a> ili <a href='#' onclick='%5'>%4</a>?", 
  ".croatian .no-explorer-items-no-folder": "Trenutno %1 ne postoje.<BR>Želite li dodati <a href='#' onclick='%3'>%2</a>?", 
  ".croatian .min-rating": "Minimalna Ocjena:", 
  ".croatian .trash": "Smeæe", 
  ".croatian .title-bar .share": "Dijeljeno",
  ".croatian .title-bar .bookmark": "Oznaka",
  ".croatian .title-bar .subscribe": "Pretplatite se",
  ".croatian .related-links": "Srodni Linkovi", 

/*
  ".finnish .keyword-search .listview .count": "Määrä",
  ".finnish .keyword-search .listview .tag": "Hakusana",
  ".finnish .explorer .listview .name": "Nimi",
  ".finnish .explorer .listview .type": "Tyyppi",
  ".finnish .explorer .listview .rating": "Laatu",
  ".finnish .explorer .listview .added_at": "Lisätty",
  ".finnish .explorer .thumbsview .name": "Nimi",
  ".finnish .explorer .thumbsview .type": "Tyyppi",
  ".finnish .explorer .thumbsview .rating": "Laatu",
  ".finnish .explorer .thumbsview .added_at": "Lisätty",
  ".finnish .logout": "kirjaudu ulos",
  ".finnish .login": "kirjaudu sisään",
  ".finnish .username": "käyttäjätunnus: ",
  ".finnish .password": "salasana: ",
  ".finnish .keyword-search .go": "hae",
  ".finnish .keyword-search .clear": "tyhjennä",
  ".finnish .selection .zero-items-selected": "0 tiedostoa valittu",
  ".finnish .title-bar .logout": "kirjaudu ulos",
  ".finnish .title-bar .language": "kieli",
  ".finnish .title-bar .theme": "teema",
  ".finnish .explorer .tooltip .details": "tiedot:",
  ".finnish .added-by": "Lisännyt %1 käyttäjä %2", 
  ".finnish .privacy": "Käyttöoikeus:", 
  ".finnish .tags": "Tagit:", 
  ".finnish .display": "Näytä:", 
  ".finnish .selected-items": "Valitut Tiedostot", 
  ".finnish .permalink": "suora linkki",
  ".finnish .no-explorer-items": "Sivulla ei ole %1/kansioita tällä hetkellä.<BR>Haluaisitko lisätä <a href='#' onclick='%3'>%2</a> tai <a href='#' onclick='%5'>%4</a>?", 
  ".finnish .no-explorer-items-no-folder": "Sivulla ei ole %1 tällä hetkellä.<BR>Haluaisitko lisätä <a href='#' onclick='%3'>%2</a>?", 
  ".finnish .min-rating": "Minimilaatu:", 
  ".finnish .trash": "Roskis", 
  ".finnish .title-bar .share": "Jaa",
  ".finnish .title-bar .bookmark": "Lisää suosikkeihin",
  ".finnish .title-bar .subscribe": "Tilaa",
  ".finnish .related-links": "Vastaavat linkit", 

*/
  ".french .keyword-search .listview .count": "Resultats",
  ".french .keyword-search .listview .tag": "Mot-Clef",
  ".french .explorer .listview .name": "Nom",
  ".french .explorer .listview .type": "Type",
  ".french .explorer .listview .rating": "Note",
  ".french .explorer .listview .added_at": "Date ajoutee",
  ".french .explorer .thumbsview .name": "Nom",
  ".french .explorer .thumbsview .type": "Type",
  ".french .explorer .thumbsview .rating": "Note",
  ".french .explorer .thumbsview .added_at": "Date ajoutee",
  ".french .logout": "Deconnecter",
  ".french .login": "Connecter",
  ".french .username": "Pseudonyme: ",
  ".french .password": "Mot de pagge: ",
  ".french .keyword-search .go": "go",
  ".french .keyword-search .clear": "Effacer",
  ".french .selection .zero-items-selected": "0 fichiers selectionnes",
  ".french .title-bar .logout": "Deconnecter",
  ".french .title-bar .language": "langue",
  ".french .title-bar .theme": "theme",
  ".french .explorer .tooltip .details": "details:",
  ".french .added-by": "Ajoute le %1 par %2", 
  ".french .privacy": "Privacy:", 
  ".french .tags": "Tags:", 
  ".french .display": "Display:", 
  ".french .selected-items": "Selected Items", 
  ".french .permalink": "permalink",
  ".french .no-explorer-items": "There are no %1/folders at the moment.<BR>Would you like to add a <a href='#' onclick='%3'>%2</a> or a <a href='#' onclick='%5'>%4</a>?", 
  ".french .no-explorer-items-no-folder": "There are no %1 at the moment.<BR>Would you like to add a <a href='#' onclick='%3'>%2</a>?", 
  ".french .min-rating": "Min Rating:", 
  ".french .trash": "Trash", 
  ".french .title-bar .share": "Share",
  ".french .title-bar .bookmark": "Bookmark",
  ".french .title-bar .subscribe": "Subscribe",
  ".french .related-links": "Related Links", 
  
 ".french .title-bar .my sites": "MySites",
".french .userpage .loading-please-wait": "(Chargement)",
".french .userpage .latest": "(Récent)",
".french .userpage .subscribe": "(S'abonner)",
".french .userpage .my-serv": "(Mon contenu)",
".french .userpage .buddies-serv": "(Mes amis)",
".french .userpage .commu-serv": "(Mes communautés)",
".french .userpage .me": "(Moi)",
".french .userpage .popular": "(Populaire)",
".french .userpage .recent": "(Récent)",
".french .userpage .public": "(Public)",
".french .userpage .userpage": "(Site)",
".french .userpage .favorites": "(Favoris)",
".french .recent-widget .todownload": "(Télécharger)",
".french .recent-widget .toembed": "(Exporter)",
".french .recent-widget .tomobile": "(Mobile)",
".french .recent-widget .sendmsg": "(Envoyer un message)",
".french .recent-widget .addbudd": "(Ajouter à mes amis)", 
 

  ".japanese .keyword-search .listview .tag": "??????", 
  ".japanese .keyword-search .listview .count": "????", 
  ".japanese .login": "????",
  ".japanese .username": "?????: ",
  ".japanese .password": "?????: ",
  ".japanese .explorer .listview .name": "????", 
  ".japanese .explorer .listview .type": "????", 
  ".japanese .explorer .listview .rating": "????", 
  ".japanese .explorer .listview .added_at": "???????",
  ".japanese .explorer .thumbsview .name": "????", 
  ".japanese .explorer .thumbsview .type": "????", 
  ".japanese .explorer .thumbsview .rating": "????", 
  ".japanese .explorer .thumbsview .added_at": "???????",
  ".japanese .title-bar .logout": "???????",
  ".japanese .min-rating": "?????:",
  ".japanese .keyword-search .go": "?????",
  ".japanese .keyword-search .clear": "???",
  ".japanese .title-bar .share": "???",
  ".japanese .title-bar .bookmark": "???",
  ".japanese .title-bar .language": "??",
  ".japanese .title-bar .theme": "??",
  ".japanese .related-links": "?????",
  ".japanese .trash": "?",
  ".japanese .selection .zero-items-selected": "????0??", 
  ".japanese .title-bar .subscribe": "????????",  
  ".japanese .explorer .tooltip .details": "??:",
  ".japanese .added-by": "%2 ????????? %1",
  ".japanese .privacy": "??????:",
  ".japanese .tags": "?:",
  ".japanese .bookmark": "???",
  ".japanese .share": "???",
  ".japanese .display": "??:",
  ".japanese .no-explorer-items": "?%1/folders????<BR>" + 
      "<a href='#' onclick='%3'>%2</a>?" + 
      "<a href='#' onclick='%5'>%4</a>" + 
      "??????????",
  ".japanese .permalink": "???????",
  ".japanese .selected-items": "????",

  ".norwegian .keyword-search .listview .count": "Treffer",
  ".norwegian .keyword-search .listview .tag": "Søkord",
  ".norwegian .explorer .listview .name": "Navn",
  ".norwegian .explorer .listview .type": "Type",
  ".norwegian .explorer .listview .rating": "Karakter",
  ".norwegian .explorer .listview .added_at": "Addered",
  ".norwegian .explorer .thumbsview .name": "Navn",
  ".norwegian .explorer .thumbsview .type": "Type",
  ".norwegian .explorer .thumbsview .rating": "Karakter",
  ".norwegian .explorer .thumbsview .added_at": "Addered",
  ".norwegian .logout": "Logg ut",
  ".norwegian .login": "Logg inn",
  ".norwegian .username": "Brukernavn: ",
  ".norwegian .password": "Passord: ",
  ".norwegian .keyword-search .go": "Søk!",
  ".norwegian .keyword-search .clear": "Tom",
  ".norwegian .selection .zero-items-selected": "0 valde tinger",
  ".norwegian .title-bar .logout": "Logg ut",
  ".norwegian .title-bar .language": "Andre språk",
  ".norwegian .title-bar .theme": "Temaet",
  ".norwegian .explorer .tooltip .details": "Detaljer:",
  ".norwegian .added-by": "Tillagd %1 av %2", 
  ".norwegian .privacy": "Privat:", 
  ".norwegian .tags": "Tagger:", 
  ".norwegian .display": "Vis:", 
  ".norwegian .selected-items": "Valgte tinger", 
  ".norwegian .permalink": "Permalenke",
  ".norwegian .no-explorer-items": "Det finnes fjerne %1/mapper just nå.<BR>Ønsker dere at legge til en <a href='#' onclick='%3'>%2</a> eller en <a href='#' onclick='%5'>%4</a>?", 
  ".norwegian .no-explorer-items-no-folder": "Det finnes fjerne %1 just nå.<BR>Ønsker dere at legge til en <a href='#' onclick='%3'>%2</a>?", 
  ".norwegian .min-rating": "Min. karakter:", 
  ".norwegian .trash": "Skrot", 
  ".norwegian .title-bar .share": "Opphev",
  ".norwegian .title-bar .bookmark": "Bokmerke",
  ".norwegian .title-bar .subscribe": "Prenumer",
  ".norwegian .related-links": "Liknande lenker", 

  ".polish .keyword-search .listview .count": "Liczba",
  ".polish .keyword-search .listview .tag": "S?owo Kluczowe",
  ".polish .explorer .listview .name": "Nazwa",
  ".polish .explorer .listview .type": "Typ",
  ".polish .explorer .listview .rating": "Ocena",
  ".polish .explorer .listview .added_at": "Czas Dodania",
  ".polish .explorer .thumbsview .name": "Nazwa",
  ".polish .explorer .thumbsview .type": "Typ",
  ".polish .explorer .thumbsview .rating": "Ocena",
  ".polish .explorer .thumbsview .added_at": "Czas Dodania",
  ".polish .logout": "wyloguj",
  ".polish .login": "zaloguj",
  ".polish .username": "nazwa u?ytkownika: ",
  ".polish .password": "has?o: ",
  ".polish .keyword-search .go": "szukaj",
  ".polish .keyword-search .clear": "wyczy??",
  ".polish .selection .zero-items-selected": "0 wybranych plik?w",
  ".polish .title-bar .logout": "wyloguj",
  ".polish .title-bar .language": "j?zyk",
  ".polish .title-bar .theme": "kompozycja",
  ".polish .explorer .tooltip .details": "szczeg??y:",
  ".polish .added-by": "%1 dodane przez %2", 
  ".polish .privacy": "Prywatno??:", 
  ".polish .tags": "Tagi:", 
  ".polish .display": "Widok:", 
  ".polish .selected-items": "Wybrane Pliki", 
  ".polish .permalink": "link",
  ".polish .no-explorer-items": "Obecnie nie ma %1/folder?w.<BR>Czy chcesz doda? <a href='#' onclick='%3'>%2</a> lub <a href='#' onclick='%5'>%4</a>?", 
  ".polish .no-explorer-items-no-folder": "Obecnie nie ma %1.<BR>Czy chcesz doda? <a href='#' onclick='%3'>%2</a>?", 
  ".polish .min-rating": "Minimalna Ocena:", 
  ".polish .trash": "Kosz", 
  ".polish .title-bar .share": "Udost?pnij",
  ".polish .title-bar .bookmark": "Dodaj do Zak?adek",
  ".polish .title-bar .subscribe": "Subskrybuj",
  ".polish .related-links": "Podobne Linki", 

  ".portuguese .keyword-search .listview .count": "Número ",
  ".portuguese .keyword-search .listview .tag": "Palavra-chave ",
  ".portuguese .explorer .listview .name": "Nome",
  ".portuguese .explorer .listview .type": "Tipo",
  ".portuguese .explorer .listview .rating": "Rating",
  ".portuguese .explorer .listview .added_at": "Tempo",
  ".portuguese .explorer .thumbsview .name": "Nome",
  ".portuguese .explorer .thumbsview .type": "Tipo",
  ".portuguese .explorer .thumbsview .rating": "Rating",
  ".portuguese .explorer .thumbsview .added_at": "Tempo",
  ".portuguese .logout": "Desconectar",
  ".portuguese .login": "Inscrever",
  ".portuguese .username": "Usuário: ",
  ".portuguese .password": "Palavra-chave: ",
  ".portuguese .keyword-search .go": "Ir",
  ".portuguese .keyword-search .clear": "Apagar",
  ".portuguese .selection .zero-items-selected": "0 items selecionados",
  ".portuguese .title-bar .logout": "Desconectar",
  ".portuguese .title-bar .language": "Lingua",
  ".portuguese .title-bar .theme": "Aparência",
  ".portuguese .explorer .tooltip .details": "Detalhes:",
  ".portuguese .added-by": "Enviado %1 por %2", 
  ".portuguese .privacy": "Privacidade:", 
  ".portuguese .tags": "Tags:", 
  ".portuguese .display": "Display:", 
  ".portuguese .selected-items": "Items selecionados", 
  ".portuguese .permalink": "permalink",
  ".portuguese .no-explorer-items": " Não hà  %1/pastas neste momento.<BR> Quer acrescentar  <a href='#' onclick='%3'>%2</a> or a <a href='#' onclick='%5'>%4</a>?", 
  ".portuguese .no-explorer-items-no-folder": "Não hà %1 neste momento.<BR> Quer acrescentar  <a href='#' onclick='%3'>%2</a>?", 
  ".portuguese .min-rating": "Rating mínimo:", 
  ".portuguese .trash": "Lixo", 
  ".portuguese .title-bar .share": "Partilhar",
  ".portuguese .title-bar .bookmark": "Bookmark",
  ".portuguese .title-bar .subscribe": "Registar",
  ".portuguese .related-links": "Links relacionados",

 ".swedish .keyword-search .listview .count": "Antal",
  ".swedish .keyword-search .listview .tag": "Sökord",
  ".swedish .explorer .listview .name": "Namn",
  ".swedish .explorer .listview .type": "Typ",
  ".swedish .explorer .listview .rating": "Betyg",
  ".swedish .explorer .listview .added_at": "Upplagd",
  ".swedish .explorer .thumbsview .name": "Namn",
  ".swedish .explorer .thumbsview .type": "Typ",
  ".swedish .explorer .thumbsview .rating": "Betyg",
  ".swedish .explorer .thumbsview .added_at": "Upplagd",
  ".swedish .logout": "Logga ut",
  ".swedish .login": "Logga in",
  ".swedish .username": "alias: ",
  ".swedish .password": "lösenord: ",
  ".swedish .keyword-search .go": "sök",
  ".swedish .keyword-search .clear": "töm",
  ".swedish .selection .zero-items-selected": "0 artiklar valt",
  ".swedish .title-bar .logout": "logga ut",
  ".swedish .title-bar .language": "språk",
  ".swedish .title-bar .theme": "tema",
  ".swedish .explorer .tooltip .details": "detaljer:",
  ".swedish .added-by": "Tillsatt %1 av %2", 
  ".swedish .privacy": "Lov:", 
  ".swedish .tags": "Taggar:", 
  ".swedish .display": "Bevisa:", 
  ".swedish .selected-items": "Valt Artiklar", 
  ".swedish .permalink": "permalänk",
  ".swedish .no-explorer-items": "Det finns inga %1/mappar just nu.<BR>Vill ni tillsätta <a href='#' onclick='%3'>%2</a> eller <a href='#' onclick='%5'>%4</a>?", 
  ".swedish .no-explorer-items-no-folder": "Det finns inga %1 just nu.<BR>Vill ni tillsätta <a href='#' onclick='%3'>%2</a>?", 
  ".swedish .min-rating": "Min. Betyg:", 
  ".swedish .trash": "Skräp", 
  ".swedish .title-bar .share": "Dela ut",
  ".swedish .title-bar .bookmark": "Bokmärk",
  ".swedish .title-bar .subscribe": "Abonnera",
  ".swedish .related-links": "Liknande Länkar"  
})

translations = $H(translations);

// languages.js
var __currentLanguage = "english"
var translationElements = {}
var translationPaths = {}
var translationsCounter = 0
var scopes = []

function scope(name, f) {
  scopes.push(name)
  var r = f()
  scopes.pop()
  return r
}

function languagePath(l, p) {
  return ("." + l + " " + p).gsub(/\s+/, " ")
}

function rawTranslation(language, o, noRevert) {
  var k = languagePath(language,  o.path)
  var text = noRevert ? null : o.revert;
  if (translations[k]) {
    text = translations[k]
  } 
  return {key: k, text: text}
}

function translateString(language, o) {
  var rt = rawTranslation(language, o)
  var r = rt.text
  if (!r) {
    d("Missing translation: '" + rt.key + "'")
  }
  if (r && o.args) {
    r = r.gsub(/%(\d+)/, function(m) { return o.args[m[1] - 1] })
  }
  return r
}

var __missingTranslations = $A()

function doTranslateElement(o, nn) {
  var text = translateString(__currentLanguage, o)
  var textDefault = translateString("english", o)
  if (!textDefault) { textDefault =  __currentLanguage.substr(0, 2) + ":" + o.id }
  if (is_developer && !text) {
    __missingTranslations.push(languagePath(__currentLanguage, o.path))
  }
  o.setText(nn, text ? text : textDefault)
}

function idize(s) {
  return s.toLowerCase().gsub(/[^A-Za-z ]/, "").gsub(/ /, "-")
}

function registerTranslateElement() {
  var o = Object.extend({
    id: null,
    revert: null,
    node: null,
    setText: function(node, text) { }
  }, arguments[0])
  o.node = $(o.node)
  if (!o.id && o.revert) {
    o.id = idize(o.revert)
  }
  if (!o.realNode) {
    o.realNode = o.node
    o.nodeWrapper = false
  }
  if (o.path == undefined) {
    o.path = 
        (scopes.length > 0 ? "." + scopes.join(" .") + " " : "") + "." + o.id
  } else {
    o.path += (o.path != "" ? " " : "") + "." +  o.id
  }
  translationsCounter++
  var k = "xl-" + translationsCounter
  o.key = k
  o.node.addClassName("xl")
  o.node.id = k
  // For Mike's testing setup
  var n = o.realNode.nodeType == Node.ELEMENT_NODE ? o.realNode : o.node
  n.setAttribute("_msid", o.path)
  var tp = translationPaths[o.path]
  //if (tp != undefined) {
    //delete translationElements[tp]
  //}
  translationElements[k] = o
  translationPaths[o.path] = k
  doTranslateElement(o, o.realNode)
  return o.node
}

function translate() {
  var o = arguments[0]
  // Yes, we have to use === here as null cases are OK!
  if (o.node.nodeValue !== undefined) {
    o.realNode = o.node
    o.nodeWrapper = true
    o.node = Builder.node('span', [ o.node ])
  }
  return registerTranslateElement(o)
}

function setLanguage(l) {
  __currentLanguage = l
  retranslate()
}

function currentLanguage() {
  return __currentLanguage
}

function retranslate() {
  document.getElementsByClassName("xl", document.body).each(function(e) {
    var o = translationElements[e.id]
    if (o) {
      var n = $(o.key)
      if (o.nodeWrapper) {
        n = n.firstChild
      }
      doTranslateElement(o, n)
    }
  })
  $H(translationElements).each(function(p) { 
    if ($(p.key) == undefined) {
      var o = p.value
      if (o.realNode.nodeType == Node.ELEMENT_NODE) {
        doTranslateElement(o, o.realNode)           
      }
      else {
        // It probably was deleted from the document (IE problem)
        delete translationElements[p.key];
      }
    }      
  })
}

function missingTranslations(l) {
  if (!l) {
    l = __currentLanguage
  }
  var c = 0
  console.group("Missing translations for: " + l)
  $H(translationElements).each(function(p) { 
    var o = p.value
    var english = rawTranslation("english", o).text || 
        "(" + languagePath(l, o.path) + ")"
    var rt = rawTranslation(l, o, true) 
    if (rt.text == undefined) {
      console.log('  "' + languagePath(l, o.path) + '": "' + english + '", ')
    }
  })
  console.groupEnd()
  console.group("Missing translations (static check) for: " + l)
  $H(translations).each(function(pair) {
    var path = pair.key
    if (path.match(/^\.english/)) {
      var langPath = path.sub(/^\.english/, "."+l)
      if (!translations[langPath]) {
        console.log(' "' + langPath + '": "' + pair.value + '",')
      }
    }
  })
  console.groupEnd()
}

function translateText(o) {
  var o = Object.extend({
    id: null,
    revert: null,
    node: TN(""),
    setText: function(n, t) {
      n.nodeValue = t
    }
  }, arguments[0])
  return translate(o)
}

function registerTranslateText(o) {
  var o = Object.extend({
    id: null,
    revert: null,
    node: null,
    setText: function(n, t) {
      n.nodeValue = t
    }
  }, arguments[0])
  o.realNode = o.node
  o.node = o.realNode.parentNode
  o.nodeWrapper = true
  return registerTranslateElement(o)
}

function translateButton(o) {
  var o = Object.extend({
    id: null,
    revert: null,
    node: Builder.node('input', {
      type: 'submit',
      value: ""
    }),
    setText: function(n, t) {
      n.value = t
    }
  }, arguments[0])
  return translate(o)
}
;

// observer.js
Observable = {
  addObserver: function(o) {
    if (this.observers == undefined) {
      this.observers = []
    }
    this.observers.push(o)
    if (o.addObservable) {
      o.addObservable(this)
    }
  },
  observeOnce: function(o) {
    if (this.one_time_observers == undefined) {
      this.one_time_observers = []
    }
    this.one_time_observers.push(o)
  },
  removeObserver: function(o) {
    $A(["observers", "one_time_observers"]).each(function(a) {
      if (this[a] != undefined) { 
        this[a] = this[a].select(function(obs) { return obs != o } )
      }      
    }.bind(this))
  },
  notifyObservers: function(name, _opts) {
    var f = function(o) {
      if (o[name]) {
        o[name](this, _opts)
        return false;
      }
      else {
        return true;
      }
    }.bind(this)
    this._notify(f)
  },
  dataChanged: function() {
    this.version = changeCounter()
    this.update()
  },
  update: function() {
    var f = function(o) { 
      if (o.rerender) {
        o.rerender(this, "u")
        return false;
      }
      else if (o.update) {
        o.update(this) 
        return false;
      }
      else {
        return true;
      }
    }.bind(this)
    this._notify(f)
  },
  _notify: function(fun) {
    if (this.observers) {
      this.observers.each(function(o) { fun(o)})
    }
    if (this.one_time_observers) {
      var notos = []
      var o = this.one_time_observers.pop()
      while (o) {
        if(fun(o)) { notos.push(o)}
        o = this.one_time_observers.pop()
      }
      this.one_time_observers = notos
    }    
  }
}
;

// formbuilder.js
AjaxFormBuilder = Class.create();
AjaxFormBuilder.prototype = {
  initialize: function() {
    var instance = this;
    this.options = Object.extend({
      controller: itemModel.controllerName(),
      service: null,
      action: 'default',
      beforeSubmit: function() { },
      onSuccessfulSubmit: function(instance, success) {
        instance.evaluateJsonUpdate(success)
      },
      onErroneousSubmit: function(instance, error) {
        if (error) {
          displayError(error.message, error.details)          
        }
        else {
          // displayError("Something went wrong. Please try again later.")
        }
      },
      afterSuccessfulSubmit: function(json_update) { },
      afterAjaxFinished: function() { },
      user: itemModel.user(),
      hiddenFields: $H()
    }, arguments[0] || {})
    if (!this.options.service) { 
      this.options.service = this.options.controller
    }
    this.options.hiddenFields = $H(this.options.hiddenFields)
    this.formNode = this.makeLinkToRemote()
  },
  actionUrl: function() {
    return serviceLink({
      service: this.options.service,
      action: this.options.action,
      noCrossSiteLink: true,
      user: this.options.user
    })
  },
  makeLinkToRemote: function() {
    var instance = this;
    var f=Builder.node('form', {
      method: this.options.get ? "get" : "post",
      //enctype: instance.options.enctype
      action: this.actionUrl()
    })
    this.options.hiddenFields.each(function(p) {
      f.appendChild(Builder.node('input', {
        type: "hidden",
        name: p.key,
        value: p.value
      }))
    })

    f.onsubmit = function() {
      instance.options.beforeSubmit();
      instance.performAjaxRequest(Form.serialize(this));
      return false;
    }
    return f;
  },
  htmlNode: function() {
    return this.formNode
  },
  evaluateJsonUpdate: function(success) {
    //eval("var update = " + json_update);
    //this.options.model = hash_merge(this.options.model, update["item_info"])
    this.options.model = hash_merge(this.options.model, success.item_info)
  },
  dictAsQueryString: function(dict) { 
    var p = []
    $H(dict).each(function(pair) {
      p.push(pair.map(encodeURIComponent).join('='))
    })
    return p.join('&')
  },
  performAjaxRequest: function(_parameters) {
    // Is not string?
    if (_parameters.length == undefined) {
      _parameters = this.dictAsQueryString(_parameters)
    }
    var instance = this;
    new Ajax.Request(this.actionUrl(),  {
        asynchronous:true,
        evalScripts:true, 
	method: this.options.get ? "get" : "post", 
        parameters:_parameters,
        onSuccess: function(t) { 
          handleJsonResponse(t.responseText, { onSuccess: function(success) {
            instance.options.onSuccessfulSubmit(instance, success)
            instance.options.afterSuccessfulSubmit(success)
          },
	  onError: function(error) {
	    instance.options.onErroneousSubmit(instance,error)
	  }})
          //if (/^success=/.test(t.responseText)) {
          //  var success;
          //  eval(t.responseText);
          //  instance.options.onSuccessfulSubmit(instance, success)
          //  instance.options.afterSuccessfulSubmit(success)
          //} else {
          //  var error;
          //  eval(t.responseText);
          //  displayError(error.message, error.details)
          //}
          instance.options.afterAjaxFinished(instance)
        }, 
        on404: function(t) {
          displayError("ERROR: "+ t.status + ": " + t.statusText )
          instance.options.afterAjaxFinished(instance)
        },
        onFailure: function(t) { 
          displayError("ERROR: "+ t.status + ": " + t.statusText )
          instance.options.afterAjaxFinished(instance)
        }
    })
  }
}

AjaxRequestMixin = {
  ajaxRequest: function(options, dict) {
    var instance = this
    var o = Object.extend({
      action: null,
      service: itemModel.service(),
      user: itemModel.user(),
      onSuccess: function(success) { }
    }, options || {})
    new AjaxFormBuilder({
      action: o.action,
      service: o.service,
      user: o.user,
      onSuccessfulSubmit: function(afb, success) {
        if (success) {
          o.onSuccess.apply(instance, [success])
        }
      }
    }).performAjaxRequest(dict)
  }
}
;

// itemadditions.js
var mm = null
var ItemCommon = {
  // gridShow(c) {
  initialize: function(model) {
    this.model = model;
    this.ignoreEditItems = true;
  },
  showItem: function() {
    var instance = this
    var o = Object.extend({
      m: null,
      model: this.model,
      displayItemClicked: null,
      folderClicked: null
    }, arguments[0])
    if (!o.displayItemClicked) {
      o.displayItemClicked = function(m) {
        instance.standardDisplayItemShow(m)
      }
    }
    if (!o.folderClicked) {
      o.folderClicked = function(o) {
        instance.model.changeToFolder(instance.model.currentPath() + 
            "/"  + o.m.pretty_name)
      }
    }
    if (this.ignoreShowClick) {
      this.ignoreShowClick = false;
      return
    }
    if (this.ignoreEditItems && (o.m.increation || o.m.edit)) {
      return
    }
    mm = o.m
    if (isFolder(o.m) || o.m.generic_folder != undefined) {
      o.folderClicked(o)
      itemInfoInstance.hideBox()
    } else {
      o.displayItemClicked(o.m)
    }
  },
  standardDisplayItemShow: function(m) {
    if (haveDisplayItemClass(m)) {
      displayItem(m, this.model) 
    } else {
      showItem(m);
    }
  }
}

var __actionsFolderTemplate = null
var __actionsFileTemplate = null
var __actionsFileInTrashTemplate = null

StandardItemAdditions = Class.create();
StandardItemAdditions.prototype = {
  inTrash: function(m) { 
    return this.model.currentPath().startsWith("/trash/")
  },
  checkIgnoreShow: function(o) { 
    if (o.ignoreShow) {
      this.ignoreShowClick = true;
    }
  },
  // actionsForItem(m, itemElement)
  actionsForItemTemplate: function(o) {
    var o = Object.extend({
      inTrash: false,
      isFolder: false
    }, arguments[0])
    var r = Builder.node('span', {},[])
    if (o.inTrash) {
      r.appendChild(writeImageActionLink({
        onclick: "template:value_of restore_action",
        fullImageName: iconPath(22, "restore"),
        title: "restore"
      })) 
    } else {
      if (!o.isFolder) {
        r.appendChild(imageActionLink({
          onclick: "template:value_of download_action",
          fullImageName: iconPath(22, "download"),
          title: "download",
          href: "template:value_of m.full_item_url"
        }))
      }
      r.appendChild(writeImageActionLink({
        onclick: "template:value_of delete_action",
        fullImageName: iconPath(22, "trash"),
        title: "move to trash"
      }))
      r.appendChild(writeImageActionLink({
        onclick: "template:value_of edit_action",
        fullImageName: iconPath(22, "edit"),
        title: "edit"
      }))
    }
    return new MSTemplate({create: function() { return r }});
  }, 
  actionsTemplateForItem: function(m) {
    if (!__actionsFolderTemplate) {
      __actionsFolderTemplate = this.actionsForItemTemplate({
        isFolder: true
      })
      __actionsFileTemplate = this.actionsForItemTemplate()
      __actionsFileInTrashTemplate = this.actionsForItemTemplate({
        inTrash: true
      })
    }
    return (this.inTrash(m) ? __actionsFileInTrashTemplate : 
          (isFolder(m) ? 
            __actionsFolderTemplate :
            __actionsFileTemplate ))
  },
  actionsForItem: function() {
    var o = Object.extend({
      m: null,
      ignoreShow: false,
      itemElement: null
    }, arguments[0])
    var actionsTemplate = this.actionsTemplateForItem(o.m)
    var instance = this
    return actionsTemplate.makeNode({
      m: o.m,
      download_action: function() {
        instance.checkIgnoreShow(o); 
        return true; 
      },
      restore_action: function() {
        instance.checkIgnoreShow(o)
        instance.restoreItem(o.m, o.itemElement)
        return false
      },
      delete_action: function() {
        instance.checkIgnoreShow(o)
        instance.moveToTrash(o.m, o.itemElement)
        return false
      },
      edit_action: function() {
        instance.checkIgnoreShow(o)
        itemDialog().withItem(o.m, instance.model).editItem()
        //editItem(o.m, instance.model)
        return false
      }
    })
  }, 
  itemFade: function(e) {
    if (e != undefined) {
      Effect.Fade(e);
    }
  },
  restoreItem: function(m, e) {
    var itemPath = normalizePath(this.model.currentPath() + "/" + m.name)
    this.ajaxRequest({
      action: "move_to_path_json",
      onSuccess: function(success) { this.model.removeItem(m) }
    }, {
      form_from_path: itemPath, 
      form_from_item_id: m.id,
      form_to_path: "/"
    })
    //new AjaxFormBuilder({
    //  action: "move_to_path_json",
    //  //onSuccessfulSubmit: function(afb, json_update) {
    //  //  eval("var update = " + json_update);
    //  //  if (update) {
    //  //    instance.model.removeItem(m);
    //  //    //instance.itemFade(e);
    //  //  }
    //  //}
    //}).performAjaxRequest({
    //  form_from_path: itemPath, 
    //  form_from_item_id: m.id,
    //  form_to_path: "/"
    //})
  },
  moveToTrash: function(m, e) {
    //var instance = this;
    //itemPath = normalizePath(this.model.currentPath() + "/" + m.name)
    this.ajaxRequest({
      action: "move_to_trash",
      onSuccess: function(success) { this.model.removeItem(m) }
    }, {
      form_path: this.model.currentPath(),
      form_name: m.pretty_name
      //form_item_to_trash: itemPath,
      //form_item_id: m.id
    })
    //new AjaxFormBuilder({
    //  action: "move_to_trash_json",
    //  onSuccessfulSubmit: function(afb, json_update) {
    //    eval("var update = " + json_update)
    //    if (update) {
    //      instance.model.removeItem(m)
    //      //instance.itemFade(e);
    //    }
    //  }
    //}).performAjaxRequest({
    //  form_item_to_trash: itemPath,
    //  form_item_id: m.id
    //})
  },
  //function instrumentTags(tags) {
  selectableTags: function(o) {
    var ta = searchWidget.tagsArray(o.m.tags)
    var sensitive = o.sensitive
    var instance = this;
    return Builder.node('span', {}, [ta.map(function(tag) { 
      var n = Builder.node('a', {
        href: '#',
        className: 'tag'
      }, [TN(tag)])
      var s = Builder.node('span', {}, [n, NBSP()]);
      if (sensitive) {
        var match = searchWidget.tagInFilter(tag)
        s.style.backgroundColor = match ? "#ccc" : null;
        n.onclick = function() {
          var match = searchWidget.tagInFilter(tag);
          (searchWidget[(match ? "removeFrom" : "addTo") + "SearchFilter"])(tag);
          s.style.backgroundColor = match ? "#ccc" : null;
          return false;
        }
      } else {
        n.onclick = function() {
          standardExplorer.options.tooltip.hideTotally();
          instance.checkIgnoreShow(o)
          searchWidget.addToSearchFilter(tag);
          return false;
        }
      }
      return s
    })])
  },
  addItem: function() {
    var options = arguments[1] || {}
    itemDialog().withItem(this.model.addItem(options), this.model).
        showAddItemForm()
  },
  addFolder: function() {
    itemDialog().withItem(this.model.addFolder(), this.model).
        showAddFolderForm();
  },
  displayItemDetail: function(o) {
    var r = Builder.node('span', {}, [
      this.actionsForItem(o), BR()
    ])
    if (!isFolder(o.m) && o.m.tags != undefined) {
      r.appendChild(translateText({path: "", id: "tags"}))
      r.appendChild(this.selectableTags(o))
    }
    return r
  }.memoize({dependencies: function(o) { 
    return o.m.name + ":" + o.m.changed 
  }})
}

Object.extend(StandardItemAdditions.prototype, ItemCommon)
Object.extend(StandardItemAdditions.prototype, AjaxRequestMixin)

var viewId;
var preparingViewId;
var nextViewId = (function() {
  var currentViewId = 0;
  viewId = function() {
    return "id-"+currentViewId;
  }

  preparingViewId = function() {
    return "id-"+currentViewId;
  }
  
  return function() {
    currentViewId++;
  }
})();

//function refresh() {
//  itemModel.update()
//}

var currentItemManager = null

ManageItemDialog = Class.create();
ManageItemDialog.prototype = {
  initialize: function() {
    this.options = Object.extend({
    }, arguments[0] || {})
    this.model = this.options.model
    this.m = null
  },
  withItem: function(m, model) {
    this.model = model
    this.m = m
    this.index = this.model.indexForItem(this.m)
    return this
  },
  withHiddenFields: function(f) { this.hiddenFields = f; return this },
  // What is this supposed to do?
  mergeWithLocal: function(start, end) {
    var r = [];
    r.push(start);
    var instance = this
    this.localFields().each(function(f) {
      r.push([f[0], f[1], instance.m[f[2]], f[3]])
    })
    if (end && end.length > 0 ) {
      r.push(end);
    }
    return r;
  }, 
  localFileType: function() {
    return ["File", "file", "",  {type: "file"}]
  },
  localTagType: function() {
    return ["Tags", "tags", ""]
  },
  localFields: function() {
    return [];
  },
  editFormGeneral: function(action, formItems, options) {
    formItems.push(["Description", "description", this.m.description || "",
      {generic: true}])
    formItems.push(["Tags", "tags", this.m.tags || ""])
    return this.makeEditFormGeneral(
        serviceLink({service: itemModel.serviceName(), action: action, 
	        noCrossSiteLink: true}), 
	      formItems, options)
  },
  makeItemCreateFormUpload: function() {
    return this.editFormGeneral("create_item",
        this.mergeWithLocal(this.localFileType()),
        { encoding: "multipart/form-data",
          ajaxRequestHandler: "ajax_request_upload", 
          onsubmitReturnValue:  "true"
        })
  },
  localNameType: function() {
    return [this.model.itemIdDisplayValue(), "item_name", this.m.name || ""]
  },
  makeItemCreateFormSimple: function() {
    return this.editFormGeneral("create_item",
        this.mergeWithLocal(this.localNameType()))
  }, 
  makeFolderCreateForm: function() {
    return this.editFormGeneral("create_folder", 
        [["Name", "item_name", ""]], { itemName: "Folder" })
  },
  makeFolderEditForm: function() {
    return this.editFormGeneral("edit",
        [["Name", "item_name", this.m.name]],
        { 
          itemName: "Folder"
        })
  },
  makeItemEditForm: function() {
    return this.editFormGeneral("edit",
        this.mergeWithLocal([this.model.itemIdDisplayValue(), "item_name", 
        this.m.name]))
  },
  editItem: function() {
    this.m.edit = true
    this.showEditItemForm();
  },
  showEditItemForm: function() {
    if (isFolder(this.m)) {
      this.showEditFolderForm();
    } else {
      this.__showEditItemForm()
    }
  },
  __showEditItemForm: function() {
    this.showWindow("Edit " + this.itemName(), this.makeItemEditForm());
  },
  showEditFolderForm: function() {
    this.showWindow("Edit Folder", this.makeFolderEditForm())
  },
  showAddFolderForm: function() {
    var title = Builder.node('span', [
      translateText({path: ".creation .new", id: "folder"})
    ]).innerHTML
    this.showWindow(title, this.makeFolderCreateForm())
  },
  showAddItemFormWithMethod: function(method) {
    var title = Builder.node('span', [
      translateText({path: ".creation", id: "new", args: [this.itemName()]})
    ]).innerHTML
    this.showWindow(title, this[method]())
  },
  showAddItemForm: function() {
    this.showAddItemFormWithMethod("makeItemCreateForm")
  },
  checkEditCancel: function() {
    if (this.m.increation || this.m.edit) {
      this.cancelEdit();
    }
  },
  cancelEdit: function() {
    if (this.model.inCancel) {
      return;
    }
    this.model.inCancel = true;
    this.closeWindow()
    if (this.m.increation) {
      this.model.removeItem(this.m)
    } else {
      this.m.edit = false
      this.model.update();
    }
    this.model.inCancel = false;
  },
  closeWindow: function() {
    if (this.window) {
      this.window.close();
    }
  },
  showWindow: function(title, content)  {
    this.window = new ThemedWindow({
      title: title,
      maximizable: false,
      minimizable: false,
      showEffect: Element.show,
      hideEffect: Element.hide,
      width: 680,
      height: 500,
      onClose: function() { this.checkEditCancel() }.bind(this)
    }, function(w) {
      if (typeof content == "string") {
        var div = Builder.node('div')
        div.innerHTML = content        
      }
      else {
        var div = content;
      }
      w.setContent(div)
      w.showCenter()
      w.toFront()
    })
  },
  refreshCurrentItemManager: function() {
    currentItemManager = this
  },
  itemName: function() {
    return this.model.itemNameCapitalized()
  },
  makeEditFormGeneral: function(url, formItems, o) {
    options = Object.extend({
      encoding: undefined,
      ajaxRequestHandler: undefined,
      onsubmitReturnValue: undefined,
      hiddenFields: undefined,
      itemName: this.itemName()
    }, o || {})
    //console.info("url: " + url)
    currentItemManager = this
    hf = Object.extend(Object.extend({
      "form_path": this.model.currentPath()//,
      //"form_id": this.m.id
    }, options.hiddenFields || {}), this.hiddenFields || {})
    return ajax_link_to_remote(url, hf, 
        function(rn, success) {
          var m = currentItemManager.m
          //eval("var update = " + json_update);
          m = hash_merge(m, success.item_info)
          m.edit = false
          m.increation = false
          currentItemManager.closeWindow()
          currentItemManager.model.update()
        }, function() {
          //return "<iframe style='border: 0pt none; width: 0px;" + 
          return "<iframe style='border: 0pt none; width: 1000px;" + 
             "height: 0px;' src='' name='upload-target-" + preparingViewId() + "-" + currentItemManager.index + 
             //"height: 500px;' src='' name='upload-target-" + //preparingViewId() + "-" + this.index + 
             "' id='upload-target-" + preparingViewId() + "-" + currentItemManager.index + "'></iframe>" + 
            "<div class='increation' style='padding-left: 10px;'>" + 
             "<table cellpadding='0' cellspacing='0'>" + 
            formItems.map(function(item) {
              input = makeInput(item)
              return "<tr><td>" + input.description + 
                  "</td><td>" + input.input + "</td></tr>"
            }).join("") + 
            "<tr><td colspan='2'>" + 
            generalFormSettings(currentItemManager.m, currentItemManager.index, options) + 
            "</td></tr>" + 
            "<tr><td colspan='2'>" + 
            "<input type='submit' value='save' style='' />"  +
            "&nbsp;&nbsp;<input onclick='currentItemManager.cancelEdit(); return false; '" +  
            " type='submit' value='cancel' /></td></tr></table></div>" + 
            "<div id='upload-status-" + preparingViewId() + "-" + currentItemManager.index + "' class='upload-status'/>"
        }, this.index, options.encoding, options.ajaxRequestHandler, 
        options.onsubmitReturnValue) 
  }
}

var standardItemDialog = null
function itemDialog() {
  if (!standardItemDialog) {
    standardItemDialog = new ManageItemDialog()
  }
  return standardItemDialog
}
;

// displaymodel.js
function isDeletingCommentAlllowed(m, c) { 
  return c.account_id == current_user_model.id || c.account_id == m.account_id
}

function isFolder(m) { return m.type.toLowerCase() == "folder" }
function isFolderItem(m) { return isFolder(m) || m.generic_folder }
function max(a, b) { return a > b ? a : b }
function min(a, b) { return a < b ? a : b }
DisplayModelCommon = {
  initialize: function() {
    var o = {
      data: null,
      user: model_data.read_username
    }
    if (this.defaultOptions) { o = Object.extend(o, this.defaultOptions()) }
    this.options = Object.extend(o, arguments[0] || {})
    if (this.setup) { this.setup() }
  },
  simpleAjax: function() {
    var o = Object.extend({
      action: null,
      service: this.service(),
      onSuccessfulSubmit: function(afb, success) {},
      fields: {},
      get: false,
      additionalFields: {}
    }, arguments[0] || {})
    o.additionalFields = $H({
      form_path: this.currentPath(),
      form_username_override: this.user()
    }).merge(o.additionalFields)
    new AjaxFormBuilder({
      user: this.user(),
      service: o.service,
      get: o.get,
      action: o.action,
      onSuccessfulSubmit: o.onSuccessfulSubmit
    }).performAjaxRequest($H(o.fields).merge(o.additionalFields))
  },
  currentPath: function(item) {
    if (item && item.item_path) {
      var path = item.item_path.gsub(/\/[^/]*$/, "")
      if (path == "") path = "/"
      return path
    }
    else {
      return this.path() || "/"
    }
  },
  setPreferences: function(data) {
    var instance = this
    this.simpleAjax({
      action: "set_preferences",
      fields: {preferences: JSON.toJSON(data)},
      onSuccessfulSubmit: function(afb, success) { 
        instance.data.preferences = success
        instance.notifyObservers("afterPreferencesChanged")
      }
    })
  },
  preferences: function() { return this.data.preferences },
  serviceName: function() { 
    return this.options.service || 
        this.options.controllerName || this.data.service_name 
  },
  controller: function() { return this.serviceName() },
  service: function() { return this.serviceName() }
}

Object.extend(DisplayModelCommon, Observable)
Object.extend(DisplayModelCommon, AjaxRequestMixin)


function genericColumnNames() {
  return ["name", "type", "rating", "added_at"]
}

function genericColumnDisplayNames() {
  return ["Name", "Type", "Rating", "Time Added"]
}

function handleUpdateException(e) {
  displayError("Something went wrong, please try again later",  
     "Exception on update: " + String(e) + "\n" + formatStackTrace(e))
}

DisplayModel = Class.create();
DisplayModel.prototype = {
  defaultOptions: function() {
    return {
      controllerName: model_data.service_name,
      sorted: false,
      reverse: false,
      sortByColumn: "name",
      columnNames: genericColumnNames(),
      itemIdDisplayValue: itemIdDisplayValue,
      columnDisplayNames: genericColumnDisplayNames(),
      itemsPerPage: 10,
      noPagination: false,
      filter: function(m) {
        return m;
      },
      actions: new StandardItemAdditions(this),
      path: null,
      addFolderAllowed: true,
      specificAddItemData: null,
      noCrossSiteFullItemURL: false,
      noCrossSiteThumbnailImageURL: false
    }
  },
  setup: function() {
    if (!this.options.columnDisplayNames) {
      this.options.columnDisplayNames = this.options.columnNames
    }
    if (this.options.sortByColumn) {
      this.options.sorted = true
    }
    if (this.options.specificAddItemData) {
      this.specificAddItemData = this.options.specificAddItemData
    }
    this.actions = this.options.actions
    this.data = this.options.data
    if (this.data == undefined) {
      this.data = {}
      var instance = this
      setTimeout(function() { 
        instance.changeToFolder(instance.options.path || "/")
      }, 10)
    }
    this._currentPage = 1
    this.version = changeCounter()
    this.sortingVersion = changeCounter()
    this._addFolderAllowed = this.options.addFolderAllowed
    if (this.options.noPagination)  {this.options.itemsPerPage = 9999}
  },
  category: function() { return this.data.category },
  columnNames: function() { return this.options.columnNames },
  addFolderAllowed: function() { return this._addFolderAllowed },
  allowAddingFolders: function() {
    this._addFolderAllowed = true
    this.notifyObservers("afterAddFolderAllowedChanged")
  },
  disallowAddingFolders: function() {
    this._addFolderAllowed = false
    this.notifyObservers("afterAddFolderAllowedChanged")
  },
  columnDisplayNames: function() {
    return this.options.columnDisplayNames;
  },
  rowResult: function(rn) {
    return this.getModel()[rn - 1]
  },
  removeItem: function(m) {
    if (!this.containsItem(m)) {
      return
    }
    this.items().splice(this.dataIndexForItem(m) - 1, 1)
    this.version = changeCounter()
    this.notifyObservers("afterRemoveItem", m)
    this.update();
  },
  moveItemToTrashOrRemoveFromModel: function(m) {
    if (m.id != -1) {
      this.actions.moveToTrash(m)
    } else {
      this.removeItem(m)
    }
  },
  setSortByColumn: function(c) {
    this.options.sorted = true
    if (this.options.sortByColumn == c) {
      this.options.reverse = !this.options.reverse
    }
    this.sortingVersion = changeCounter()      
    this.options.sortByColumn = c;
    this.update()
  },
  reverse: function() {
    return this.options.reverse;
  },
  currentResults: function() {
    return this.getModel()
  },
  // aka prepareModel
  // aka currentResults
  sortedModel: function() {
    if (!this.options.sorted) {
      return this.items()
    }
    var empty = this.nullValue(this.options.sortByColumn)
    var firstColumn = this.options.columnNames[0]
    var e1 = this.nullValue(firstColumn)
    var c = this.options.sortByColumn
    var _reverse = this.options.reverse
    var cmp = function (a, b, e) {
      if (a == undefined)  { a = e.v }
      if (b == undefined)  { b = e.v }
      a = e.p(a)
      b = e.p(b)
      return a < b ? -1 : a > b ? 1 : 0; 
    }
    var result = this.items().sort(function(a, b) { 
      var r = cmp(a[c], b[c], empty);
      if (r == 0) {
        r = cmp(a[firstColumn], b[firstColumn], e1)
      }
      return _reverse ? r * -1 : r;
    })
    return result;
  },
  nullValue: function(c) {
    var r = undefined;
    this.items().each(function(i) {
      var v = i[c]
      if (typeof v == "string") {
        r = "";
      }
    })
    return r == undefined ? {v: 0, p: function(v) { return v }} : 
        {v: r, p: function(v) { 
          return v.toLowerCase != undefined && v.toLowerCase();
        }};
  },
  _getModel: function() {
    return this.options.filter(this.sortedModel().select(function(m) {
      return m.name != "trash"
    }))
  }.memoize({dependencies: function() { 
    return this.version + ":" + this.sortingVersion 
  }}),
  getModel: function() {
    return this._getModel().clone().splice(
        (this.currentPage() - 1) * this.itemsPerPage(), this.itemsPerPage())
  },
  itemsPerPage: function () { return this.options.itemsPerPage; },
  pageCount: function() { 
      var a = this._getModel().size()
      var b = this.itemsPerPage()
      return parseInt(a / b)  + (a % b == 0 ? 0 : 1)
   },
  currentPage: function() { return this._currentPage; },
  setPage: function(v) { 
    this._currentPage = min(max(v, 1), this.pageCount())
    this.update()
  },
  nextPage: function() { 
      this.setPage(min(this.pageCount(), this.currentPage() + 1)) },
  previousPage: function() { 
      this.setPage(max(1, this.currentPage() - 1)) },
  //rowToResultModel -> indexForItem
  indexInData: function(data, m) {
    var n = m.name
    var c = 0;
    data.find(function(i) { ++c; return i.name == n} )
    return c 
  },
  findItemByName: function(name) {
    return this.items().find(function(i) { return i.name == name} )
  },
  findItemByPrettyName: function(pn) {
    return this.items().find(function(i) { return i.pretty_name == pn} )
  },
  containsItem: function(m) {
    return this.findItemByPrettyName(m.pretty_name) != undefined
  },
  makeSureNameIsUnique: function(name) {
    var b  = name
    var c = 1
    while (this.findItemByName(name)) {
      c++
      name = b + " " + c
    }
    return name
  },
  dataIndexForItem: function(m) {
    return this.indexInData(this.items(), m)
  },
  indexForItem: function(m) {
    return this.indexInData(this.getModel(), m)
  },
  hasNext: function(m) {
    this.displayItems()
    return m.index < this.displayItems().length
  },
  hasPrevious: function(m) {
    this.displayItems()
    return m.index > 1
  },
  next: function(m) {
    this.displayItems()
    return this.displayItems()[m.index]
  },
  previous: function(m) {
    this.displayItems()
    return this.displayItems()[m.index - 2]
  },
  displayItems: function() {
    var c = 0;
    return this.getModel().select(function(m) { 
      return !isFolder(m)
    }).map(function(m) {
      m.index = ++c
      return m
    })
  }.memoize({dependencies: function() { 
    return this.version + ":" + this.sortingVersion 
  }}),
  disallowChangeFolder: function() {
    this.noChangeFolder = true
  },
  changeToFolder: function(path) {
    if (this.noChangeFolder) { return }
    path = normalizePath(path).replace(/\/$/, "")
    if (path == "") {
      path = "/"
    }
    startIdle();
    var instance = this
    var ar = (new AjaxFormBuilder({
      user: this.user(),
      get: true,
      service: this.service(),
      action: "model",
      onSuccessfulSubmit: function(afb, success) {
        try {
          instance.data = success
          instance.version = changeCounter()
          instance.notifyObservers("afterPathChanged")
          instance.update()
          endIdle()
        } catch (e) {
          endIdle()
          handleUpdateException(e)
          throw e
        }
      }
    }))
    ar.performAjaxRequest({
      form_path: path, 
      form_username_override: this.user()
    })
  },
  emptyTrash: function() {
    startIdle();
    var instance = this;
    var ar = (new AjaxFormBuilder({
      controller: this.service(),
      action: "clear_trash",
      onSuccessfulSubmit: function(afb, success) {
        try {
          if (instance.currentPath() == "/trash") {
            instance.changeToFolder("/")
          }
          instance.notifyObservers("afterTrashEmptied")
          endIdle();
        } catch (e) {
          endIdle();
          throw e;
        }
      }
    }))
    ar.performAjaxRequest({})
  },
  //currentPathPermalinkPN: function(pn, item_path) {
  //  var pname = displayString(pn)
  //  var m = this.findItemByPrettyName(pn)
  //  if (item_path) {
  //    var fullPath = item_path
  //  }
  //  else {
  //    var c = this.currentPath();
  //    var fullPath = 
  //        pname ? c + (c.charAt(c.length - 1) == "/" ? "" : "/") + pname : c      
  //  }
  //  var service = this.serviceName()
  //  //if (this.queryDataAction) {
  //  //  // This takes care of items in sticky folders!
  //  //  var un = this.userForItem(m)
  //  //  if (pn) service = this.findItemByPrettyName(pn).service || service
  //  //}
  //  //else {
  //  //  var un = this.user()
  //  //}
  //  var sl = serviceLink({service: service, user: un})
  //  if (fullPath == "/") {
  //    return sl + fullPath
  //  }
  //  return encodeURI(sl + fullPath.gsub(/^\//, "/-"));
  //},
  currentPathPermalink: function(m) { 
    if (m && m.item_path) {
      var fullPath = m.item_path
    } else {
      var c = this.currentPath();
      var fullPath = (m && m.pretty_name) ? c + (c.charAt(c.length - 1) == "/" ? "" : "/") + m.pretty_name : c      
    }
    var un = m ? this.userForItem(m) : this.user()
    var service = this.serviceName()
    var sl = serviceLink({service: service, user: un})
    if (fullPath == "/") {
      return sl + fullPath
    }
    return encodeURI(sl + fullPath.gsub(/^\//, "/-"));
  },
  permalinkNode: function() {
    return Builder.node('a', {
      href: this.currentPathPermalink(), 
      target: "_permalink" + randomNumber()
    }, [ 
      TN("link") 
    ])
  },
  permalinkPresentation: function() {
    var link
    var r = Builder.node('span', {id: "permalink"}, [
      TN("["), 
      link = this.permalinkNode(),
      TN("]")
    ])
    return {link: link, node: r}
  },
  setNewController: function(c) {
    this.data = {}
    this.data.controller_name = c
    var instance = this
    setTimeout(function() { 
      instance.changeToFolder("/")
    }, 100)
  },
  storeRequestFieldsForItem: function(m) {
    var r = $H()
    var formFields = [ "pretty_name", "name",  "tags",  "permission"]
    var ignoreFields = ["thumbnail_image_url", "full_item_url",
        "rating", "voted", "added_at", "edit", "increation",
        "comments", "changed", "version", "content_type", "account_id", 
        "folder_id", "cat_id", "selected", "id"]
    $H(m).each(function(f) {
      var k = f[0]
      var value = f[1]
      var key = k
      if (formFields.any(function(i) { return i == k })) { key = "form_" + k }
      if (!ignoreFields.any(function(i) { return i == k })) { r[key] = value }
    })
    return r
  },
  storeItem: function(m) {
    if (!this.canWrite()) { return }
    var options = Object.extend({
      afterStore: function() {}
    }, arguments[1] || {})
    var instance = this
    var fields = this.storeRequestFieldsForItem(m)
    var action = (m.id == -1 ? "create_item" : "edit")
    if (isFolder(m) || m.generic_folder) {
      action = (m.id == -1 ? "create_folder" : "edit")
    }
    if (__patch_ajax_action) { action = __patch_ajax_action }
    new AjaxFormBuilder({
      user: this.user(),
      service: this.service(),
      action: action,
      onSuccessfulSubmit: function(afb, success) {
        hash_merge(m, success.item_info)
        m.edit = false
        m.increation = false
        instance.update()
        instance.notifyObservers("afterStore")
        options.afterStore(m)
      }
    }).performAjaxRequest(fields.merge({
      form_path: this.currentPath(),
      form_username_override: this.user()
    }))
  },
  itemAjax: function(m) {
    var o = Object.extend({
      fields: {}
    }, arguments[1] || {})
    o.fields = $H(o.fields).merge({form_pretty_name: m.pretty_name})
    o.service = m.service || this.service()
    if (m.item_path) {
      var form_path = m.item_path.gsub(/\/[^/]*$/, "")
      if (form_path == "") form_path = "/"
      o.additionalFields = {
        form_path: form_path,
        form_username_override: this.userForItem(m)
      }
    }
    this.simpleAjax(o)
  },
  commentsForItem: function(m) {
    var o = Object.extend({
      success: function(commments) {}
    }, arguments[1] || {})
    this.itemAjax(m, {
      action: "comments",
      get: true,
      onSuccessfulSubmit: function(afb, success) { o.success(success) }
    })
  },
  addCommentForItem: function(m, comment) {
    var o = Object.extend({
      success: function(newComment) {}
    }, arguments[2] || {})
    this.itemAjax(m, {
      action: "add_comment",
      fields: {content: comment},
      onSuccessfulSubmit: function(afb, success) { 
        m.comment_count = success.comment_count
        o.success(success) 
      }
    })
  },
  removeCommentForItemAndId: function(m, id) {
    var o = Object.extend({
      success: function(id) {}
    }, arguments[2] || {})
    this.itemAjax(m, {
      action: "remove_comment",
      fields: {id: id},
      onSuccessfulSubmit: function(afb, success) { 
        m.comment_count = success.comment_count
        o.success(success) 
      }
    })
  },
  lastItemInData: function() {
    return this.items()[this.items().length-1]
  },
  folderItem: function() { return this.data.folder_item },
  addItem: function() {
    var options = arguments[0] || {}
    var m = Object.extend(Object.extend({
      id: -1,
      name: "New Item",
      pretty_name: "",
      thumbnail_image_url: "/images/file.gif",
      full_item_url: "",
      rating: 0,
      voted: 0,
      tags: "",
      type: "???",
      permission: "onlyme",
      comments: [],
      added_at: new Date(),
      edit: true,
      increation: true
    }, this.specificAddItemData()), options)
    m.name = this.makeSureNameIsUnique(m.name)
    this.items().push(m)
    this.version = changeCounter()
    return this.lastItemInData()
  },
  createItem: function() {
    var m = this.addItem(arguments[0] || {})
    this.storeItem(m)
    return m
  },
  specificAddItemData: function() {
    return {
      "name": "New " + this.itemNameCapitalized() ,
      "thumbnail_image_url": iconPath(128, this.itemName() + "_thumb"),
      "type": this.itemType()
    }
  },
  addFolder: function() {
    this.items().push({
      "id": "-1",
      "name": "New Folder",
      "pretty_name": "",
      "thumbnail_image_url": "/images/folder.gif",
      "full_item_url": "",
      "rating": 0,
      "voted": 0,
      "tags": "",
      "type": "folder",
      "comments": [],
      "added_at": new Date(),
      "permission": "onlyme",
      "edit": true,
      "increation": true
    })
    this.version = changeCounter()
    return this.lastItemInData()
  },
  itemName: function() { return this.defaultItemName() },
  itemNamePlural: function() { return this.defaultItemName().pluralize() },
  itemNamePluralCapitalized: function() { 
    return this.defaultItemName().pluralize().capitalize() },
  itemNameCapitalized: function() { return this.defaultItemName().capitalize() },
  itemType: function() { return this.defaultItemName().capitalize() },
  itemIdDisplayValue: function() { return this.options.itemIdDisplayValue() },
  items: function() {
    var ret = this.data.items
    if (ret) {
      var instance = this;
      $H({
        "full_item_url": this.options.noCrossSiteFullItemURL,
        "thumbnail_image_url": this.options.noCrossSiteThumbnailImageURL
      }).each(function(pair) {
        if (pair.value) {
          ret = $A(ret).map(function(m) {
            if (m[pair.key]) {
              m[pair.key] = localizeURL(m[pair.key])              
            }
            return m
          })
        }
      })
    }
    return ret || []
  },
  paramsController: function() { return this.data.service_name },
  userForItem: function(m) { return this.users()[m.account_id].username },
  user: function(pretty_name) {
    return this.data.user || this.options.user
  }
}

function camelizeWithUnderscores(s) {
  var parts = s.split('_'), len = parts.length;
  if (len == 1) return parts[0];

  var camelized = s.charAt(0) == '_'
    ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
    : parts[0];

  for (var i = 1; i < len; i++)
    camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

  return camelized;
}

function dataReaders(instance) {
  var i = 1
  for (; i < arguments.length; i++) {
    var n = arguments[i]
    instance[camelizeWithUnderscores(n)] = new Function("return this.data." + n)
  }
}

dataReaders(DisplayModel.prototype,
  "default_item_name",
  "params_action",
  "params_info",
  "params_login_error",
  "can_write",
  "category",
  "server_name",
  "path_folder_type",
  "users"
)

function dataReadersWithOptionsFallback(instance) {
  var i = 1
  for (; i < arguments.length; i++) {
    var n = arguments[i]
    instance[camelizeWithUnderscores(n)] =
        new Function("return this.data." + n + 
          " || this.options." + camelizeWithUnderscores(n)
        )
  }
}

dataReadersWithOptionsFallback(DisplayModelCommon,
  "path"
)

dataReadersWithOptionsFallback(DisplayModel.prototype, 
  "controller_name"
)
Object.extend(DisplayModel.prototype, DisplayModelCommon)

ModelFilter = Class.create();
ModelFilter.prototype = {
  initialize: function(model) {
    this.model = model
    var cf = ""
    this.options = Object.extend({
      filter: function(m) { return m; }
    }, arguments[1] || {})

    for (var property in model) {
      var v = model[property]
      if (typeof v == "function" && property != "initialize" && 
            property != "getModel") {
        cf += "this['" + property + "'] = function() {\n" + 
          "  return this.model['" + property + "']." + 
              "apply(this.model, arguments)\n" + 
          "};\n"
      }
    }
    this.createFunctions = cf
    eval(this.createFunctions)
  },
  getModel: function() {
    return this.options.filter(
        this.model.getModel.apply(this.model, arguments))
  }
}

var itemModel = new NullObject(DisplayModel);
var searchWidget = null

function createItemModel() {
  return new DisplayModel({
    data: model_data,
    columnNames: ["name", "type", "rating", "added_at"],
    columnDisplayNames: [itemIdDisplayValue(), 
        "Type", "Rating", "Time Added"],
    sortByColumn: "added_at",
    reverse: true
  })
}

function makeItemModel() {
  // searchWidget = new SearchWidget()
  itemModel = createItemModel()
  makeUserModels()
  // searchWidget.setModel(itemModel)
}

ProfileDisplayModel = Class.create();
ProfileDisplayModel.prototype = {
  defaultOptions: function() {
    return {
      data: null,
      user: write_username
    }
  },
  setup: function() {
    this.options.service = "profiles@core"
    this.data = this.options.data
  },
  editAccountSettingsActionLink: function() {
    // This action can only be called form myprofil.es!
    // fields:
    // name
    // file, ... (generic item)
    return serviceLink({service: this.service(), 
        action: "edit_account_settings", user: this.user()}) 
  },
  createAccountLink: function() {
    // This action can only be called form myprofil.es!
    // fields:
    // username, password, email
    return serviceLink({service: this.service(), action: "create_account"}) 
  },
  editAccountSettings: function(fields) {
    fields = $H(fields)
    new AjaxFormBuilder({
      user: this.options.user,
      service: this.options.service,
      action: "edit_account_settings",
      onSuccessfulSubmit: function(afb, success) {
        // this.data = success.item_info
        Object.extend(this.data, success.item_info)
        this.update()
      }.bind(this)
    }).performAjaxRequest(fields.merge({
      form_name: "settings"
    }))
  }
}

Object.extend(ProfileDisplayModel.prototype, DisplayModelCommon)
var readUserModel = new NullObject(ProfileDisplayModel);
var writeUserModel  = new NullObject(ProfileDisplayModel);

function makeUserModels() {
  readUserModel = new ProfileDisplayModel({data: read_user_model_data})
  writeUserModel = new ProfileDisplayModel({data: write_user_model_data})
  profileModel = writeUserModel;
}

MainPageDisplayModelCommon = {}
Object.extend(MainPageDisplayModelCommon, DisplayModel.prototype)
MainPageDisplayModelCommon.origDefaultOptions = MainPageDisplayModelCommon.defaultOptions
Object.extend(MainPageDisplayModelCommon,  {
  defaultOptions: function() {
    if (this.defaultOptionsOverrides) {
      var overrides = this.defaultOptionsOverrides()
    }
    else {
      var overrides = {}
    }
    return Object.extend({
      n: 40,
      any_item: false,
      mime_cat: null
    }, Object.extend(this.origDefaultOptions(), overrides))
  },
  changeToFolder: function(p) { 
    if (this.noChangeFolder) { return }
    startIdle();
    var instance = this
    var fields = {
      form_username_override: this.user(),
      n: this.options.n
    }
    if (this.options.mime_cat) {
      fields.mime_cat = this.options.mime_cat
    }
    // adding any_item=false to the service list returns all items anyway!
    if (this.options.any_item) {
      fields.any_item = this.options.any_item
    }
    if (this.options.service) {
      fields.service = this.options.service
    }
    if (this.options.target_user) {
      fields.target_user = this.options.target_user
    }
    if (this.additionalFields) Object.extend(fields, this.additionalFields())
    var ar = (new AjaxFormBuilder({
      user: this.user(),
      service: this.service(),
      get: true,
      action: this.queryDataAction(),
      onSuccessfulSubmit: function(afb, success) {
        try {
          instance.data = success
          instance.version = changeCounter()
          instance.notifyObservers("afterPathChanged")
          instance.update()
          endIdle()
        } catch (e) {
          endIdle()
          handleUpdateException(e)
          throw e
        }
      }
    }))
    ar.performAjaxRequest(fields)
  }
})

RecentDisplayModel = Class.create();
RecentDisplayModel.prototype = {
  defaultOptionsOverrides: function() {
    return {
      sortByColumn: "added_at",
      reverse: true
    }
  },
  queryDataAction: function() { return "recent_json" }
}
Object.extend(RecentDisplayModel.prototype, MainPageDisplayModelCommon)

TopRatedDisplayModel = Class.create();
TopRatedDisplayModel.prototype = {
  defaultOptionsOverrides: function() {
    return {
      sortByColumn: "rating",
      reverse: true
    }
  },
  queryDataAction: function() { return "top_rated_json" }
}
Object.extend(TopRatedDisplayModel.prototype, MainPageDisplayModelCommon)

RecentBuddyDisplayModel = Class.create();
Object.extend(RecentBuddyDisplayModel.prototype, RecentDisplayModel.prototype)
Object.extend(RecentBuddyDisplayModel.prototype, {
  queryDataAction: function() { return "recent_json" },
  additionalFields: function() { return {"buddies": true} }
})

SingleItemDisplayModel = Class.create();
Object.extend(SingleItemDisplayModel.prototype, DisplayModel.prototype)
Object.extend(SingleItemDisplayModel.prototype, {
  changeToFolder: function(p) { 
    if (this.noChangeFolder) { return }
    startIdle();
    var fields = {
      form_username_override: this.user(),
      path: (this.options.item_path + "/" + this.options.pretty_name).gsub(/\/\//, "/")
    }
    var ar = (new AjaxFormBuilder({
      user: this.user(),
      service: this.service(),
      get: true,
      action: "model_for_single_item",
      onSuccessfulSubmit: function(afb, success) {
        try {
          this.data = {
            can_write: logged_in && writeUserModel.data.username == this.user(),
            path: this.options.item_path
          }
          this.data.items = [success]
          this.version = changeCounter()
          this.notifyObservers("afterPathChanged")
          this.update()
          endIdle()
        } catch (e) {
          endIdle()
          handleUpdateException(e)
          throw e
        }
      }.bind(this),
      onErroneousSubmit: function(instance, error) {
        if (this.options.onErroneousSubmit) {
          this.options.onErroneousSubmit(instance, error)
        }
      }.bind(this)
    }))
    ar.performAjaxRequest(fields)
  },
  getItem: function() {
    return this.getModel()[0]
  },
  defaultItemName: function() {
    return this.options.service.split("@")[0].singularize()
  },
  users: function() {
    var u = {}
    u[this.getItem().account_id] = {username: this.user()}
    return u
  }
})
;

// widget.js
var WidgetCommon = {
  initialize: function() {
    this.observables = []
    this.hidden = true;
    var defaultOpts = Object.extend(this.defaultOptions(), { 
      hiddenByDefault: false
    })
    this.options = Object.extend(defaultOpts, arguments[0] || {})
    if (this.setup != undefined) {
      this.setup();
    }
    this.content = this.makeWidget()
    if (this.options.hiddenByDefault) {
      this.hide();
    } else {
      this.show();
    }
  },
  redrawCompletely: function() {
    var nc = this.makeWidgetContent()
    this.replaceChild(this.content, this.widgetContent, nc)
    this.widgetContent = nc
  },
  makeWidget: function() {
    return $(Builder.node('div', {style: this.options.style || ""},
      [ this.widgetContent = this.makeWidgetContent() ]
    ));
  },
  widgetContent: function() {
    return this.widgetContent;
  },
  htmlNode: function() {
    return this.content
  },
  hide: function() {
    this.hidden = true;
    this.htmlNode().style.display = "none"
  },
  show: function() {
    this.hidden = false;
    this.htmlNode().style.display = ""
  },
  addObservable: function(o) {
    this.observables.push(o)
  },
  release: function() {
    var instance = this
    this.observables.each(function(o) { o.removeObserver(instance) })
  },
  replaceChild: function(e, _old, _new) {
    e.removeChild(_old)
    e.appendChild(_new)
  }/*,
  rerender: function() {
    var nc = this.makeWidgetContent()
    this.replaceChild(this.content, this.widgetContent, nc)
    this.widgetContent = nc
  }*/
}

AutoAfterShow = Class.create();
AutoAfterShow.prototype = {
  defaultOptions: function() {
    return { f: function() { } }
  },
  setup: function() {
    this.i = new Image()
    this.counter = 0
    var instance = this
    this.i.onload = function() {
      try {
        instance.options.f()
        instance.counter = 0
      }
      catch (e) {
        if (instance.counter < 5) {
          instance.counter++
          setInterval(instance.i.onload, 100)
        }
        else {
          throw e
        }
      }
    }
    this.i.src = "/images/empty.gif"
  },
  makeWidgetContent: function() { return this.i }
}
Object.extend(AutoAfterShow.prototype, WidgetCommon)

function autoAfterShowWidget(call) {
  return new AutoAfterShow({f: call}).htmlNode()
}

EasyWidget = Class.create();
EasyWidget.prototype = {
  defaultOptions: function() {
    return {
      render: null
    };
  },
  makeWidgetContent: function() {
    return [ this.easyWidgetContent = this.makeEasyWidgetContent() ]
  },
  makeEasyWidgetContent: function() {
    return this.options.render.apply(this)
  }
}
Object.extend(EasyWidget.prototype, WidgetCommon)

;

// tabs.js
ActiveTabsWidget = Class.create();
ActiveTabsWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  setup: function() {
    tabs.addObserver(this)
  },
  makeTabEntry: function(t) {
    return actionLink({
      className: (tabs.isActive(t) ? "active " : "hidden ") + "entry",
      text: t.name,
      href: "javascript:void(0)",
      onclick: function() { tabs.activateTab(t) }
    })
  },
  makeWidgetContent: function() {
    return [ this.tabsContent = this.makeTabsContent() ]
  },
  makeTabsContent: function() {
    var instance = this
    if (!tabs.haveTabs()) {
      return EMPTY()
    }
    return Builder.node('span', {className: "tab-list"}, [
      tabs.tabs.map(function(t) { return instance.makeTabEntry(t) })
    ])
  },
  rerender: function() {
    var nc = this.makeTabsContent()
    this.replaceChild(this.content, this.tabsContent, nc)
    this.tabsContent = nc
  }
}

Object.extend(ActiveTabsWidget.prototype, WidgetCommon)

var activeTabsWidget = null
function makeActiveTabsWidget() {
  $('tabbing-header').appendChild(
      (activeTabsWidget = new ActiveTabsWidget()).htmlNode())
}

ActiveTabs = Class.create();
ActiveTabs.prototype = {
  initialize: function() {
    this.tabs = []
    this.current = null
  },
  haveTabs: function() { return this.current && this.tabs.length > 1 },
  registerTab: function(t) {
    if (!this.current) {
      var rootTab = new Tab(null, {
        bodyOverflow: "", 
        register: false,
        name: "Explorer"
      });
      this.tabs.push(rootTab)
      this.current = rootTab
    }
    hideAllTooltips()
    this.tabs.push(t)
    if (!t.name)  { t.name = this.tabs.length }
    this.current.hide()
    this.current = t
    this.update()
  },
  activateTab: function(t) {
    hideAllTooltips()
    if (this.current) { this.current.hide() }
    this.current = t
    if (this.current) { this.current.show() }
    this.update()
  },
  isActive: function(t) { return t == this.current },
  removeTab: function(t) {
    this.tabs = this.tabs.remove(t)
    this.current.hide()
    this.current = null
    this.activateTab(this.tabs.last())
  },
  findTabNamed: function(n) {
    return this.tabs.find(function(t) { return t.name == n })
  }
}

Object.extend(ActiveTabs.prototype, Observable)

tabs = new ActiveTabs()

Tab = Class.create()
Tab.prototype = {
  initialize: function(observer) {
    if (observer) { this.addObserver(observer) } // afterClear, reshowingTab 
    this.options = Object.extend({
      bodyOverflow: "hidden",
      register: true,
      window: observer,
      name: null
    }, arguments[1] || {})
    if (this.options.name) { this.name = this.options.name }
    if (this.options.register) { tabs.registerTab(this) }
    this.elements = []
    this.hidden = true
    this.show()
  },
  saveElements: function(resave) {
    this.bgColor = documentStyle().backgroundColor
    this.sOverflow = documentStyle().overflow
    var e = documentRoot()
    for (var i = 0; i < e.childNodes.length; ++i) {
      var n = $(e.childNodes[i])
      if (n.visible) {
        this.elements.push({e: n, v: n.visible()})
      }
    }
  },
  restoreElements: function() {
    this.elements.each(function(p) { p.e[p.v ? "show" : "hide"]() })
    documentStyle().backgroundColor = this.bgColor 
    this.sOverflow = documentStyle().overflow
  },
  show: function() {
    if (!this.hidden) { return }
    this.restoreElements()
    documentStyle().overflow = this.options.bodyOverflow
    this.hidden = false
    if (this.elements.length > 0) { 
      this.notifyObservers("afterReshowingTab") 
    } 
    else { 
      this.notifyObservers("afterInitiallyShowingTab") 
    }
  },
  hide: function() {
    if (this.hidden) { return }
    this.notifyObservers("beforeTabHide")
    this.saveElements()
    documentStyle().backgroundColor = this.savedBgColor
    this.elements.each(function(p) { if (p.v) { p.e.hide() } })
    documentStyle().overflow = this.sOverflow
    this.hidden = true
  },
  remove: function() {
    tabs.removeTab(this)
  }
}

Object.extend(Tab.prototype, Observable)
;

// iteminfo.js
ItemInfo = Class.create();
ItemInfo.prototype = {
  initialize: function(o) {
    this.options = Object.extend({
      
    }, o || {})
    this.previousElement = null;
    this.mboxes            = {};
    this.content    = {};
    this.boxesToHide = ['searchBox','ownerWidgetBox','folderWidgetBox',
      'actionNodeBox']
  },
  _deselect: function() {
    if (this.previousElement) {
      this.previousElement.style.background = ""
    }    
  },
  _showBoxes: function() {
    $A(this.boxesToHide).each(function(b) {
      var box = $(b)
      if (box) box.hide()
    })
    $H(this.mboxes).each(function(pair) {
      $(pair.value).show()
    })
  },
  _hideBoxes: function(slide) {
    $A(this.boxesToHide).each(function(b) {
      var box = $(b)
      if (box) box.show()
    })
    $H(this.mboxes).each(function(pair) {
      var box = pair.value
      box.hide()
    })
  },
  _select: function(m, model, newElement) {
    if (!this.mboxes.info) {
      scope("iteminfo", function() {this._createBoxes() }.bind(this))
    }
    if (newElement != this.previousElement) {
      newElement.style.background = "#CCC"
      this._buildContent(m, model)
      if (!this.previousElement) this._showBoxes()
      this.previousElement = newElement;
    }
  },
  _buildContent: function(m, model) {
    $A(["info", "share", "actions"]).each(function(b) {
      var content = this.content[b]
      var node = Builder.node('div')
      scope("iteminfo", function() {
        this["_build"+b.capitalize()+"Content"](m, model, node)        
      }.bind(this))
      content.replaceChild(node,content.firstChild)
    }.bind(this))
  },
  _buildInfoContent: function(m, model, node) {
    var user;
    var t
    node.appendChild(table({}, [
      Builder.node('div', [
        Builder.node('strong', [translateText({id: "name"}),TN(": ")]), TN(m.name)
      ])
    ],[
      Builder.node('div', [
        Builder.node('strong', [translateText({id: "added-by"}), TN(" ")]),
        user = Builder.node('a', [
          TN(model.user(m.pretty_name))
        ])
      ])
    ], [
      makeNiceDate(m.added_at)
    ],[
      t = table({cellspacing: "2"}, [
        Builder.node('span', {style: "font-weight: bolder"}, [
          translateText({id: "privacy"}), TN(": ")
        ]),
        {columnData: [TN(m.permission)],
         columnOptions: {width: "100%"}}
      ])
    ]))
    Event.observe(user, 'click', function(event) {
      Event.stop(event)
      var csw = new changeSiteWidget({
        profile: new ProfileDisplayModel({data: model.users()[m.account_id]}),
        text: ""
      })
      csw.onclick()
    })
    if (model.canWrite()) {
      var tagsTd = Element.down(t, 'td', 1)
      Element.setStyle(tagsTd, {
        cursor: "pointer"
      })
      Event.observe(tagsTd, 'mouseover', function(event) {
        if (!selectPresent) {
          tagsTd.style.backgroundColor = "#EFEFEF"
        }
      })
      Event.observe(tagsTd, 'mouseout', function(event) {
        if (!selectPresent) {
          tagsTd.style.backgroundColor = ""
        }
      })
      var selectPresent = false;
      Event.observe(tagsTd, 'click', function(event) {
        Event.stop(event)
        if (selectPresent) return;
        var select = Builder.node('select', {
          type: "text",
          style: "width:"+(tagsTd.offsetWidth-15)+"px;border:1px solid black;" +
                 "padding: 2px;"
        }, [
          Builder.node('option', {value: "everyone"}, [TN("public")]),
          //Builder.node('option', {value: "allbuddies"}, [TN("allbuddies")]),
          Builder.node('option', {value: "onlyme"}, [TN("private")])
        ])
        for (var i = 0; i < select.options.length; i++) {
          if (select.options[i].value == m.permission) {
            select.selectedIndex = i;
            break;
          }
        }
        Event.observe(select, 'change', function(event) {
          Event.stop(event)
          m.permission = select.options[select.selectedIndex].value
          tagsTd.innerHTML = ""
          tagsTd.appendChild(Builder.node('img', {src: "/images/spinner.gif"}))
          model.storeItem(m, {afterStore: function(newM) {
            m = newM;
            tagsTd.innerHTML = m.permission
            selectPresent = false;
          }})
        })
        tagsTd.innerHTML = ""
        tagsTd.style.backgroundColor = ""
        tagsTd.appendChild(select)
        select.focus()
        selectPresent = true;
      })
    }
  },
  _buildShareContent: function(m, model, node) {
    var sw = new shareWidget({item:m,model: model, compact: true})
    var wc = $(sw.windowContent(m))
    wc.style.padding = "2px"
    wc.style.marginBottom = "5px"
    node.appendChild(wc)
  },
  _buildActionsContent: function(m, model, node) {
    var rows = [{}]
    if (!model.actions.isFolder(m) && logged_in) {
      rows.push([
        table({cellspacing: "2"}, [
          Builder.node('span', {
            style: "font-weight:bolder;white-space:nowrap;font-size:bigger;"
          }, [
            translateText({id: "rate"}),TN(": ")
          ]),
          {
            columnOptions: {width: "100%"},
            columnData:[makeRatingWidget(model, m).htmlNode()]
          }
        ])
      ])
    }
    var itemType = model.actions.isFolder(m) ? "Folder" : model.itemType()
    rows.push([
      ActionWidget.prototype.generalAction("play", "open item",
        translateText({id: "open-action", args: [itemType]}), function(event) {
          this.previousElement.ondblclick(event)
        }.bind(this))
    ])
    var cw = new CommentsWidget({m:m,model: model})
    rows.push([
      ActionWidget.prototype.generalAction("comment", "comment on item",
        cw.htmlNode(), function(event) {
          cw.onclick()
        })
    ])
    var report = Builder.node('span', [
      translateText({id: "report-action", args: [itemType]})
    ])
    var formPresent;
    if (logged_in && !model.canWrite() && !model.actions.isFolder(m)) {
      rows.push([
        ActionWidget.prototype.generalAction("close", "report item",
          report, function(event) {
            if (formPresent) return;
            Event.stop(event);
            var cancel, textarea;
            var f = Builder.node('form', [
              textarea = Builder.node('textarea', {
                rows: 3,
                style: "width:90%;border:1px solid black;padding: 2px;",
                name: "report"
              }),
              Builder.node('input', {type: "submit", value: "Send"}),
              cancel = Builder.node('input', {type: "button", value: "Cancel"})
            ])
            report.appendChild(f)
            formPresent = true;
            Event.observe(textarea, 'click', function(event) {Event.stop(event)})
            $A(['click','submit']).each(function(e) {
              Event.observe(f, e, function(event) {
                Event.stop(event)
                var messageText = Builder.node('span', [
                  TN("User " + writeUserModel.data.username +
                     " reported your item "),
                  Builder.node('a', {
                    href: model.currentPathPermalink(m)
                  }, [TN(m.name)]),
                  BR(),
                  TN(writeUserModel.data.username + " also said the following:"), BR(),
                  TN(f.report.value),BR(),BR(),
                  TN("Also, the MySites administrations have been informed.")
                ]).innerHTML
                new UserMessages({
                  user: readUserModel.data.username,
                  afterLoad: function(um) {
                    um.sendMessage(messageText, {
                      report: true,
                      afterSend: function() {
                        Element.remove(f)
                        formPresent = false;
                      }
                    })
                  }
                })
              })              
            })
            Event.observe(cancel, 'click', function(event) {
              Event.stop(event)
              Element.remove(f)
              formPresent = false;
            })
          })
      ])
    }
    if (model.canWrite()) {
      var cp = model.currentPath()
      if (cp.startsWith("/trash/") || cp == "/trash") {
        rows.push([
          ActionWidget.prototype.generalAction("previous", "restore",
            translateText({id: "restore-action"}), function(event) {
              model.actions.restoreItem(m)
            })
        ])
      }
      else {
        rows.push([
          ActionWidget.prototype.generalAction("edit", "edit item",
            translateText({id: "edit-action"}), function(event) {
              itemDialog().withItem(m, model).editItem()
            })
        ])
        rows.push([
          ActionWidget.prototype.generalAction("delete", "delete item",
            translateText({id: "delete-action"}), function(event) {
              model.actions.moveToTrash(m)
            })
        ])
      }      
    }
    if (!model.actions.isFolder(m)) {
      function tags() {
        if (model.canWrite()) {
          return m.tags == "" ? translateText({id: "click-to-add-tags"}) : TN(m.tags)
        }
        else {
          return TN(m.tags)
        }
      }
      var t = table({cellspacing: "2"}, [
        Builder.node('span', {style: "font-weight: bolder"}, [
          translateText({id: 'tags'})
        ]),
        {columnData: [tags()],
         columnOptions: {width: "100%"}}
      ])
      var nameTd = Element.down(t, 'td')
      Element.setStyle(nameTd, {
        verticalAlign: "top",
        paddingTop: "5px"
      })
      var tagsTd = Element.down(t, 'td', 1)
      Element.setStyle(tagsTd, {
        padding: "5px"
      })
      if (model.canWrite()) {
        tagsTd.style.cursor = "pointer";
        Event.observe(tagsTd, 'mouseover', function(event) {
          if (!formPresent) {
            tagsTd.style.backgroundColor = "#EFEFEF"
          }
        })
        Event.observe(tagsTd, 'mouseout', function(event) {
          if (!formPresent) {
            tagsTd.style.backgroundColor = ""
          }
        })
        var formPresent = false;
        Event.observe(tagsTd, 'click', function(event) {
          Event.stop(event)
          if (formPresent) return;
          var input;
          var form = Builder.node('form', [
            input = Builder.node('input', {
              type: "text",
              style: "width:"+(tagsTd.offsetWidth-15)+"px;border:1px solid black;" +
                     "padding: 2px;",
              value: m.tags,
              name: "tags"
            })
          ])
          Event.observe(form, 'submit', function(event) {
            Event.stop(event)
            m.tags = form.tags.value
            tagsTd.innerHTML = ""
            tagsTd.appendChild(Builder.node('img', {src: "/images/spinner.gif"}))
            model.storeItem(m, {afterStore: function(newM) {
              m = newM;
              tagsTd.innerHTML = ""
              tagsTd.appendChild(tags())
              formPresent = false;
            }})
          })
          tagsTd.innerHTML = ""
          tagsTd.style.backgroundColor = ""
          tagsTd.appendChild(form)
          input.focus()
          formPresent = true;
        })        
      }
      rows.push([t])
    }
    node.appendChild(table.apply(this, rows))
  },
  _createBoxes: function() {
    $A([
      ["share", "Share"],
      ["actions", "Actions"],
      ["info", "Info"]
    ]).each(function(pair) {
      var k = pair[0]
      var v = pair[1]
      this.content[k] = Builder.node('div', {
        style: "margin:5px;font-size:bigger;"
      },[Builder.node('div')])
      this.mboxes[k] = new BoxWidget({
        id: k+'-box',
        title: translateText({id: k+"-title"}),
        node: this.content[k],
        className: "mybox"
      }).htmlNode();
      $(this.mboxes[k]).hide()
      var lb = $('left-bar')
      lb.insertBefore(this.mboxes[k], lb.firstChild)      
    }.bind(this))
  },
  showActionsForItem: function(m, model, newElement) {
    if (this.previousElement != newElement) {
      this._deselect()
      this._select(m, model, newElement)
    }
  },
  hideBox: function() {
    this._hideBoxes();
    this._deselect();
    this.previousElement = null;    
  }
}

itemInfoInstance = new ItemInfo()
axInitList.push(function() {
  Event.observe(document.body, 'click', function(event) {
    var element = Event.element(event)
    var hide = true;
    var skip = $A([
      /^left-bar-placeholder$/,
      /^window_\d+$/ // all Window divs
    ])
    do {
      var id = element.id
      if (id) {
        hide = skip.all(function(regexp) {
          return !regexp.test(id)
        })
      }
      element = element.parentNode
    } while (hide && element)
    if (hide) itemInfoInstance.hideBox()
  })  
})
;

// easysliderwidget.js
EasySliderWidget = Class.create();
EasySliderWidget.prototype = {
  defaultOptions: function() {
    return {
      onSlide: function() { },
      onChange: function() { },
      defaultSliderValue: 0.2,
      trackImage: "scaler_slider_track.gif",
      axis: 'horizontal',
      width: "default"
    }
  },
  initSlider: function() {
    if (this.slider) { this.slider.dispose() }
    this.slider = new Control.Slider(this.sliderHandle, this.sliderBar, {
      axis: this.options.axis, minimum: 20, maximum: 20, alignX: 0, 
      increment: 2, sliderValue: this.options.defaultSliderValue });
    var instance = this;
    this.slider.options.onSlide = function(value) { 
      instance.options.onSlide(value);
    }
	  this.slider.options.onChange = function(value) { 
      instance.options.onChange(value);
    }
  },
  setup: function() {
    var instance = this;
    this.sliderWidget = Builder.node('span', {className: "slider"}, [
      this.sliderBar = Builder.node('div', {
          className: "slider-bar-" + this.options.axis,
          style: "background-image: url('/images/" +
              this.options.trackImage + "'); " + 
              "width: " + this.options.width
        }, [
        this.sliderHandle = Builder.node('span', {className: "slider-handle"}, [
          Builder.node('img', {
            alt: "scaling", 
            src: "/images/scaler_slider_" + this.options.axis + ".gif"
          })
        ])
      ]),
      Builder.node('div', {style: 'display:none;'}, [
        autoAfterShowWidget(function () {
          function f() {
            instance.initSlider() 
            instance.options.onChange(instance.options.defaultSliderValue)            
          }
          if (Prototype.Browser.IE) {
            setTimeout(f, 500)
          }
          else {
            f()
          }
        })
      ])
    ])
  },
  makeWidgetContent: function() {
    return this.sliderWidget
  }
}
Object.extend(EasySliderWidget.prototype, WidgetCommon)
;

// debugwidget.js
DebugWidget = Class.create();
DebugWidget.prototype = {
  defaultOptions: function() {
    this.messages = []
    var instance = this;
    //(8).times(function() { instance.messages.push("") })
    return {};
  },
  makeWidgetContent: function() {
    return [
      this.messagesContent = this.makeMessagesContent()
    ]
  },
  makeMessagesContent: function() {
    this.div = Builder.node('div', {
      id:'debug-messages'
    })
    var instance = this
    this.messages.each( function(m) {
      instance.div.appendChild(Builder.node('span', {}, [
        TN(m), BR() 
      ]))
    })
    return this.div
  },
  rerender: function() {
    var nc = this.makeMessagesContent()
    this.replaceChild(this.content, this.messagesContent, nc)
    this.messagesContent = nc
  },
  appendLine: function(m) {
    this.messages.push(m)
    var child = null
    this.div.appendChild(child = Builder.node('span', {}, [
      TN(m), BR() 
    ]))
    this.div.scrollTop = child.offsetTop
  },
  appendMessage: function(m) {
    var instance = this
    var first = true
    m.split(/\n/).each(function(l) { 
      instance.appendLine((first ? "" : "+    ") + l) 
      first = false
    })
  }
}

var debugWidget = null;

function makeDebugWidget() {
  debugWidget = new DebugWidget()
  return __debug ? debugWidget.htmlNode() : TN("")
}

function d(m) {
  if (!__debug || !debugWidget) {
    return
  }
  debugWidget.appendMessage(m)
}


Object.extend(DebugWidget.prototype, WidgetCommon)
;

// newswidget.js
NewsWidget = Class.create();
NewsWidget.prototype = {
  defaultOptions: function() {
    this.messages = [ 
      "09-13: !! All tags and",
      "  permission reset ",
      "  - sorry",
    ];
    return {};
  },
  makeWidgetContent: function() {
    return [
      TN("Developer News"), BR(),
      this.messagesContent = this.makeMessagesContent()
    ]
  },
  makeMessagesContent: function() {
    var div = Builder.node('div', {
      id:'news-messages'
    })
    this.messages.each( function(m) {
      div.appendChild(TN(m))
      div.appendChild(BR())
    })
    return div
  }
}

Object.extend(NewsWidget.prototype, WidgetCommon)
;

// ie6hover.js
var IE6Hover = Class.create();
IE6Hover.prototype = {
  initialize: function(className) {
    if (/MSIE/.test(navigator.userAgent)) {
      this.options = Object.extend({
        onmouseover: function(e, ctx) {},
        onmouseout: function(e, ctx) {}
      }, arguments[1] || {})
      this.elements = document.getElementsByClassName(className)
      var myhover = this;
      this.elements.each(function(e) {
        e.onmouseover = function() {
          myhover.oldBackgroundColor = this.style.backgroundColor;
          myhover.oldColor = this.style.color;
          myhover.oldClassName = this.className;
          myhover.options.onmouseover(this, myhover)
        }
        e.onmouseout = function() {
          myhover.options.onmouseout(this, myhover)
          this.style.backgroundColor = myhover.oldBackgroundColor;
          this.style.color = myhover.oldColor;
          this.className = myhover.oldClassName;
        }
      })
    }
  }
}
;

// texteditor.js
var __o = null
var __art = null
EditableIFrame = Class.create();
EditableIFrame.prototype = {
  initialize: function(callee) {
    var instance = this;
    this.options = Object.extend({
      content: ''
    }, arguments[1] || {});
    this.callee = callee

    this.iframe = $(Builder.node('iframe', {
      width: "0px",
      height: "0px"
    }));

    this.root = $(Builder.node('div', {
      className: 'editable-iframe',
      style: 'display: none'
    }, [
      this.iframe
    ]))

    this.iframeAccessor = new IFrameAccessor(this.iframe)
    document.body.appendChild(this.root)
    //afterShow( function(o) {
    instance.waitForTextEditorToAppear({})
    //})
    this.commandToTagHash = {}
    if (Prototype.Browser.IE) {
      this.commandToTagHash = {
        "Bold"      : {"IE": "STRONG", "Gecko" : "B"},
        "Underline" : "U",
        "Italic"    : {"IE": "EM", "Gecko" : "I"},
        "FontName"  : "FONT",
        "FontSize"  : "FONT",
        "ForeColor" : "nothing"
      }
    }
  },
  reuse: function(callee) {
    this.options = Object.extend({
      content: ''
    }, arguments[1] || {});
    var c = this.callee
    if (c) {
      if (c.beforeIFrameReuse) { c.beforeIFrameReuse(this) }
    }
    this.callee = callee
    this.iframe.hide()
    this.setContent(this.options.content)
    if (callee) {
      if (callee.beforeIFrameAcquire) { callee.beforeIFrameAcquire(this) }
    }
  },
  waitForTextEditorToAppear: function(options) {
    startIdle()
    var instance = this;
    setTimeout(function() {
      instance.checkIfTextEditorHasAppeared(options)
    }, 1000)
  },
  positionOverPlaceholder: function(pp) {
    this.iframe.height = pp.height
    this.iframe.width = pp.width
    this.root.style.top = pp.top
    this.root.style.left = pp.left
    this.root.show()
    this.iframe.show()
    // Focus on the iframe so that a backspace as a first character doesn't
    // make the browser go back in its history!
    if (Prototype.Browser.Gecko) this.iframe.contentWindow.focus()		  
  },
  checkIfTextEditorHasAppeared: function(options) {
    try {
      this.makeContentEditable();
      this.afterAppearCallbacks(options)
      this.callee.afterAppearCallbacks(options)
      //if (this.callee.beforeIFrameAcquire) { this.callee.beforeIFrameAcquire() }
      endIdle();
    } catch (e) {
      // The editor has not yet been rendered, wait some more.
      this.waitForTextEditorToAppear(options);
    } 
  },
  makeContentEditable: function() {
    var instance = this;
    this.document().designMode = "on"
    this.body().contentEditable = true
    this.setContent(this.options.content)
    this.body().focus()
    if (Prototype.Browser.IE) {
      Event.observe(this.body(), 'keydown', function(event) {
        // Insert <br /> instead of wrapping line in
        var us = new UserSelection(this.iframe)
        if (event.keyCode == 13 && us.text() == "") {
          Event.stop(event)
          us.userSelection.pasteHTML("<br />")
          us.userSelection.select()
        }
      }.bindAsEventListener(this))
    }
    // By default Firefox uses span tags with style attributes. This creates problems
    // when the document is edited with IE afterwards.
    if (Prototype.Browser.Gecko) {
      $A(["styleWithCSS"/*, "insertBrOnReturn"*/]).each(function(c) {
        instance.document().execCommand(c, false, false)
      })
    }
  },
  document: function() {
    return this.iframeAccessor.content()
  },
  body: function() {
    return this.document().body;
  },
  content: function() {
    return this.body().innerHTML;
  },
  setContent: function(v) {
    if (v == undefined) v = ""
    if (Prototype.Browser.IE && this.iframe.readyState != "complete") {
      var instance = this;
      setTimeout(function() { instance.setContent(v) }, 200)
    }
    else {
      this.body().innerHTML = v      
    }
  },
  specialEmptyHandling: function(command) {
    return this.commandToTagHash[command.capitalize()]
  },
  commandToTag: function(command) {
    return this.specialEmptyHandling(command)[Prototype.Browser.asString];
  },
  // IE and Firefox use different tags as markup
  canonicalTagName: function(tagName) {
    switch (tagName) {
    case "B": case "STRONG":
      return this.commandToTag("Bold");
    case "I": case "EM":
      return this.commandToTag("Italic");
    default:
      return tagName;
    }
  },
  performBrowserCommand: function(command, value, us) {
    command = command.name ? command.name : command
    this.iframe.contentWindow.focus();
    if (!us) us = new UserSelection(this.iframe)
    if (Prototype.Browser.IE && this.specialEmptyHandling(command)) {
      if (us.text() == "") {
        us.userSelection.pasteHTML("&nbsp;")
        us.userSelection.moveStart("character", -1)
        us.userSelection.select()
        this.document().execCommand(command, false, value);        
      }
      else if ($A(["FontName", "FontSize"]).include(command)) {
        us.userSelection.execCommand(command, false, value)
        us.userSelection.select()
      }
      else {
        this.document().execCommand(command, false, value);        
      }
    }
    else {
      this.document().execCommand(command, false, value);
    }
  },
  insertElement: function(element, fill, attributes) {
    this.performBrowserCommand("inserthtml", "<" + element + " " + 
        attributes + " _moz_dirty=''>" + fill + "</" + element + ">")
  },
  insertSpan: function(fill) { this.insertElement("span", fill) },
  queryCommandValue: function(command) {
    return this.document().queryCommandValue(command)
  },
  release: function() {
    this.iframe.hide()
    var instance = this
    var f = __editableIframes.find(function(i) { 
        return i.iframe == instance })
    if (f) { f.used = false }
  },
  hide: function() {
    this.iframe.hide()
    if (Prototype.Browser.IE) {
      // We put focus on the iframe so that the cursor appears. Because of this we
      // need to remove the focus again so that the cursor disappears when the iframe
      // is hidden.
      window.focus()
    }
  },
	show: function() {
		this.iframe.show()
	}
}
Object.extend(EditableIFrame.prototype, AfterAppearHandling)

var __editableIframes = []
function findNewEditableIframe(callee, options) {
  var f = __editableIframes.find(function(i) { return !i.used })
  if (!f) {
    __editableIframes.push({
      used: false,
      iframe: new EditableIFrame(callee, options)
    })
    f = __editableIframes[__editableIframes.length - 1]
  } else {
    f.iframe.reuse(callee, options)
  }
  f.used = true
  return f.iframe
}

var __namedIframes = {}
function getEditableIFrame(callee, options, id) {
  if (id == undefined) { id = "default" }
  var f = __namedIframes[id]
  if (!f) {
    f = __namedIframes[id] = findNewEditableIframe(callee, options)
  }
  else {
    f.reuse(callee, options)
  }
  return f
}

function hideEditableIframe(id) {
  if (id == undefined) { id = "default" }
  var f = __namedIframes[id]
  if (f)  { f.hide() }
}

var __ec = null

EditorCommon = {
  initialize: function() {
    __ec = this
    var instance = this
    this.options = Object.extend({
      width: "100%",
      height: "100%",
      editorImages: '/images/editor/',
      reserveVerticalSpace: 100
    }, arguments[0] || {});
    if (this.initializeOptions) { this.initializeOptions(this.options) }
    this.placeholder = Builder.node('div', {className: 'place-holder'})
    this.acquireIframe()
    afterShow( function(o) {
      instance.adjustPlaceholder(o)
    })
    if (this.initializeEditor) { this.initializeEditor() }
    var children = [];
    if (this.header) { children.push(this.header) }
    children.push(this.placeholder)
    if (this.footer) { children.push(this.footer) }
    if (this.debug) { children.push(this.debug) }

    this.textEditor = Builder.node('div', {
      className: 'editor'
    }, children);
    afterShow( function(o) {
      tabs.current.addObserver(instance)
    })
  },
  adjustPlaceholder: function(o) {
    var a = o.maxHeightWindowAccessor
    var the = a.totalHeightElement()
    var pcHeight = the.offsetHeight - 
        // 20: save, cancel 
        (20 + 
        (this.addVerticalSpace ? this.addVerticalSpace : 0) + 
        (this.options.reserveVerticalSpace ?  
        this.options.reserveVerticalSpace : 0)); 
    this.placeholder.style.height = pcHeight + "px"
    this.rememberPlaceholderPosition()
    this.editableIframe.positionOverPlaceholder(this.placeholderPosition)
  },
  rememberPlaceholderPosition: function() {
    var ph = this.placeholder
    this.placeholderPosition = {
      height: ph.offsetHeight + "px",
      width: (ph.offsetWidth - 10) + "px",
      top: totalTop(ph) + "px",
      left: totalLeft(ph) + "px"
    }
  },
  beforeTabHide: function() {
    this.contentData = this.content()
    this.editableIframe.hide()
  },
  afterReshowingTab: function() {
    this.acquireIframe()
    this.setContent(this.contentData)
    this.editableIframe.positionOverPlaceholder(this.placeholderPosition)
  },
  acquireIframe: function() {
    // FIXME: Doesn't work!
    // if (this.preProcessContent) {
    //   this.options.content = this.preProcessContent(this.options.content)
    // }
    this.editableIframe = getEditableIFrame(this, this.options)
  },
  editorImages: function() {
    return this.options.editorImages;
  },
  verticalSeparator: function() {
    var d = Builder.node('span', {}, [
      image = Builder.node("img", {
        src: this.editorImages() + "vseparator.gif",
        alt: '', width: 2, height: 18
      })
    ])
    return d
  },
  makeSimpleCommand: function(command) {
    var instance = this
    return this.makeCommand(command, function() {
      instance.editableIframe.performBrowserCommand(command);
    })
  },
  makeCommand: function(command, call) {
    if (!command.name) {
      command = {name: command, description: ""}
    }
    var image = null;
    var imageName = command.name.toLowerCase()
    var w = 20;
    var h = 20;
    if (imageName == "fontname") {
      w = 85
    }
    else if (imageName == "fontsize") {
      w = 49
    }
    var d = Builder.node('span', {id: 'texteditor-'+imageName}, [
      image = Builder.node("img", {
        src: this.editorImages() + imageName + '.gif',
        alt: command.name,
        title: command.name,
        width: w, height: h
      }),
      Builder.node('span', {
        style: (Prototype.Browser.IE ? '' : 'cursor:pointer;color:red;')
      }, [
        (command.description != "" ?
          translateText({path: ".texteditor", id: command.description}) :
          EMPTY()
        )
      ])
    ])
    var instance = this
    if (Prototype.Browser.IE) {
      // FIXME: The onclick handler doesn't keep the selection in the
      // iframe if the user clicks on the text instead of the image so
      // we turned that off.
      image.onclick = call
    }
    else {
      d.onclick = call      
    }
    image.onmouseover = function() {
      this.src = instance.editorImages() +  imageName + "_on.gif"
    }
    image.onmouseout = function() {
      this.src = instance.editorImages() +  imageName + ".gif"
    }
    return $(d)
  },
  content: function() {
    var content = this.editableIframe.content()
    if (this.postProcessContent) {
      content = this.postProcessContent(content)
    }
    return content
  },
  setContent: function(v) {
    this.contentData = v
    if (this.preProcessContent) {
      v = this.preProcessContent(v)
    }
    this.editableIframe.setContent(v)
  },
  performBrowserCommand: function(command, value) {
    return this.editableIframe.performBrowserCommand(command, value)
  },
  htmlNode: function() {
    return this.textEditor
  }
}

Object.extend(EditorCommon, AfterAppearHandling)

/*
 * Implements a TextEditor
 * 
 * required files: 
 *   - easyhtml.js
 *   - utilities.js
 *   - widget.js
 *   - debugwidget.js
 *   - presentation.js (for processAfterShow())
 *
 * Example usage:
 *
 *   // Setup
 *   var textEditor = new TextEditor({
 *     name: "comment",
 *     content: "foo",
 *     height: "5em",  // default is "100%"
 *     cols: "5em"     // default is "100%"
 *   });
 * 
 *   // Add HTML to Dom
 *   textEditor.htmlNode();
 *
 *   // some initialization can only be done when 
 *   // the editor is actually shown on the screen:
 *   processAfterShow();
 *
 *   // Get current content
 *   textEditor.content();
 *
 */

TextEditor = Class.create();
TextEditor.prototype = {
  initializeOptions: function(options) {
    this.options = Object.extend({
      name:         'texteditor',
      showCommands: true,
      rte:          true
    }, this.options);
  },
  initializeEditor: function() {
    this.commands = Builder.node('div', {
      className: 'commands'
      }, [
        this.makeFontCommand("FontName"),
        this.makeFontCommand("FontSize"),
        this.makeSimpleCommand("Bold"),
        this.makeSimpleCommand("Underline"),
        this.makeSimpleCommand("Italic"),
        this.makeCreateLink(),
        this.verticalSeparator(), 
        this.makeSimpleCommand("Justifyleft"),
        this.makeSimpleCommand("Justifycenter"),
        this.makeSimpleCommand("Justifyright"),
        this.verticalSeparator(), 
        this.makeSimpleCommand("InsertUnorderedList"),
        this.makeSimpleCommand("InsertOrderedList"),
        this.makeSimpleCommand("Indent"),
        this.makeSimpleCommand("Outdent"),
        /*this.verticalSeparator(), 
        this.makeSimpleCommand("Cut"),
        this.makeSimpleCommand("Copy"),
        this.makeSimpleCommand("Paste"),*/
        this.verticalSeparator(), 
        this.makeSimpleCommand("Undo"),
        this.makeSimpleCommand("Redo"),
        this.verticalSeparator(), 
        this.makeColorCommand("ForeColor"),
				// Use "BackColor" in IE and Safari but "hilitecolor" in Opera
				// and Firefox!
        // This feature was disabled because of the following things:
        // * Unlike the other commands it didn't work in FF without a selection
        // * In FF it sets the color of the surrounding P element but in IE it
        //   doesn't.
        // this.makeColorCommand(this.backColor()),
        this.makeImageSelection()
    ]);
    // build child-nodes of textEditor
    // if this.options.showCommands == false,
    // you can place the this.commands node
    // at your own will!
    if (this.options.showCommands) {
      this.header = this.commands;
      this.addVerticalSpace = 40 // header
    }
  },
  preProcessContent: function(c) {
    // IE and Firefox use different markup. So before displaying and editing the
    // content we replace the tags.
    var replacements = $MSH({"b": "strong", "i": "em"})
    replacements.each(function(pair) {
      if (Prototype.Browser.IE) {
        var f = pair.key
        var r = pair.value
      }
      else {
        var f = pair.value
        var r = pair.key
      }
      c = c.replace(new RegExp("(<\/?)"+ f +">","gi"), "$1"+r+">")
    })
    // insertBrOnReturn only works if we are already in a P tag!!!
    return this.convertToP(c)
  },
  convertToP: function(content) {
    // FIXME: Buggy so it's disabled for now.
    return content;
    // content = content.replace(/[\r\n]{1,2}/gi, '')
    // content = $A(content.split(/<br\/?>/gi)).map(function(l) {
    //   if (l.match(/^\s*<p>.*<\/p>\s*$/gi)) {
    //     return l
    //   }
    //   else if (l.match(/^\s*<p>.*<\/p>/gi)) {
    //     var e = l.split(/<\/p>/gi)
    //     e[e.length-1] = "<p>" + e[e.length-1] + "</p>"
    //     return e.join('')
    //   }
    //   else {
    //     return "<p>" + l + "</p>"          
    //   }
    // }).join('')
    // while(content.match(/<p>(&nbsp;)?<\/p>\s*$/gi)) {
    //   content = content.replace(/<p>(&nbsp;)?<\/p>\s*$/gi, '')
    // }
    // // The space is necessary so that the paragraph takes up space.
    // return content.replace(/<p><\/p>/gi, "<p>&nbsp;</p>")
  },
  postProcessContent: function(c) {
    return this.convertToP(c)
  },
  backColor: function() {
    if (Prototype.Browser.IE || Prototype.Browser.WebKit)
      return "BackColor";
    else
      return "hilitecolor";
  },
  fonts: function() {
    return ["Arial", "Sans Serif", "Tahoma", "Verdana", "Courier New",
        "Georgia", "Times New Roman", "Impact", "Comic Sans MS",
        "Trebuchet MS"]
  },
  makeFontDropDownList: function(command) {
    var instance = this;
    var l = Builder.node('ul',
      {className: 'drop-down', style: 'display:none;'},
      this.fonts().sort().map(function(f) {
        var node = Builder.node('li',
          {style: 'font-family: ' + f + '; font-size: 12px;'}, [TN(f)]);
        node.onmouseover = node.onmouseout = function() {
          this.toggleClassName('selected');
        }
        node.onclick = function() {
          if (Prototype.Browser.IE) {
            instance.editableIframe.performBrowserCommand(command, f, instance.us);
          }
          else {
            instance.editableIframe.performBrowserCommand(command, f);
          }
          Effect.Fade(this.up(), {duration:0.15});
        }
        return node;
    }));
    return l;
  },
  // These are the only sizes supported by FontSize!
  sizes : function() {
    return [1,2,3,4,5,6,7];
  },
  makeSizeDropDownList: function(command) {
    instance = this;
    var l = Builder.node('ul',
      {className: 'drop-down', style: 'display:none;'},
      this.sizes().map(function(s) {
        var node = Builder.node('li', {style: 'font-size: 12px;'}, [TN(s)]);
        node.onmouseover = node.onmouseout = function() {
          this.toggleClassName('selected');
        }
        node.onclick = function() {
          if (Prototype.Browser.IE) {
            instance.editableIframe.performBrowserCommand(command, s, instance.us);
          }
          else {
            instance.editableIframe.performBrowserCommand(command, s);
          }
          Effect.Fade(this.up(), {duration:0.15});
        }
        return node;
    }));
    return l;
  },
  makeFontCommand: function(command) {
    var instance = this;
    var l    = null;
    var opts = {};
    switch (command) {
      case 'FontName':
        l = this.makeFontDropDownList(command);
        opts = {setWidth: false};
        break;
      case 'FontSize':
        l = this.makeSizeDropDownList(command);
        break;
    }
    // FIXME: Find a way to do this without using element ids.
    var f = function() {
      instance.us = new UserSelection(instance.editableIframe.iframe);
      var e = $('texteditor-'+command.toLowerCase());
      if(!l.style.position || l.style.position=='absolute') {
        l.style.position = 'absolute';
        Position.clone(e, l,
          Object.extend({setHeight: false, offsetTop: e.offsetHeight}, opts));
      }
      Effect.toggle(l, 'appear', {duration:0.15});
      instance.editableIframe.iframe.contentWindow.focus();
    };
    return Builder.node('span', {}, [this.makeCommand(command, f), l]);
  },
  normalizeURL: function(string) {
    var url = string.replace(/^\s*/, "").replace(/\s*$/, "")
    if (!url.match(/^http:\/\//)) {
      url = "http://" + url;
    }
    return url
  },
  makeCreateLink: function(description) {
    var instance = this;
    return this.makeCommand({name: "CreateLink", description: description}, function() {
      // Using instance.editableIframe.document() doesn't work
      var us = new UserSelection(instance.editableIframe.iframe);
      us.savePosition();
      var emptySelection = us.isEmpty();
      var formFields     = {'url': 'URL'}
      var f              = emptySelection ? "insertAtPoint" : "wrap"
      if (emptySelection) {
        formFields['text'] = 'Text'
      }
      var formWidget = new FormWidget({
        formFields: $MSH(formFields),
        submit: function(fields) {
          w.close();
          if (fields.url) {
            var link = Builder.node('a',
              {href: instance.normalizeURL(fields.url)}, [])              
            if (emptySelection) {
              link.appendChild(TN(fields.text ? fields.text : fields.url));
            }
						// The iframe might be different so we have to set it
						// here again!                
            us[f](link, instance.editableIframe.iframe.contentWindow);
          }
        },
        cancel: function() {
          w.close();
        }
      })
      var explorerWidget = new ExplorerWidget({
        model: (model = new DisplayModel({
          path: "/",
          controllerName: "main"
        })),
        folderClicked: function(f) {
          if (isFolder(f.m)) {
            model.changeToFolder(model.currentPath() + "/"  + f.m.name)
          }
          else {
            formWidget.htmlNode().down('input[name="url"]').value = 
                model.currentPathPermalink(f.m)
            var name = formWidget.htmlNode().down('input[name="text"]')
            if (name) {
              name.value = f.m.name
            }
          }
          return false;
        }
      })
      var linkToExplorer = Builder.node('a', {href: 'javascript:void(0)'}, [
        TN("View your websites and insert links to them")
      ])
      explorerWidget.hide()
      linkToExplorer.onclick = function() {
        explorerWidget[(explorerWidget.hidden ? "show" : "hide")]()
      }
      var w = new PWindow({
        name: "Create Link",
        contentWidget: new AggregationWidget({contentWidgets: [
          formWidget,
          new EasyWidget({render: function() { return linkToExplorer}}),
          explorerWidget
        ]})
      })
    })
  },
  makeImageSelection: function(description) {
    var instance = this;
    return this.makeCommand({name: "selectimage", description: description}, function() {
      var us = new UserSelection(instance.editableIframe.iframe);
      us.savePosition();
      var w = new PWindow({
        contentWidget: new ExplorerWidget({
          model: (model = new DisplayModel({
            path: "/",
            controllerName: "pictures",
            noCrossSiteThumbnailImageURL: true,
            noCrossSiteFullItemURL: true
          })),
          displayItemClicked: function(m) {
            w.close();
            var node = Builder.node('img', {src: m.full_item_url})
            us.insertAtPoint(node, instance.editableIframe.iframe.contentWindow);
            return false
          }
        })
      })
    })
  },
  makeColorCommand: function(command) {
    var instance = this
    var opened = false;
    var i = Builder.node('input', {
      type: 'text',
      value: '',
      style: 'width: 0px; border: 0px; visibility: hidden'
    })
    var b = Builder.node('span', {
      style: "height:1.5em;border:1px outset black;margin:5px;display:none;"
    })
    b.innerHTML = "&nbsp;&nbsp;&nbsp;"
    var colorpicker =  null;
    var c = this.makeCommand(command, function() {
      if (Prototype.Browser.IE) {
        instance.us = new UserSelection(instance.editableIframe.iframe);
        i.value = instance.us.userSelection.queryCommandValue("ForeColor").toString(16)
      }
      else {
        i.value = instance.editableIframe.queryCommandValue("ForeColor")
      }
      colorpicker.open()
      opened = true;
      c.down('img').hide()
      b.show()
      if (Prototype.Browser.IE) {
        instance.editableIframe.body().focus()
        instance.us.userSelection.select()
      }
    })
    c.down('img').up().insertBefore(b, c.down('img'))
    function focus() {
      if (Prototype.Browser.IE) {
        instance.editableIframe.body().focus()
        instance.us.userSelection.select()              
      }      
    }
    var cpi = setInterval(function() {
      colorpicker = new Control.ColorPicker(i, {
        IMAGE_BASE: "/images/colorpicker/",
        onUpdate: function(value) {
          b.style.backgroundColor = "#" + value;
          focus()
        },
        onOpen: function() { focus() },
        onClose: function() {
          if (opened) {
            if (!this.isCancel) {
              if (command.name == "hilitecolor") {
                instance.editableIframe.performBrowserCommand("styleWithCSS", true)
              }
              instance.editableIframe.performBrowserCommand(command.name, i.value)
              if (command.name == "hilitecolor") {
                instance.editableIframe.performBrowserCommand("styleWithCSS", false)
              }
            }
            opened = false;
            b.hide()
            c.down('img').show()
            c.down('img').onmouseout();
            focus()
          }
        }
      })
      clearInterval(cpi)
    }, 100)
    return [c, i]
  }
}

Object.extend(TextEditor.prototype, EditorCommon)

SidebarTextEditor = Class.create();
Object.extend(SidebarTextEditor.prototype, TextEditor.prototype)
SidebarTextEditor.prototype.initializeEditor = function() {
  this.commands = Builder.node('div', {
    className: 'commands'
  }, [
    translateText({path: ".texteditor", id: "edit-text"}),
    TN(":"),
    Builder.node('br'),      
    this.makeFontCommand("FontName"),
    this.makeFontCommand("FontSize"),
    Builder.node('br'),
    this.makeSimpleCommand({name: "Undo", description: "undo"}),
    this.makeSimpleCommand({name: "Redo", description: "redo"}),
    Builder.node('br'),
    this.makeSimpleCommand("Bold"),
    this.makeSimpleCommand("Underline"),
    this.makeSimpleCommand("Italic"),
    Builder.node('br'),
    this.makeSimpleCommand("Justifyleft"),
    this.makeSimpleCommand("Justifycenter"),
    this.makeSimpleCommand("Justifyright"),
    Builder.node('br'),
    this.makeSimpleCommand("InsertUnorderedList"),
    this.makeSimpleCommand("InsertOrderedList"),
    Builder.node('br'),
    this.makeSimpleCommand("Indent"),
    this.makeSimpleCommand("Outdent"),
    Builder.node('br'),
    this.makeColorCommand({name: "ForeColor", description: "text-color"}),
    Builder.node('br'),
    this.makeCreateLink("link"),
    Builder.node('br'),
    this.makeImageSelection("picture")
  ]);
  this.header                       = this.commands;
  this.addVerticalSpace             = 0
  this.options.reserveVerticalSpace = 0
}
;

// codeeditors.js
// Highlighting: _te.document().body.
//   innerHTML.gsub("on", function(m) { return "<b>" + m[0] + "</b>" })


CodeEditor = Class.create();
CodeEditor.prototype = {
  initializeOptions: function(options) {
    this.options = Object.extend({
      name: 'codeeditor'
    }, this.options);
  },
  initializeEditor: function() {
    this.commands = Builder.node('div', {
      className: 'commands'
      }, [
        this.makeSimpleCommand("Indent"),
        this.makeSimpleCommand("Outdent"),
        /*this.verticalSeparator(), 
        this.makeSimpleCommand("Cut"),
        this.makeSimpleCommand("Copy"),
        this.makeSimpleCommand("Paste"),*/
        this.verticalSeparator(), 
        this.makeSimpleCommand("Undo"),
        this.makeSimpleCommand("Redo"),
    ]);
    var instance = this
    this.newline = true
    this.quote = false;
    if (this.afterInitialize) {
      this.afterInitialize()
    }
    this.indent = 0
		var instance = this;
		this.afterAppear(function() {
		  instance.setContent(instance.contentData)
		})
  },
  keySpace: 32,
  keyDoubleQuote: 34,
  keySingleQuote: 39,
  keyBackslash: 47,
  doSyntaxHighlighting: function(v) {
    tstart("syntaxhighlighting")
    var v = this.syntaxHighlighting ? this.syntaxHighlighting(v) : v
    tstop("syntaxhighlighting")
    return v
  },
  preProcessContent: function(v) {
    return "<span style='font-family: monospace'>" + 
        this.doSyntaxHighlighting((v || " ").
          replace(/ /g, "&nbsp;").
          replace(/</g, "&lt;").
          replace(/>/g, "&gt;").
          replace(/\n/g, "\n<BR>")) + 
        "</span>"
  },
  postProcessContent: function(v) {
    return v.replace(/\n*<BR[^>]*>/ig, '\n').replace(/&nbsp;/ig, " ").
        replace(/<\/?span[^>]*>/gi, "").
        replace(/<img[^>]*>/gi, "").
        replace(/&gt;/gi, ">").
        replace(/&lt;/gi, "<")
  }
}

Object.extend(CodeEditor.prototype, EditorCommon)

var __globalCompletionTerms = null

function globalCompletionTerms() {
  if (__globalCompletionTerms) {
    return __globalCompletionTerms
  }
  var r = []
  $MSH(javaScript).each(function(p) {
    var n = p.key
    var v = p.value
    if (typeof v == "function") { 
      r.push(n) 
      //if (v.prototype) {
      //  $MSH(v.prototype).each(function(p) { r.push(p.key) })
      //}
    }
  })
  __globalCompletionTerms = r
  return r
}

function createTrigger(instance, name) {
  // name: "afterShowNextCharacter"
  tn = name + "Trigger"
  var v = "this." + tn
  instance[name] = new Function("call", 
      "  if (!" + v + ") { " + v + " = [] }\n" + 
      "  " + v + ".push(call)\n")
  instance["check" + name.capitalize()] = new Function(
      "  if (" + v + " && " + v + ".length > 0) {\n" +
      "    var l = " + v + ".clone()\n" +
      "    " + v + " = [] \n" + 
      "    this.eventsLog.push('t:' + l.length)\n" + 
      "    var instance = this\n" + 
      "    l.each(function(c) { c.apply(instance)})\n" + 
      "  }\n")
}

var __characters = null

function matchingCharacters(re) {
  if (!__characters) {
    __characters = [];
    (256).times(function(c) {
      __characters.push(String.fromCharCode(c))
    })
  }
  return __characters.grep(re)
}

__markerCounter = 0

Marker = Class.create();
Marker.prototype = {
  initialize: function() {
    this.o = Object.extend({
      regExps: [],
      colorize: function() { return "" }
    }, arguments[0] || {});
    this.markerPrefix = "marker" + __markerCounter++
  },
  mark: function(v) {
    //v = v.gsub(re, function(m) {
    //instance.snippets.push(m[0])
    this.snippets = []
    var c = -1
    var instance = this
    var rc = 0
    this.o.regExps.each(function(re) {
      var marker = instance.markerPrefix + "_" + (rc++)
      //console.info("m: marker: " + marker)
      v = v.replace(re, function(m) {
        instance.snippets.push(m)
        c++
        return "{[" + marker + "!" + c + "]}"
      })
    })
    return v
  },
  resolve: function(v) {
    //return v.gsub(new RegExp('\\{\\[' + this.marker + '!(\\d+)\\]\\}'), 
    var instance = this
    var rc = this.o.regExps.length
    this.o.regExps.each(function() {
      var marker = instance.markerPrefix + "_" + (--rc)
      //console.info("r: marker: " + marker)
      v = v.replace(new RegExp('\\{\\[' + marker + '!(\\d+)\\]\\}', "g"), 
      function(d, m) { return instance.o.colorize(instance.snippets[m]) })
    })
    return v
  }
}


var __n = null

JsAndCssEditCommon = {
  afterInitialize: function() {
    var instance = this
    this.comments = new Marker({
       regExps: [/\/\*[\W\w]*?\*\//g, /\/\/.*/g],
       colorize:  function(s) { return instance.bgcolor("white", "gray", s) }
    })
    this.strings = new Marker({
        regExps: [/"[^"]*"/g, /'[^']*'/g], 
        colorize: function(s) { return instance.bgcolor("white", "blue", s) }
    })
    //this.fullComments = new Marker()
    //this.oneLineComments = new Marker()
    //this.doubleQuotedStrings = new Marker()
    //this.singleQuotedStrings = new Marker()

    this.colorMarker = "rgb(1, 2, 3)"
    this.addVerticalSpace = 100 // footer
    this.footer = Builder.node('div', {className: "code-editor-footer"})
    this.debug = Builder.node('div', {className: "code-editor-footer"})
    this.currentColor = "black"
    this.setNewCurentNode(null)
    this.keypresses = {}
    this.keyups = {}
    this.registerKeypress({ regExp: /^[-\s{.\[\]\(\)]$/,
      action: function(a, e) { this.newWordAboutToStart(e) }})
    this.registerKeypress({ ctrl: true, key: "p",
      action: function() { this.selectCompletion(1) }})
    this.registerKeypress({ ctrl: true, key: "n",
      action: function() { this.selectCompletion(-1) }})
    this.registerKeypress({ keys: ['"', "'"],
      action: function(k) { 
        if (!this.quote) {
          //console.info("press: quote")
          this.setCurrentColor("blue")
          this.quote = true
          this.quoteChar = k
        } else if (k == this.quoteChar) {
          //console.info("quote end")
          this.quote = false
          this.afterShowCharacter(function() {
            //console.info("afterShowCharacter: quote end")
            this.setCurrentColor("black")
          })
        }
    }})
    this.registerKeypress({ ctrl: true, key: "l",
      action: function() { this.setContent(this.content()) }})
    //this.registerKeypress({ ctrl: true, key: "i",
    //  action: function() { this.performBrowserCommand("indent") }})
    this.registerKeypress({ ctrl: true, key: "d",
      action: function() { this.changeCurrentIndent(-2) }})
    this.registerKeypress({ ctrl: true, key: "i",
      action: function() { this.changeCurrentIndent(2) }})
    if (this.setupCssOrJs) { this.setupCssOrJs() }
    this.eventsLog = []
  },
  beforeIFrameReuse: function(iframe) {
    //console.info("beforeIFrameReuse")
    if (!this.observers) { return }
    //console.info("2")
    var instance = this
    this.observers.each(function(o) {
      //console.info("rel: " + o.name)
      iframe.document().removeEventListener(o.name, o.observer, true)
    })
  },
  beforeIFrameAcquire: function(iframe) {
    //console.info("beforeIFrameAcquire")
    var instance = this
    this.observers = [];
    ["keyup", "keypress"].each(function(eventName) {
      if (instance[eventName]) {
        //console.info("ael: " + eventName)
        var observer = function(e) { return instance[eventName](e) }
        instance.observers.push({name: eventName, observer: observer})
        iframe.document().addEventListener(eventName, observer, true)
      }
    })
  },
  setKeywords: function(words) {
    this.keywords = $MSH(words)
  },
  acquireCompletionWords: function(v) {
   //this.localCompletionWords = v.split(/[^\w]+/).uniq()
   this.localCompletionWords = []
   //this.globalCompletionTerms().concat(v.split(/[^\w.]+/)).uniq().sort()
  },
  findMarkedCurrentItem: function(cc) {
    var instance = this
    this.eventsLog.push("mark?")
    var found = false
    $A(this.editableIframe.document().getElementsByTagName('span')).
    each(function(n) { 
      if (n.style.color == instance.colorMarker) {
        found = true
        instance.eventsLog.push("found!")
        instance.setNewCurentNode(n.firstChild)
        n.style.color = instance.currentColor
        //n.style.height = "12px"
        //n.style.width = "0px"
        if (instance.newLine && cc == 32) {
          instance.checkSpaceAtStartOfLine(n)
        }
      }
    })
    if (!found) {
      this.eventsLog.push("FAILED!")
    }
  },
  findCurrentIndent: function(cc) {
    var instance = this
    this.eventsLog.push("ci?")
    var found = false
    $A(this.editableIframe.document().getElementsByTagName('span')).
    each(function(n) { 
      if (n.getAttribute("indent") == "p") {
        found = true
        instance.eventsLog.push("!")
        n.setAttribute("indent", "")
        instance.currentIndent = n.firstChild
      }
    })
    if (!found) {
      this.eventsLog.push("FAILED!")
    }
  },
  newWordAboutToStart: function(e, event) {
    //this.performBrowserCommand("insertimage", this.emptyUrl)
    //this.performBrowserCommand("inserthtml", "<span style='color: blue' " + 
        //"_moz_dirty=''>FOOO</span>")
    if (!event) { event = "afterShowCharacter" }
    if (this.cwColorized) {
      this.cwColorized = false
      this.currentColor = "black"
    }
    this.performBrowserCommand("forecolor", this.colorMarker)
    var cc = (e && e.charCode) || 0
    this.eventsLog.push("newword:" + event)
    var instance = this
    this[event](function() { 
      instance.findMarkedCurrentItem(cc) 
    })
  },
  //markComments: function(v) {
  //  v = this.fullComments.mark(/\/\*[\W\w]*?\*\//, v)
  //  return this.oneLineComments.mark(/\/\/.*/, v)
  //},
  //resolveComments: function(v) {
  //  var instance = this
  //  v = this.oneLineComments.resolve(v, function(s) {
  //    return instance.bgcolor("white", "gray", s)
  //  })
  //  return this.fullComments.resolve(v, function(s) {
  //    return instance.bgcolor("white", "gray", s)
  //  })
  //},
  //markStrings: function(v) {
  //  v = this.doubleQuotedStrings.mark(
  //      replace(/("[^"]*")/g, this.bgcolor("white", "blue")).
  //      replace(/('[^']*')/g, this.bgcolor("white", "blue"))
  //},
  //resolveStrings: function(v) {
  //},
  hspan: function(style, fill) {
    return "<span style=,," + style + ",,>" + fill + "</span>"
  },
  color: function(color, fill) {
    return this.hspan("color: " + color, fill || "$1")
  },
  bgcolor: function(color, bgColor, fill) {
    return this.hspan("color: " + color + "; background-color: " + bgColor, 
        fill || "$1")
  },
  colorizeKeywords: function(v) {
    var instance = this
    instance.keywords.each(function(p) { 
      v = v.colorizeWord(p.key, p.value)
    })
    return v
  },
  syntaxHighlighting: function(v) {
    this.acquireCompletionWords(v)
    var instance = this
    String.prototype.colorizeWord = function(word, _color) {
      return this.replace(new RegExp('\\b(' + word + ')\\b', "ig"), 
          instance.color(_color))
    }
    var r = v;
    r = this.comments.mark(r)
    r = this.strings.mark(r)
    r = this.colorizeKeywords(r)
        //replace(/("[^"]*")/g, this.bgcolor("white", "blue")).
        //replace(/('[^']*')/g, this.bgcolor("white", "blue")))
    r = this.strings.resolve(r)
    r = this.comments.resolve(r)
    return r.replace(/,,/g, "'")
  },
  getCurrentTextNode: function() {
    var cn = this.currentNode
    if (!cn) { return {n: null} }
    if (cn.tagName == "BR" && cn.nextSibling) { // new line
      return {n: cn.nextSibling}
    }
    if (!cn.nodeValue && cn.firstChild && cn.firstChild.nodeValue) {
      return {n: cn.firstChild}
    }
    return {first: true, f: " ", n: cn}
  },
  setCurrentWord: function(v) {
    var ctn = this.getCurrentTextNode() 
    var n = ctn.n
    if (n) { n.nodeValue = (ctn.first ? ctn.f : "") + v }
  },
  onTextParent: function(lambda) {
    var ctn = this.getCurrentTextNode()
    var n = ctn.n
    if (n && n.parentNode) { lambda.apply(this, [n.parentNode]); return true }
    return false
  },
  currentWord: function() {
    var cn = this.getCurrentTextNode()
    var n = cn.n
    if (!n || !n.nodeValue) { return "" }
    return n.nodeValue.substr(cn.first ? 1 : 0)
  },
  setNewCurentNode: function(n) {
    this.currentNode = n
    this.completionWord = null
    this.completion = null
    this.completionPos = -1
  },
  clearFooterDisplay: function() {
    removeAllChildren(this.footer)
  },
  clearDebugDisplay: function() {
    removeAllChildren(this.debug)
  },
  clearCompletion: function() {
    this.completion = null
    this.clearFooterDisplay()
  },
  displayCompletion: function() {
    this.clearFooterDisplay()
    var c = -1
    var instance = this
    this.getCompletion().each(function(m) {
      c++
      var s = Builder.node('span', {className: "completion-term"}, 
          [TN(m)])
      if (c == instance.completionPos) {
        s.className = "completion-term selected"
      }
      instance.footer.appendChild(s)
    })
  },
  displayCurrentWord: function() {
    this.clearDebugDisplay()
    this.debug.appendChild(TN("cw: " + this.currentWord()))
  },
  displayCurrentStatus: function(e) {
    function N(n) {
      if (n == undefined || !n) { return "" }
      return Number(n)
    }
    var fields = [
      {i: N(this.indent)},
      {cc: e.charCode},
      {kc: e.keyCode},
      {kpcc: this.kpCharCode},
      {kpkc: this.kpKeyCode},
      {q: N(this.quote)},
      {qc: N(this.quoteChar)},
      {cwc: N(this.cwColorized)},
      {als: N(this.atLineStart)},
      {nl: N(this.newLine)},
      {cw: this.currentWord()},
    ]
    this.clearDebugDisplay()
    appendChildren(this.debug, 
        fields.map(function(f) { 
          var k = $MSH(f).keys().first()
          return TN(" " + k + ":" + f[k]) 
        }))
    appendChildren(this.debug, 
        this.eventsLog.map(function(l) { return TN(" " + l) }))
    this.eventsLog = []
  },
  getCompletion: function() {
    if (this.currentWord().length <= 2) { return [] }
    if (!this.completion) {
      this.completionWord = this.currentWord()
      this.completion = 
          this.localCompletionWords.grep(new RegExp("^" + this.completionWord))
    }
    return this.completion
  },
  __registerEvent: function(o, dictName) {
    var o = Object.extend({
      key: null,
      keys: null,
      regExp: null,
      ctrl: false,
      action: function() {
      }
    }, o || {});
    var ks = o.keys || (o.key && [o.key]) || matchingCharacters(o.regExp)
    var i = this
    ks.each(function(k) {
      i[dictName][o.ctrl * 1000 + k.charCodeAt(0)] = o.action
    })
  },
  registerKeyup: function() {
    return this.__registerEvent(arguments[0], "keyups")
  },
  registerKeypress: function() {
    return this.__registerEvent(arguments[0], "keypresses")
  },
  checkCwColorized: function() {
    if (this.cwColorized && !this.keywords[this.currentWord()]) {
      var ctn = this.getCurrentTextNode()
      var n = ctn.n
      if (!this.onTextParent(function(p) { p.style.color = "" })) {
        this.cwColorized = false
        this.setCurrentColor("black")
      }
    }
  },
  _keyup: function(e) {
    this.checkAfterShowCharacter()
    this.checkCwColorized()
    if (this.checkTrigger("keyups", e)) { return }
    this.checkCwColorized()
    var cwColor = this.keywords[this.currentWord()]
    if (cwColor) {
      this.onTextParent(function(p) {
        p.style.color = cwColor
        this.cwColorized = true
        this.setCurrentColor("black")
      })
    }
    this.checkAfterNextNormalKeyup()
  },
  keyup: function(e) {
    this._keyup(e)
    this.displayCurrentStatus(e)
  },
  handleNewLine: function() {
    this.newLine = true
    this.atLineStart = true
    this.afterNextNormalKeypress(function() { 
      this.eventsLog.push("newline over")
      this.newLine = false
      this.atLineStart = false
    })
    this.afterNextNormalKeyup(function() {
      this.autoIndent()
    })
  },
  setFontToDefault: function() {
    this.performBrowserCommand("fontname", "monospace")
  },
  afterSecondNormalKeyup: function(handler) {
    var instance = this
    this.eventsLog.push("a2:1st")
    this.afterNextNormalKeyup(function(e) {
      instance.eventsLog.push("a2:2nd")
      instance.afterNextNormalKeyup(function(e) { 
        instance.eventsLog.push("a2:3rd")
        handler(e) 
      })
    })
  },
  repeatIndent: function(c) {
    return this.indent == 0 ? " " : c.repeat(this.indent)
  },
  autoIndent: function() {
    if (this.atLineStart) {
      this.atLineStart = false
      this.performBrowserCommand("inserthtml", 
          "<span indent='p' _moz_dirty=''>" + 
          this.repeatIndent("&nbsp;") + "</span>")
      this.eventsLog.push("ai:" + this.indent)
      this.setFontToDefault()
      //this.findMarkedCurrentItem()
      this.newWordAboutToStart(null, "afterSecondNormalKeyup")
      this.findCurrentIndent()
    }
  },
  checkSpaceAtStartOfLine: function(n) {
    this.atLineStart = false
    this.changeIndent(1)
  },
  changeCurrentIndent: function(d) {
    this.changeIndent(d)
    var ci = this.currentIndent
    if (ci && ci.nodeValue) { ci.nodeValue = this.repeatIndent('\u00a0') }
  },
  changeIndent: function(d) {
    this.eventsLog.push("ci:" + d)
    this.indent += d
    if (this.indent < 0) { this.indent = 0 }
  },
  _keypress: function(e) {
    this.kpCharCode = e.charCode
    this.kpKeyCode = e.keyCode
    // '{'
    if (!this.quote && e.charCode == 123) { this.changeIndent(2) }
    // '}'
    if (!this.quote && e.charCode == 125) { this.changeIndent(-2) }
    if (e.keyCode == e.DOM_VK_RETURN || e.charCode != 0 && !this.currentNode) { 
      if (e.keyCode == e.DOM_VK_RETURN) {
        this.handleNewLine()
      }
      this.newWordAboutToStart(e) 
      return 
    } 
    this.checkAfterShowCharacter()
    this.checkCwColorized()
    if (this.checkTrigger("keypresses", e)) { return }
    this.checkAfterNextNormalKeypress()
  },
  keypress: function(e) {
    this._keypress(e)
  },
  setCurrentColor: function(c) {
    this.currentColor = c
    this.performBrowserCommand("forecolor", c)
  },
  changeCompletionPos: function(p) {
    var max = this.getCompletion().length - 1
    this.completionPos += p
    if (this.completionPos < 0) { this.completionPos = max }
    if (this.completionPos > max) { this.completionPos = 0 }
  },
  selectCompletion: function(direction) {
    this.changeCompletionPos(direction)
    this.displayCompletion()
    this.setCurrentWord(this.getCompletion()[this.completionPos])
    this.afterNextNormalKeypress(function() {
      this.clearCompletion()
    })
  },
  checkTrigger: function(dictName, e) {
    var i = e.ctrlKey * 1000 + e.charCode
    var trigger = this[dictName][i]
    if (trigger) { 
      this.eventsLog.push("T:" + String(trigger).substr(0,50))
      trigger.apply(this, [String.fromCharCode(e.charCode), e]) 
      return true
    }
    return false
  }
}

createTrigger(JsAndCssEditCommon, "afterShowCharacter")
createTrigger(JsAndCssEditCommon, "afterNextNormalKeypress")
createTrigger(JsAndCssEditCommon, "afterNextNormalKeyup")

function codeEditorHelp() {
  return Builder.node('div', {className: "code-editor-help"}, [
    TN("ctrl-l: repaint"), BR(),
    TN("ctrl-p: completion: previous"), BR(),
    TN("ctrl-n: completion: next"), BR(),
    TN("ctrl-d: outdent current line"), BR(),
    TN("ctrl-i: indent current line"), BR(),
  ])
}

var __jsE = null

JSCodeEditor = Class.create();
JSCodeEditor.prototype = {
  setupCssOrJs: function() {
    __jsE = this
    this.setKeywords({
      "this": "red",
      "function": "blue",
      "return": "blue",
      "break": "blue",
      "case": "blue",
      "if": "blue",
      "else": "blue"
    })
  }, 
  globalCompletionTerms: function() {
    return globalCompletionTerms()
  }
}

Object.extend(JSCodeEditor.prototype, CodeEditor.prototype)
Object.extend(JSCodeEditor.prototype, JsAndCssEditCommon)

var __cssE = null

CSSCodeEditor = Class.create();
CSSCodeEditor.prototype = {
  setupCssOrJs: function() {
    __cssE = this
    this.setKeywords({
      "color": "red",
      "background": "red",
      "background-color": "red",
      "background-image": "red",
      "background-position": "red",
      "background-repeat": "red",
      "border": "red",
      "border-color": "red",
      "border-style": "red",
      "border-width": "red",
      "bottom": "red",
      "content": "red",
      "cursor": "red",
      "display": "red",
      "filter": "red",
      "float": "red",
      "font-family": "red",
      "font-size": "red",
      "font-weight": "red",
      "height": "red",
      "left": "red",
      "line-height": "red",
      "list-style": "red",
      "list-style-type": "red",
      "margin": "red",
      "margin-bottom": "red",
      "margin-left": "red",
      "margin-right": "red",
      "margin-top": "red",
      "max-height": "red",
      "max-width": "red",
      "opacity": "red",
      "overflow": "red",
      "overflow-y": "red",
      "padding": "red",
      "padding-bottom": "red",
      "padding-left": "red",
      "padding-right": "red",
      "padding-top": "red",
      "position": "red",
      "text-align": "red",
      "text-decoration": "red",
      "th": "red",
      "top": "red",
      "tr": "red",
      "vertical-align": "red",
      "white-space": "red",
      "width": "red",
      "z-index": "red"
    })
  },
  globalCompletionTerms: function() {
    return [];
  }
}

Object.extend(CSSCodeEditor.prototype, CodeEditor.prototype)
Object.extend(CSSCodeEditor.prototype, JsAndCssEditCommon)

;

// inlineedit.js
function inlineDescr(description) {
  if (typeof description == "string") {
    var descr = (description == "" ? EMPTY() : TN(description + ':'))    
  }
  else {
    var descr = Builder.node('span', [
      description, TN(":")
    ])
  }
  return descr
}

function makeInlineEditString(description, fieldname, genericField) {
  return { 
      description: inlineDescr(description),
      show: function(value) {
        return TN(value)
      },
      edit: function(value) { 
        return Builder.node('input', {
          className: 'inline-edit',
          name: (genericField ? "" : "form_") + fieldname,
          value: value
        })
      },
      fieldname: fieldname
  }
}


TextAreaEditor = Class.create();
TextAreaEditor.prototype = {
  initialize: function(ta) {
    this.textArea = ta
  },
  setContent: function(text) {
    removeAllChildren(this.textArea)
    this.textArea.appendChild(TN(text))
  },
  content: function() {
    return this.textArea.childNodes[0].nodeValue
  }
}


function makeInlineEditTextArea(description, fieldname) {
  var textArea = null
  var editor = null
  var options = Object.extend({
    generic: true,
    rows: 50,
    style: "width:100%;"
  }, arguments[2] || {})
  return { 
      description: inlineDescr(description),
      show: function(value) {
        return TN(value)
      },
      edit: function(value) { 
        textArea = Builder.node('textarea', {
          className: 'inline-edit',
          name: (options.generic ? "" : "form_") + fieldname,
          rows: options.rows,
          style: options.style
        },[TN(value)])
	editor = new TextAreaEditor(textArea)
	return textArea
      },
      editor: function() { return editor },
      fieldname: fieldname
  }
}

function makeInlineEditBoolean(description, fieldname, genericField) {
  function isEnabled(v) { return v == "true" }
  function cb(v) { 
    var d = Builder.node('input', {
      className: 'inline-edit',
      //name: (genericField ? "" : "form_") + fieldname,
      type: 'checkbox'
    }) 
    d.checked = isEnabled(v)
    return d
  }
  var hiddenInput = null
  var checkbox = null
  return { 
      description: inlineDescr(description),
      show: function(value) {
        return TN(value == "true" ? "X" : " ")
      },
      edit: function(value) { 
        return [
          hiddenInput = Builder.node('input', {
            type: 'hidden',
            name: (genericField ? "" : 'form_') + fieldname,
            value: value
          }),
          checkbox = cb(value)
        ]
      },
      fieldname: fieldname,
      beforeSubmit: function() {
        hiddenInput.value = checkbox.checked
      }
  }
}

function makeInlineEditTextCommon(editorClassName, description, fieldname, 
    genericField) {
  var editor = null;
  var hiddenInput = null;
  return { 
    description: inlineDescr(description),
    show: function(value) {
      var s = Builder.node('span', {}, [])
      s.innerHTML = value;
      return s
    },
    edit: function(value) { 
      editor = new javaScript[editorClassName]({
        content: value
      })
      hiddenInput = Builder.node('input', {
        type: 'hidden',
        name: (genericField ? "" : "form_") + fieldname
      })
      hiddenInput.value = value
      return [hiddenInput, editor.htmlNode()]
    },
    fieldname: fieldname,
    beforeSubmit: function() {
      hiddenInput.value = editor.content();
    },
    editor: function() { return editor }
  }
}

function makeInlineEditText(description, fieldname, genericField) {
  return makeInlineEditTextCommon("TextEditor", description, fieldname, 
      genericField)
}

function makeInlineEditTextSidebar(description, fieldname, genericField) {
  return makeInlineEditTextCommon("SidebarTextEditor", description, fieldname, 
      genericField)
}

function makeInlineEditTextWithEditPaneAsDescription(fieldname, genericField) {
  var editor = makeInlineEditTextSidebar('Content', fieldname, genericField)
  var descrNode = Builder.node('div', {style: "padding-right: 5px;"})
  var inlineEditor = {
    description: descrNode,
    edit: function(value) {
      var placeholder = Builder.node('div', {
        style: "position:absolute;"
      })
      var e = editor.edit(value)
      $A(e).each(function(n) { descrNode.appendChild(n) })
      editor.editor().placeholder = placeholder
      var i = setInterval(function() {
        var container = Element.up(placeholder)
        if (container && container.offsetHeight > 0) {
          var top = getTop(container)
          placeholder.style.top    = top + "px"
          placeholder.style.left   = getLeft(container)    + "px"
          placeholder.style.width  = container.offsetWidth + "px"
          if (window.innerHeight) {
            var viewPortHeight = window.innerHeight            
          }
          else { // IE
            var viewPortHeight = document.documentElement.clientHeight
          }
          var height = viewPortHeight - top - 10
          placeholder.style.height = height + "px"
          editor.editor().rememberPlaceholderPosition()
          editor.editor().editableIframe.positionOverPlaceholder(
            editor.editor().placeholderPosition
          )
          clearInterval(i)
        }
      }, 100)
      return placeholder
    },
    show: function(value) {
      return editor.show(value)
    },
    fieldname: editor.fieldname,
    beforeSubmit: function() {
      editor.beforeSubmit()
    }
  }
  return inlineEditor
}

function makeInlineEditJSCode(description, fieldname, genericField) {
  return makeInlineEditTextCommon("JSCodeEditor", description, fieldname, 
      genericField)
}

function makeInlineEditCSSCode(description, fieldname, genericField) {
  return makeInlineEditTextCommon("CSSCodeEditor", description, fieldname, 
      genericField)
}

function makeInlineEditSelection(description, fieldname, values,
    genericField) {
  var hash = mergeHashArray(values)
  return { 
      description: inlineDescr(description),
      show: function(key) {
        return TN(hash[key])
      },
      edit: function(value) { 
        return selectionBox(values, {
          fieldName: (genericField ? "" : "form_") + fieldname,
          currentlySelected: value
        })
      },
      fieldname: fieldname
  }
}

function makeInlineElement(node) {
  return { 
      show: function() { return node; },
      edit: function() { return node; }
  }
}

var InlineEditDialogCommon = {
  defaultOptions: function() {
    return {
      controller: itemModel.controllerName(),
      action: 'default',
      items: [],
      afterShowEdit: function() {},
      hiddenFields: $H(),
      onlyEdit: false,
      noCancel: false,
      afterSaveOrCancel: function() { },
      afterSuccessfulSubmit: function() { }
    }
  },
  setup: function() {
    this.items=this.options.items
    this.dialogs = this.makeDialogs()
    var asc = this.options.afterSaveOrCancel
    this.options.afterSaveOrCancel = function() {
      asc()
      endIdle()
    } 
  },
  makeWidgetContent: function() {
    this.dialogContainer = Builder.node('div', {
      className: 'inline-edit-dialog-container'
    })
    this.dialogContainer.appendChild(
        this.options.onlyEdit ? this.dialogs.edit : this.dialogs.show)
    if (this.options.onlyEdit) {
      var instance = this;
      this.dialogContainer.appendChild(
        autoAfterShowWidget(function() {
          instance.options.afterShowEdit();
        })
      )
    }
    return this.dialogContainer
  },
  switchToShow: function() {
    this.replaceChild(this.dialogContainer, this.dialogs.edit, 
        this.dialogs.show)
  },
  switchToEdit: function() {
    this.replaceChild(this.dialogContainer, 
        this.dialogs.show, this.dialogs.edit)
    this.options.afterShowEdit()
  }
}
Object.extend(InlineEditDialogCommon, WidgetCommon)

InlineEditDialog = Class.create();
InlineEditDialog.prototype = {
  makeDialogs: function() {
    var instance=this
    var editRoot = (new AjaxFormBuilder({
      controller: this.options.controller,
      action: this.options.action,
      model: this.options.model,
      afterSuccessfulSubmit: function(success) {
        if (!instance.options.onlyEdit) {
          var dialogs = instance.makeDialogs()
          instance.show = dialogs.show
          instance.switchToShow()
          instance.edit = dialogs.edit
        }
        instance.options.afterSuccessfulSubmit(success);
        instance.options.afterSaveOrCancel();
      },
      hiddenFields: this.options.hiddenFields,
      beforeSubmit: function() {
        instance.options.items.each(function(item) {
          if (item.beforeSubmit != undefined) {
            item.beforeSubmit();
          }
        })
      }
    })).htmlNode()
    var edit = null
    var editRootSpan = Builder.node('span', {
      className: 'inline-edit'
    }, [
      Builder.node('table', {
        cellspacing: 0,
        cellpadding: 0,
        border: 0,
        width: "100%"
      }, [
          edit = Builder.node('tbody')
      ])
    ])
    editRoot.appendChild(editRootSpan)

    var show = Builder.node('span', {
      className: 'inline-edit-show'
    })
    this.items.each(function(item) {
      var i = Object.extend({
        description: EMPTY(),
        edit: EMPTY(),
        show: EMPTY()
      }, item)
      var fieldname = i.fieldname
      var value = checkNull(instance.options.model[fieldname])
      var showDiv = Builder.node('span', {
        className: 'item'
      }, [
        i.description,
        i.show(value),
        Builder.node('br')
      ])
      highlightItem(showDiv, {
        onclick: function() {
          instance.switchToEdit()
        }
      })
      show.appendChild(showDiv)

      var editDiv = Builder.node('tr', {}, [
        Builder.node('td', {className: "description"}, [i.description]),
        Builder.node('td', {width: "100%"}, [i.edit(value)])
      ])
      edit.appendChild(editDiv)
    })
    editRoot.appendChild(Builder.node('br'))
    editRoot.appendChild(translateButton({
      path: ".inline-edit",
      id: "save",
      node: makeButton({name: 'save'})
    }))
    if (!this.options.noCancel) {
      editRoot.appendChild(translateButton({
        path: ".inline-edit",
        id: "cancel",
        node: makeButton({
          name: 'cancel',
          onclick: function() {
            if (!instance.options.onlyEdit) {
              instance.switchToShow();
            }
            instance.options.afterSaveOrCancel();
            return false;
          }
        })
      }))
    }
    return {edit: editRoot, show: show}
  }
}
Object.extend(InlineEditDialog.prototype, InlineEditDialogCommon)
;

// presentation.js

//var proposedItem = null;
//var selectedItem = null;

//var current_user_model = [];
//var result_model = [];
//var users_model = [];

var __patch_ajax_action = null

function itemIdDisplayValue() {
  return "Name" 
}

var actionsHidden = false;

//function hideActions() {
//  $('action-bar').style.display = "none"
//  $('trash').style.display = "none"
//  actionsHidden = true;
//}

  function ajax_request(e, url, successLambda, _parameters, iid, _finally) {
    new Ajax.Request(url,  {asynchronous:true, 
        evalScripts:true, parameters:_parameters, 
        onSuccess: function(t) { 
          var instance = this
          handleJsonResponse(t.responseText, { onSuccess: function(success) {
            successLambda(iid, success, t, instance)
          }})
          //if (/^success=/.test(t.responseText)) { 
          //  var success;
          //  eval(t.responseText);
          //  successLambda(iid, success, t, this)
          //} else {
          //  var error;
          //  eval(t.responseText);
          //  displayError(error.message, error.details)
          //  //alert(t.responseText)
          //}
          if (_finally) {
            _finally()
          }
        },
        on404: function(t) {
          if (_finally) {
            _finally()
          }
        },
        onFailure: function(t) { 
          displayError("ERROR: "+ t.status + ": " + t.statusText )
          if (_finally) {
            _finally()
          }
        }
    })
  }

  var current_observer = null;

  var upload_iframe  = null;

  function __observe_upload(rn, successLambda) {
    var cd = upload_iframe.contentDocument

    if (!cd) {
      // IE6.0
      if (upload_iframe.contentWindow) {
        cd = upload_iframe.contentWindow.document
      }
    }
    if (cd) {
      element = cd.getElementById('result')
      if (!element) {
        element = cd.body.firstChild
      }
      if (element) {
        clearInterval(current_observer)
        response = element.innerHTML
        if (!response) {
          response = element.nodeValue
        }
        handleJsonResponse(response, { onSuccess: function(success) {
          successLambda(rn, success)
        }})
        //if (/^success=/.test(response)) { 
        //  //var json_update = response.replace(/^success:/, ""); 
        //  var success;
        //  eval(response);
        //  successLambda(rn, success)
        //} else {
        //  alert(response)
        //}
        return
      }
    }
  }

  function observe_upload(rn, successLambda) {
    current_observer = setInterval("__observe_upload(" + rn + ", " + 
        successLambda + ")", 500);
  }

  function ajax_request_upload(e, url, successLambda, _parameters, rn) {
    e.target = "upload-target-" + viewId() + "-" + rn;
    upload_iframe  = $('upload-target-' + viewId() + "-" + rn);
    $('upload-status-' + viewId() + "-" + rn).innerHTML = "<img alt='progress' src='/images/progress.gif' />"
    observe_upload(rn, successLambda)
  }
  
  function ajax_link_to_remote(url, hiddenFields, successLambda, lambda, iid, 
      enctype, ar, returnValue) {
    if (__patch_ajax_action) { url = url.gsub(/[^/]*$/, __patch_ajax_action) }
    // Needed for Ajax Forms to work in the test environment
    // url = relativeLink(url)
    // If you need relativeLink() here, then you need to adjust your url to be 
    // generated with serviceLink()!  
    if (!ar) {
      ar = "ajax_request"
    }
    if (!returnValue) {
      returnValue = "false";
    }
    postAction = "var sl = " + successLambda + "; " + 
        ar + "(this, " + '"' + url + '"' + ", sl, values, " + 
        iid + "); return " + returnValue + "; "
    return "<form onsubmit='var values=Form.serialize(this); " + postAction + "' " +
        "method='post' " + (enctype ? "enctype='" + enctype + "' " : "") + 
        " action='" +  url + "'>" + 
        $MSH(hiddenFields).map(function(p) {
          return "<input type='hidden' name='" + p.key + "' " +
              "value='" + p.value + "'/>";
        }).join("") + 
        lambda(postAction) + "</form>";
  }

  function nonform_ajax_request(url, successLambda, parameters, iid) {
    var p = []
    parameters.each(function(pair) {
      p.push(pair.map(encodeURIComponent).join('='))
    })
    var _p = p.join('&')
    ajax_request(undefined, url, successLambda, _p, iid);
  }

//  function $P(element, tagname) {
//    var re = new RegExp("^" + tagname + "$", 'i')
//    var e = element;
//    while (e && !re.test(e.tagName)) { 
//      e = e.parentNode 
//    }
//    return e
//  }
//
//  function $C(element, tagname) {
//    var re = new RegExp("^" + tagname + "$", 'i')
//    var r = null
//    $A(element.childNodes).each(function(c) {
//      if (!r) {
//        if (re.test(c.className))  {
//          r = c;
//        } else {
//	  if (c.childNodes.length > 0) {
//	    r = $C(c, tagname)
//	  }
//	}
//      }
//    })
//    return r
//  }

//function makeCommentIcon(rowNumber, prefix) {
//  if (prefix == undefined) {
//    prefix = ""
//  }
//  var m = rowResult(rowNumber)
//  comments = m["comments"]
//  if (m["increation"] || m["type"] == "Folder") {
//    return ""
//  }
//  return actionLink(prefix + "showComments(this, " + rowNumber + ")", 
//      "comments.gif", "comments(" + comments.length + ")") + 
//       "(" + comments.length + ")"
//}

function makeInputNodes(item) {
  var name = item[0]
  var id = item[1]
  var value = item[2] || ""
  var options = item[3] || {}
  var inputNode;
  var elName = (options.generic ? "" : "form_") +  id;
  if (options.textarea) {
    inputNode = Builder.node('textarea',
      {cols: options.cols, rows: options.rows, name: elName})
    inputNode.value = value
  }
  else {
    var opts = {value: value, name: elName}
    if (options.type) {
      opts.type = options.type 
    }
    if (options.size) {
      opts.size = options.size
    }
    inputNode = Builder.node('input', opts)
  }
  return {
    description: Builder.node('b', [TN(name)]),
    input: inputNode
  }
}

// MOVED INTO THE FRONTEND SERVICE!
// function makeInput(item) {
//   var name = item[0]
//   var id = item[1]
//   var value = item[2]
//   var options = item[3] || {}
//   return  {
//       description: (name == "" ? "" : "<b>" + name + "</b>:"), 
//       input: (options.textarea ? "<textarea cols='" + options.cols + "' " + 
//           "rows='" + options.rows + "' name='form_" + id +  "'>" +
//           (value ? value : "") + "</textarea><br/>"  :
//       "<input " + (options.type ? "type='" + options.type + "' " : "")  + 
//       (value ? "value='" + value + "' " : "") +
//       (options.size ? "size='" + options.size + "' " : "") +
//       "name='" + (options.generic ? "" : "form_") +  id + "'/>")
//   }
// }

function generalFormSettings(m, count, options) {
  p = m["permission"]
  return "<div class='share'><br>" + 
    "<b>Who Can View this " + options.itemName + ": </b>" + 
    "<ul>" + 
    '<input class="rb" name="form_permission" value="onlyme" ' + 
    (p == 'onlyme' ? 'checked="checked"' : '') + 
    ' type="radio"><b>Private</b></input><br>' + 
    //'<input class="rb" name="form_permission" value="allbuddies" ' + 
    //(p == 'allbuddies' ? 'checked="checked"' : '') + 
    //'type="radio"><b>All my Buddies</b></input><br>' + 
    '<input class="rb" name="form_permission" value="everyone" ' + 
    (p == 'everyone' ? 'checked="checked"' : '') + 
    'type="radio"><b>Public</b></input><br>' +
    "</ul>" + 
    "</div>"
}



//  function initGrid(containerId, sortByColumn, dataId, noItemsText, containerRef) {
//    new IE6Hover("odd", {
//      onmouseover: function(e, ctx) { 
//        e.style.backgroundColor = "#e2f3fc"
//    }})
//    new IE6Hover("even", {
//      onmouseover: function(e, ctx) { 
//        e.style.backgroundColor = "#e2f3fc"
//    }})
//    new IE6Hover("top", {
//      onmouseover: function(e, ctx) { 
//        e.style.backgroundColor = "#f6f6f6"
//	ctx.bevel = $C(e, "bevel")
//	ctx.bevel.style.backgroundColor = "#ffcc33"
//      },
//      onmouseout: function(e, ctx) { 
//	ctx.bevel.style.backgroundColor = "rgb(208, 208, 208)"
//    }})
//  }
//
//  function initThumbs(containerId, sortByColumn, dataId, noItemsText) {
//    new IE6Hover("img-container", {
//      onmouseover: function(e, ctx) { 
//        e.style.backgroundColor = "#4169E1"
//        e.style.color = "white"
//	ctx.image = $C(e, "image-frame")
//	ctx.image.style.border = "1px solid #fff"
//        ctx.hideme = $C(e, "hide-me") 
//	if (ctx.hideme) { 
//	  ctx.hideme.style.color = "#4169E1"
//	}
//      }, 
//      onmouseout: function(e, ctx) { 
//	ctx.image.style.border = "1px solid #000"
//	if (ctx.hideme) { 
//	  ctx.hideme.style.color = "#fff";
//	}
//    }})
//    new IE6Hover("top", {
//      onmouseover: function(e, ctx) { 
//        e.style.backgroundColor = "#f6f6f6"
//	      ctx.bevel = $C(e, "bevel")
//	      ctx.bevel.style.backgroundColor = "#ffcc33"
//      },
//      onmouseout: function(e, ctx) { 
//	      ctx.bevel.style.backgroundColor = "rgb(208, 208, 208)"
//    }})
//    tstop("thumbsview")
//  }

;

// tooltipwidget.js
var __ttw = null
TooltipWidget = Class.create();
TooltipWidget.prototype = {
  defaultOptions: function() {
    return {
      className: "",
      zIndex: 20,
      icon: function() { return TN("No icon") },
      tooltipContent: function(o) { return TN("No content") }
    }
  },
  setup: function() {
    var instance = this;
    this.tooltip = new Tooltip({
      className: this.options.className,
      zIndex: this.options.zIndex,
      position: {y: 0.7},
      autoHide: true,
      generateTooltipContent: function(o) {
        return instance.options.tooltipContent(o)
      }
    })
  },
  makeWidgetContent: function() {
    __ttw = this
    var div = this.options.icon()
    var instance = this;
    div.onmouseover = function() {
      instance.tooltip.delayedShow({element: div})
    }
    //div.onmouseout = function() {
    //  instance.tooltip.hideTotally()
    //}
    return div
  }
}

Object.extend(TooltipWidget.prototype, WidgetCommon)
;

// pathwidget.js
PathWidget = Class.create();
PathWidget.prototype = {
  defaultOptions: function() {
    return {
      model: null
    };
  },
  setup: function() {
    this.options.model.addObserver(this); // afterPathChanged
  },
  currentPath: function() {
    var cp = this.options.model.currentPath()
    if (cp == "/") {
      n = itemModel.user()
      cp = n.charAt(0).toUpperCase() + n.substring(1)
    }
    return cp.replace(/^\//, "")
  },
  makeWidgetContent: function() {
    var instance = this;
    //var r = [TN("My " + itemNameCapitalized() + "s")]
    var r = []
    if (!actionsHidden) {
      var plp 
      r.push(
        Builder.node('strong', {}, [ 
          imageActionLink({fullImageName: iconPath(22, "back"), 
            title: "previous folder",
            onclick: function() {
              if (instance.options.model.currentPath() != '/') {
                instance.options.model.changeToFolder(instance.options.model.
                    currentPath().replace(/\/[^\/]*$/, "/"))
              }
              return false
            }
          }) 
        ]), 
        NBSP(), NBSP(),
        Builder.node('span', {id: "mysites-path"}, [
          this.path = TN(""),
          NBSP()
        ]), 
        (plp = this.options.model.permalinkPresentation()).node,
        NBSP(), NBSP()
      )
      this.plink = plp.link
    }
    this.showPath()
    return r
  },
  afterPathChanged: function() {
    this.showPath()
  },
  update: function() {
    this.showPath()
  },
  showPath: function() {
    this.path.nodeValue = this.currentPath();
    this.plink.href = this.options.model.currentPathPermalink();
  }
}

Object.extend(PathWidget.prototype, WidgetCommon)
;

// ratingwidgets.js
var __ratingTemplates = null;
RatingWidget = Class.create();
RatingWidget.prototype = {
  defaultOptions: function() {
    return {
      description: null,
      onchange: function(rating) {},
      m: null
    }
  },
  setup: function() {
    this.options.model.addObserver(this)
  },
  voteUp: function(num) {
    return this.vote({img: "thumb_up.png", rating: 5, num: num})
  },
  voteDown: function(num) {
    return this.vote({img: "thumb_down.png", rating: 1, num: num})
  },
  vote: function(o) {
    var nt = logged_in ? 'a' : 'span'
    var div = Builder.node(nt, {
      style: "margin-right:10px;color:black;",
      href: "javascript:void(0)"
    }, [table({width: ""}, [
      Builder.node('img', {
        src: "/images/"+o.img,
        style: "border: 0px;",
        title: this.you()
      }),
      TN(o.num)
    ])])
    if (logged_in) {
      Event.observe(div, 'click', function(event) {
        Event.stop(event)
        this.onchange(o.rating)
      }.bindAsEventListener(this))      
    }
    return div
  },
  you: function() {
    if (!logged_in) {
      return "Login to vote!"
    }
    else if (this.options.m.voted) {
      return "You voted!"
    }
    else if (this.options.m.voted == undefined) {
      // Doesn't exist for recent and top rated
      return ""
    }
    else {
      return "You didn't vote, yet!"
    }
  },
  makeWidgetContent: function() {
    var votes = this.votesFromRating()
    var div = Builder.node('div', [table({width:"", cellspacing: 4}, [
      this.voteUp(votes.up),
      this.voteDown(votes.down)
    ])])
    return div
  },
  rerender: function() {
    this.redrawCompletely()
  },
  votesFromRating: function() {
    var m = this.options.m
    var up = Math.floor(((m.rating*m.nr_votes) - m.nr_votes)/4)
    return {
      up: up,
      down: m.nr_votes - up
    }
  },
  onchange: function(rating) {
    var m = this.options.m
    var model = this.options.model
    if (m.item_path) {
      var form_path = m.item_path.gsub(/\/[^/]*$/, "")
    }
    else {
      var form_path = model.currentPath()        
    }
    if (form_path == "") form_path = "/"
    var user = model.user(m.pretty_name)
    if (!model.queryDataAction) user = model.user()
    var dict = {
      form_name: m.pretty_name, 
      form_path: form_path, 
      form_rating: rating,
      form_username_override: user
    }
    if (logged_in) {
      AjaxRequestMixin.ajaxRequest({
        service: model.service(),
        user: user,
        action: "vote",
        onSuccess: function(success) {
          m = hash_merge(m, success)
          model.update();
        }
      }, dict)        
    }
    else {
      verify(
        function() {},
        "You need to be logged in to vote!",
        {cancelButton: false}
      )
    }
  }
}

Object.extend(RatingWidget.prototype, WidgetCommon)

function makeRatingWidget(model, m) {
  return new RatingWidget({m: m, model: model})
}
;

// relatedlinkswidget.js
RelatedLinksWidget = Class.create();
RelatedLinksWidget.prototype = {
  defaultOptions: function() {
    var instance = this;
    return {};
  },
  makeWidgetContent: function() {
    return [
      translateText({id: "related-links", revert:"Related Links"}), BR(),
      this.quickLinksContent = this.makeRelatedLinksContent()
    ]
  },
  siteRootLink: function() {
    return Builder.node('a', {
      href: "http://" + itemModel.serverName() + "/",
      className: 'action-link'
    }, [
      Builder.node('img', {
        src: iconPath(22, 'service_my' + itemModel.itemNamePlural()),
        alt: '',
        title: '',
        border: 0,
        align: "absmiddle"
      }),
      TN(" My " + itemModel.itemNamePluralCapitalized()), BR()
    ])
  },
  makeRelatedLinksContent: function() {
    var div = Builder.node('div', {
      id:'quicklinks-content'
    })
    div.appendChild(this.rootLink = this.siteRootLink())
    this.rootLink.onclick = function() {
      itemModel.changeToFolder("/")
      return false
    }
    div.appendChild(
      Builder.node('div', {id: "trash"}, [
        imageActionLink({
          fullImageName: iconPath(22, "trash"), 
          className: 'action-link',
          title: "trash", 
          onclick: function() {
            itemModel.changeToFolder("/trash")
            return false
          },
          text: 'Trash'
        })
      ])
    )
    // emptyTrash() is buggy so don't expose it until it's fixed.
    // div.appendChild(
    //   Builder.node('div', {id: "empty-trash"}, [
    //     imageActionLink({
    //       fullImageName: iconPath(22, "placeholder"),
    //       className: 'action-link',
    //       title: "placeholder",
    //       href: "javascript:void(0)",
    //       onclick: function() {
    //         itemModel.emptyTrash();
    //         return false
    //       },
    //       text: 'Empty Trash'
    //     })
    //   ])
    // )
    return div
  },
  rerender: function() {
    var nc = this.makeRelatedLinksContent()
    this.replaceChild(this.content, this.quickLinksContent, nc)
    this.quickLinksContent = nc
  }
}

var quickLinksWidget = null;

function makeRelatedLinksWidget() {
  quickLinksWidget = new RelatedLinksWidget()
  return quickLinksWidget.htmlNode()
}

Object.extend(RelatedLinksWidget.prototype, WidgetCommon)
;

// selectionwidget.js
SelectionWidget = Class.create();
SelectionWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  makeWidgetContent: function() {
    return [
      translateText({id: 'selected-items'}), BR(),
      Builder.node('div', {id:'selection-list-content'}, 
          [ this.options.listContent.htmlNode() ])
    ]
  }
}

Object.extend(SelectionWidget.prototype, WidgetCommon)
;

// actionwidget.js
ActionWidget = Class.create();
ActionWidget.prototype = {
  defaultOptions: function() {
    return {
      model: itemModel
    };
  },
  setup: function() {
    this.options.model.addObserver(this) // afterAddFolderAllowedChanged
  },
  afterAddFolderAllowedChanged: function() {
    this.redrawCompletely()
  },
  makeWidgetContent: function() {
    var instance = this
    var r = []
    if (this.options.model.canWrite()) {
      if (this.options.model.addFolderAllowed()) {
        r.push(
          imageActionLink({fullImageName:iconPath(48, "add_folder"), 
              title: "add folder",
              onclick: function() {
                instance.options.model.actions.addFolder()
                return false
              }
          })
        )
      }
      r.push(
        imageActionLink({fullImageName: iconPath(48, "add_item"), 
            title: "add " + this.options.model.itemName(),
            onclick: function() {
              instance.options.model.actions.addItem()
              return false
            }
        })
      )
    }
    return Builder.node('span', {}, [r]);
  }
}

Object.extend(ActionWidget.prototype, WidgetCommon)
;

// explorerwidget.js
ExplorerComponent = {
  defaultOptions: function() {
    var instance = this;
    return Object.extend(this.defaultOptionsAdditions(), {
      model: itemModel,
      presenter: null,
      noItems: function() {
        return TN("")
      },
      showHeader: false,
      noCounter: false,
      displayItemClicked: null,
      scope: "explorer"
   });
  },
  makeHeaderTemplate: function() {
    var instance = this;
    var t = Builder.node('table', {
      cellspacing: 0,
      width: "100%"
    })
    var thead = Builder.node('thead')
    t.appendChild(thead)
    var tr = null;
    thead.appendChild(tr = Builder.node('tr', {className: "header"}))
    if (!this.options.noCounter) {
      tr.appendChild(Builder.node('th', {className: "upper-left-cell"}))
    }
    var c = -1;
    this.options.model.columnDisplayNames().each(function(i) { 
      c += 1;
      var columnName = instance.options.model.columnNames()[c]
      var img = null;
      var th = null;
      tr.appendChild(th = Builder.node('th', {}, [
        translateText({ id: columnName, revert: i }), 
        NBSP(),
      ]))
      th.appendChild(Builder.node('img', { 
        id: 'template:reference_as img' + c,
        alt: "up/down",
        src: "/images/empty.gif"
      }))
      th.appendChild(Builder.node('div', {className: 'bevel'}))
      var columnName = i
      th.onclick = "template:value_of onclick" + c
    })
    return new MSTemplate({create: function() { return t}});
  },
  makeHeader: function() {
    if (!this.headerTemplate) {
      this.headerTemplate = this.makeHeaderTemplate()
    }
    var cns = this.options.model.columnNames()
    var count = cns.length
    var p = {}
    var instance = this;
    count.times(function(i) { 
      p["onclick" + i] = function() {
        instance.resortByColumn(cns[i])
      }
    })
    var nr = this.headerTemplate.makeNodeAndReferences(p)
    this.headerColumns = {};
    count.times(function(i) {
      instance.headerColumns[cns[i]] = nr.references["img" + i]
    })
    return nr.node
  },
  indicateSorting: function() {
    if (!this.options.showHeader) {
      return 
    }
    var columnName = this.options.model.options.sortByColumn
    if (this.lastSortColumn != undefined && this.lastSortColumn != columnName) {
      this.headerColumns[this.lastSortColumn].src = "/images/empty.gif"
    }
    if (columnName) {
      this.headerColumns[columnName].src = "/images/" + 
          (this.options.model.reverse() ? "up" : "down") + ".gif"
      this.lastSortColumn = columnName
    }
  },
  resortByColumn: function(columnName) {
    this.options.model.setSortByColumn(columnName);
  }
}

Object.extend(ExplorerComponent, WidgetCommon)

ExplorerWidget = Class.create();
ExplorerWidget.prototype = {
  defaultOptions: function() {
    return {
      model: itemModel,
      presenters: {
        listView: null, // default: ListViewContainerItemPresenter
        thumbsView: null // default: ImageContainerDefaultPresenter
      },
      noItems: function() { return TN("") },
      thumbsViewActiveByDefault: true,
      path: "/",
      afterPathChanged: function() {},
      showPathWidget: true,
      displayItemClicked: null,
      folderClicked: null
    }
  },
  setup: function() {
    this.viewVisible = this.options.thumbsViewActiveByDefault ? 0 : 1;
    if (!this.options.presenters.listView) {
      this.options.presenters.listView = new ListViewContainerItemPresenter({
        model: this.options.model
      })
    }
    this.slider = new SliderNavigationWidget({})
    if (!this.options.presenters.thumbsView) {
      var instance = this;
      this.options.presenters.thumbsView = new ImageContainerDefaultPresenter({
        model: this.options.model,
        slider: this.slider
      })
    }
    this.listView = new ListViewWidget(hashMergeNonNull({
      model: this.options.model,
      presenter: this.options.presenters.listView,
      noItems: this.options.noItems
    }, {
      displayItemClicked: this.options.displayItemClicked,
      folderClicked: this.options.folderClicked
    }))
    this.thumbsView = new ThumbsViewWidget(hashMergeNonNull({
      model: this.options.model,
      presenter: this.options.presenters.thumbsView,
      noItems: this.options.noItems,
      slider: this.slider
    }, {
      displayItemClicked: this.options.displayItemClicked,
      folderClicked: this.options.folderClicked
    }))
    this.slider.setView(this.thumbsView)
    this.navigation = new ThumbsListviewNavigationWidget({
      explorer: this
    })
    this[(this.viewVisible == 1 ? "list" : "thumbs") + "View"].hidden = false
    this[(this.viewVisible == 1 ? "thumbs" : "list") + "View"].hidden = true
    this.pathWidget = new PathWidget({ model: this.options.model })
    this.options.model.addObserver(this); // for afterPathChanged
  },
  switchToListView: function() {
    this.slider.hide();
    this.viewVisible = 1;
    this.listView.hidden = false
    this.rerender();
    this.thumbsView.hidden = true
    this.listView.rerender();
  },
  switchToThumbsView: function() {
    this.viewVisible = 0;
    this.slider.show();
    this.thumbsView.hidden = false
    this.listView.hidden = true
    this.rerender();
    this.thumbsView.rerender();
  },
  sortingSelection: function() {
    this._ss = Builder.node('select', [
      Builder.node('option', {value: "name false"},     [TN("Name (A-Z)")]),
      Builder.node('option', {value: "name true"},      [TN("Name (Z-A)")]),
      Builder.node('option', {value: "added_at true"},  [TN("Latest")]),
      Builder.node('option', {value: "added_at false"}, [TN("Oldest")]),
      Builder.node('option', {value: "rating true"},    [TN("Highest Rating")]),
      Builder.node('option', {value: "rating false"},   [TN("Lowest Rating")]),
      Builder.node('option', {value: "type false"},     [TN("Type")])
    ])
    Event.observe(this._ss, 'change', function(event) {
      Event.stop(event)
      var dm = this.options.model
      var value = this._ss.options[this._ss.selectedIndex].value
      var parts = value.split(/\s+/)
      var sbc = parts[0]
      var reverse = (parts[1] == "true")
      if (sbc == dm.options.sortByColumn && reverse != dm.reverse()) {
        dm.setSortByColumn(parts[0])
      }
      else {
        dm.setSortByColumn(parts[0])
        if (reverse != dm.reverse()) { dm.setSortByColumn(parts[0]) }
      }
    }.bindAsEventListener(this))
    this.resortSortingSelection()
    return this._ss
  },
  resortSortingSelection: function() {
    var v = this.options.model.options.sortByColumn + " " + this.options.model.reverse()
    for(var i = 0; i < this._ss.childNodes.length; i++) {
      var n = this._ss.childNodes[i]
      if (n.value == v) {
        this._ss.selectedIndex = i;
        break;
      }
    }    
  },
  makeNavigationHeader: function() {
    var r = [
      Builder.node('span', {style: 'white-space: nowrap'}, [ 
        translateText({id: "display"}),
        helpTexts.helperNode("Change View", 14)
      ]),
      this.navigation.htmlNode(),
      this.slider.htmlNode(),
      Builder.node('span', {style: "white-space: nowrap;"}, [
        translateText({path: ".explorer", id: "sort-by"}),
        TN(": "),
        helpTexts.helperNode("Sort By",14)
      ]),
      this.sortingSelection(),
      {
        columnOptions: {
          width: "100%"
        },
        columnData: [TN("")]
      }
    ]
    if (this.options.showPathWidget) {
      r.unshift(Builder.node('span', {style: 'white-space: nowrap;'}, 
          [ this.pathWidget.htmlNode() ]))
    }
    return table(r)
  },
  makeWidgetContent: function() {
    return Builder.node('div', {className: 'explorer'}, [ 
      this.makeNavigationHeader(),
      (this.viewVisible == 0 ? this.thumbsView : this.listView).
          htmlNode()
    ])
  },
  afterPathChanged: function() {
  },
  rerender: function(i, flag) {
    if (flag == "u") {
      this.resortSortingSelection()
      return; // ignore observer updates
    }
    var nc = this.makeWidgetContent()
    this.replaceChild(this.content.parentNode, this.content, nc)
    this.content = nc
  }
};

Object.extend(ExplorerWidget.prototype, WidgetCommon)

var standardExplorer = null;

function makeStandardExplorerWidget(opts) {
  if (!opts) opts = {}
  var localOptions =  Object.extend(opts, javaScript["localExplorerOptions"] ? 
      localExplorerOptions() : {});
  var se = new ExplorerWidget(hashMergeNonNull({
    path: itemModel.currentPath(),
    noItems: function() {
      if (!itemModel.canWrite()) {
        return ""
      }
      var r = [ 
        translate({
          path: "",
          id: 'no-explorer-items' + 
              (itemModel.addFolderAllowed() ?  "" : "-no-folder"),
          node: Builder.node('div', {
            style: "background-color:white;width:100%;padding:5px;"
          }),
          setText: function(n, t) {
            n.innerHTML = t
          },
          revert: "There are no %1/folders at the moment.<BR>" + 
              "Would you like to add a <a href='#' onclick='%3'>%2</a> or " + 
              "a <a href='#' onclick='%5'>%4</a>?",
          args: [
              itemModel.itemNamePlural(), 
              itemModel.itemName(), 
              'itemModel.actions.addItem(); return false;',
              "folder", 
              'itemModel.actions.addFolder(); return false;'
          ]
        })
      ]
      return r;
    },
    showPathWidget: false
  }, localOptions))
  if (!standardExplorer) standardExplorer = se
  return se;
}

function toggleSelection() {
  var o = Object.extend({
    m: null,
    element: null,
    onselect: function(e) {
      e.addClassName("container-selected")
    },
    onunselect: function(e) {
      e.removeClassName("container-selected")
    }
  }, arguments[0] || {})
  var s = o.m.selected; 
  if (s == undefined || !s) {
    o.m.selected = true;
    o.m.changed = changeCounter()
    o.onselect(o.element)
  } else {
    o.m.selected = false;
    o.m.changed = changeCounter()
    o.onunselect(o.element)
  }
  globalSelectionListContent.rerender()
}

SelectionListWidget = Class.create();
SelectionListWidget.prototype = {
  defaultOptions: function() {
    return {
      model: []
    }
  },
  setup: function() {
    this.m = this.options.model
    this.m.addObserver(this)
  },
  selectedItems: function() {
    return this.m.getModel().select(function(item) { return item.selected })
  },
  makeWidgetContent: function() {
    var ii = this
    return scope("selection", function() {
      var tr=null;
      var listContent = Builder.node('table', {
        cellpadding: 0,
        cellspacing: 0,
        border: 0,
        width: "100%" 
      })

      var noneSelected = true;
      ii.selectedItems().each (function(item) {
        noneSelected = false;
        listContent.appendChild(tr = Builder.node('tr'))
        tr.appendChild(Builder.node('td', {}, [ TN(item.name) ]))
      })
      if (noneSelected) {
        listContent.appendChild(Builder.node('tr', {}, [
            Builder.node('td', {}, [ translateText({id:
            "zero-items-selected"}) /*TN("0 items selected")*/ ])]))
      }

      return listContent
    })
  },
  rerender: function() {
    this.redrawCompletely()
  }
}
Object.extend(SelectionListWidget.prototype, WidgetCommon)

var globalSelectionListContent = null;

function makeGlobalSelectionListContent() {
  globalSelectionListContent = new SelectionListWidget(
      {model: itemModel})
  return globalSelectionListContent;

}

var __demoModel = null

function demoExplorer() {
  var w = new PWindow({
    name: "Demo Explorer - Pictures",
    contentWidget: new ExplorerWidget({
      model: (__demoModel = new DisplayModel({
        path: "/",
        controllerName: "pictures"
      })),
      displayItemClicked: function(m) {
        alert("Item has been chosen: " + m.name)
        // m.full_item_url
        w.close()
        return false
      }
    })
  })
}
;

// thumbsviewwidget.js
var __tvw = null;
ThumbsViewWidget = Class.create();
ThumbsViewWidget.prototype = {
  defaultOptionsAdditions: function() {
    return {
      tooltip: {hideTotally: function() { /*itemInfoInstance.hideBox()*/ }}
    };
  },
  setup: function() {
    __tvw = this
    this.options.model.addObserver(this)
    if (!this.options.presenter) {
      this.options.presenter = new ImageContainerDefaultPresenter({
        model: this.options.model
      })
    }
  },
  getDisplayElements: function() {
    return this.displayElements;
  },
  makeWidgetContent: function() {
    var ii = this
    return scope(this.options.scope + " .thumbsview", function() {
      var r = Builder.node('div', {className: 'ax-thumbs'})
      if (ii.options.showHeader) {
        r.appendChild(ii.makeHeader());
      }
      ii.thumbsRoot = r;
      r.appendChild(ii.thumbsViewContent = ii.makeThumbsViewContent())
      return r;
    })
  },
  makeThumbsViewContent: function() {
    this.displayElements = []
    var model = this.options.model.getModel()
    if (model.length == 0) {
      return Builder.node('div', {}, [this.options.noItems()])
    }
    tstart("sthumbsview")
    var instance = this;
    var d = Builder.node('div', {
      className: 'thumbs-container'
    })
    var count = -1;
    instance.options.presenter.loadVariables(this)
    model.each(function(m) {
      count += 1;
      var e = instance.options.presenter.draw(m, count)
      instance.displayElements.push(e)
      d.appendChild(e.content)
    })
    this.indicateSorting();
    tstop("sthumbsview")
    return d;
  },
  rerender: function() {
    if (this.hidden) { return }
    // itemInfoInstance.hideBox()
    var nc = this.makeThumbsViewContent()
    this.replaceChild(this.thumbsRoot, this.thumbsViewContent, nc)
    this.thumbsViewContent = nc
  }
}

Object.extend(ThumbsViewWidget.prototype, ExplorerComponent)

FixedScaling = Class.create();
FixedScaling.prototype = {
  initialize: function() {
    this.options = Object.extend({
      value: 1
    }, arguments[0] || {})
  }, 
  scaleImageContainerWidth: function() {
    return this.options.value * 220 + 'px';
  }, 
  scaleImageContainerHeight: function() {
    return this.options.value * 230 + 'px';
  }, 
  scaleImageWidth: function() {
    return this.options.value * 190 + 'px';
  }, 
  scaleFontSize: function() {
    return this.options.value * 16 + 'pt';
  }
}

var __icdp;

ImageContainerDefaultPresenter = Class.create();
ImageContainerDefaultPresenter.prototype = {
  initialize: function() {
    __icdp = this
    this.options = Object.extend({
      slider: null,
      imgContainerClassName: 'img-container',
      model: itemModel,
      tooltip: {hideTotally: function() { itemInfoInstance.hideBox() }}
    }, arguments[0] || {})
  },
  beforeTabHide: function() {
    this.options.tooltip.hideTotally()
  },
  loadVariables: function(tv) {
    this.thumbsview = tv
    this.newWidth = 0
    this.cWidth = 0
    this.fSize = 0
    if (this.options.slider) {
      this.newWidth = this.options.slider.scaleImageWidth()
      this.cWidth = this.options.slider.scaleImageContainerWidth()
      this.fSize = this.options.slider.scaleFontSize()
    }
  },
  draw: function(m) {
    var imgContainer = null;
    var imageDiv = null;
    var image = null;
    var edit = m["edit"]
    var d = document.createElement("div")
    var imgContClass = this.options.imgContainerClassName
    d.innerHTML =
      "<div class='" + imgContClass + "' id='item_"+m.pretty_name+"'>" +
        "<div class='scale-image'>" +
          "<a class='white-link' href='" + m.full_item_url + "' " + 
              "title='" + m.name + "'> " + 
            "<img width='100%' alt='thumb' class='image-frame' " + 
                "src='" + m.thumbnail_image_url + "'/>" + 
          "</a>" + 
          m.name.replace(/(.)/g, "$1&#8203;") + "<br/>" + 
        "</div>" + 
      "</div>"
    var imgContainer = d.lastChild;
    var imageDiv     = imgContainer.lastChild
    var image        = imageDiv.firstChild.firstChild;
    var itemName     = $(d).down('.item-name')
    if (this.options.slider) {
      imgContainer.style.width = this.cWidth
      imgContainer.style.fontSize = this.fSize;
      imageDiv.style.width = this.newWidth;
    }
    imgContainer.onclick = function(event) {
      if (event) Event.stop(event)
      itemInfoInstance.showActionsForItem(m, this.options.model, imgContainer)
    }.bindAsEventListener(this)
    Event.observe(imgContainer, 'mouseover', function() {
      imgContainer.setOpacity(0.6)
    })
    Event.observe(imgContainer, 'mouseout', function() {
      imgContainer.setOpacity(1.0)
    })
    var instance = this;
    imgContainer.ondblclick = catchExceptionsForOnClickHandler(function() {
      instance.options.model.actions.showItem({
        m: m,
        displayItemClicked: instance.thumbsview.options.displayItemClicked,
        folderClicked: instance.thumbsview.options.folderClicked
      })
    })
    if (m.selected) {
      imgContainer.addClassName("container-selected")
    }
    return {
      image: imageDiv,
      container: imgContainer,
      content: d
    };
  }
}

;

// listviewwidget.js
ListViewWidget = Class.create();
ListViewWidget.prototype = {
  defaultOptionsAdditions: function() {
    return {
    }
  },
  setup: function() {
    this.evenRowTemplate = this.makeRowTemplate("even")
    this.oddRowTemplate = this.makeRowTemplate("odd")
    this.options.model.addObserver(this)
    if (!this.options.presenter) {
      this.options.presenter = new ListViewContainerItemPresenter({
        model: this.options.model
      })
    }
  },
  getDisplayElements: function() {
    return this.displayElements;
  },
  makeWidgetContent: function() {
    var r = Builder.node('div', {className: 'listview'}, [
      this.listViewContent = this.__makeWidget()
    ])
    this.lvRoot = r;
    return r;
  },
  __makeWidget: function() {
    return scope(this.options.scope + " .listview", function() {
      var r = Builder.node('div')
      if (this.options.showHeader) {
        r.appendChild(this.makeHeader())
      }
      r.appendChild(this.makeListViewContent())
      return r
    }.bind(this))
  },
  makeRowTemplate: function(_className) {
    var instance = this;
    return new MSTemplate({
      create: function() {
        var row = Builder.node('tr', {
            className: _className
        })
        if (!instance.options.noCounter) {
          row.appendChild(Builder.node('td', { className: 'count'},
              [TN("template:value_of count")]))
        }
        return row;
      }
    })
  },
  makeListViewContent: function() {
    this.displayElements = []
    var model = this.options.model.getModel();
    var tbody = Builder.node('tbody')
    var table = Builder.node('table', {
      cellpadding: "0",
      cellspacing: "0",
      border: "0",
      width: "100%"
    }, [tbody])
    if (model.length == 0) {
      tbody.appendChild(Builder.node('tr', {}, [
        Builder.node('td', {colspan: 99}, [
          Builder.node('div', {}, [this.options.noItems()])
        ])
      ]))
      return table;
    }
    tstart("slistview")
    var instance = this;
    var count = 0;
    instance.options.presenter.loadVariables(this)
    model.each(function(m) {
      count += 1;
      var row = ((count & 1) == 0 ? instance.evenRowTemplate : 
          instance.oddRowTemplate).makeNode({count: count})
      var e = instance.options.presenter.draw(m, count, row)
      e.content.each(function(c) {
        row.appendChild(c)
      })
      tbody.appendChild(row)
    })
    this.indicateSorting();
    tstop("slistview")
    return table;
  },
  rerender: function() {
    if (this.hidden) { return }
    // itemInfoInstance.hideBox()
    var nc = this.__makeWidget()
    this.replaceChild(this.lvRoot, this.listViewContent, nc)
    this.listViewContent = nc
  }
}

Object.extend(ListViewWidget.prototype, ExplorerComponent)

function asTableData(a) {
  return a.map(function(i) {
    return Builder.node('td', {}, [i])
  })
}

ListViewContainerTagPresenter = Class.create();
ListViewContainerTagPresenter.prototype = {
  initialize: function() {
    this.options = Object.extend({
    }, arguments[0] || {})
  }, 
  loadVariables: function() {
  },
  draw: function(m, c, element) {
    element.onclick = function() {
      searchWidget.addToSearchFilter(m.tag)
    }
    return {content: asTableData([TN(m.tag), TN(m.count)])}
  }
}

ListViewContainerItemPresenter = Class.create();
ListViewContainerItemPresenter.prototype = {
  initialize: function() {
    var instance = this
    this.options = Object.extend({
      model: itemModel,
      tooltip: {hideTotally: function() { /*itemInfoInstance.hideBox()*/ }}
    }, arguments[0] || {})
    var plp = this.options.model.permalinkPresentation()
    plp.link.href = "template:value_of permalink"
    this.nodeTemplate = new MSTemplate({
      create: function() {
         var cols = [Builder.node('span', {}, [
            asTableData([
              Builder.node('div', {
                style: 'float: left'
              }, [
                Builder.node('img', {
                  alt: 'thumb',
                  align: 'absmiddle',
                  style:'height: 50px',
                  src: "template:value_of m.thumbnail_image_url"
                })
              ]), 
              Builder.node('div', {
                id: 'template:reference_as details'
              }, [ 
                TN("template:value_of m.name"), BR()
              ])
            ])
          ]),
          TN("template:value_of m.display_type"),
          Builder.node('span', {
            id: 'template:reference_as rating'
          }, []),
          Builder.node('span', {
            id: 'template:reference_as d_added_at'
          })
        ]
        return Builder.node('span', {}, asTableData(cols))
      }
    })
  }, 
  loadVariables: function(lv) {
    this.listview = lv
    // itemInfoInstance.hideBox()
  },
  __draw: function(m, c, element) {
    var instance = this;
    var nr = this.nodeTemplate.makeNodeAndReferences({
      m: m,
      permalink: this.options.model.currentPathPermalink(m)
    })

    if (!this.options.model.actions.isFolder(m) && m.rating != undefined) {
      nr.references.rating.appendChild(makeRatingWidget(
          this.options.model, m).htmlNode())
    }
    return {
      content: $A(nr.node.childNodes),
      references: nr.references
    }
  }.memoize({dependencies: function(m) { 
    return m.name + ":" + m.changed 
  }}),
  draw: function(m, c, element) {
    var instance = this
    var r = this.__draw(m, c, element)
    r.references.d_added_at.innerHTML = ""
    r.references.d_added_at.appendChild(makeNiceDate(m.added_at))
    element.onclick = function(event) {
      Event.stop(event)
      itemInfoInstance.showActionsForItem(m, this.options.model, element)
    }.bindAsEventListener(this)
    element.ondblclick = catchExceptionsForOnClickHandler(function() {
      instance.options.model.actions.showItem({
        m: m,
        displayItemClicked: instance.listview.options.displayItemClicked,
        folderClicked: instance.listview.options.folderClicked
      })
    })
    return r;
  }
}
;

// badgewidget.js
// Moved into the frontend service;

// myserviceswidget.js
MyServicesWidget = Class.create();
MyServicesWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  url: function(service) {
    return serviceLink({
      user: (logged_in ? write_username : "www"), 
      service: service
    })
    //if (logged_in) {
    //  return "http://" + write_username + "." + domainName + "/";
    //} else {
    //  return "http://www." + domainName + "/"
    //}
  },
  serviceLink: function(service, iconName, displayName) {
    return Builder.node('a', {
      href: this.url(service),
      className: 'action-link'
    }, [
      Builder.node('img', {
        //src: '/icons3/crimson/16x16/service_' + iconName + '.gif',
        src: iconPath(16, "service_" + iconName),
        alt: '',
        title: '',
        border: 0,
        align: "absmiddle"
      }),
      TN(" " + displayName), BR()
    ])
  },
  makeWidgetContent: function() {
    return Builder.node('div', {id: 'myservices' }, [
      this.serviceLink('sites', 'mysites', 'My Sites'),
      this.serviceLink('favorites', 'myfavorites', 'My Favorites'),
      this.serviceLink('files', 'myfiles', 'My Files'),
      this.serviceLink('buddies', 'mybuddies', 'My Buddies'),
      //this.serviceLink('profiles', 'myprofiles', 'My Profiles'),
      this.serviceLink('articles', 'myarticles', 'My Articles'),
      this.serviceLink('pictures', 'mypictures', 'My Pictures'),
      this.serviceLink('movies', 'mymovies', 'My Movies'),
      this.serviceLink('tunes', 'mytunes', 'My Tunes'),
      this.serviceLink('pages', 'mypages', 'My Pages'),
      //this.serviceLink('communities', 'mycommunities', 'My Communities')
    ])
  }
}

Object.extend(MyServicesWidget.prototype, WidgetCommon)
;

// logoutwidget.js
LogoutWidget = Class.create();
LogoutWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  makeWidgetContent: function() {
    return logged_in ? Builder.node('div', [
      Builder.node('form', {
        action: serviceLink({
          user: "www",
          service: "sites",
          controllerName: "account",
          action: "logout"
        }),
        method: "POST"
      }, [
        Builder.node('input', {type: "hidden", name: "desired_service", 
            value: itemModel.serviceName() }), 
        Builder.node('input', {type: "hidden", name: "desired_url", 
            value: window.location.href }), 
        translateButton({id: "logout"})
        /*translate({
          id: "logout",
          makeNode: function(text) {
            return*/ /*Builder.node('input', {
              type: "submit", 
              value: "logout", 
              className:"button"
            })*/
          //}
        //})
      ])
    ]) : Builder.node('span')
  }
}

Object.extend(LogoutWidget.prototype, WidgetCommon)
;

// featuredsearchwidget.js
FeaturedSearchWidget = Class.create();
FeaturedSearchWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  makeWidgetContent: function() {
    var go = null;
    var form = null;
    var clear = null;
    var r = [
      form = Builder.node('form', {method: "post", action: ""}, [
        this.input = Builder.node('input', {
          onsubmit: "",
          onblur: "",
          type: "text",
          maxlength: "30",
          size: "26",
          style: "color: gray",
          value: "Search",
          id: 'sq'
        }), 
        go = Builder.node('input', {
          type: "submit", 
          value: "search"
        }), 
      ])
    ]
    var instance = this;
    this.input.onclick = function(e) {
      if (instance.input.value == "Search") {
        instance.input.value = "";
        instance.input.style.color="black"
      }
    }
    this.input.onblur = function(e) {
      if (instance.input.value == "") {
        instance.input.style.color="gray"
        instance.input.value = "Search";
      }
    }
    go.onclick = function() {
      //instance.searchSubmitted();
    }
    return r;
  }
}

Object.extend(FeaturedSearchWidget.prototype, WidgetCommon)
;

// toptagswidget.js
TopTagsWidget = Class.create();
TopTagsWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  makeWidgetContent: function() {
    var go = null;
    var form = null;
    var clear = null;
    var r = [TN("Top keywords: "), 
      TN("Beach "), NBSP(),
      TN("Summer "), NBSP(), 
      TN("Holiday")]
    return r;
  }
}

Object.extend(TopTagsWidget.prototype, WidgetCommon)
;

// languagewidget.js
LanguageWidget = Class.create();
LanguageWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  setup: function() {
    this.s = Builder.node('select', {
      size: 1
    }, $A(this.supportedLanguages()).map(function(l) {
      return Builder.node('option', [TN(l)])
    }))
    this.s.selectedIndex = 0
    this.s.onchange = function(event) {
      var lang = this.s.options[this.s.selectedIndex].firstChild.nodeValue.toLowerCase()
      setLanguage(lang)
      if (logged_in) {
        var prefs = this.dm.preferences()
        if (prefs.lang != lang) {
          prefs.lang = lang
          this.dm.setPreferences(prefs)          
        }
      }
    }.bindAsEventListener(this)
    if (logged_in) {
      this.dm = new DisplayModel({
        user: writeUserModel.data.username,
        service: "profiles"
      })
      this.dm.observeOnce({update: function() {
        var lang = this.dm.preferences().lang
        if (lang && this.supportedLanguages().include(lang.capitalize())) {
          this.setLanguage(lang)
        }
        else if (parameters().lang && this.supportedLanguages().include(parameters().lang.capitalize())) {
          this.setLanguage(parameters().lang)
          var prefs = this.dm.preferences()
          prefs.lang = parameters().lang
          this.dm.setPreferences(prefs)          
        }
      }.bind(this)})
    }
    else if (parameters().lang) {
      var l = parameters().lang
      if (this.supportedLanguages().include(l.capitalize())) {
        this.setLanguage(l)
      }
    }
  },
  setLanguage: function(lang) {
    setLanguage(lang)
    for(var i = 0; i < this.s.options.length; i++) {
      if (this.s.options[i].firstChild.nodeValue.toLowerCase() == lang) {
        this.s.selectedIndex = i
        break;
      }
    }    
  },
  supportedLanguages: function() {
    return ["English", "German"]
  },
  makeWidgetContent: function() {
    return [this.s]
  }
}

Object.extend(LanguageWidget.prototype, WidgetCommon)
;

// themewidget.js
ThemeWidget = Class.create();
ThemeWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  makeWidgetContent: function() {
    var s = Builder.node('select', {
      size: 1
    }, [ 
      Builder.node('option', {}, [ TN("Modest") ]),
      Builder.node('option', {}, [ TN("None") ]),
    ])
    s.selectedIndex = 0
    s.onchange = function() {
      setTheme(s.options[s.selectedIndex].firstChild.nodeValue.toLowerCase())
    }
    return [
      Builder.node('img', {
        src: iconPath(22, "rss"),
        align: "absmiddle"
      }), 
      TN(" "),
      translateText({id: "theme"}),
      TN(": "),
      s
    ]
  }
}

Object.extend(ThemeWidget.prototype, WidgetCommon)
;

// newuserwidget.js
NewUserWidget = Class.create();
NewUserWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  makeWidgetContent: function() {
    var instance = this;
    var go = null;
    var clear = null;
    this.model = []
    var root = (new AjaxFormBuilder({
      controller: "account",
      action: "signup_json",
      model: this.model,
      afterSuccessfulSubmit: function() {
        /*var dialogs = instance.makeDialogs()
        instance.show = dialogs.show
        instance.switchToShow()
        instance.edit = dialogs.edit*/
        alert("You can log in now.")
      }
    })).htmlNode()
    var r = [
      TN("New here?  Try the site without registering and see " + 
          "if you like it, or create a new account for free!"),BR(),
      table([
        this.input = Builder.node('input', {
          type: "text",
          maxlength: "10",
          name: "username",
          size: "10"
        }), [ TN(".mysit.es"), this.statusImg = Builder.node('img') ]
      ], [
        TN("Password:"), Builder.node('input', {
          type: "password",
          name: "password",
          size: "12",
          value: ""
        })
      ], [
        TN("Confirm Pw:"), Builder.node('input', {
          type: "password",
          name: "password_confirmation",
          size: "12",
          value: ""
        })
      ], [
        TN("Email:"), Builder.node('input', {
          name: "email",
          size: "12",
          value: ""
        })
      ]),
      go = Builder.node('input', {
        type: "submit", 
        value: "create"
      }) 
    ]
    root.appendChild(Builder.node('span', {}, r))
    return root;
  }
}

Object.extend(NewUserWidget.prototype, WidgetCommon)
;

// textwidget.js
TextWidget = Class.create();
TextWidget.prototype = {
  defaultOptions: function() {
    return {text: ""};
  },
  makeWidgetContent: function() {
    return [TN(this.options.text)]
  }
}

Object.extend(TextWidget.prototype, WidgetCommon)
;

// formwidget.js
FormWidget = Class.create();
FormWidget.prototype = {
  defaultOptions: function() {
    return {title: "", submit: function() {}, cancel: function() {}};
  },
  makeDisplayContent : function() {
    var nodes = [];
    this.options.formFields.each(function(pair) {
      nodes.push(Builder.node('tr', {}, [
        Builder.node('td', {}, [TN(pair.last())]),
        Builder.node('td', {}, [
          Builder.node('input', {type: 'text', name: pair.first()}, [])
        ])
      ]))
    })
    return Builder.node('table', [Builder.node('tbody', nodes)]);
  },
	makeWidgetContent: function() {
		var cancelButton = null;
		var r = Builder.node('form', {}, [
			this.makeDisplayContent(),
			Builder.node("input", {type: 'submit', value: 'save'}),
			cancelButton = Builder.node("input", {type: 'button', value: 'cancel'})
		]);
		var instance = this;
		cancelButton.onclick = function() {
			instance.options.cancel();
			return false; 
		}
		r.onsubmit = function() {
			var fields = {};
			var form = this
			for(var i = 0; i < form.elements.length; i++) {
				fields[form.elements[i].name] = form.elements[i].value;
			}
			instance.options.submit(fields);
			return false;
		};	  
    return r;
  }
}

Object.extend(FormWidget.prototype, WidgetCommon)
;

// navigationwidgets.js
ThumbsListviewNavigationWidget = Class.create();
ThumbsListviewNavigationWidget.prototype = {
  defaultOptions: function() {
    return {
      explorer: null
    };
  },
  indicateListView: function() {
    this.thumbsViewSpan.style.border = "1px solid #fff";
    this.listViewSpan.style.border = "1px solid gray";
  },
  indicateThumbsView: function() {
    this.thumbsViewSpan.style.border = "1px solid gray";
    this.listViewSpan.style.border = "1px solid #fff";
  },
  showThumbsView: function() {
    this.indicateThumbsView();
    this.options.explorer.switchToThumbsView();
  },
  showListView: function() {
    this.indicateListView();
    this.options.explorer.switchToListView();
  },
  makeWidgetContent: function() {
    if (actionsHidden) {
      return [TN("")]
    }
    var instance = this;
    var r = [
      table([
        this.thumbsViewSpan = Builder.node('div',
          {className: 'result-display-icon'}, [
          imageActionLink({imageName: "thumb-view.gif", title: "thumbsview",
              onclick: function() {
                instance.showThumbsView();
                return false
              }
          })
        ]), 
        this.listViewSpan = Builder.node('div', 
          {className: 'result-display-icon'}, [
          imageActionLink({imageName: "list-view.gif", title: "listview",
              onclick: function() {
                instance.showListView();
                return false
              }
          })
        ])
      ])
    ]
    this.indicateThumbsView();
    return r;
  }
}
Object.extend(ThumbsListviewNavigationWidget.prototype, WidgetCommon)

var thumbsListviewNavigationWidget = null;

function makeThumbsListviewNavigationWidget() {
  thumbsListviewNavigationWidget=new ThumbsListviewNavigationWidget({
    explorer: standardExplorer
  })
  return thumbsListviewNavigationWidget.htmlNode();
}

var __t = null

IconWidget = Class.create();
IconWidget.prototype = {
  defaultOptions: function() {
    return {
      title: "", 
      iconName: "",
      dimension: 22
    };
  },
  makeWidgetContent: function() {
    var title = this.options.title
    var t = null
    if (typeof title == "string") {
      t = Builder.node('span', {}, [TN(" "), translateText({
        id: title.toLowerCase(), revert: title
      }), TN(" ")])
    } else {
      d("node")
      t = Builder.node('span', {}, [TN(" "), title, TN(" ")]);
    }
    return [ 
      Builder.node('img', {
        src: iconPath(this.options.dimension, this.options.iconName),
        align: "absmiddle"
      }), t
    ]
  }
}
Object.extend(IconWidget.prototype, WidgetCommon)

SliderNavigationWidget = Class.create();
SliderNavigationWidget.prototype = {
  defaultOptions: function() {
    return {
      view: null,
      defaultSliderValue: 0.2
    };
  },
  setup: function() {
    var instance = this
    this.slider = new EasySliderWidget({
      onChange: function(v) { instance.scale(v) },
      onSlide: function(v) { instance.scale(v) },
      defaultSliderValue: this.options.defaultSliderValue
    })
    this.ensureSliderValue()
  },
  setView: function(v) {
    this.options.view = v;
  },
  sliderToScalingValue: function(sv) {
    var floorSize = .50;
    var ceilingSize = 1.0;
    return (floorSize + (sv * (ceilingSize - floorSize)));
  },
  scale: function(sv) {
    // Might get called before setView has been called
    var instance = this;
    function f() {
      instance.options.view.options.tooltip.hideTotally();
      var v = instance.sliderToScalingValue(sv)
      instance.currentScalingValue = v;
      var newWidth = instance.scaleImageWidth()
      var cWidth   = instance.scaleImageContainerWidth()
      var fSize    = instance.scaleFontSize()
      var de       = instance.options.view.getDisplayElements();
      for (var i = 0; i < de.length; i++) {
        with(de[i].image.style) { width = newWidth; }
        with(de[i].container.style) { 
          width = cWidth; 
          fontSize = fSize; 
        }
      }            
    }
    if (!this.options.view) {
      var i = setInterval(function() {
        if (!instance.options.view) return;
        f()
        clearInterval(i)
      }, 100)
    }
    else {
      f()
    }
  },
  currentImageContainersHeight: function() {
    return this.imageContainersHeight;
  },
  hide: function() {
    this.sliderWidget.style.display = "none";
  },
  show: function() {
    this.sliderWidget.style.display = "";
  },
  makeWidgetContent: function() {
    return [ this.slider.htmlNode() ]
  },
  ensureSliderValue: function() {
    if (!this.currentScalingValue) {
      this.currentScalingValue = 
          this.sliderToScalingValue(this.options.defaultSliderValue)
    }
  },
  scaleImageContainerWidth: function() {
    return this.currentScalingValue * 220 + 'px';
  }, 
  scaleImageContainerHeight: function() {
    return this.currentScalingValue * 230 + 'px';
  }, 
  scaleImageWidth: function() {
    return this.currentScalingValue * 190 + 'px';
  }, 
  scaleFontSize: function() {
    return this.currentScalingValue * 16 + 'pt';
  }
}
Object.extend(SliderNavigationWidget.prototype, WidgetCommon)
;

// swfobject.js
/**
 * SWFObject v1.4.4: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
 *
 * SWFObject is (c) 2006 Geoff Stearns and is released under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * **SWFObject is the SWF embed script formerly known as FlashObject. The name was changed for
 *   legal reasons.
 */
if(typeof deconcept == "undefined") var deconcept = new Object();
if(typeof deconcept.util == "undefined") deconcept.util = new Object();
if(typeof deconcept.SWFObjectUtil == "undefined") deconcept.SWFObjectUtil = new Object();
deconcept.SWFObject = function(swf, id, w, h, ver, c, useExpressInstall, quality, xiRedirectUrl, redirectUrl, detectKey){
	if (!document.getElementById) { return; }
	this.DETECT_KEY = detectKey ? detectKey : 'detectflash';
	this.skipDetect = deconcept.util.getRequestParameter(this.DETECT_KEY);
	this.params = new Object();
	this.variables = new Object();
	this.attributes = new Array();
	if(swf) { this.setAttribute('swf', swf); }
	if(id) { this.setAttribute('id', id); }
	if(w) { this.setAttribute('width', w); }
	if(h) { this.setAttribute('height', h); }
	if(ver) { this.setAttribute('version', new deconcept.PlayerVersion(ver.toString().split("."))); }
	this.installedVer = deconcept.SWFObjectUtil.getPlayerVersion();
	if(c) { this.addParam('bgcolor', c); }
	var q = quality ? quality : 'high';
	this.addParam('quality', q);
	this.setAttribute('useExpressInstall', useExpressInstall);
	this.setAttribute('doExpressInstall', false);
	var xir = (xiRedirectUrl) ? xiRedirectUrl : window.location;
	this.setAttribute('xiRedirectUrl', xir);
	this.setAttribute('redirectUrl', '');
	if(redirectUrl) { this.setAttribute('redirectUrl', redirectUrl); }
}
deconcept.SWFObject.prototype = {
	setAttribute: function(name, value){
		this.attributes[name] = value;
	},
	getAttribute: function(name){
		return this.attributes[name];
	},
	addParam: function(name, value){
		this.params[name] = value;
	},
	getParams: function(){
		return this.params;
	},
	addVariable: function(name, value){
		this.variables[name] = value;
	},
	getVariable: function(name){
		return this.variables[name];
	},
	getVariables: function(){
		return this.variables;
	},
	getVariablePairs: function(){
		var variablePairs = new Array();
		var key;
		var variables = this.getVariables();
		for(key in variables){
		  if (typeof key != "function") {
        variablePairs.push(key +"="+ variables[key]);
      }
		}
		return variablePairs;
	},
	getSWFHTML: function() {
		var swfNode = "";
		if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture
			if (this.getAttribute("doExpressInstall")) { this.addVariable("MMplayerType", "PlugIn"); }
			swfNode = '<embed type="application/x-shockwave-flash" src="'+ this.getAttribute('swf') +'" width="'+ this.getAttribute('width') +'" height="'+ this.getAttribute('height') +'"';
			swfNode += ' id="'+ this.getAttribute('id') +'" name="'+ this.getAttribute('id') +'" ';
			var params = this.getParams();
			 for(var key in params){
			   if (typeof key != "function") {
			     swfNode += [key] +'="'+ params[key] +'" ';
         }
       }
			var pairs = this.getVariablePairs().join("&");
			 if (pairs.length > 0){ swfNode += 'flashvars="'+ pairs +'"'; }
			swfNode += '/>';
		} else { // PC IE
			if (this.getAttribute("doExpressInstall")) { this.addVariable("MMplayerType", "ActiveX"); }
			swfNode = '<object id="'+ this.getAttribute('id') +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+ this.getAttribute('width') +'" height="'+ this.getAttribute('height') +'">';
			swfNode += '<param name="movie" value="'+ this.getAttribute('swf') +'" />';
			var params = this.getParams();
			for(var key in params) {
			  if (typeof key != "function") {
          swfNode += '<param name="'+ key +'" value="'+ params[key] +'" />';
        }
			}
			var pairs = this.getVariablePairs().join("&");
			if(pairs.length > 0) {swfNode += '<param name="flashvars" value="'+ pairs +'" />';}
			swfNode += "</object>";
		}
		return swfNode;
	},
	write: function(elementId){
		if(this.getAttribute('useExpressInstall')) {
			// check to see if we need to do an express install
			var expressInstallReqVer = new deconcept.PlayerVersion([6,0,65]);
			if (this.installedVer.versionIsValid(expressInstallReqVer) && !this.installedVer.versionIsValid(this.getAttribute('version'))) {
				this.setAttribute('doExpressInstall', true);
				this.addVariable("MMredirectURL", escape(this.getAttribute('xiRedirectUrl')));
				document.title = document.title.slice(0, 47) + " - Flash Player Installation";
				this.addVariable("MMdoctitle", document.title);
			}
		}
		if(this.skipDetect || this.getAttribute('doExpressInstall') || this.installedVer.versionIsValid(this.getAttribute('version'))){
			var n = (typeof elementId == 'string') ? document.getElementById(elementId) : elementId;
			n.innerHTML = this.getSWFHTML();
			return true;
		}else{
			if(this.getAttribute('redirectUrl') != "") {
				document.location.replace(this.getAttribute('redirectUrl'));
			}
		}
		return false;
	}
}

/* ---- detection functions ---- */
deconcept.SWFObjectUtil.getPlayerVersion = function(){
	var PlayerVersion = new deconcept.PlayerVersion([0,0,0]);
	if(navigator.plugins && navigator.mimeTypes.length){
		var x = navigator.plugins["Shockwave Flash"];
		if(x && x.description) {
			PlayerVersion = new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split("."));
		}
	}else{
		// do minor version lookup in IE, but avoid fp6 crashing issues
		// see http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/
		try{
			var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
		}catch(e){
			try {
				var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
				PlayerVersion = new deconcept.PlayerVersion([6,0,21]);
				axo.AllowScriptAccess = "always"; // throws if player version < 6.0.47 (thanks to Michael Williams @ Adobe for this code)
			} catch(e) {
				if (PlayerVersion.major == 6) {
					return PlayerVersion;
				}
			}
			try {
				axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
			} catch(e) {}
		}
		if (axo != null) {
			PlayerVersion = new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));
		}
	}
	return PlayerVersion;
}
deconcept.PlayerVersion = function(arrVersion){
	this.major = arrVersion[0] != null ? parseInt(arrVersion[0]) : 0;
	this.minor = arrVersion[1] != null ? parseInt(arrVersion[1]) : 0;
	this.rev = arrVersion[2] != null ? parseInt(arrVersion[2]) : 0;
}
deconcept.PlayerVersion.prototype.versionIsValid = function(fv){
	if(this.major < fv.major) return false;
	if(this.major > fv.major) return true;
	if(this.minor < fv.minor) return false;
	if(this.minor > fv.minor) return true;
	if(this.rev < fv.rev) return false;
	return true;
}
/* ---- get value of query string param ---- */
deconcept.util = {
	getRequestParameter: function(param) {
		var q = document.location.search || document.location.hash;
		if(q) {
			var pairs = q.substring(1).split("&");
			for (var i=0; i < pairs.length; i++) {
				if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
					return pairs[i].substring((pairs[i].indexOf("=")+1));
				}
			}
		}
		return "";
	}
}
/* fix for video streaming bug */
deconcept.SWFObjectUtil.cleanupSWFs = function() {
	if (window.opera || !document.all) return;
	var objects = document.getElementsByTagName("OBJECT");
	for (var i=0; i < objects.length; i++) {
		objects[i].style.display = 'none';
		for (var x in objects[i]) {
			if (typeof objects[i][x] == 'function') {
				objects[i][x] = function(){};
			}
		}
	}
}
// fixes bug in fp9 see http://blog.deconcept.com/2006/07/28/swfobject-143-released/
deconcept.SWFObjectUtil.prepUnload = function() {
	__flash_unloadHandler = function(){};
	__flash_savedUnloadHandler = function(){};
	if (typeof window.onunload == 'function') {
		var oldUnload = window.onunload;
		window.onunload = function() {
			deconcept.SWFObjectUtil.cleanupSWFs();
			oldUnload();
		}
	} else {
		window.onunload = deconcept.SWFObjectUtil.cleanupSWFs;
	}
}
if (typeof window.onbeforeunload == 'function') {
	var oldBeforeUnload = window.onbeforeunload;
	window.onbeforeunload = function() {
		deconcept.SWFObjectUtil.prepUnload();
		oldBeforeUnload();
	}
} else {
	window.onbeforeunload = deconcept.SWFObjectUtil.prepUnload;
}
/* add Array.push if needed (ie5) */
if (Array.prototype.push == null) { Array.prototype.push = function(item) { this[this.length] = item; return this.length; }}

/* add some aliases for ease of use/backwards compatibility */
var getQueryParamValue = deconcept.util.getRequestParameter;
var FlashObject = deconcept.SWFObject; // for legacy support
var SWFObject = deconcept.SWFObject;
;

// thumbsviewwidget.js
var __tvw = null;
ThumbsViewWidget = Class.create();
ThumbsViewWidget.prototype = {
  defaultOptionsAdditions: function() {
    return {
      tooltip: {hideTotally: function() { /*itemInfoInstance.hideBox()*/ }}
    };
  },
  setup: function() {
    __tvw = this
    this.options.model.addObserver(this)
    if (!this.options.presenter) {
      this.options.presenter = new ImageContainerDefaultPresenter({
        model: this.options.model
      })
    }
  },
  getDisplayElements: function() {
    return this.displayElements;
  },
  makeWidgetContent: function() {
    var ii = this
    return scope(this.options.scope + " .thumbsview", function() {
      var r = Builder.node('div', {className: 'ax-thumbs'})
      if (ii.options.showHeader) {
        r.appendChild(ii.makeHeader());
      }
      ii.thumbsRoot = r;
      r.appendChild(ii.thumbsViewContent = ii.makeThumbsViewContent())
      return r;
    })
  },
  makeThumbsViewContent: function() {
    this.displayElements = []
    var model = this.options.model.getModel()
    if (model.length == 0) {
      return Builder.node('div', {}, [this.options.noItems()])
    }
    tstart("sthumbsview")
    var instance = this;
    var d = Builder.node('div', {
      className: 'thumbs-container'
    })
    var count = -1;
    instance.options.presenter.loadVariables(this)
    model.each(function(m) {
      count += 1;
      var e = instance.options.presenter.draw(m, count)
      instance.displayElements.push(e)
      d.appendChild(e.content)
    })
    this.indicateSorting();
    tstop("sthumbsview")
    return d;
  },
  rerender: function() {
    if (this.hidden) { return }
    // itemInfoInstance.hideBox()
    var nc = this.makeThumbsViewContent()
    this.replaceChild(this.thumbsRoot, this.thumbsViewContent, nc)
    this.thumbsViewContent = nc
  }
}

Object.extend(ThumbsViewWidget.prototype, ExplorerComponent)

FixedScaling = Class.create();
FixedScaling.prototype = {
  initialize: function() {
    this.options = Object.extend({
      value: 1
    }, arguments[0] || {})
  }, 
  scaleImageContainerWidth: function() {
    return this.options.value * 220 + 'px';
  }, 
  scaleImageContainerHeight: function() {
    return this.options.value * 230 + 'px';
  }, 
  scaleImageWidth: function() {
    return this.options.value * 190 + 'px';
  }, 
  scaleFontSize: function() {
    return this.options.value * 16 + 'pt';
  }
}

var __icdp;

ImageContainerDefaultPresenter = Class.create();
ImageContainerDefaultPresenter.prototype = {
  initialize: function() {
    __icdp = this
    this.options = Object.extend({
      slider: null,
      imgContainerClassName: 'img-container',
      model: itemModel,
      tooltip: {hideTotally: function() { itemInfoInstance.hideBox() }}
    }, arguments[0] || {})
  },
  beforeTabHide: function() {
    this.options.tooltip.hideTotally()
  },
  loadVariables: function(tv) {
    this.thumbsview = tv
    this.newWidth = 0
    this.cWidth = 0
    this.fSize = 0
    if (this.options.slider) {
      this.newWidth = this.options.slider.scaleImageWidth()
      this.cWidth = this.options.slider.scaleImageContainerWidth()
      this.fSize = this.options.slider.scaleFontSize()
    }
  },
  draw: function(m) {
    var imgContainer = null;
    var imageDiv = null;
    var image = null;
    var edit = m["edit"]
    var d = document.createElement("div")
    var imgContClass = this.options.imgContainerClassName
    d.innerHTML =
      "<div class='" + imgContClass + "' id='item_"+m.pretty_name+"'>" +
        "<div class='scale-image'>" +
          "<a class='white-link' href='" + m.full_item_url + "' " + 
              "title='" + m.name + "'> " + 
            "<img width='100%' alt='thumb' class='image-frame' " + 
                "src='" + m.thumbnail_image_url + "'/>" + 
          "</a>" + 
          m.name.replace(/(.)/g, "$1&#8203;") + "<br/>" + 
        "</div>" + 
      "</div>"
    var imgContainer = d.lastChild;
    var imageDiv     = imgContainer.lastChild
    var image        = imageDiv.firstChild.firstChild;
    var itemName     = $(d).down('.item-name')
    if (this.options.slider) {
      imgContainer.style.width = this.cWidth
      imgContainer.style.fontSize = this.fSize;
      imageDiv.style.width = this.newWidth;
    }
    imgContainer.onclick = function(event) {
      if (event) Event.stop(event)
      itemInfoInstance.showActionsForItem(m, this.options.model, imgContainer)
    }.bindAsEventListener(this)
    Event.observe(imgContainer, 'mouseover', function() {
      imgContainer.setOpacity(0.6)
    })
    Event.observe(imgContainer, 'mouseout', function() {
      imgContainer.setOpacity(1.0)
    })
    var instance = this;
    imgContainer.ondblclick = catchExceptionsForOnClickHandler(function() {
      instance.options.model.actions.showItem({
        m: m,
        displayItemClicked: instance.thumbsview.options.displayItemClicked,
        folderClicked: instance.thumbsview.options.folderClicked
      })
    })
    if (m.selected) {
      imgContainer.addClassName("container-selected")
    }
    return {
      image: imageDiv,
      container: imgContainer,
      content: d
    };
  }
}

;

// configurablewidget.js
ConfigurableWidget = Class.create();
ConfigurableWidget.prototype = {
  setup: function() {
    this.prefId = parameters().prefId || "default"
    try {
      this.options.preferences = JSON.parseJSON(this.options.allPreferences[this.prefId])
      if (!this.options.preferences) this.options.preferences = {}
    }
    catch(e) {
      this.options.preferences = {}
    }
    // canWrite doesn't take the sticky permissions into account
    this.options.prefModel.canWrite = function() { return true; }
  },
  editView: function() {
    if (!this.localEditNode && this.editFields().length == 0) {
      return TN("No configuration for this module!")      
    }
    else {
      return this.editForm()      
    }
  },
  editForm: function() {
    var thiscw = this;
    var save   = Builder.node('input', {type: 'submit', value: 'save'})
    var cancel = Builder.node('input', {type: 'button', value: 'cancel'})
    var form   = Builder.node('form', [this.editNode(), cancel, save])
    cancel.onclick = function() {
      if (!this.options.noRedirect) {
        window.location = window.location.href.replace(/\?.*/,"") +
                          "?cancel=true&prefId=" + this.prefId        
      }
    }.bind(this)
    form.onsubmit = function() {
      $MSH(Form.serialize(form, {hash: true})).each(function(pair) {
        this.options.preferences[pair.key] = pair.value;
      }.bind(this))
      var obs = {update: function() {
        this.options.prefModel.removeObserver(obs)
        if (this.options.onSave) { this.options.onSave() }
        if (!this.options.noRedirect) {
          window.location = window.location.href.replace(/\?.*/,"") +
                            "?done=true&prefId=" + this.prefId
        }        
      }.bind(this)}
      this.options.prefModel.addObserver(obs)
      this.options.allPreferences[this.prefId] = JSON.toJSON(this.options.preferences)
      this.options.prefModel.storeItem(this.options.allPreferences)
      return false;
    }.bind(this)
    return form
  },
  editNode: function() {
    if (this.localEditNode) {
      return this.localEditNode()
    }
    var trs = [{}]
    this.editFields().each(function(f) {
      var i = makeInputNodes([f.display+":", f.name,
        this.options.preferences[f.name] || "", {generic: true}])
      trs.push([i.description])
      trs.push([i.input])
    }.bind(this))
    return table.apply(null, trs)      
  }
}
Object.extend(ConfigurableWidget.prototype, WidgetCommon)
;

// standardlayout.js
function checkDebugger() {
  if (__debug) {
    appendChildren($('debug-bar'), [ debugWidget ])
  }
}

function enableDebugger() {
  __debug = true
  checkDebugger()
}

function standardLayout() {

  makeActiveTabsWidget()

  makeDebugWidget();
  checkDebugger()

  scope("title-bar", function() {
    $('title-bar-left').appendChild(table([
      new TooltipWidget({
        icon: function() { 
          return new IconWidget({
            title: "My Sites", 
            iconName: "service_mysites"
          }).htmlNode()
        },
        tooltipContent: function() {
          return new MyServicesWidget().htmlNode()
        }
      })
    ]))
    appendChildren($('title-bar-right'), [ 
      new LogoutWidget() 
    ])
  })

  $('left-bar-placeholder').appendChild(
      roundBox({node: Builder.node('div', {id: 'left-bar'})}))
  $('main-content-placeholder').appendChild(
      roundBox({node: Builder.node('div', {id: 'main-content'})}))

  //appendChildren($('left-bar'), [ 
  //  new BadgeWidget()
  //])
  if (!tabs.current) {
    var rootTab = new Tab(null, {
      bodyOverflow: "", 
      register: false,
      name: "Explorer"
    });
    tabs.tabs.push(rootTab)
    tabs.current = rootTab
  }
}

function checkCurrentItem() {
  var m = null
  if (current_item && (m = itemModel.findItemByPrettyName(current_item))) {
    if (simpleDisplayItemIsSupportedItemType(m)) {
      displayItemSimple(m)
      return true
    } else {
      setTimeout(function() {
        itemModel.actions.showItem({m: m})
        endIdle()
      }, 500)
      return true
    }
  }
  return false
}

;

// userselection.js
// Represents "text" selected by the user in a cross-browser form. See
// http://www.quirksmode.org/dom/range_intro.html for more info.
UserSelection = Class.create();
UserSelection.prototype = {
  initialize: function(frame) {
    if (!frame) {
      frame = window;
    }
    this.window = frame.contentWindow;
    if (this.window.getSelection) {
      this.userSelection = this.window.getSelection();
      if (this.userSelection.getRangeAt) {
        if (this.userSelection.anchorNode) {
          // getRangeAt doesn't return if there's no selection!
          this.range = this.userSelection.getRangeAt(0);
        }
      }
      else { // Safari!
        // (untested)
        var r = document.createRange();
        if (this.userSelection.anchorNode) {
          r.setStart(this.userSelection.anchorNode, this.userSelection.anchorOffset);
          r.setEnd(this.userSelection.focusNode, this.userSelection.focusOffset);
          this.range = r;
        }
      }
    }
    // The IE way should come last as Opera supports both but we want to use
    // the W3C way for it!
    else if (document.selection) {
      this.userSelection = this.window.document.selection.createRange();
      this.range = this.userSelection;
    }
    // IE
    if (this.userSelection.text != undefined) {
      this.textString = this.userSelection.text;
    }
    else {
      this.textString = this.userSelection.toString();
    }
  },
  savePosition: function() {
    var tc = this.window.document.getElementById("texteditor-cursor");
    if (tc) Element.remove(tc)
    var mark = Builder.node('span', {id: 'texteditor-cursor'})
    if (Prototype.Browser.IE) {
      mark.innerHTML = this.userSelection.htmlText
      this.userSelection.pasteHTML(mark.outerHTML)
    }
    else {
      this.range.surroundContents(mark)
    }
  },
  ie: function() {
    return Prototype.Browser.IE;
  },
  text: function() {
    return this.textString;
  },
  isEmpty: function() {
    return this.text().length == 0;
  },
  insertAtPoint: function(node) {
    if (arguments.length > 1) {
      this.window = arguments[1]
    }
    var mark = this.window.document.getElementById("texteditor-cursor")
    if (Prototype.Browser.IE) {
      var n = this.window.document.createElement('span')
      n.innerHTML = node.outerHTML
      node = n.firstChild
    }
    mark.parentNode.insertBefore(node, mark)
    Element.remove(mark)
  },
  wrap: function(node) {
    if (arguments.length > 1) {
      this.window = arguments[1]
    }
    var mark = this.window.document.getElementById("texteditor-cursor")
    if (Prototype.Browser.IE) {
      var n = this.window.document.createElement('span')
      n.innerHTML = node.outerHTML
      node = n.firstChild
    }
    mark.parentNode.insertBefore(node, mark)
    $A(mark.childNodes).each(function(c) { node.appendChild(c) })
    Element.remove(mark)
  }
};
;

// page.js
function commonHeader(pw) {
  scope("title-bar", function() {
    $('title-bar-main').appendChild(table([
      pw || EMPTY(),
      new TooltipWidget({
        icon: function() { 
          return new IconWidget({
            title: "Share", 
            iconName: "rss"
          }).htmlNode()
        },
        tooltipContent: function() {
          return Builder.node('span', {}, [
            TN("permalink"), BR(),
            TN("via email"), BR(),
            TN("with buddy"), BR(),
          ])
        }
      }),
      new TooltipWidget({
        icon: function() { 
          return new IconWidget({
            title: "Bookmark", 
            iconName: "rss"
          }).htmlNode()
        },
        tooltipContent: function() {
          return Builder.node('span', {}, [
            TN("Add this page to My Favorites"), BR()
          ])
        }
      }),
      new TooltipWidget({
        icon: function() { 
          return new IconWidget({
            title: "Subscribe", 
            iconName: "rss"
          }).htmlNode()
        },
        tooltipContent: function() {
          return Builder.node('span', {}, [
            TN("Notify when page is updated! (coming soon)"), BR()
          ])
        }
      }),
      new LanguageWidget(),
      new ThemeWidget(),
      {
        columnOptions: {
          width: "100%"
        },
        columnData: [TN("")]
      }
    ]))
  })
}

function initUserPage() {
  makeStandardExplorerWidget();
  commonHeader(standardExplorer.pathWidget)

  appendChildren($('left-bar'), [ 
    makeRelatedLinksWidget(),
    // searchWidget.htmlNode(),
    new SelectionWidget({ listContent: makeGlobalSelectionListContent() })/*,
    new ActionWidget({model: itemModel})*/ // Interfering with new frontend code
  ])

  $('main-content').appendChild(standardExplorer.htmlNode())
}


function overviewWidget(model) {
  var fixedScalingPresenter = new ImageContainerDefaultPresenter({
    ctx: new FixedScaling({value: 0.6}),
    imgContainerClassName: 'img-container-no-hover',
    model: model
  })
  featuredThumbsView = new ThumbsViewWidget({
    model: model,
    presenter: fixedScalingPresenter,
    showHeader: false
  })
  return featuredThumbsView.htmlNode()
}


function initMainPage() {
  commonHeader()
  appendChildren($('main-content'), [
    Builder.node('div', {className: "recent"}, [ 
      Builder.node('div', {className: "header"}, [TN("Recent items") ]),
      overviewWidget(itemModel)
    ]),
  ])
}

function showPage(page, opts) {
  if (!javaScript["serviceLayout"]) {
    javaScript[page]()
  }
}

function hook(n) {
  if (typeof javaScript[n] == 'function') { javaScript[n]() }
}

function folderPermalink(m) {
  alert("[folderPermalink] Would show contents of folder " + m.name + " here")
}

function initPage() {
  checkForSupportedBrowser(function() {
    makeItemModel();
    document.title = "MySit.es - My " + itemModel.itemNamePluralCapitalized()
    if (current_item) {
      checkCurrentItem()
    } else if (folder_permalink) {
      folderPermalink(itemModel.folderItem())
    }else {
      standardLayout();

      hook("initializeService")
      hook("serviceLayout")
      showPage(is_main_page ? "initMainPage" : "initUserPage")
      processAfterShow()
      hook("afterInitPage")    
    }
  })
}

function checkForSupportedBrowser(fun) {
  fun()
  return 
  var supported = $A([
    /Firefox/,
    /MSIE 7\.0/
  ])
  // For browsers that would be included by the regexps above
  var exclude = $A([])
  var s = supported.any(function(r) { return window.navigator.userAgent.match(r)})
  var e = exclude.any(function(r) { return window.navigator.userAgent.match(r)})
  if (s && !e) {
    fun()
  }
  else {
    verify(fun,
      "Your browser isn't supported by Mysites, yet. Some " +
      "parts of the site may not work for you. Use Firefox or Internet " +
      "Explorer 7 for a better experience.",
      {cancelButton: false, big: true}
    )
  }
}
;

// window.js
// Copyright (c) 2006 SÃ©bastien Gruhier (http://xilinus.com, http://itseb.com)
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// VERSION 1.3

var Window = Class.create();

Window.keepMultiModalWindow = false;
Window.hasEffectLib = (typeof Effect != 'undefined');
Window.resizeEffectDuration = 0.4;

Window.prototype = {
  // Constructor
  // Available parameters : className, blurClassName, title, minWidth, minHeight, maxWidth, maxHeight, width, height, top, left, bottom, right, resizable, zIndex, opacity, recenterAuto, wiredDrag
  //                        hideEffect, showEffect, showEffectOptions, hideEffectOptions, effectOptions, url, draggable, closable, minimizable, maximizable, parent, onload
  //                        add all callbacks (if you do not use an observer)
  //                        onDestroy onStartResize onStartMove onResize onMove onEndResize onEndMove onFocus onBlur onBeforeShow onShow onHide onMinimize onMaximize onClose
  
  initialize: function() {
    var id;
    var optionIndex = 0;
    // For backward compatibility like win= new Window("id", {...}) instead of win = new Window({id: "id", ...})
    if (arguments.length > 0) {
      if (typeof arguments[0] == "string" ) {
        id = arguments[0];
        optionIndex = 1;
      }
      else
        id = arguments[0] ? arguments[0].id : null;
    }
    
    // Generate unique ID if not specified
    if (!id)
      id = "window_" + new Date().getTime();
      
    if ($(id))
      if (is_developer) {
        alert("Window " + id + " is already registered in the DOM! Make sure you use setDestroyOnClose() or destroyOnClose: true in the constructor");
      }

    this.options = Object.extend({
      className:         "dialog",
      blurClassName:     null,
      minWidth:          100, 
      minHeight:         20,
      resizable:         true,
      closable:          true,
      minimizable:       true,
      maximizable:       true,
      draggable:         true,
      userData:          null,
      showEffect:        (Window.hasEffectLib ? Effect.Appear : Element.show),
      hideEffect:        (Window.hasEffectLib ? Effect.Fade : Element.hide),
      showEffectOptions: {},
      hideEffectOptions: {},
      effectOptions:     null,
      parent:            document.body,
      title:             "&nbsp;",
      url:               null,
      onload:            Prototype.emptyFunction,
      width:             200,
      height:            300,
      opacity:           1,
      recenterAuto:      true,
      wiredDrag:         false,
      closeCallback:     null,
      destroyOnClose:    false,
      gridX:             1, 
      gridY:             1      
    }, arguments[optionIndex] || {});
    if (this.options.blurClassName)
      this.options.focusClassName = this.options.className;
      
    if (typeof this.options.top == "undefined" &&  typeof this.options.bottom ==  "undefined") 
      this.options.top = this._round(Math.random()*500, this.options.gridY);
    if (typeof this.options.left == "undefined" &&  typeof this.options.right ==  "undefined") 
      this.options.left = this._round(Math.random()*500, this.options.gridX);

    if (this.options.effectOptions) {
      Object.extend(this.options.hideEffectOptions, this.options.effectOptions);
      Object.extend(this.options.showEffectOptions, this.options.effectOptions);
      if (this.options.showEffect == Element.Appear)
        this.options.showEffectOptions.to = this.options.opacity;
    }
    if (Window.hasEffectLib) {
      if (this.options.showEffect == Effect.Appear)
        this.options.showEffectOptions.to = this.options.opacity;
    
      if (this.options.hideEffect == Effect.Fade)
        this.options.hideEffectOptions.from = this.options.opacity;
    }
    if (this.options.hideEffect == Element.hide)
      this.options.hideEffect = function(){ Element.hide(this.element); if (this.options.destroyOnClose) this.destroy(); }.bind(this)
    
    if (this.options.parent != document.body)  
      this.options.parent = $(this.options.parent);
      
    this.element = this._createWindow(id);       
    this.element.win = this;
    
    // Bind event listener
    this.eventMouseDown = this._initDrag.bindAsEventListener(this);
    this.eventMouseUp   = this._endDrag.bindAsEventListener(this);
    this.eventMouseMove = this._updateDrag.bindAsEventListener(this);
    this.eventOnLoad    = this._getWindowBorderSize.bindAsEventListener(this);
    this.eventMouseDownContent = this.toFront.bindAsEventListener(this);
    this.eventResize = this._recenter.bindAsEventListener(this);
 
    this.topbar = $(this.element.id + "_top");
    this.bottombar = $(this.element.id + "_bottom");
    this.content = $(this.element.id + "_content");
    
    Event.observe(this.topbar, "mousedown", this.eventMouseDown);
    Event.observe(this.bottombar, "mousedown", this.eventMouseDown);
    Event.observe(this.content, "mousedown", this.eventMouseDownContent);
    Event.observe(window, "load", this.eventOnLoad);
    Event.observe(window, "resize", this.eventResize);
    Event.observe(window, "scroll", this.eventResize);
    Event.observe(this.options.parent, "scroll", this.eventResize);
    
    if (this.options.draggable)  {
      var that = this;
      [this.topbar, this.topbar.up().previous(), this.topbar.up().next()].each(function(element) {
        element.observe("mousedown", that.eventMouseDown);
        element.addClassName("top_draggable");
      });
      [this.bottombar.up(), this.bottombar.up().previous(), this.bottombar.up().next()].each(function(element) {
        element.observe("mousedown", that.eventMouseDown);
        element.addClassName("bottom_draggable");
      });
      
    }    
    
    if (this.options.resizable) {
      this.sizer = $(this.element.id + "_sizer");
      Event.observe(this.sizer, "mousedown", this.eventMouseDown);
    }  
    
    this.useLeft = null;
    this.useTop = null;
    if (typeof this.options.left != "undefined") {
      this.element.setStyle({left: parseFloat(this.options.left) + 'px'});
      this.useLeft = true;
    }
    else {
      this.element.setStyle({right: parseFloat(this.options.right) + 'px'});
      this.useLeft = false;
    }
    
    if (typeof this.options.top != "undefined") {
      this.element.setStyle({top: parseFloat(this.options.top) + 'px'});
      this.useTop = true;
    }
    else {
      this.element.setStyle({bottom: parseFloat(this.options.bottom) + 'px'});      
      this.useTop = false;
    }
      
    this.storedLocation = null;
    
    this.setOpacity(this.options.opacity);
    if (this.options.zIndex)
      this.setZIndex(this.options.zIndex)

    if (this.options.destroyOnClose)
      this.setDestroyOnClose(true);

    this._getWindowBorderSize();
    this.width = this.options.width;
    this.height = this.options.height;
    this.visible = false;
    
    this.constraint = false;
    this.constraintPad = {top: 0, left:0, bottom:0, right:0};
    
    if (this.width && this.height)
      this.setSize(this.options.width, this.options.height);
    this.setTitle(this.options.title)
    Windows.register(this);      
  },
  
  // Destructor
  destroy: function() {
    this._notify("onDestroy");
    Event.stopObserving(this.topbar, "mousedown", this.eventMouseDown);
    Event.stopObserving(this.bottombar, "mousedown", this.eventMouseDown);
    Event.stopObserving(this.content, "mousedown", this.eventMouseDownContent);
    
    Event.stopObserving(window, "load", this.eventOnLoad);
    Event.stopObserving(window, "resize", this.eventResize);
    Event.stopObserving(window, "scroll", this.eventResize);
    
    Event.stopObserving(this.content, "load", this.options.onload);

    if (this._oldParent) {
      var content = this.getContent();
      var originalContent = null;
      for(var i = 0; i < content.childNodes.length; i++) {
        originalContent = content.childNodes[i];
        if (originalContent.nodeType == 1) 
          break;
        originalContent = null;
      }
      if (originalContent)
        this._oldParent.appendChild(originalContent);
      this._oldParent = null;
    }

    if (this.sizer)
        Event.stopObserving(this.sizer, "mousedown", this.eventMouseDown);

    if (this.options.url) 
      this.content.src = null

     if(this.iefix) 
      Element.remove(this.iefix);

    Element.remove(this.element);
    Windows.unregister(this);      
  },
    
  // Sets close callback, if it sets, it should return true to be able to close the window.
  setCloseCallback: function(callback) {
    this.options.closeCallback = callback;
  },
  
  // Gets window content
  getContent: function () {
    return this.content;
  },
  
  // Sets the content with an element id
  setContent: function(id, autoresize, autoposition) {
    var element = $(id);
    if (null == element) throw "Unable to find element '" + id + "' in DOM";
    this._oldParent = element.parentNode;

    var d = null;
    var p = null;

    if (autoresize) 
      d = Element.getDimensions(element);
    if (autoposition) 
      p = Position.cumulativeOffset(element);

    var content = this.getContent();
    // Clear HTML (and even iframe)
    this.setHTMLContent("");
    content = this.getContent();
    
    content.appendChild(element);
    element.show();
    if (autoresize) 
      this.setSize(d.width, d.height);
    if (autoposition) 
      this.setLocation(p[1] - this.heightN, p[0] - this.widthW);    
  },
  
  setHTMLContent: function(html) {
    // It was an url (iframe), recreate a div content instead of iframe content
    if (this.options.url) {
      this.content.src = null;
      this.options.url = null;
      
  	  var content ="<div id=\"" + this.getId() + "_content\" class=\"" + this.options.className + "_content\"> </div>";
      $(this.getId() +"_table_content").innerHTML = content;
      
      this.content = $(this.element.id + "_content");
    }
      
    this.getContent().innerHTML = html;
  },
  
  setAjaxContent: function(url, options, showCentered, showModal) {
    this.showFunction = showCentered ? "showCenter" : "show";
    this.showModal = showModal || false;
  
    options = options || {};

    // Clear HTML (and even iframe)
    this.setHTMLContent("");
 
    this.onComplete = options.onComplete;
    if (! this._onCompleteHandler)
      this._onCompleteHandler = this._setAjaxContent.bind(this);
    options.onComplete = this._onCompleteHandler;

    new Ajax.Request(url, options);    
    options.onComplete = this.onComplete;
  },
  
  _setAjaxContent: function(originalRequest) {
    Element.update(this.getContent(), originalRequest.responseText);
    if (this.onComplete)
      this.onComplete(originalRequest);
    this.onComplete = null;
    this[this.showFunction](this.showModal)
  },
  
  setURL: function(url) {
    // Not an url content, change div to iframe
    if (this.options.url) 
      this.content.src = null;
    this.options.url = url;
    var content= "<iframe frameborder='0' name='" + this.getId() + "_content'  id='" + this.getId() + "_content' src='" + url + "' width='" + this.width + "' height='" + this.height + "'> </iframe>";
    $(this.getId() +"_table_content").innerHTML = content;
    
    this.content = $(this.element.id + "_content");
  },

  getURL: function() {
  	return this.options.url ? this.options.url : null;
  },

  refresh: function() {
    if (this.options.url)
	    $(this.element.getAttribute('id') + '_content').src = this.options.url;
  },
  
  // Stores position/size in a cookie, by default named with window id
  setCookie: function(name, expires, path, domain, secure) {
    name = name || this.element.id;
    this.cookie = [name, expires, path, domain, secure];
    
    // Get cookie
    var value = WindowUtilities.getCookie(name)
    // If exists
    if (value) {
      var values = value.split(',');
      var x = values[0].split(':');
      var y = values[1].split(':');

      var w = parseFloat(values[2]), h = parseFloat(values[3]);
      var mini = values[4];
      var maxi = values[5];

      this.setSize(w, h);
      if (mini == "true")
        this.doMinimize = true; // Minimize will be done at onload window event
      else if (maxi == "true")
        this.doMaximize = true; // Maximize will be done at onload window event

      this.useLeft = x[0] == "l";
      this.useTop = y[0] == "t";

      this.element.setStyle(this.useLeft ? {left: x[1]} : {right: x[1]});
      this.element.setStyle(this.useTop ? {top: y[1]} : {bottom: y[1]});
    }
  },
  
  // Gets window ID
  getId: function() {
    return this.element.id;
  },
  
  // Detroys itself when closing 
  setDestroyOnClose: function() {
    this.options.destroyOnClose = true;
  },
  
  setConstraint: function(bool, padding) {
    this.constraint = bool;
    this.constraintPad = Object.extend(this.constraintPad, padding || {});
    // Reset location to apply constraint
    if (this.useTop && this.useLeft)
      this.setLocation(parseFloat(this.element.style.top), parseFloat(this.element.style.left));
  },
  
  // initDrag event

  _initDrag: function(event) {
    // No resize on minimized window
    if (Event.element(event) == this.sizer && this.isMinimized())
      return;

    // No move on maximzed window
    if (Event.element(event) != this.sizer && this.isMaximized())
      return;
      
    if (Prototype.Browser.IE && this.heightN == 0)
      this._getWindowBorderSize();
    
    // Get pointer X,Y
    this.pointer = [this._round(Event.pointerX(event), this.options.gridX), this._round(Event.pointerY(event), this.options.gridY)];
    if (this.options.wiredDrag) 
      this.currentDrag = this._createWiredElement();
    else
      this.currentDrag = this.element;
      
    // Resize
    if (Event.element(event) == this.sizer) {
      this.doResize = true;
      this.widthOrg = this.width;
      this.heightOrg = this.height;
      this.bottomOrg = parseFloat(this.element.getStyle('bottom'));
      this.rightOrg = parseFloat(this.element.getStyle('right'));
      this._notify("onStartResize");
    }
    else {
      this.doResize = false;

      // Check if click on close button, 
      var closeButton = $(this.getId() + '_close');
      if (closeButton && Position.within(closeButton, this.pointer[0], this.pointer[1])) {
        this.currentDrag = null;
        return;
      }

      this.toFront();

      if (! this.options.draggable) 
        return;
      this._notify("onStartMove");
    }    
    // Register global event to capture mouseUp and mouseMove
    Event.observe(document, "mouseup", this.eventMouseUp, false);
    Event.observe(document, "mousemove", this.eventMouseMove, false);
    
    // Add an invisible div to keep catching mouse event over iframes
    WindowUtilities.disableScreen('__invisible__', '__invisible__', this.overlayOpacity);

    // Stop selection while dragging
    document.body.ondrag = function () { return false; };
    document.body.onselectstart = function () { return false; };
    
    this.currentDrag.show();
    Event.stop(event);
  },
  
  _round: function(val, round) {
    return round == 1 ? val  : val = Math.floor(val / round) * round;
  },

  // updateDrag event
  _updateDrag: function(event) {
    var pointer =  [this._round(Event.pointerX(event), this.options.gridX), this._round(Event.pointerY(event), this.options.gridY)];  
    var dx = pointer[0] - this.pointer[0];
    var dy = pointer[1] - this.pointer[1];
    
    // Resize case, update width/height
    if (this.doResize) {
      var w = this.widthOrg + dx;
      var h = this.heightOrg + dy;
      
      dx = this.width - this.widthOrg
      dy = this.height - this.heightOrg
      
      // Check if it's a right position, update it to keep upper-left corner at the same position
      if (this.useLeft) 
        w = this._updateWidthConstraint(w)
      else 
        this.currentDrag.setStyle({right: (this.rightOrg -dx) + 'px'});
      // Check if it's a bottom position, update it to keep upper-left corner at the same position
      if (this.useTop) 
        h = this._updateHeightConstraint(h)
      else
        this.currentDrag.setStyle({bottom: (this.bottomOrg -dy) + 'px'});
        
      this.setSize(w , h);
      this._notify("onResize");
    }
    // Move case, update top/left
    else {
      this.pointer = pointer;
      
      if (this.useLeft) {
        var left =  parseFloat(this.currentDrag.getStyle('left')) + dx;
        var newLeft = this._updateLeftConstraint(left);
        // Keep mouse pointer correct
        this.pointer[0] += newLeft-left;
        this.currentDrag.setStyle({left: newLeft + 'px'});
      }
      else 
        this.currentDrag.setStyle({right: parseFloat(this.currentDrag.getStyle('right')) - dx + 'px'});
      
      if (this.useTop) {
        var top =  parseFloat(this.currentDrag.getStyle('top')) + dy;
        var newTop = this._updateTopConstraint(top);
        // Keep mouse pointer correct
        this.pointer[1] += newTop - top;
        this.currentDrag.setStyle({top: newTop + 'px'});
      }
      else 
        this.currentDrag.setStyle({bottom: parseFloat(this.currentDrag.getStyle('bottom')) - dy + 'px'});

      this._notify("onMove");
    }
    if (this.iefix) 
      this._fixIEOverlapping(); 
      
    this._removeStoreLocation();
    Event.stop(event);
  },

   // endDrag callback
   _endDrag: function(event) {
    // Remove temporary div over iframes
     WindowUtilities.enableScreen('__invisible__');
    
    if (this.doResize)
      this._notify("onEndResize");
    else
      this._notify("onEndMove");
    
    // Release event observing
    Event.stopObserving(document, "mouseup", this.eventMouseUp,false);
    Event.stopObserving(document, "mousemove", this.eventMouseMove, false);

    Event.stop(event);
    
    this._hideWiredElement();

    // Store new location/size if need be
    this._saveCookie()
      
    // Restore selection
    document.body.ondrag = null;
    document.body.onselectstart = null;
  },

  _updateLeftConstraint: function(left) {
    if (this.constraint && this.useLeft && this.useTop) {
      var width = this.options.parent == document.body ? WindowUtilities.getPageSize().windowWidth : this.options.parent.getDimensions().width;

      if (left < this.constraintPad.left)
        left = this.constraintPad.left;
      if (left + this.width + this.widthE + this.widthW > width - this.constraintPad.right) 
        left = width - this.constraintPad.right - this.width - this.widthE - this.widthW;
    }
    return left;
  },
  
  _updateTopConstraint: function(top) {
    if (this.constraint && this.useLeft && this.useTop) {        
      var height = this.options.parent == document.body ? WindowUtilities.getPageSize().windowHeight : this.options.parent.getDimensions().height;
      
      var h = this.height + this.heightN + this.heightS;

      if (top < this.constraintPad.top)
        top = this.constraintPad.top;
      if (top + h > height - this.constraintPad.bottom) 
        top = height - this.constraintPad.bottom - h;
    }
    return top;
  },
  
  _updateWidthConstraint: function(w) {
    if (this.constraint && this.useLeft && this.useTop) {
      var width = this.options.parent == document.body ? WindowUtilities.getPageSize().windowWidth : this.options.parent.getDimensions().width;
      var left =  parseFloat(this.element.getStyle("left"));

      if (left + w + this.widthE + this.widthW > width - this.constraintPad.right) 
        w = width - this.constraintPad.right - left - this.widthE - this.widthW;
    }
    return w;
  },
  
  _updateHeightConstraint: function(h) {
    if (this.constraint && this.useLeft && this.useTop) {
      var height = this.options.parent == document.body ? WindowUtilities.getPageSize().windowHeight : this.options.parent.getDimensions().height;
      var top =  parseFloat(this.element.getStyle("top"));

      if (top + h + this.heightN + this.heightS > height - this.constraintPad.bottom) 
        h = height - this.constraintPad.bottom - top - this.heightN - this.heightS;
    }
    return h;
  },
  
  
  // Creates HTML window code
  _createWindow: function(id) {
    var className = this.options.className;
    var win = document.createElement("div");
    win.setAttribute('id', id);
    win.className = "dialog";

    var content;
    if (this.options.url)
      content= "<iframe frameborder=\"0\" name=\"" + id + "_content\"  id=\"" + id + "_content\" src=\"" + this.options.url + "\"> </iframe>";
    else
      content ="<div id=\"" + id + "_content\" class=\"" +className + "_content\"> </div>";

    var closeDiv = this.options.closable ? "<div class='"+ className +"_close' id='"+ id +"_close' onclick='Windows.close(\""+ id +"\", event)'> </div>" : "";
    var minDiv = this.options.minimizable ? "<div class='"+ className + "_minimize' id='"+ id +"_minimize' onclick='Windows.minimize(\""+ id +"\", event)'> </div>" : "";
    var maxDiv = this.options.maximizable ? "<div class='"+ className + "_maximize' id='"+ id +"_maximize' onclick='Windows.maximize(\""+ id +"\", event)'> </div>" : "";
    var seAttributes = this.options.resizable ? "class='" + className + "_sizer' id='" + id + "_sizer'" : "class='"  + className + "_se'";
    var blank = "../themes/default/blank.gif";
    
    win.innerHTML = closeDiv + minDiv + maxDiv + "\
      <table id='"+ id +"_row1' class=\"top table_window\">\
        <tr>\
          <td class='"+ className +"_nw'></td>\
          <td class='"+ className +"_n'><div id='"+ id +"_top' class='"+ className +"_title title_window'>"+ this.options.title +"</div></td>\
          <td class='"+ className +"_ne'></td>\
        </tr>\
      </table>\
      <table id='"+ id +"_row2' class=\"mid table_window\">\
        <tr>\
          <td class='"+ className +"_w'></td>\
            <td id='"+ id +"_table_content' class='"+ className +"_content' valign='top'>" + content + "</td>\
          <td class='"+ className +"_e'></td>\
        </tr>\
      </table>\
        <table id='"+ id +"_row3' class=\"bot table_window\">\
        <tr>\
          <td class='"+ className +"_sw'></td>\
            <td class='"+ className +"_s'><div id='"+ id +"_bottom' class='status_bar'><span style='float:left; width:1px; height:1px'></span></div></td>\
            <td " + seAttributes + "></td>\
        </tr>\
      </table>\
    ";
    Element.hide(win);
    this.options.parent.insertBefore(win, this.options.parent.firstChild);
    Event.observe($(id + "_content"), "load", this.options.onload);
    return win;
  },
  
  
  changeClassName: function(newClassName) {    
    var className = this.options.className;
    var id = this.getId();
    $A(["_close", "_minimize", "_maximize", "_sizer", "_content"]).each(function(value) { this._toggleClassName($(id + value), className + value, newClassName + value) }.bind(this));
    this._toggleClassName($(id + "_top"), className + "_title", newClassName + "_title");
    $$("#" + id + " td").each(function(td) {td.className = td.className.sub(className,newClassName); });
    this.options.className = newClassName;
  },
  
  _toggleClassName: function(element, oldClassName, newClassName) { 
    if (element) {
      element.removeClassName(oldClassName);
      element.addClassName(newClassName);
    }
  },
  
  // Sets window location
  setLocation: function(top, left) {
    top = this._updateTopConstraint(top);
    left = this._updateLeftConstraint(left);

    var e = this.currentDrag || this.element;
    e.setStyle({top: top + 'px'});
    e.setStyle({left: left + 'px'});

    this.useLeft = true;
    this.useTop = true;
  },
    
  getLocation: function() {
    var location = {};
    if (this.useTop)
      location = Object.extend(location, {top: this.element.getStyle("top")});
    else
      location = Object.extend(location, {bottom: this.element.getStyle("bottom")});
    if (this.useLeft)
      location = Object.extend(location, {left: this.element.getStyle("left")});
    else
      location = Object.extend(location, {right: this.element.getStyle("right")});
    
    return location;
  },
  
  // Gets window size
  getSize: function() {
    return {width: this.width, height: this.height};
  },
    
  // Sets window size
  setSize: function(width, height, useEffect) {    
    width = parseFloat(width);
    height = parseFloat(height);
    
    // Check min and max size
    if (!this.minimized && width < this.options.minWidth)
      width = this.options.minWidth;

    if (!this.minimized && height < this.options.minHeight)
      height = this.options.minHeight;
      
    if (this.options. maxHeight && height > this.options. maxHeight)
      height = this.options. maxHeight;

    if (this.options. maxWidth && width > this.options. maxWidth)
      width = this.options. maxWidth;

    
    if (this.useTop && this.useLeft && Window.hasEffectLib && Effect.ResizeWindow && useEffect) {
      new Effect.ResizeWindow(this, null, null, width, height, {duration: Window.resizeEffectDuration});
    } else {
      this.width = width;
      this.height = height;
      var e = this.currentDrag ? this.currentDrag : this.element;

      e.setStyle({width: width + this.widthW + this.widthE + "px"})
      e.setStyle({height: height  + this.heightN + this.heightS + "px"})

      // Update content size
      if (!this.currentDrag || this.currentDrag == this.element) {
        var content = $(this.element.id + '_content');
        content.setStyle({height: height  + 'px'});
        content.setStyle({width: width  + 'px'});
      }
    }
  },
  
  updateHeight: function() {
    this.setSize(this.width, this.content.scrollHeight, true);
  },
  
  updateWidth: function() {
    this.setSize(this.content.scrollWidth, this.height, true);
  },
  
  // Brings window to front
  toFront: function() {
    if (this.element.style.zIndex < Windows.maxZIndex)  
      this.setZIndex(Windows.maxZIndex + 1);
    if (this.iefix) 
      this._fixIEOverlapping(); 
  },
   
  getBounds: function(insideOnly) {
    if (! this.width || !this.height || !this.visible)  
      this.computeBounds();
    var w = this.width;
    var h = this.height;

    if (!insideOnly) {
      w += this.widthW + this.widthE;
      h += this.heightN + this.heightS;
    }
    var bounds = Object.extend(this.getLocation(), {width: w + "px", height: h + "px"});
    return bounds;
  },
      
  computeBounds: function() {
     if (! this.width || !this.height) {
      var size = WindowUtilities._computeSize(this.content.innerHTML, this.content.id, this.width, this.height, 0, this.options.className)
      if (this.height)
        this.width = size + 5
      else
        this.height = size + 5
    }

    this.setSize(this.width, this.height);
    if (this.centered)
      this._center(this.centerTop, this.centerLeft);    
  },
  
  // Displays window modal state or not
  show: function(modal) {
    this.visible = true;
    if (modal) {
      // Hack for Safari !!
      if (typeof this.overlayOpacity == "undefined") {
        var that = this;
        setTimeout(function() {that.show(modal)}, 10);
        return;
      }
      Windows.addModalWindow(this);
      
      this.modal = true;      
      this.setZIndex(Windows.maxZIndex + 1);
      Windows.unsetOverflow(this);
    }
    else    
      if (!this.element.style.zIndex) 
        this.setZIndex(Windows.maxZIndex + 1);        
      
    // To restore overflow if need be
    if (this.oldStyle)
      this.getContent().setStyle({overflow: this.oldStyle});
      
    this.computeBounds();
    
    this._notify("onBeforeShow");   
    if (this.options.showEffect != Element.show && this.options.showEffectOptions)
      this.options.showEffect(this.element, this.options.showEffectOptions);  
    else
      this.options.showEffect(this.element);  
      
    this._checkIEOverlapping();
    WindowUtilities.focusedWindow = this
    this._notify("onShow");   
  },
  
  // Displays window modal state or not at the center of the page
  showCenter: function(modal, top, left) {
    this.centered = true;
    this.centerTop = top;
    this.centerLeft = left;

    this.show(modal);
  },
  
  isVisible: function() {
    return this.visible;
  },
  
  _center: function(top, left) {    
    var windowScroll = WindowUtilities.getWindowScroll(this.options.parent);    
    var pageSize = WindowUtilities.getPageSize(this.options.parent);    
    if (typeof top == "undefined")
      top = (pageSize.windowHeight - (this.height + this.heightN + this.heightS))/2;
    top += windowScroll.top
    
    if (typeof left == "undefined")
      left = (pageSize.windowWidth - (this.width + this.widthW + this.widthE))/2;
    left += windowScroll.left      
    this.setLocation(top, left);
    this.toFront();
  },
  
  _recenter: function(event) {     
    if (this.centered) {
      var pageSize = WindowUtilities.getPageSize(this.options.parent);
      var windowScroll = WindowUtilities.getWindowScroll(this.options.parent);    

      // Check for this stupid IE that sends dumb events
      if (this.pageSize && this.pageSize.windowWidth == pageSize.windowWidth && this.pageSize.windowHeight == pageSize.windowHeight && 
          this.windowScroll.left == windowScroll.left && this.windowScroll.top == windowScroll.top) 
        return;
      this.pageSize = pageSize;
      this.windowScroll = windowScroll;
      // set height of Overlay to take up whole page and show
      if ($('overlay_modal')) 
        $('overlay_modal').setStyle({height: (pageSize.pageHeight + 'px')});
      
      if (this.options.recenterAuto)
        this._center(this.centerTop, this.centerLeft);    
    }
  },
  
  // Hides window
  hide: function() {
    this.visible = false;
    if (this.modal) {
      Windows.removeModalWindow(this);
      Windows.resetOverflow();
    }
    // To avoid bug on scrolling bar
    this.oldStyle = this.getContent().getStyle('overflow') || "auto"
    this.getContent().setStyle({overflow: "hidden"});

    this.options.hideEffect(this.element, this.options.hideEffectOptions);  

     if(this.iefix) 
      this.iefix.hide();

    if (!this.doNotNotifyHide)
      this._notify("onHide");
  },

  close: function() {
    // Asks closeCallback if exists
    if (this.visible) {
      if (this.options.closeCallback && ! this.options.closeCallback(this)) 
        return;

      if (this.options.destroyOnClose) {
        var destroyFunc = this.destroy.bind(this);
        if (this.options.hideEffectOptions.afterFinish) {
          var func = this.options.hideEffectOptions.afterFinish;
          this.options.hideEffectOptions.afterFinish = function() {func();destroyFunc() }
        }
        else 
          this.options.hideEffectOptions.afterFinish = function() {destroyFunc() }
      }
      Windows.updateFocusedWindow();
      
      this.doNotNotifyHide = true;
      this.hide();
      this.doNotNotifyHide = false;
      this._notify("onClose");
    }
  },
  
  minimize: function() {
    if (this.resizing)
      return;
    
    var r2 = $(this.getId() + "_row2");
    
    if (!this.minimized) {
      this.minimized = true;

      var dh = r2.getDimensions().height;
      this.r2Height = dh;
      var h  = this.element.getHeight() - dh;

      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
        new Effect.ResizeWindow(this, null, null, null, this.height -dh, {duration: Window.resizeEffectDuration});
      } else  {
        this.height -= dh;
        this.element.setStyle({height: h + "px"});
        r2.hide();
      }

      if (! this.useTop) {
        var bottom = parseFloat(this.element.getStyle('bottom'));
        this.element.setStyle({bottom: (bottom + dh) + 'px'});
      }
    } 
    else {      
      this.minimized = false;
      
      var dh = this.r2Height;
      this.r2Height = null;
      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
        new Effect.ResizeWindow(this, null, null, null, this.height + dh, {duration: Window.resizeEffectDuration});
      }
      else {
        var h  = this.element.getHeight() + dh;
        this.height += dh;
        this.element.setStyle({height: h + "px"})
        r2.show();
      }
      if (! this.useTop) {
        var bottom = parseFloat(this.element.getStyle('bottom'));
        this.element.setStyle({bottom: (bottom - dh) + 'px'});
      }
      this.toFront();
    }
    this._notify("onMinimize");
    
    // Store new location/size if need be
    this._saveCookie()
  },
  
  maximize: function() {
    if (this.isMinimized() || this.resizing)
      return;
  
    if (Prototype.Browser.IE && this.heightN == 0)
      this._getWindowBorderSize();
      
    if (this.storedLocation != null) {
      this._restoreLocation();
      if(this.iefix) 
        this.iefix.hide();
    }
    else {
      this._storeLocation();
      Windows.unsetOverflow(this);
      
      var windowScroll = WindowUtilities.getWindowScroll(this.options.parent);
      var pageSize = WindowUtilities.getPageSize(this.options.parent);    
      var left = windowScroll.left;
      var top = windowScroll.top;
      
      if (this.options.parent != document.body) {
        windowScroll =  {top:0, left:0, bottom:0, right:0};
        var dim = this.options.parent.getDimensions();
        pageSize.windowWidth = dim.width;
        pageSize.windowHeight = dim.height;
        top = 0; 
        left = 0;
      }
      
      if (this.constraint) {
        pageSize.windowWidth -= Math.max(0, this.constraintPad.left) + Math.max(0, this.constraintPad.right);
        pageSize.windowHeight -= Math.max(0, this.constraintPad.top) + Math.max(0, this.constraintPad.bottom);
        left +=  Math.max(0, this.constraintPad.left);
        top +=  Math.max(0, this.constraintPad.top);
      }
      
      var width = pageSize.windowWidth - this.widthW - this.widthE;
      var height= pageSize.windowHeight - this.heightN - this.heightS;

      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
        new Effect.ResizeWindow(this, top, left, width, height, {duration: Window.resizeEffectDuration});
      }
      else {
        this.setSize(width, height);
        this.element.setStyle(this.useLeft ? {left: left} : {right: left});
        this.element.setStyle(this.useTop ? {top: top} : {bottom: top});
      }
        
      this.toFront();
      if (this.iefix) 
        this._fixIEOverlapping(); 
    }
    this._notify("onMaximize");

    // Store new location/size if need be
    this._saveCookie()
  },
  
  isMinimized: function() {
    return this.minimized;
  },
  
  isMaximized: function() {
    return (this.storedLocation != null);
  },
  
  setOpacity: function(opacity) {
    if (Element.setOpacity)
      Element.setOpacity(this.element, opacity);
  },
  
  setZIndex: function(zindex) {
    this.element.setStyle({zIndex: zindex});
    Windows.updateZindex(zindex, this);
  },

  setTitle: function(newTitle) {
    if (!newTitle || newTitle == "") 
      newTitle = "&nbsp;";
      
    Element.update(this.element.id + '_top', newTitle);
  },
   
  getTitle: function() {
    return $(this.element.id + '_top').innerHTML;
  },
  
  setStatusBar: function(element) {
    var statusBar = $(this.getId() + "_bottom");

    if (typeof(element) == "object") {
      if (this.bottombar.firstChild)
        this.bottombar.replaceChild(element, this.bottombar.firstChild);
      else
        this.bottombar.appendChild(element);
    }
    else
      this.bottombar.innerHTML = element;
  },

  _checkIEOverlapping: function() {
    if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (navigator.userAgent.indexOf('Opera')<0) && (this.element.getStyle('position')=='absolute')) {
        new Insertion.After(this.element.id, '<iframe id="' + this.element.id + '_iefix" '+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
        this.iefix = $(this.element.id+'_iefix');
    }
    if(this.iefix) 
      setTimeout(this._fixIEOverlapping.bind(this), 50);
  },

  _fixIEOverlapping: function() {
      Position.clone(this.element, this.iefix);
      this.iefix.style.zIndex = this.element.style.zIndex - 1;
      this.iefix.show();
  },
  
  _getWindowBorderSize: function(event) {
    // Hack to get real window border size!!
    var div = this._createHiddenDiv(this.options.className + "_n")
    this.heightN = Element.getDimensions(div).height;    
    div.parentNode.removeChild(div)

    var div = this._createHiddenDiv(this.options.className + "_s")
    this.heightS = Element.getDimensions(div).height;    
    div.parentNode.removeChild(div)

    var div = this._createHiddenDiv(this.options.className + "_e")
    this.widthE = Element.getDimensions(div).width;    
    div.parentNode.removeChild(div)

    var div = this._createHiddenDiv(this.options.className + "_w")
    this.widthW = Element.getDimensions(div).width;
    div.parentNode.removeChild(div);
    
    var div = document.createElement("div");
    div.className = "overlay_" + this.options.className ;
    document.body.appendChild(div);
    //alert("no timeout:\nopacity: " + div.getStyle("opacity") + "\nwidth: " + document.defaultView.getComputedStyle(div, null).width);
    var that = this;
    
    // Workaround for Safari!!
    setTimeout(function() {that.overlayOpacity = ($(div).getStyle("opacity")); div.parentNode.removeChild(div);}, 10);
    
    // Workaround for IE!!
    if (Prototype.Browser.IE) {
      this.heightS = $(this.getId() +"_row3").getDimensions().height;
      this.heightN = $(this.getId() +"_row1").getDimensions().height;
    }

    // Safari size fix
    if (Prototype.Browser.WebKit && Prototype.Browser.WebKitVersion < 420)
      this.setSize(this.width, this.height);
    if (this.doMaximize)
      this.maximize();
    if (this.doMinimize)
      this.minimize();
  },
 
  _createHiddenDiv: function(className) {
    var objBody = document.body;
    var win = document.createElement("div");
    win.setAttribute('id', this.element.id+ "_tmp");
    win.className = className;
    win.style.display = 'none';
    win.innerHTML = '';
    objBody.insertBefore(win, objBody.firstChild);
    return win;
  },
  
  _storeLocation: function() {
    if (this.storedLocation == null) {
      this.storedLocation = {useTop: this.useTop, useLeft: this.useLeft, 
                             top: this.element.getStyle('top'), bottom: this.element.getStyle('bottom'),
                             left: this.element.getStyle('left'), right: this.element.getStyle('right'),
                             width: this.width, height: this.height };
    }
  },
  
  _restoreLocation: function() {
    if (this.storedLocation != null) {
      this.useLeft = this.storedLocation.useLeft;
      this.useTop = this.storedLocation.useTop;
      
      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow)
        new Effect.ResizeWindow(this, this.storedLocation.top, this.storedLocation.left, this.storedLocation.width, this.storedLocation.height, {duration: Window.resizeEffectDuration});
      else {
        this.element.setStyle(this.useLeft ? {left: this.storedLocation.left} : {right: this.storedLocation.right});
        this.element.setStyle(this.useTop ? {top: this.storedLocation.top} : {bottom: this.storedLocation.bottom});
        this.setSize(this.storedLocation.width, this.storedLocation.height);
      }
      
      Windows.resetOverflow();
      this._removeStoreLocation();
    }
  },
  
  _removeStoreLocation: function() {
    this.storedLocation = null;
  },
  
  _saveCookie: function() {
    if (this.cookie) {
      var value = "";
      if (this.useLeft)
        value += "l:" +  (this.storedLocation ? this.storedLocation.left : this.element.getStyle('left'))
      else
        value += "r:" + (this.storedLocation ? this.storedLocation.right : this.element.getStyle('right'))
      if (this.useTop)
        value += ",t:" + (this.storedLocation ? this.storedLocation.top : this.element.getStyle('top'))
      else
        value += ",b:" + (this.storedLocation ? this.storedLocation.bottom :this.element.getStyle('bottom'))
        
      value += "," + (this.storedLocation ? this.storedLocation.width : this.width);
      value += "," + (this.storedLocation ? this.storedLocation.height : this.height);
      value += "," + this.isMinimized();
      value += "," + this.isMaximized();
      WindowUtilities.setCookie(value, this.cookie)
    }
  },
  
  _createWiredElement: function() {
    if (! this.wiredElement) {
      if (Prototype.Browser.IE)
        this._getWindowBorderSize();
      var div = document.createElement("div");
      div.className = "wired_frame " + this.options.className + "_wired_frame";
      
      div.style.position = 'absolute';
      this.options.parent.insertBefore(div, this.options.parent.firstChild);
      this.wiredElement = $(div);
    }
    if (this.useLeft) 
      this.wiredElement.setStyle({left: this.element.getStyle('left')});
    else 
      this.wiredElement.setStyle({right: this.element.getStyle('right')});
      
    if (this.useTop) 
      this.wiredElement.setStyle({top: this.element.getStyle('top')});
    else 
      this.wiredElement.setStyle({bottom: this.element.getStyle('bottom')});

    var dim = this.element.getDimensions();
    this.wiredElement.setStyle({width: dim.width + "px", height: dim.height +"px"});

    this.wiredElement.setStyle({zIndex: Windows.maxZIndex+30});
    return this.wiredElement;
  },
  
  _hideWiredElement: function() {
    if (! this.wiredElement || ! this.currentDrag)
      return;
    if (this.currentDrag == this.element) 
      this.currentDrag = null;
    else {
      if (this.useLeft) 
        this.element.setStyle({left: this.currentDrag.getStyle('left')});
      else 
        this.element.setStyle({right: this.currentDrag.getStyle('right')});

      if (this.useTop) 
        this.element.setStyle({top: this.currentDrag.getStyle('top')});
      else 
        this.element.setStyle({bottom: this.currentDrag.getStyle('bottom')});

      this.currentDrag.hide();
      this.currentDrag = null;
      if (this.doResize)
        this.setSize(this.width, this.height);
    } 
  },
  
  _notify: function(eventName) {
    if (this.options[eventName])
      this.options[eventName](this);
    else
      Windows.notify(eventName, this);
  }
};

// Windows containers, register all page windows
var Windows = {
  windows: [],
  modalWindows: [],
  observers: [],
  focusedWindow: null,
  maxZIndex: 0,
  overlayShowEffectOptions: {duration: 0.5},
  overlayHideEffectOptions: {duration: 0.5},

  addObserver: function(observer) {
    this.removeObserver(observer);
    this.observers.push(observer);
  },
  
  removeObserver: function(observer) {  
    this.observers = this.observers.reject( function(o) { return o==observer });
  },
  
  // onDestroy onStartResize onStartMove onResize onMove onEndResize onEndMove onFocus onBlur onBeforeShow onShow onHide onMinimize onMaximize onClose
  notify: function(eventName, win) {  
    this.observers.each( function(o) {if(o[eventName]) o[eventName](eventName, win);});
  },

  // Gets window from its id
  getWindow: function(id) {
    return this.windows.detect(function(d) { return d.getId() ==id });
  },

  // Gets the last focused window
  getFocusedWindow: function() {
    return this.focusedWindow;
  },

  updateFocusedWindow: function() {
    this.focusedWindow = this.windows.length >=2 ? this.windows[this.windows.length-2] : null;    
  },
  
  // Registers a new window (called by Windows constructor)
  register: function(win) {
    this.windows.push(win);
  },
    
  // Add a modal window in the stack
  addModalWindow: function(win) {
    // Disable screen if first modal window
    if (this.modalWindows.length == 0) {
      WindowUtilities.disableScreen(win.options.className, 'overlay_modal', win.overlayOpacity, win.getId(), win.options.parent);
    }
    else {
      // Move overlay over all windows
      if (Window.keepMultiModalWindow) {
        $('overlay_modal').style.zIndex = Windows.maxZIndex + 1;
        Windows.maxZIndex += 1;
        WindowUtilities._hideSelect(this.modalWindows.last().getId());
      }
      // Hide current modal window
      else
        this.modalWindows.last().element.hide();
      // Fucking IE select issue
      WindowUtilities._showSelect(win.getId());
    }      
    this.modalWindows.push(win);    
  },
  
  removeModalWindow: function(win) {
    this.modalWindows.pop();
    
    // No more modal windows
    if (this.modalWindows.length == 0)
      WindowUtilities.enableScreen();     
    else {
      if (Window.keepMultiModalWindow) {
        this.modalWindows.last().toFront();
        WindowUtilities._showSelect(this.modalWindows.last().getId());        
      }
      else
        this.modalWindows.last().element.show();
    }
  },
  
  // Registers a new window (called by Windows constructor)
  register: function(win) {
    this.windows.push(win);
  },
  
  // Unregisters a window (called by Windows destructor)
  unregister: function(win) {
    this.windows = this.windows.reject(function(d) { return d==win });
  }, 
  
  // Closes all windows
  closeAll: function() {  
    this.windows.each( function(w) {Windows.close(w.getId())} );
  },
  
  closeAllModalWindows: function() {
    WindowUtilities.enableScreen();     
    this.modalWindows.each( function(win) {if (win) win.close()});    
  },

  // Minimizes a window with its id
  minimize: function(id, event) {
    var win = this.getWindow(id)
    if (win && win.visible)
      win.minimize();
    Event.stop(event);
  },
  
  // Maximizes a window with its id
  maximize: function(id, event) {
    var win = this.getWindow(id)
    if (win && win.visible)
      win.maximize();
    Event.stop(event);
  },

  // Closes a window with its id
  close: function(id, event) {
    var win = this.getWindow(id);
    if (win) 
      win.close();
    if (event)
      Event.stop(event);
  },
  
  blur: function(id) {
    var win = this.getWindow(id);  
    if (!win)
      return;
    if (win.options.blurClassName)
      win.changeClassName(win.options.blurClassName);
    if (this.focusedWindow == win)  
      this.focusedWindow = null;
    win._notify("onBlur");  
  },
  
  focus: function(id) {
    var win = this.getWindow(id);  
    if (!win)
      return;       
    if (this.focusedWindow)
      this.blur(this.focusedWindow.getId())

    if (win.options.focusClassName)
      win.changeClassName(win.options.focusClassName);  
    this.focusedWindow = win;
    win._notify("onFocus");
  },
  
  unsetOverflow: function(except) {    
    this.windows.each(function(d) { d.oldOverflow = d.getContent().getStyle("overflow") || "auto" ; d.getContent().setStyle({overflow: "hidden"}) });
    if (except && except.oldOverflow)
      except.getContent().setStyle({overflow: except.oldOverflow});
  },

  resetOverflow: function() {
    this.windows.each(function(d) { if (d.oldOverflow) d.getContent().setStyle({overflow: d.oldOverflow}) });
  },

  updateZindex: function(zindex, win) { 
    if (zindex > this.maxZIndex) {   
      this.maxZIndex = zindex;    
      if (this.focusedWindow) 
        this.blur(this.focusedWindow.getId())
    }
    this.focusedWindow = win;
    if (this.focusedWindow) 
      this.focus(this.focusedWindow.getId())
  }
};

var Dialog = {
  dialogId: null,
  onCompleteFunc: null,
  callFunc: null, 
  parameters: null, 
    
  confirm: function(content, parameters) {
    // Get Ajax return before
    if (content && typeof content != "string") {
      Dialog._runAjaxRequest(content, parameters, Dialog.confirm);
      return 
    }
    content = content || "";
    
    parameters = parameters || {};
    var okLabel = parameters.okLabel ? parameters.okLabel : "Ok";
    var cancelLabel = parameters.cancelLabel ? parameters.cancelLabel : "Cancel";

    // Backward compatibility
    parameters = Object.extend(parameters, parameters.windowParameters || {});
    parameters.windowParameters = parameters.windowParameters || {};

    parameters.className = parameters.className || "alert";

    var okButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " ok_button'" 
    var cancelButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " cancel_button'" 
    var content = "\
      <div class='" + parameters.className + "_message'>" + content  + "</div>\
        <div class='" + parameters.className + "_buttons'>\
          <input type='button' value='" + okLabel + "' onclick='Dialog.okCallback()' " + okButtonClass + "/>\
          <input type='button' value='" + cancelLabel + "' onclick='Dialog.cancelCallback()' " + cancelButtonClass + "/>\
        </div>\
    ";
    return this._openDialog(content, parameters)
  },
  
  alert: function(content, parameters) {
    // Get Ajax return before
    if (content && typeof content != "string") {
      Dialog._runAjaxRequest(content, parameters, Dialog.alert);
      return 
    }
    content = content || "";
    
    parameters = parameters || {};
    var okLabel = parameters.okLabel ? parameters.okLabel : "Ok";

    // Backward compatibility    
    parameters = Object.extend(parameters, parameters.windowParameters || {});
    parameters.windowParameters = parameters.windowParameters || {};
    
    parameters.className = parameters.className || "alert";
    
    var okButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " ok_button'" 
    var content = "\
      <div class='" + parameters.className + "_message'>" + content  + "</div>\
        <div class='" + parameters.className + "_buttons'>\
          <input type='button' value='" + okLabel + "' onclick='Dialog.okCallback()' " + okButtonClass + "/>\
        </div>";                  
    return this._openDialog(content, parameters)
  },
  
  info: function(content, parameters) {   
    // Get Ajax return before
    if (content && typeof content != "string") {
      Dialog._runAjaxRequest(content, parameters, Dialog.info);
      return 
    }
    content = content || "";
     
    // Backward compatibility
    parameters = parameters || {};
    parameters = Object.extend(parameters, parameters.windowParameters || {});
    parameters.windowParameters = parameters.windowParameters || {};
    
    parameters.className = parameters.className || "alert";
    
    var content = "<div id='modal_dialog_message' class='" + parameters.className + "_message'>" + content  + "</div>";
    if (parameters.showProgress)
      content += "<div id='modal_dialog_progress' class='" + parameters.className + "_progress'>  </div>";

    parameters.ok = null;
    parameters.cancel = null;
    
    return this._openDialog(content, parameters)
  },
  
  setInfoMessage: function(message) {
    $('modal_dialog_message').update(message);
  },
  
  closeInfo: function() {
    Windows.close(this.dialogId);
  },
  
  _openDialog: function(content, parameters) {
    var className = parameters.className;
    
    if (! parameters.height && ! parameters.width) {
      parameters.width = WindowUtilities.getPageSize(parameters.options.parent || document.body).pageWidth / 2;
    }
    if (parameters.id)
      this.dialogId = parameters.id;
    else { 
      var t = new Date();
      this.dialogId = 'modal_dialog_' + t.getTime();
      parameters.id = this.dialogId;
    }

    // compute height or width if need be
    if (! parameters.height || ! parameters.width) {
      var size = WindowUtilities._computeSize(content, this.dialogId, parameters.width, parameters.height, 5, className)
      if (parameters.height)
        parameters.width = size + 5
      else
        parameters.height = size + 5
    }
    parameters.effectOptions = parameters.effectOptions ;
    parameters.resizable   = parameters.resizable || false;
    parameters.minimizable = parameters.minimizable || false;
    parameters.maximizable = parameters.maximizable ||  false;
    parameters.draggable   = parameters.draggable || false;
    parameters.closable    = parameters.closable || false;
    
    var win = new Window(parameters);
    win.getContent().innerHTML = content;
    
    win.showCenter(true, parameters.top, parameters.left);  
    win.setDestroyOnClose();
    
    win.cancelCallback = parameters.onCancel || parameters.cancel; 
    win.okCallback = parameters.onOk || parameters.ok;
    
    return win;    
  },
  
  _getAjaxContent: function(originalRequest)  {
      Dialog.callFunc(originalRequest.responseText, Dialog.parameters)
  },
  
  _runAjaxRequest: function(message, parameters, callFunc) {
    if (message.options == null)
      message.options = {}  
    Dialog.onCompleteFunc = message.options.onComplete;
    Dialog.parameters = parameters;
    Dialog.callFunc = callFunc;
    
    message.options.onComplete = Dialog._getAjaxContent;
    new Ajax.Request(message.url, message.options);
  },
  
  okCallback: function() {
    var win = Windows.focusedWindow;
    if (!win.okCallback || win.okCallback(win)) {
      // Remove onclick on button
      $$("#" + win.getId()+" input").each(function(element) {element.onclick=null;})
      win.close();
    }
  },

  cancelCallback: function() {
    var win = Windows.focusedWindow;
    // Remove onclick on button
    $$("#" + win.getId()+" input").each(function(element) {element.onclick=null})
    win.close();
    if (win.cancelCallback)
      win.cancelCallback(win);
  }
}
/*
  Based on Lightbox JS: Fullsize Image Overlays 
  by Lokesh Dhakar - http://www.huddletogether.com

  For more information on this script, visit:
  http://huddletogether.com/projects/lightbox/

  Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
  (basically, do anything you want, just leave my name and link)
*/

if (Prototype.Browser.WebKit) {
  var array = navigator.userAgent.match(new RegExp(/AppleWebKit\/([\d\.\+]*)/));
  Prototype.Browser.WebKitVersion = parseFloat(array[1]);
}

var WindowUtilities = {  
  // From dragdrop.js
  getWindowScroll: function(parent) {
    var T, L, W, H;
    parent = parent || document.body;              
    if (parent != document.body) {
      T = parent.scrollTop;
      L = parent.scrollLeft;
      W = parent.scrollWidth;
      H = parent.scrollHeight;
    } 
    else {
      var w = window;
      with (w.document) {
        if (w.document.documentElement && documentElement.scrollTop) {
          T = documentElement.scrollTop;
          L = documentElement.scrollLeft;
        } else if (w.document.body) {
          T = body.scrollTop;
          L = body.scrollLeft;
        }
        if (w.innerWidth) {
          W = w.innerWidth;
          H = w.innerHeight;
        } else if (w.document.documentElement && documentElement.clientWidth) {
          W = documentElement.clientWidth;
          H = documentElement.clientHeight;
        } else {
          W = body.offsetWidth;
          H = body.offsetHeight
        }
      }
    }
    return { top: T, left: L, width: W, height: H };
  }, 
  //
  // getPageSize()
  // Returns array with page width, height and window width, height
  // Core code from - quirksmode.org
  // Edit for Firefox by pHaez
  //
  getPageSize: function(parent){
    parent = parent || document.body;              
    var windowWidth, windowHeight;
    var pageHeight, pageWidth;
    if (parent != document.body) {
      windowWidth = parent.getWidth();
      windowHeight = parent.getHeight();                                
      pageWidth = parent.scrollWidth;
      pageHeight = parent.scrollHeight;                                
    } 
    else {
      var xScroll, yScroll;

      if (window.innerHeight && window.scrollMaxY) {  
        xScroll = document.body.scrollWidth;
        yScroll = window.innerHeight + window.scrollMaxY;
      } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
        xScroll = document.body.scrollWidth;
        yScroll = document.body.scrollHeight;
      } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
        xScroll = document.body.offsetWidth;
        yScroll = document.body.offsetHeight;
      }


      if (self.innerHeight) {  // all except Explorer
        windowWidth = self.innerWidth;
        windowHeight = self.innerHeight;
      } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
        windowWidth = document.documentElement.clientWidth;
        windowHeight = document.documentElement.clientHeight;
      } else if (document.body) { // other Explorers
        windowWidth = document.body.clientWidth;
        windowHeight = document.body.clientHeight;
      }  

      // for small pages with total height less then height of the viewport
      if(yScroll < windowHeight){
        pageHeight = windowHeight;
      } else { 
        pageHeight = yScroll;
      }

      // for small pages with total width less then width of the viewport
      if(xScroll < windowWidth){  
        pageWidth = windowWidth;
      } else {
        pageWidth = xScroll;
      }
    }             
    return {pageWidth: pageWidth ,pageHeight: pageHeight , windowWidth: windowWidth, windowHeight: windowHeight};
  },

  disableScreen: function(className, overlayId, overlayOpacity, contentId, parent) {
    WindowUtilities.initLightbox(overlayId, className, function() {this._disableScreen(className, overlayId, overlayOpacity, contentId)}.bind(this), parent || document.body);
  },

  _disableScreen: function(className, overlayId, overlayOpacity, contentId) {
    // prep objects
    var objOverlay = $(overlayId);

    var pageSize = WindowUtilities.getPageSize(objOverlay.parentNode);

    // Hide select boxes as they will 'peek' through the image in IE, store old value
    if (contentId && Prototype.Browser.IE) {
      WindowUtilities._hideSelect();
      WindowUtilities._showSelect(contentId);
    }  
  
    // set height of Overlay to take up whole page and show
    objOverlay.style.height = (pageSize.pageHeight + 'px');
    objOverlay.style.display = 'none'; 
    if (overlayId == "overlay_modal" && Window.hasEffectLib && Windows.overlayShowEffectOptions) {
      objOverlay.overlayOpacity = overlayOpacity;
      new Effect.Appear(objOverlay, Object.extend({from: 0, to: overlayOpacity}, Windows.overlayShowEffectOptions));
    }
    else
      objOverlay.style.display = "block";
  },
  
  enableScreen: function(id) {
    id = id || 'overlay_modal';
    var objOverlay =  $(id);
    if (objOverlay) {
      // hide lightbox and overlay
      if (id == "overlay_modal" && Window.hasEffectLib && Windows.overlayHideEffectOptions)
        new Effect.Fade(objOverlay, Object.extend({from: objOverlay.overlayOpacity, to:0}, Windows.overlayHideEffectOptions));
      else {
        objOverlay.style.display = 'none';
        objOverlay.parentNode.removeChild(objOverlay);
      }
      
      // make select boxes visible using old value
      if (id != "__invisible__") 
        WindowUtilities._showSelect();
    }
  },

  _hideSelect: function(id) {
    if (Prototype.Browser.IE) {
      id = id ==  null ? "" : "#" + id + " ";
      $$(id + 'select').each(function(element) {
        if (! WindowUtilities.isDefined(element.oldVisibility)) {
          element.oldVisibility = element.style.visibility ? element.style.visibility : "visible";
          element.style.visibility = "hidden";
        }
      });
    }
  },
  
  _showSelect: function(id) {
    if (Prototype.Browser.IE) {
      id = id ==  null ? "" : "#" + id + " ";
      $$(id + 'select').each(function(element) {
        if (WindowUtilities.isDefined(element.oldVisibility)) {
          // Why?? Ask IE
          try {
            element.style.visibility = element.oldVisibility;
          } catch(e) {
            element.style.visibility = "visible";
          }
          element.oldVisibility = null;
        }
        else {
          if (element.style.visibility)
            element.style.visibility = "visible";
        }
      });
    }
  },

  isDefined: function(object) {
    return typeof(object) != "undefined" && object != null;
  },
  
  // initLightbox()
  // Function runs on window load, going through link tags looking for rel="lightbox".
  // These links receive onclick events that enable the lightbox display for their targets.
  // The function also inserts html markup at the top of the page which will be used as a
  // container for the overlay pattern and the inline image.
  initLightbox: function(id, className, doneHandler, parent) {
    // Already done, just update zIndex
    if ($(id)) {
      Element.setStyle(id, {zIndex: Windows.maxZIndex + 1});
      Windows.maxZIndex++;
      doneHandler();
    }
    // create overlay div and hardcode some functional styles (aesthetic styles are in CSS file)
    else {
      var objOverlay = document.createElement("div");
      objOverlay.setAttribute('id', id);
      objOverlay.className = "overlay_" + className
      objOverlay.style.display = 'none';
      objOverlay.style.position = 'absolute';
      objOverlay.style.top = '0';
      objOverlay.style.left = '0';
      objOverlay.style.zIndex = Windows.maxZIndex + 1;
      Windows.maxZIndex++;
      objOverlay.style.width = '100%';
      parent.insertBefore(objOverlay, parent.firstChild);
      if (Prototype.Browser.WebKit && id == "overlay_modal") {
        setTimeout(function() {doneHandler()}, 10);
      }
      else
        doneHandler();
    }    
  },
  
  setCookie: function(value, parameters) {
    document.cookie= parameters[0] + "=" + escape(value) +
      ((parameters[1]) ? "; expires=" + parameters[1].toGMTString() : "") +
      ((parameters[2]) ? "; path=" + parameters[2] : "") +
      ((parameters[3]) ? "; domain=" + parameters[3] : "") +
      ((parameters[4]) ? "; secure" : "");
  },

  getCookie: function(name) {
    var dc = document.cookie;
    var prefix = name + "=";
    var begin = dc.indexOf("; " + prefix);
    if (begin == -1) {
      begin = dc.indexOf(prefix);
      if (begin != 0) return null;
    } else {
      begin += 2;
    }
    var end = document.cookie.indexOf(";", begin);
    if (end == -1) {
      end = dc.length;
    }
    return unescape(dc.substring(begin + prefix.length, end));
  },
    
  _computeSize: function(content, id, width, height, margin, className) {
    var objBody = document.body;
    var tmpObj = document.createElement("div");
    tmpObj.setAttribute('id', id);
    tmpObj.className = className + "_content";

    if (height)
      tmpObj.style.height = height + "px"
    else
      tmpObj.style.width = width + "px"
  
    tmpObj.style.position = 'absolute';
    tmpObj.style.top = '0';
    tmpObj.style.left = '0';
    tmpObj.style.display = 'none';

    tmpObj.innerHTML = content;
    objBody.insertBefore(tmpObj, objBody.firstChild);

    var size;
    if (height)
      size = $(tmpObj).getDimensions().width + margin;
    else
      size = $(tmpObj).getDimensions().height + margin;
    objBody.removeChild(tmpObj);
    return size;
  }  
}
 
;

// simpledisplayitem.js
function simpleDisplayImage(domNode, m) {
  var r = Builder.node('img', {
    className: 'item',
    src: m.display_item_url
  })
  domNode.appendChild(r)
  return r
}

function simpleDisplayFileDownloadGraphics(m) {
  return TN("download")
}

function simpleDisplayFile(domNode, m) {
  var r = Builder.node('a', {
    className: 'item',
    href: m.full_item_url
  }, [simpleDisplayFileDownloadGraphics(m)])
  domNode.appendChild(r)
  return r
}

function simpleDisplayGetSwfObject(flv,type){       
  var so = new SWFObject(
    '/core-services/myplayer.swf?'+Math.random(),
    "mymovie",
    "100%",
    "100%",
    "7",
    "#FFFFFF"
  );
  so.addParam('allowScriptAccess', 'always'); 
  var flvpath = flv;  
  so.addParam('flashVars', 'v='+flvpath+'&d=true&t='+type);   
  so.addParam('allowFullscreen', "true");  
  return so;   
} 

function simpleFlashDisplayCommon(domNode, m) {
  simpleDisplayGetSwfObject(m.flv_url, "movie").write(domNode)
}

function simpleDisplayAudio(domNode, m) { simpleFlashDisplayCommon(domNode, m) }
function simpleDisplayVideo(domNode, m) { simpleFlashDisplayCommon(domNode, m) }
function simpleDisplayArticle(domNode, m) { 
  var n = Builder.node('div', {className: 'article'}, [])
  n.innerHTML = m.body ? m.body : "<b>Nothing here.</b>" 
  domNode.appendChild(n)
}

function __contentFunction(c) { return javaScript["simpleDisplay" + c] }
function mimeTypeCategory(m) {
  if (m.type.toLowerCase() == "article") { return "Article" }
  var c = m.content_type.gsub(/\/.*/, "").capitalize()
  var f = __contentFunction(c)
  return f ? c : "File"
}

function displayItemFunction(m) {
  return __contentFunction(mimeTypeCategory(m))
}

function simpleDisplayItem(domNode, m) {
  (displayItemFunction(m))(domNode, m)
}

function displayItemSimple(m) {
  document.body.innerHTML = ""
  simpleDisplayItem(document.body, m)
}

function simpleDisplayItemIsSupportedItemType(m) {
  var t = mimeTypeCategory(m)
  return t == "Article" || t == "Audio" || t == "Image" || 
      t == "Video" || t == "File"
}
;

// prototype-window-ext.js
WindowStore = {
  doSetCookie: false,
  cookieName:  "__window_store__",
  expired:     null,
  
  // Init function with two optional parameters
  // - cookieName (default = __window_store__)
  // - expiration date (default 3 years from now)
  init: function(cookieName, expired) {
    WindowStore.cookieName = cookieName || WindowStore.cookieName

    if (! expired) {
      var today = new Date();
      today.setYear(today.getYear()+1903);
      WindowStore.expired = today;
    }
    else
      WindowStore.expired = expired;

    Windows.windows.each(function(win) {
      win.setCookie(win.getId(), WindowStore.expired);
    });

    // Create observer on show/hide events
    var myObserver = {
        onShow: function(eventName, win) {
          WindowStore._saveCookie();
        },
        
        onClose: function(eventName, win) {
          WindowStore._saveCookie();
        },
        
        onHide: function(eventName, win) {
          WindowStore._saveCookie();
        }
    }
    Windows.addObserver(myObserver);

    WindowStore._restoreWindows();
    WindowStore._saveCookie();
  },
  
  show: function(win) {
    eval("var cookie = " + WindowUtilities.getCookie(WindowStore.cookieName));
    if (cookie != null) {
      if (cookie[win.getId()])
        win.show();
    }
    else
      win.show();
  },

  // Function to store windows show/hide status in a cookie 
  _saveCookie: function() {
    if (!doSetCookie)
      return;
    
    var cookieValue = "{";
    Windows.windows.each(function(win) {
      if (cookieValue != "{")
        cookieValue += ","
      cookieValue += win.getId() + ": " + win.isVisible();
    });
    cookieValue += "}"
  
    WindowUtilities.setCookie(cookieValue, [WindowStore.cookieName, WindowStore.expired]);  
  },

  // Function to restore windows show/hide status from a cookie if exists
  _restoreWindows: function() {
    eval("var cookie = " + WindowUtilities.getCookie(WindowStore.cookieName));
    if (cookie != null) {
      doSetCookie = false;
      Windows.windows.each(function(win) {
        if (cookie[win.getId()])
          win.show();
      });
    }
    doSetCookie = true;
  }
}

// Object to set a close key an all windows
WindowCloseKey = {
  keyCode: Event.KEY_ESC,
  
  init: function(keyCode) {
    if (keyCode)
      WindowCloseKey.keyCode = keyCode;
    Event.observe(document, 'keydown', this._closeCurrentWindow.bindAsEventListener(this));
  },
  
  _closeCurrentWindow: function(event) {
    var e = event || window.event
      var characterCode = e.which || e.keyCode;
      var win = Windows.focusedWindow;
    if (characterCode == WindowCloseKey.keyCode && win) {
      if (win.cancelCallback) 
        Dialog.cancelCallback();      
      else if (win.okCallback) 
        Dialog.okCallback();
      else
        Windows.close(Windows.focusedWindow.getId());
    }
  }
} 

WidgetWindow = Class.create();
WidgetWindow.prototype = {
  initialize: function() {
    this.options = Object.extend({
      classSuffix: "",
      contentWidget: null,
      centered: true,
      maximizable: false,
      minimizable: false,
      closable:true,
      showEffect: Element.show,
      hideEffect: Element.hide,
      resizable: false,
      wiredDrag: false,
      modal: true,
      zIndex:8888,
      title:"",
      destroyOnClose: true,
      autoResize: true,
      autoPosition: false
    }, arguments[0] || {})
    this.options.windowHandle = (this.options.centered ? "centered-" : "") + 
        "widget-window" + this.options.classSuffix
    this.window = new Window(this.options.windowHandle, this.options)
  },
  getWindow: function() {
    return this.window
  },
  content: function() {
    var i = this
    return scope("widget-window",  function() {
      var close = null
      var c = table(
        [cell("content", i.options.contentWidget.htmlNode())],
        [cell("close", close = translateButton({id: "close"}))]
      )
      close.onclick = function() {
        i.close()
      }
      return c
    })
  },
  show: function() {
    this.window.setContent(this.content(), this.options.autoResize, 
        this.options.autoPosition)
    this.window.showCenter(this.options.modal)
  },
  close: function() {
    this.window.close()
  }
}

ErrorWidget = Class.create();
ErrorWidget.prototype = {
  defaultOptions: function() {
    return {
      message: null,
      details: null
    }
  },
  makeWidgetContent: function() {
    var i = this;
    var r = [ TN(i.options.message || "<empty>") ]
    if (this.options.details) {
      r.push(BR(), 
        actionLink({
          text: "Details",
          onclick: function() { 
            var d = i.details.style.display
            i.details.style.display = (d == "block" ? "none" : "block")
          }
        }), BR(),
        (this.details = Builder.node("div", {style: "display: none"}, 
          [Builder.node('textarea', {
            cols: 80,
            rows: 20
          }, [TN(i.options.details)])
        ]))
      )
    }
    return r
  }
}
Object.extend(ErrorWidget.prototype, WidgetCommon)
;

// minimizablewindow.js
MinimizableWindow = Class.create()
Object.extend(MinimizableWindow.prototype, Window.prototype)
MinimizableWindow.prototype.origMinimize = MinimizableWindow.prototype.minimize;
MinimizableWindow.prototype.minimize = function() {
  this.hide();
  if (this.options.editEl) {
    this.options.editEl.editor().editableIframe.hide();
  }
  var name = this.topbar
  if (this.topbar.nodeType == Node.ELEMENT_NODE) {
    while (name.firstChild && name.nodeType != Node.TEXT_NODE) {
      var fc = name.firstChild;
      if (fc) name = fc;
    }
  }
  var element = Builder.node("span",{className:"mybox_dock dock"},
    [name.cloneNode(true)]);
  Event.observe(element, 'click', function(event) {
    Event.stop(event);
    this.show();
    if (this.options.editEl) {
      this.options.editEl.editor().editableIframe.show();
    }
    this.toFront();
    Element.hide(element);
    Element.remove(element);
  }.bindAsEventListener(this))
  var dock = this.options.dock || $('title-bar-main');
  dock.appendChild(element);
  this._notify("onMinimize");
}
;

// themedwindow.js
ThemedWindow = Class.create()
ThemedWindow.prototype = {
  adjustOverlay: function() {
    var content = this.content
    this.overlay.style.height = content.style.height
    this.overlay.style.width  = content.style.width
    this.overlay.style.top    = this.topbar.up().offsetHeight+"px"
    var left = (content.up('table').offsetWidth-content.offsetWidth)/2;
    this.overlay.style.left   = Math.ceil(left) + "px"
  },
  changeTheme: function(theme) {
    var instance = this;
    this.loadCSS(theme, true, function(className) {
      instance.changeClassName(className);
      instance.options.theme = theme
    })
  },
  setContentOpacity: function(opacity) {
    Element.setOpacity(this.overlay, opacity)
  }
}
Object.extend(ThemedWindow.prototype, MinimizableWindow.prototype)
ThemedWindow.prototype.loadCSS = function(theme, reload, afterLoad) {
  var instance = this;
  if (!theme.path) {
    theme.path = "/alphacube"
    theme.user = "core"
  }
  var className = ThemedWindow.themeClassName(theme)
  var id = "theme-" + className
  var e  = $(id)
  if (!e || reload) {
    var themeModel = new DisplayModel({user: theme.user,
      path: theme.path, service: "themes"})
    themeModel.addObserver({update: function() {
      var css = "";
      themeModel.getModel().each(function(i) {
        if (i.name.match(/\.css$/)) {
          css += i.content + "\n"
        }
      })
      if (e) Element.remove(e)
      e = Builder.node('style', {id: id, type: 'text/css'})
      $(document.documentElement).down('head').appendChild(e)
      if (Prototype.Browser.IE) {
        // URLs like core.mythem.es/get_file?id=XXX don't work in IE so
        // we have to replace them by URLs on the same server.
        css = localizeURL(css, "g")
        var ss = $A(document.styleSheets).find(function(s) { return s.id == id })
        ss.cssText = css
      }
      else {
        e.innerHTML = css;        
      }
      afterLoad(className);
    }})
  }
  else {
    afterLoad(className);    
  }  
}
ThemedWindow.prototype.orig_initialize = ThemedWindow.prototype.initialize;
// You have to wait until the CSS is loaded before calling anything on
// the window. This is necessary as otherwise the theme won't be displayed
// correctly. If you need a way to check if it's loaded you can chech for
// the loaded flag in the window instance.
ThemedWindow.prototype.initialize = function(options, afterLoad) {
  var instance = this;
  if (!options.theme) {
    options.theme = {}
  }
  if (!options.content_opacity) {
    options.content_opacity = 1.0
  }
  if (options.baseURL) options.baseURL = localizeURL(options.baseURL)
  this.stoppedNotifications = $A([])
  this.loadCSS(options.theme, options.reload, function(className) {
    options.className = className;
    if (options.editCallback) {
      var editLink = Builder.node('div', [
        Builder.node('div',
          {className: className+"_edit_link"},
          [TN("edit")]
        )
      ])
      options.custom_header_div = editLink.innerHTML
    }
    instance.orig_initialize(options);
    instance.overlay = Builder.node('div', {
      style: "position:absolute;overflow:auto;",
      id: instance.element.id + "_overlay"
    })
    instance.element.appendChild(instance.overlay)
    instance.setContentOpacity(options.content_opacity)
    if (options.editCallback) {
      Element.observe(instance.element.down('.'+className+'_edit_link'),
        'click', options.editCallback.bindAsEventListener(this))
    }
    instance.loaded = true;
    afterLoad(instance);
  })
}

ThemedWindow.themeClassName = function(theme) {
  var path = theme.path
  return theme.user + "_" + path.gsub(/^\//,"").gsub(/\//,"-")
}

ThemedWindow.prototype.setOpacity = function(value) {
  var instance = this;
  var buttons = this.element.id + "_buttons"
  var overlay = this.element.id + "_overlay"
  $A(this.element.childNodes).each(function(e) {
    if (e.nodeType == Node.ELEMENT_NODE) {
      e = $(e)
      if (e.id != buttons && e.id != overlay && !e.hasClassName("no_opacity_adjust")) {
        e.setOpacity(value)              
      }
    }
  })
}

ThemedWindow.prototype.getContent = function() {
  return this.overlay
}

ThemedWindow.prototype.origShow = ThemedWindow.prototype.show
ThemedWindow.prototype.show = function(modal) {
  this.origShow(modal)
  var instance = this;
  setTimeout(function() {instance.adjustOverlay()}, 500)
}

ThemedWindow.prototype.origSetURL = ThemedWindow.prototype.setURL
ThemedWindow.prototype.setURL = function(url) {
  // Not an url content, change div to iframe
  if (!this.options.url) {
    this.options.url = url;
    var content= "<iframe frameborder=\"0\" name=\"" +
      this.getId() + "_content_iframe\"  id=\"" + this.getId() +
      "_content_iframe\" src=\"" + url + "\" width=\"" +
      this.options.width + "\" height=\"" + this.options.height +
      "\" allowtransparency=\"true\"> </iframe>";
    this.getContent().innerHTML = content;
  }
  else {
    this.options.url = url;
    this.getContent().down('iframe').src = url;
  }
}

ThemedWindow.prototype.orig_notify = ThemedWindow.prototype._notify
ThemedWindow.prototype._notify = function(eventName) {
  this.orig_notify(eventName)
  switch (eventName) {
    case "onResize":
      this.adjustOverlay()
      if (this.options.url) {
        var iframe = this.overlay.down('iframe')
        iframe.width = parseInt(this.content.style.width)
        iframe.height = parseInt(this.content.style.height)
      }
      break;
    default:
      break;
  }
}
;

// aggregationwidget.js
// Allows for more than one widget to be used in a context where only
// one widget is allowed.
AggregationWidget = Class.create();

AggregationWidget.prototype = {
  defaultOptions: function() {
    return {};
  },
  makeWidgetContent: function() {
    var f = document.createDocumentFragment();
    this.options.contentWidgets.each(function(w) {
      f.appendChild(w.htmlNode());
    })
    return f;
  }
}

Object.extend(AggregationWidget.prototype, WidgetCommon)

;

