commit 18ad148479c5f3b659861b82a4e5c8cd7b4e7302 Author: tonio Date: Tue Mar 24 16:59:18 2009 +0100 Initial import diff --git a/.hgignore b/.hgignore new file mode 100644 index 000000000..178309130 --- /dev/null +++ b/.hgignore @@ -0,0 +1,2 @@ +.*~$ +^[^\.]+\.pyc diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..9ef7ecc00 --- /dev/null +++ b/COPYING @@ -0,0 +1,13 @@ +Copyright (c) 2009, Antoine Nguyen + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..0d4b5a0b8 --- /dev/null +++ b/INSTALL @@ -0,0 +1,29 @@ +Installation +============ + +Requirements +------------ + +- Python version 2.5+ +- Django version 1.0+ (http://docs.djangoproject.com/en/dev/intro/install/#intro-install) + +How to install +-------------- + +Download Mailng's tarball and extract it:: + tar xzf mailng-.tar.gz + +Enter the mailng repository, create a settings.py file (you can use settings-sample.py as a model). Specify database relative informations. You must choose between mysql and postgresql, other types are not supported by major email projects like postfix or dovecot. + +Initialise the database by running the following command:: + python manage.py syncdb + +Create the super admin account (just follow the procedure). + +Testing installation +-------------------- + +As a typical Django application, Mailng can be tested using a simple web server. Just run this command to start it:: + python manage.py runserver + +You will be able to play with mailng by looking at http://:8000/mailng/admin/ diff --git a/README b/README new file mode 100644 index 000000000..89bb35293 --- /dev/null +++ b/README @@ -0,0 +1,7 @@ +Mailng is a light web messaging solution. It provides different tools that facilitates virtual domains hosting. It is designed to be integrated with email servers like postfix (http://www.postfix.org/) and/or dovecot (http://www.dovecot.org/). + +For now, only the administration component is available. Additional tools will come in a near future. + +Django is needed to use Mailng (http://www.djangoproject.com/) + +Mootools version 1.2 (http://www.mootools.net/) and SqueezeBox version 1.1 (http://digitarald.de/project/squeezebox/) are provided with Mailng. diff --git a/__init__.py b/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/admin/__init__.py b/admin/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/admin/forms.py b/admin/forms.py new file mode 100644 index 000000000..d24370960 --- /dev/null +++ b/admin/forms.py @@ -0,0 +1,34 @@ +from django import forms +from mailng.admin.models import Domain, Mailbox, Alias + +class DomainForm(forms.ModelForm): + class Meta: + model = Domain + +class MailboxForm(forms.ModelForm): + password1 = forms.CharField(label="Password", widget=forms.PasswordInput) + password2 = forms.CharField(label="Confirmation", widget=forms.PasswordInput) + enabled = forms.BooleanField(label="Enabled", required=False) + + class Meta: + model = Mailbox + fields = ('name', 'address', 'quota') + + def clean_password2(self): + if self.cleaned_data["password1"] != self.cleaned_data["password2"]: + raise forms.ValidationError("Passwords mismatch") + return self.cleaned_data["password2"] + +class AliasForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + domain = None + if kwargs.has_key("domain"): + domain = kwargs["domain"] + del kwargs["domain"] + super(AliasForm, self).__init__(*args, **kwargs) + if domain: + self.fields['mbox'].queryset = Mailbox.objects.filter(domain=domain.id) + + class Meta: + model = Alias + diff --git a/admin/models.py b/admin/models.py new file mode 100644 index 000000000..1ab12639a --- /dev/null +++ b/admin/models.py @@ -0,0 +1,30 @@ +from django.db import models +from django.contrib.auth.models import User + +# Create your models here. + +class Domain(models.Model): + name = models.CharField(max_length=100) + quota = models.IntegerField() + enabled = models.BooleanField() + +class Mailbox(models.Model): + name = models.CharField(max_length=100) + address = models.CharField(max_length=100) +# password = models.CharField(max_length=100) + quota = models.IntegerField() +# enabled = models.BooleanField() + uid = models.IntegerField() + gid = models.IntegerField() + path = models.CharField(max_length=200) + domain = models.ForeignKey(Domain) + user = models.ForeignKey(User) + + def __str__(self): + return "%s" % (self.address) + + +class Alias(models.Model): + address = models.CharField(max_length=100) + mbox = models.ForeignKey(Mailbox) + enabled = models.BooleanField() diff --git a/admin/templatetags/__init__.py b/admin/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/admin/templatetags/admin_extras.py b/admin/templatetags/admin_extras.py new file mode 100644 index 000000000..fcc4ae1f9 --- /dev/null +++ b/admin/templatetags/admin_extras.py @@ -0,0 +1,36 @@ +from django import template +from django.template.loader import render_to_string + +register = template.Library() + +@register.simple_tag +def domain_menu(domain_id, selection): + return render_to_string('admin/domain_menu.html', + {"selection" : selection, + "domain_id" : domain_id}) + + +# class MyVar(template.Node): +# def __init__(self, value, var_name): +# self.var_name = var_name +# self.value = value + +# def render(self, context): +# context[self.var_name] = self.value +# return "" + +# import re + +# @register.tag(name="var") +# def do_my_var(parser, token): +# try: +# tag_name, arg = token.contents.split(None, 1) +# except ValueError: +# raise template.TemplateSyntaxError, '%r tag requires arguments' \ +# % token.contents.split()[0] +# m = re.search('(.*?) as (\w+)', arg) +# if not m: +# raise template.TemplateSyntaxError, "%r tag had invalid arguments" \ +# % tag_name +# value, var_name = m.groups() +# return MyVar(value, var_name) diff --git a/admin/urls.py b/admin/urls.py new file mode 100644 index 000000000..466d752d3 --- /dev/null +++ b/admin/urls.py @@ -0,0 +1,18 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns('mailng.admin.views', + (r'^$', 'domains'), + (r'^domains/$', 'domains'), + (r'^domains/new/', 'newdomain'), + (r'^domains/(?P\d+)/edit/$', 'editdomain'), + (r'^domains/(?P\d+)/delete/$', 'deldomain'), + (r'^domains/(?P\d+)/newmailbox/$', 'newmailbox'), + (r'^domains/(?P\d+)/editmailbox/(?P\d+)/$', 'editmailbox'), + (r'^domains/(?P\d+)/deletemailbox/(?P\d+)/$', 'delmailbox'), + (r'^domains/(?P\d+)/newalias/(?P\d+)/$', 'newalias'), + (r'^domains/(?P\d+)/editalias/(?P\d+)/$', 'editalias'), + (r'^domains/(?P\d+)/$', 'mailboxes'), + url(r'^domains/(?P\d+)/aliases/$', "aliases", name="full-aliases"), + url(r'^domains/(?P\d+)/aliases/(?P\d+)/$', "aliases", name="mbox-aliases"), + (r'^domains/(?P\d+)/delalias/(?P\d+)/$', 'delalias'), + ) diff --git a/admin/views.py b/admin/views.py new file mode 100644 index 000000000..6de6feec5 --- /dev/null +++ b/admin/views.py @@ -0,0 +1,222 @@ +from django.shortcuts import get_object_or_404, render_to_response +from django.template.loader import render_to_string +from django.template import RequestContext +from django.http import HttpResponse, HttpResponseRedirect +from django.utils import simplejson +from django.core import serializers +from django.core.urlresolvers import reverse +from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User +from mailng import admin +from mailng.admin.models import Domain, Mailbox, Alias +from forms import MailboxForm, DomainForm, AliasForm + +def _ctx_ok(url): + return {"status" : "ok", "url" : url} + +def _ctx_ko(tpl, ctx): + return {"status" : "ko", "content" : render_to_string(tpl, ctx)} + +def _render(request, tpl, user_context): + return render_to_response(tpl, user_context, + context_instance=RequestContext(request)) + +@login_required +def domains(request): + domains = Domain.objects.all() + counters = {} + for dom in domains: + dom.mboxcounter = len(dom.mailbox_set.all()) + return _render(request, 'admin/domains.html', { + "domains" : domains, "counters" : counters + }) + +@login_required +def newdomain(request): + if request.method == "POST": + form = DomainForm(request.POST) + error = None + if form.is_valid(): + if Domain.objects.filter(name=request.POST["name"]): + error = "Domain with this name already defined" + else: + form.save() + ctx = _ctx_ok(reverse(admin.views.domains)) + return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") + ctx = _ctx_ko("admin/newdomain.html", { + "form" : form, "error" : error + }) + return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") + + form = DomainForm() + return render_to_response('admin/newdomain.html', { + "form" : form + }) +@login_required +def editdomain(request, dom_id): + domain = Domain.objects.get(pk=dom_id) + if request.method == "POST": + form = DomainForm(request.POST, instance=domain) + if form.is_valid(): + form.save() + ctx = _ctx_ok(reverse(admin.views.domains)) + return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") + ctx = _ctx_ko("admin/editdomain.html", { + "domain" : domain, "form" : form + }) + return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") + + form = DomainForm(instance=domain) + return render_to_response('admin/editdomain.html', { + "domain" : domain, "form" : form + }) + +@login_required +def deldomain(request, dom_id): + domain = Domain.objects.get(pk=dom_id) + domain.delete() + return HttpResponseRedirect('/mailng/admin/') + +@login_required +def mailboxes(request, dom_id=None): + domain = Domain.objects.get(pk=dom_id) + mailboxes = Mailbox.objects.filter(domain=dom_id) + return _render(request, 'admin/mailboxes.html', { + "mailboxes" : mailboxes, "domain" : domain + }) + +@login_required +def newmailbox(request, dom_id=None): + domain = Domain.objects.get(pk=dom_id) + if request.method == "POST": + form = MailboxForm(request.POST) + error = None + if form.is_valid(): + if Mailbox.objects.filter(address=request.POST["address"], + domain=dom_id): + error = "Mailbox with this address already exists" + else: + mb = form.save(commit=False) + user = User() + user.username = user.email = "%s@%s" % (mb.address, domain.name) + user.set_password(request.POST["password1"]) + user.is_active = request.POST.has_key("enabled") \ + and True or False + fname, lname = mb.name.split() + user.first_name = fname + + user.last_name = lname + user.save() + mb.user = user + + mb.uid = mb.gid = 500 + mb.domain = domain + mb.save() + ctx = _ctx_ok(reverse(admin.views.mailboxes, args=[domain.id])) + return HttpResponse(simplejson.dumps(ctx), + mimetype="application/json") + ctx = _ctx_ko("admin/newmailbox.html", { + "form" : form, "domain" : domain, "error" : error + }) + return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") + + form = MailboxForm() + return render_to_response('admin/newmailbox.html', { + "domain" : domain, "form" : form + }) + +@login_required +def editmailbox(request, dom_id, mbox_id=None): + mb = Mailbox.objects.get(pk=mbox_id) + if request.method == "POST": + form = MailboxForm(request.POST, instance=mb) + if form.is_valid(): + if request.POST["enabled"] != mb.user.is_active \ + or request.POST["password1"] != mb.user.password: + mb.user.is_active = request.POST["enabled"] + mb.user.password = request.POST["password1"] + mb.user.save() + form.save() + ctx = _ctx_ok(reverse(admin.views.mailboxes, args=[dom_id])) + else: + ctx = _ctx_ko("admin/editmailbox.html", { + "mbox" : mb, "form" : form + }) + json = simplejson.dumps(ctx) + return HttpResponse(json, mimetype="application/json") + + form = MailboxForm(instance=mb) + form.fields['enabled'].initial = mb.user.is_active + form.fields['password1'].initial = mb.user.password + form.fields['password2'].initial = mb.user.password + return _render(request, 'admin/editmailbox.html', { + "form" : form, "mbox" : mb + }) + +@login_required +def delmailbox(request, dom_id, mbox_id=None): + mb = Mailbox.objects.get(pk=mbox_id) + mb.delete() + return HttpResponseRedirect(reverse(admin.views.mailboxes, + args=[dom_id])) + +@login_required +def aliases(request, dom_id=None, mbox_id=None): + domain = Domain.objects.get(pk=dom_id) + if not mbox_id: + mboxes = Mailbox.objects.filter(domain=dom_id) + aliases = [] + for mb in mboxes: + aliases += Alias.objects.filter(mbox=mb.id) + else: + aliases = Alias.objects.filter(mbox=mbox_id) + return _render(request, 'admin/aliases.html', { + "aliases" : aliases, "domain" : domain + }) + +@login_required +def newalias(request, dom_id, mbox_id): + mbox = Mailbox.objects.get(pk=mbox_id) + if request.method == "POST": + form = AliasForm(request.POST) + if form.is_valid(): + form.save() + ctx = _ctx_ok(reverse(admin.views.aliases, args=[mbox.domain.id])) + else: + ctx = _ctx_ko("admin/newalias.html", { + "mbox" : mbox, "form" : form + }) + json = simplejson.dumps(ctx) + return HttpResponse(json, mimetype="application/json") + + form = AliasForm({"mbox" : mbox_id}, domain=mbox.domain) + return render_to_response('admin/newalias.html', { + "mbox" : mbox, "form" : form, "noerrors" : True + }) + +@login_required +def editalias(request, dom_id, alias_id): + alias = Alias.objects.get(pk=alias_id) + if request.method == "POST": + form = AliasForm(request.POST, instance=alias) + if form.is_valid(): + form.save() + ctx = _ctx_ok(reverse(admin.views.aliases, args=[dom_id])) + else: + ctx = _ctx_ko("admin/editalias.html", { + "alias" : alias, "form" : form + }) + json = simplejson.dumps(ctx) + return HttpResponse(json, mimetype="application/json") + form = AliasForm(instance=alias) + return _render(request, 'admin/editalias.html', { + "form" : form, "alias" : alias + }) + +@login_required +def delalias(request, dom_id, alias_id): + alias = Alias.objects.get(pk=alias_id) + alias.delete() + return HttpResponseRedirect(reverse(admin.views.aliases, + args=[dom_id])) + diff --git a/manage.py b/manage.py new file mode 100755 index 000000000..5e78ea979 --- /dev/null +++ b/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/settings_sample.py b/settings_sample.py new file mode 100644 index 000000000..049d462dd --- /dev/null +++ b/settings_sample.py @@ -0,0 +1,82 @@ +# Django settings for mailng project. + +DEBUG = False +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. +DATABASE_NAME = 'mailng' # Or path to database file if using sqlite3. +DATABASE_USER = '' # Not used with sqlite3. +DATABASE_PASSWORD = '' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +LOGIN_REDIRECT_URL = '/mailng/admin/' + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '2)e+la#y$1b&v!p)s^&4lwpc92o59fye)mj^3^7$jytmvx379d' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +) + +ROOT_URLCONF = 'mailng.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'mailng.admin' +) diff --git a/static/js/SqueezeBox.js b/static/js/SqueezeBox.js new file mode 100644 index 000000000..980ba8778 --- /dev/null +++ b/static/js/SqueezeBox.js @@ -0,0 +1,384 @@ +/** + * SqueezeBox - Expandable Lightbox + * + * Allows to open various content as modal, + * centered and animated box. + * + * Dependencies: MooTools 1.2 trunk (04/2008) + * + * Inspired by + * ... Lokesh Dhakar - The original Lightbox v2 + * + * @version 1.1 rc2 + * + * @license MIT-style license + * @author Harald Kirschner + * @copyright Author + */ +var SqueezeBox = { + + presets: { + size: {x: 600, y: 450}, + sizeLoading: {x: 200, y: 150}, + marginInner: {x: 20, y: 20}, + marginImage: {x: 50, y: 75}, + handler: false, + target: null, + closable: true, + closeBtn: true, + zIndex: 65555, + overlayOpacity: 0.7, + classWindow: '', + classOverlay: '', + overlayFx: {}, + resizeFx: {}, + contentFx: {}, + parse: false, // 'rel' + parseSecure: false, + ajaxOptions: {}, + onOpen: $empty, + onClose: $empty, + onUpdate: $empty, + onResize: $empty, + onMove: $empty, + onShow: $empty, + onHide: $empty + }, + + initialize: function(presets) { + if (this.options) return this; + this.presets = $merge(this.presets, presets); + this.options = {}; + this.setOptions(this.presets).build(); + this.bound = { + window: this.reposition.bind(this, [null]), + scroll: this.checkTarget.bind(this), + close: this.close.bind(this), + key: this.onKey.bind(this) + }; + this.isOpen = this.isLoading = false; + return this; + }, + + build: function() { + this.overlay = new Element('div', { + id: 'sbox-overlay', + styles: {display: 'none', zIndex: this.options.zIndex} + }); + this.content = new Element('div', {id: 'sbox-content'}); + this.closeBtn = new Element('a', {id: 'sbox-btn-close', href: '#'}); + this.win = new Element('div', { + id: 'sbox-window', + styles: {display: 'none', zIndex: this.options.zIndex + 2} + }).adopt(this.closeBtn, this.content); + this.fx = { + overlay: new Fx.Tween(this.overlay, $merge({ + property: 'opacity', + onStart: Events.prototype.clearChain, + duration: 250, + link: 'cancel' + }, this.options.overlayFx)).set(0), + win: new Fx.Morph(this.win, $merge({ + onStart: Events.prototype.clearChain, + unit: 'px', + duration: 750, + transition: Fx.Transitions.Quint.easeOut, + link: 'cancel', + unit: 'px' + }, this.options.resizeFx)), + content: new Fx.Tween(this.content, $merge({ + property: 'opacity', + duration: 250, + link: 'cancel' + }, this.options.contentFx)).set(0) + }; + $(document.body).adopt(this.overlay, this.win); + }, + + assign: function(to, options) { + return to.addEvent('click', function() { + return !SqueezeBox.fromElement(this, options); + }); + }, + + fromElement: function(from, options) { + this.initialize(); + if (this.element) this.trash(); + this.element = $(from); + this.setOptions($merge(this.presets, options || {})); + if (this.element && this.options.parse) { + var obj = this.element.getProperty(this.options.parse); + if (obj && (obj = JSON.decode(obj, this.options.parseSecure))) this.setOptions(obj); + } + this.assignOptions(); + this.url = ((this.element) ? (this.options.url || this.element.get('href')) : from) || ''; + var handler = this.options.handler; + if (handler) return this.setContent(handler, this.parsers[handler].call(this, true)); + var ret = false; + this.parsers.some(function(parser, key) { + var content = parser.call(this); + if (content) { + ret = this.setContent(key, content); + return true; + } + return false; + }, this); + return ret; + }, + + assignOptions: function() { + this.overlay.set('class', this.options.classOverlay); + this.win.set('class', this.options.classWindow); + if (Browser.Engine.trident4) this.win.addClass('sbox-window-ie6'); + }, + + close: function(e) { + var stoppable = ($type(e) == 'event'); + if (stoppable) e.stop(); + if (!this.isOpen || (stoppable && !$lambda(this.options.closable).call(this, e))) return this; + this.fx.overlay.start(0).chain(this.toggleOverlay.bind(this)); + this.win.setStyle('display', 'none'); + this.toggleListeners(); + this.isOpen = false; + this.fireEvent('onClose', [this.content]); + this.trash(); + return this; + }, + + trash: function() { + this.element = this.asset = null; + this.options = {}; + this.removeEvents().setOptions(this.presets).callChain(); + }, + + onError: function() { + this.asset = null; + this.setContent('string', 'Error during loading'); + }, + + setContent: function(handler, content) { + if (!this.handlers[handler]) return false; + this.content.className = 'sbox-content-' + handler; + this.applyTimer = this.applyContent.delay(this.fx.overlay.options.duration, this, this.handlers[handler].call(this, content)); + if (this.overlay.retrieve('opacity')) return this; + this.toggleOverlay(true); + this.fx.overlay.start(this.options.overlayOpacity); + return this.reposition(); + }, + + applyContent: function(content, size) { + this.applyTimer = $clear(this.applyTimer); + this.hideContent(); + if (!content) { + this.toggleLoading(true); + } else { + if (this.isLoading) this.toggleLoading(false); + this.fireEvent('onUpdate', [this.content], 20); + } + this.content.empty(); + if (['string', 'array', false].contains($type(content))) this.content.set('html', content || ''); + else this.content.adopt(content); + this.callChain(); + if (!this.isOpen) { + this.toggleListeners(true); + this.resize(size, true); + this.isOpen = true; + this.fireEvent('onOpen', [this.content]); + } else { + this.resize(size); + } + }, + + resize: function(size, instantly) { + var box = document.getSize(), scroll = document.getScroll(); + this.size = $merge((this.isLoading) ? this.options.sizeLoading : this.options.size, size); + var to = { + width: this.size.x, + height: this.size.y, + left: (scroll.x + (box.x - this.size.x - this.options.marginInner.x) / 2).toInt(), + top: (scroll.y + (box.y - this.size.y - this.options.marginInner.y) / 2).toInt() + }; + $clear(this.showTimer || null); + this.hideContent(); + if (!instantly) { + this.fx.win.start(to).chain(this.showContent.bind(this)); + } else { + this.win.setStyles(to).setStyle('display', ''); + this.showTimer = this.showContent.delay(50, this); + } + return this.reposition(); + }, + + toggleListeners: function(state) { + var fn = (state) ? 'addEvent' : 'removeEvent'; + this.closeBtn[fn]('click', this.bound.close); + this.overlay[fn]('click', this.bound.close); + document[fn]('keydown', this.bound.key)[fn]('mousewheel', this.bound.scroll); + window[fn]('resize', this.bound.window)[fn]('scroll', this.bound.window); + }, + + toggleLoading: function(state) { + this.isLoading = state; + this.win[(state) ? 'addClass' : 'removeClass']('sbox-loading'); + if (state) this.fireEvent('onLoading', [this.win]); + }, + + toggleOverlay: function(state) { + this.overlay.setStyle('display', (state) ? '' : 'none'); + $(document.body)[(state) ? 'addClass' : 'removeClass']('body-overlayed'); + }, + + showContent: function() { + if (this.content.get('opacity')) this.fireEvent('onShow', [this.win]); + this.fx.content.start(1); + }, + + hideContent: function() { + if (!this.content.get('opacity')) this.fireEvent('onHide', [this.win]); + this.fx.content.set(0); + }, + + onKey: function(e) { + switch (e.key) { + case 'esc': this.close(e); + case 'up': case 'down': return false; + } + }, + + checkTarget: function(e) { + return this.content.hasChild(e.target); + }, + + reposition: function() { + var size = document.getSize(), scroll = document.getScroll(); + this.overlay.setStyles({ + left: scroll.x + 'px', + top: scroll.y + 'px', + width: size.x + 'px', + height: size.y + 'px' + }); + this.win.setStyles({ + left: (scroll.x + (size.x - this.win.offsetWidth) / 2).toInt() + 'px', + top: (scroll.y + (size.y - this.win.offsetHeight) / 2).toInt() + 'px' + }); + return this.fireEvent('onMove', [this.overlay, this.win]); + }, + + removeEvents: function(type){ + if (!this.$events) return this; + if (!type) this.$events = null; + else if (this.$events[type]) this.$events[type] = null; + return this; + }, + + extend: function(properties) { + return $extend(this, properties); + }, + + handlers: new Hash(), + + parsers: new Hash() + +}; + +SqueezeBox.extend(new Events($empty)).extend(new Options($empty)).extend(new Chain($empty)); + +SqueezeBox.parsers.extend({ + + image: function(preset) { + return (preset || (/\.(?:jpg|png|gif)$/i).test(this.url)) ? this.url : false; + }, + + clone: function(preset) { + if ($(this.options.target)) return $(this.options.target); + if (this.element && !this.element.parentNode) return this.element; + var bits = this.url.match(/#([\w-]+)$/); + return (bits) ? $(bits[1]) : (preset ? this.element : false); + }, + + ajax: function(preset) { + return (preset || (this.url && !(/^(?:javascript|#)/i).test(this.url))) ? this.url : false; + }, + + iframe: function(preset) { + return (preset || this.url) ? this.url : false; + }, + + string: function(preset) { + return true; + } +}); + +SqueezeBox.handlers.extend({ + + image: function(url) { + var size, tmp = new Image(); + this.asset = null; + tmp.onload = tmp.onabort = tmp.onerror = (function() { + tmp.onload = tmp.onabort = tmp.onerror = null; + if (!tmp.width) { + this.onError.delay(10, this); + return; + } + var box = document.getSize(); + box.x -= this.options.marginImage.x; + box.y -= this.options.marginImage.y; + size = {x: tmp.width, y: tmp.height}; + for (var i = 2; i--;) { + if (size.x > box.x) { + size.y *= box.x / size.x; + size.x = box.x; + } else if (size.y > box.y) { + size.x *= box.y / size.y; + size.y = box.y; + } + } + size.x = size.x.toInt(); + size.y = size.y.toInt(); + this.asset = $(tmp); + tmp = null; + this.asset.setProperties({width: size.x, height: size.y}); + if (this.isOpen) this.applyContent(this.asset, size); + }).bind(this); + tmp.src = url; + if (tmp && tmp.onload && tmp.complete) tmp.onload(); + return (this.asset) ? [this.asset, size] : null; + }, + + clone: function(el) { + return el.clone(); + }, + + adopt: $arguments(0), + + ajax: function(url) { + this.asset = new Request.HTML($merge({ + method: 'get' + }, this.options.ajaxOptions)).addEvents({ + onSuccess: function(resp) { + this.applyContent(resp); + this.asset = null; + }.bind(this), + onFailure: this.onError.bind(this) + }); + this.asset.send.delay(10, this.asset, [{url: url}]); + }, + + iframe: function(url) { + return new Element('iframe', $merge({ + src: url, + frameBorder: 0, + width: this.options.size.x, + height: this.options.size.y + }, this.options.iframeOptions)); + }, + + string: function(str) { + return str; + } + +}); + +SqueezeBox.handlers.url = SqueezeBox.handlers.ajax; +SqueezeBox.parsers.url = SqueezeBox.parsers.ajax; +SqueezeBox.parsers.adopt = SqueezeBox.parsers.clone; \ No newline at end of file diff --git a/static/js/domains.js b/static/js/domains.js new file mode 100644 index 000000000..b0f603c53 --- /dev/null +++ b/static/js/domains.js @@ -0,0 +1,92 @@ + +window.addEvent('domready', function() { + var handlers = new Array(); + +// handlers['loadDom'] = function(obj, target) { +/* $$('a[name=loadDom]').setStyles({'color' : '#999'});*/ +/* obj.setStyles({"color": "black"});*/ +// loadPage(target, "content", null); +// }; + +// handlers['loadContent'] = function(obj, target) { +// loadPage(target, "content", null); +// }; + + if ($$("a[name=deleteDom]")) + $$("a[name=deleteDom]").addEvent('click', function(event) { + if (!confirm("Delete this domain?")) + event.stop(); + }); + + if ($$("a[name=deleteMb]")) + $$("a[name=deleteMb]").addEvent('click', function(event) { + if (!confirm("Delete this mailbox?")) + event.stop(); + }); + + if ($$("a[name=deleteAlias]")) + $$("a[name=deleteAlias]").addEvent('click', function(event) { + if (!confirm("Delete this alias?")) + event.stop(); + }); + + handlers['deleteDom'] = function(obj, target) { + return confirm("Supprimer ce domaine?"); + } + + $('body').addEvent('click', function(event) { + target = event.target; + if (target.get('tag') == 'a') { + name = target.get('name'); + if (name == 'undefined') + return; + if (typeof(handlers[name]) != 'undefined') { + if (!handlers[name](target, target.get('href'))) { + new Event(event).stop(); + } + } + } + }); + + $('body').addEvent('submit', function(event) { + alert("newodm submit"); + target = event.target; + if (target.get('tag') == 'form') { + name = target.get('name'); + if (name == 'undefined') + return; + if (typeof(handlers[name]) != 'undefined') { + new Event(event).stop(); + handlers[name](target); + } + } + }); + + SqueezeBox.assign($$('a.boxed'), { + //size: {x:300, y:180} + parse: 'rel', + + }); +}); + +newdom = function() { + alert("pouet"); + form = $("form[name=newdom]"); + var params = obj.toQueryString(); + form.set('send', { + onSuccess: function(response) { + SqueezeBox.loadModal(obj.action + '?' + params); + } + }).send(); +}; + +loadPage = function(url, destination, callback) { + var destination = (destination == null) ? "page" : destination; + + if (callback) + new Request({url: url, method: "get", onSuccess: callback}).send(); + else + new Request({url: url, method: "get", onSuccess: function(data) { + $(destination).set('html', data); + }}).send(); +}; \ No newline at end of file diff --git a/static/js/form.js b/static/js/form.js new file mode 100644 index 000000000..2d9f2b43b --- /dev/null +++ b/static/js/form.js @@ -0,0 +1,40 @@ +window.addEvent("domready", function() { + sqbsubmit = function(obj, dest, id, callback) { + obj.set('send', { + onSuccess: function(responseText) { + response = JSON.decode(responseText); + if (response.status == "ko") { + $(dest).set("html", response.content); + $(id).addEvent("submit", callback); + } else { + parent.SqueezeBox.close(); + parent.location.href = response.url; + } + } + }); + obj.send(); + } + + generic_submit = function(obj, event, id, callback) { + event.stop(); + sqbsubmit(obj, "content", id, callback); + } + domain_submit = function(event) { + generic_submit(this, event, "domform", domain_submit); + } + mailbox_submit = function(event) { + generic_submit(this, event, "mboxform", mailbox_submit); + } + alias_submit = function(event) { + generic_submit(this, event, "aliasform", alias_submit); + } + + if ($("domform")) + $("domform").addEvent("submit", domain_submit); + + if ($("mboxform")) + $("mboxform").addEvent("submit", mailbox_submit); + + if ($("aliasform")) + $("aliasform").addEvent("submit", alias_submit); +}); diff --git a/static/js/mootools.js b/static/js/mootools.js new file mode 100644 index 000000000..7e1e482bf --- /dev/null +++ b/static/js/mootools.js @@ -0,0 +1,3946 @@ +/* +Script: Core.js + MooTools - My Object Oriented JavaScript Tools. + +License: + MIT-style license. + +Copyright: + Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/). + +Code & Documentation: + [The MooTools production team](http://mootools.net/developers/). + +Inspiration: + - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) + - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) +*/ + +var MooTools = { + 'version': '1.2.1', + 'build': '0d4845aab3d9a4fdee2f0d4a6dd59210e4b697cf' +}; + +var Native = function(options){ + options = options || {}; + var name = options.name; + var legacy = options.legacy; + var protect = options.protect; + var methods = options.implement; + var generics = options.generics; + var initialize = options.initialize; + var afterImplement = options.afterImplement || function(){}; + var object = initialize || legacy; + generics = generics !== false; + + object.constructor = Native; + object.$family = {name: 'native'}; + if (legacy && initialize) object.prototype = legacy.prototype; + object.prototype.constructor = object; + + if (name){ + var family = name.toLowerCase(); + object.prototype.$family = {name: family}; + Native.typize(object, family); + } + + var add = function(obj, name, method, force){ + if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method; + if (generics) Native.genericize(obj, name, protect); + afterImplement.call(obj, name, method); + return obj; + }; + + object.alias = function(a1, a2, a3){ + if (typeof a1 == 'string'){ + if ((a1 = this.prototype[a1])) return add(this, a2, a1, a3); + } + for (var a in a1) this.alias(a, a1[a], a2); + return this; + }; + + object.implement = function(a1, a2, a3){ + if (typeof a1 == 'string') return add(this, a1, a2, a3); + for (var p in a1) add(this, p, a1[p], a2); + return this; + }; + + if (methods) object.implement(methods); + + return object; +}; + +Native.genericize = function(object, property, check){ + if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){ + var args = Array.prototype.slice.call(arguments); + return object.prototype[property].apply(args.shift(), args); + }; +}; + +Native.implement = function(objects, properties){ + for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties); +}; + +Native.typize = function(object, family){ + if (!object.type) object.type = function(item){ + return ($type(item) === family); + }; +}; + +(function(){ + var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String}; + for (var n in natives) new Native({name: n, initialize: natives[n], protect: true}); + + var types = {'boolean': Boolean, 'native': Native, 'object': Object}; + for (var t in types) Native.typize(types[t], t); + + var generics = { + 'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"], + 'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"] + }; + for (var g in generics){ + for (var i = generics[g].length; i--;) Native.genericize(window[g], generics[g][i], true); + }; +})(); + +var Hash = new Native({ + + name: 'Hash', + + initialize: function(object){ + if ($type(object) == 'hash') object = $unlink(object.getClean()); + for (var key in object) this[key] = object[key]; + return this; + } + +}); + +Hash.implement({ + + forEach: function(fn, bind){ + for (var key in this){ + if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this); + } + }, + + getClean: function(){ + var clean = {}; + for (var key in this){ + if (this.hasOwnProperty(key)) clean[key] = this[key]; + } + return clean; + }, + + getLength: function(){ + var length = 0; + for (var key in this){ + if (this.hasOwnProperty(key)) length++; + } + return length; + } + +}); + +Hash.alias('forEach', 'each'); + +Array.implement({ + + forEach: function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this); + } + +}); + +Array.alias('forEach', 'each'); + +function $A(iterable){ + if (iterable.item){ + var array = []; + for (var i = 0, l = iterable.length; i < l; i++) array[i] = iterable[i]; + return array; + } + return Array.prototype.slice.call(iterable); +}; + +function $arguments(i){ + return function(){ + return arguments[i]; + }; +}; + +function $chk(obj){ + return !!(obj || obj === 0); +}; + +function $clear(timer){ + clearTimeout(timer); + clearInterval(timer); + return null; +}; + +function $defined(obj){ + return (obj != undefined); +}; + +function $each(iterable, fn, bind){ + var type = $type(iterable); + ((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind); +}; + +function $empty(){}; + +function $extend(original, extended){ + for (var key in (extended || {})) original[key] = extended[key]; + return original; +}; + +function $H(object){ + return new Hash(object); +}; + +function $lambda(value){ + return (typeof value == 'function') ? value : function(){ + return value; + }; +}; + +function $merge(){ + var mix = {}; + for (var i = 0, l = arguments.length; i < l; i++){ + var object = arguments[i]; + if ($type(object) != 'object') continue; + for (var key in object){ + var op = object[key], mp = mix[key]; + mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $merge(mp, op) : $unlink(op); + } + } + return mix; +}; + +function $pick(){ + for (var i = 0, l = arguments.length; i < l; i++){ + if (arguments[i] != undefined) return arguments[i]; + } + return null; +}; + +function $random(min, max){ + return Math.floor(Math.random() * (max - min + 1) + min); +}; + +function $splat(obj){ + var type = $type(obj); + return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : []; +}; + +var $time = Date.now || function(){ + return +new Date; +}; + +function $try(){ + for (var i = 0, l = arguments.length; i < l; i++){ + try { + return arguments[i](); + } catch(e){} + } + return null; +}; + +function $type(obj){ + if (obj == undefined) return false; + if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name; + if (obj.nodeName){ + switch (obj.nodeType){ + case 1: return 'element'; + case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace'; + } + } else if (typeof obj.length == 'number'){ + if (obj.callee) return 'arguments'; + else if (obj.item) return 'collection'; + } + return typeof obj; +}; + +function $unlink(object){ + var unlinked; + switch ($type(object)){ + case 'object': + unlinked = {}; + for (var p in object) unlinked[p] = $unlink(object[p]); + break; + case 'hash': + unlinked = new Hash(object); + break; + case 'array': + unlinked = []; + for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]); + break; + default: return object; + } + return unlinked; +}; + + +/* +Script: Browser.js + The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash. + +License: + MIT-style license. +*/ + +var Browser = $merge({ + + Engine: {name: 'unknown', version: 0}, + + Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()}, + + Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}, + + Plugins: {}, + + Engines: { + + presto: function(){ + return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); + }, + + trident: function(){ + return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? 5 : 4); + }, + + webkit: function(){ + return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419); + }, + + gecko: function(){ + return (document.getBoxObjectFor == undefined) ? false : ((document.getElementsByClassName) ? 19 : 18); + } + + } + +}, Browser || {}); + +Browser.Platform[Browser.Platform.name] = true; + +Browser.detect = function(){ + + for (var engine in this.Engines){ + var version = this.Engines[engine](); + if (version){ + this.Engine = {name: engine, version: version}; + this.Engine[engine] = this.Engine[engine + version] = true; + break; + } + } + + return {name: engine, version: version}; + +}; + +Browser.detect(); + +Browser.Request = function(){ + return $try(function(){ + return new XMLHttpRequest(); + }, function(){ + return new ActiveXObject('MSXML2.XMLHTTP'); + }); +}; + +Browser.Features.xhr = !!(Browser.Request()); + +Browser.Plugins.Flash = (function(){ + var version = ($try(function(){ + return navigator.plugins['Shockwave Flash'].description; + }, function(){ + return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); + }) || '0 r0').match(/\d+/g); + return {version: parseInt(version[0] || 0 + '.' + version[1] || 0), build: parseInt(version[2] || 0)}; +})(); + +function $exec(text){ + if (!text) return text; + if (window.execScript){ + window.execScript(text); + } else { + var script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text; + document.head.appendChild(script); + document.head.removeChild(script); + } + return text; +}; + +Native.UID = 1; + +var $uid = (Browser.Engine.trident) ? function(item){ + return (item.uid || (item.uid = [Native.UID++]))[0]; +} : function(item){ + return item.uid || (item.uid = Native.UID++); +}; + +var Window = new Native({ + + name: 'Window', + + legacy: (Browser.Engine.trident) ? null: window.Window, + + initialize: function(win){ + $uid(win); + if (!win.Element){ + win.Element = $empty; + if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2 + win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {}; + } + win.document.window = win; + return $extend(win, Window.Prototype); + }, + + afterImplement: function(property, value){ + window[property] = Window.Prototype[property] = value; + } + +}); + +Window.Prototype = {$family: {name: 'window'}}; + +new Window(window); + +var Document = new Native({ + + name: 'Document', + + legacy: (Browser.Engine.trident) ? null: window.Document, + + initialize: function(doc){ + $uid(doc); + doc.head = doc.getElementsByTagName('head')[0]; + doc.html = doc.getElementsByTagName('html')[0]; + if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){ + doc.execCommand("BackgroundImageCache", false, true); + }); + if (Browser.Engine.trident) doc.window.attachEvent('onunload', function() { + doc.window.detachEvent('onunload', arguments.callee); + doc.head = doc.html = doc.window = null; + }); + return $extend(doc, Document.Prototype); + }, + + afterImplement: function(property, value){ + document[property] = Document.Prototype[property] = value; + } + +}); + +Document.Prototype = {$family: {name: 'document'}}; + +new Document(document); + + +/* +Script: Array.js + Contains Array Prototypes like each, contains, and erase. + +License: + MIT-style license. +*/ + +Array.implement({ + + every: function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++){ + if (!fn.call(bind, this[i], i, this)) return false; + } + return true; + }, + + filter: function(fn, bind){ + var results = []; + for (var i = 0, l = this.length; i < l; i++){ + if (fn.call(bind, this[i], i, this)) results.push(this[i]); + } + return results; + }, + + clean: function() { + return this.filter($defined); + }, + + indexOf: function(item, from){ + var len = this.length; + for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){ + if (this[i] === item) return i; + } + return -1; + }, + + map: function(fn, bind){ + var results = []; + for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this); + return results; + }, + + some: function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++){ + if (fn.call(bind, this[i], i, this)) return true; + } + return false; + }, + + associate: function(keys){ + var obj = {}, length = Math.min(this.length, keys.length); + for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; + return obj; + }, + + link: function(object){ + var result = {}; + for (var i = 0, l = this.length; i < l; i++){ + for (var key in object){ + if (object[key](this[i])){ + result[key] = this[i]; + delete object[key]; + break; + } + } + } + return result; + }, + + contains: function(item, from){ + return this.indexOf(item, from) != -1; + }, + + extend: function(array){ + for (var i = 0, j = array.length; i < j; i++) this.push(array[i]); + return this; + }, + + getLast: function(){ + return (this.length) ? this[this.length - 1] : null; + }, + + getRandom: function(){ + return (this.length) ? this[$random(0, this.length - 1)] : null; + }, + + include: function(item){ + if (!this.contains(item)) this.push(item); + return this; + }, + + combine: function(array){ + for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); + return this; + }, + + erase: function(item){ + for (var i = this.length; i--; i){ + if (this[i] === item) this.splice(i, 1); + } + return this; + }, + + empty: function(){ + this.length = 0; + return this; + }, + + flatten: function(){ + var array = []; + for (var i = 0, l = this.length; i < l; i++){ + var type = $type(this[i]); + if (!type) continue; + array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]); + } + return array; + }, + + hexToRgb: function(array){ + if (this.length != 3) return null; + var rgb = this.map(function(value){ + if (value.length == 1) value += value; + return value.toInt(16); + }); + return (array) ? rgb : 'rgb(' + rgb + ')'; + }, + + rgbToHex: function(array){ + if (this.length < 3) return null; + if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; + var hex = []; + for (var i = 0; i < 3; i++){ + var bit = (this[i] - 0).toString(16); + hex.push((bit.length == 1) ? '0' + bit : bit); + } + return (array) ? hex : '#' + hex.join(''); + } + +}); + + +/* +Script: Function.js + Contains Function Prototypes like create, bind, pass, and delay. + +License: + MIT-style license. +*/ + +Function.implement({ + + extend: function(properties){ + for (var property in properties) this[property] = properties[property]; + return this; + }, + + create: function(options){ + var self = this; + options = options || {}; + return function(event){ + var args = options.arguments; + args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0); + if (options.event) args = [event || window.event].extend(args); + var returns = function(){ + return self.apply(options.bind || null, args); + }; + if (options.delay) return setTimeout(returns, options.delay); + if (options.periodical) return setInterval(returns, options.periodical); + if (options.attempt) return $try(returns); + return returns(); + }; + }, + + run: function(args, bind){ + return this.apply(bind, $splat(args)); + }, + + pass: function(args, bind){ + return this.create({bind: bind, arguments: args}); + }, + + bind: function(bind, args){ + return this.create({bind: bind, arguments: args}); + }, + + bindWithEvent: function(bind, args){ + return this.create({bind: bind, arguments: args, event: true}); + }, + + attempt: function(args, bind){ + return this.create({bind: bind, arguments: args, attempt: true})(); + }, + + delay: function(delay, bind, args){ + return this.create({bind: bind, arguments: args, delay: delay})(); + }, + + periodical: function(periodical, bind, args){ + return this.create({bind: bind, arguments: args, periodical: periodical})(); + } + +}); + + +/* +Script: Number.js + Contains Number Prototypes like limit, round, times, and ceil. + +License: + MIT-style license. +*/ + +Number.implement({ + + limit: function(min, max){ + return Math.min(max, Math.max(min, this)); + }, + + round: function(precision){ + precision = Math.pow(10, precision || 0); + return Math.round(this * precision) / precision; + }, + + times: function(fn, bind){ + for (var i = 0; i < this; i++) fn.call(bind, i, this); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + } + +}); + +Number.alias('times', 'each'); + +(function(math){ + var methods = {}; + math.each(function(name){ + if (!Number[name]) methods[name] = function(){ + return Math[name].apply(null, [this].concat($A(arguments))); + }; + }); + Number.implement(methods); +})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); + + +/* +Script: String.js + Contains String Prototypes like camelCase, capitalize, test, and toInt. + +License: + MIT-style license. +*/ + +String.implement({ + + test: function(regex, params){ + return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this); + }, + + contains: function(string, separator){ + return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1; + }, + + trim: function(){ + return this.replace(/^\s+|\s+$/g, ''); + }, + + clean: function(){ + return this.replace(/\s+/g, ' ').trim(); + }, + + camelCase: function(){ + return this.replace(/-\D/g, function(match){ + return match.charAt(1).toUpperCase(); + }); + }, + + hyphenate: function(){ + return this.replace(/[A-Z]/g, function(match){ + return ('-' + match.charAt(0).toLowerCase()); + }); + }, + + capitalize: function(){ + return this.replace(/\b[a-z]/g, function(match){ + return match.toUpperCase(); + }); + }, + + escapeRegExp: function(){ + return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + hexToRgb: function(array){ + var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); + return (hex) ? hex.slice(1).hexToRgb(array) : null; + }, + + rgbToHex: function(array){ + var rgb = this.match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHex(array) : null; + }, + + stripScripts: function(option){ + var scripts = ''; + var text = this.replace(/]*>([\s\S]*?)<\/script>/gi, function(){ + scripts += arguments[1] + '\n'; + return ''; + }); + if (option === true) $exec(scripts); + else if ($type(option) == 'function') option(scripts, text); + return text; + }, + + substitute: function(object, regexp){ + return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ + if (match.charAt(0) == '\\') return match.slice(1); + return (object[name] != undefined) ? object[name] : ''; + }); + } + +}); + + +/* +Script: Hash.js + Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. + +License: + MIT-style license. +*/ + +Hash.implement({ + + has: Object.prototype.hasOwnProperty, + + keyOf: function(value){ + for (var key in this){ + if (this.hasOwnProperty(key) && this[key] === value) return key; + } + return null; + }, + + hasValue: function(value){ + return (Hash.keyOf(this, value) !== null); + }, + + extend: function(properties){ + Hash.each(properties, function(value, key){ + Hash.set(this, key, value); + }, this); + return this; + }, + + combine: function(properties){ + Hash.each(properties, function(value, key){ + Hash.include(this, key, value); + }, this); + return this; + }, + + erase: function(key){ + if (this.hasOwnProperty(key)) delete this[key]; + return this; + }, + + get: function(key){ + return (this.hasOwnProperty(key)) ? this[key] : null; + }, + + set: function(key, value){ + if (!this[key] || this.hasOwnProperty(key)) this[key] = value; + return this; + }, + + empty: function(){ + Hash.each(this, function(value, key){ + delete this[key]; + }, this); + return this; + }, + + include: function(key, value){ + var k = this[key]; + if (k == undefined) this[key] = value; + return this; + }, + + map: function(fn, bind){ + var results = new Hash; + Hash.each(this, function(value, key){ + results.set(key, fn.call(bind, value, key, this)); + }, this); + return results; + }, + + filter: function(fn, bind){ + var results = new Hash; + Hash.each(this, function(value, key){ + if (fn.call(bind, value, key, this)) results.set(key, value); + }, this); + return results; + }, + + every: function(fn, bind){ + for (var key in this){ + if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false; + } + return true; + }, + + some: function(fn, bind){ + for (var key in this){ + if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true; + } + return false; + }, + + getKeys: function(){ + var keys = []; + Hash.each(this, function(value, key){ + keys.push(key); + }); + return keys; + }, + + getValues: function(){ + var values = []; + Hash.each(this, function(value){ + values.push(value); + }); + return values; + }, + + toQueryString: function(base){ + var queryString = []; + Hash.each(this, function(value, key){ + if (base) key = base + '[' + key + ']'; + var result; + switch ($type(value)){ + case 'object': result = Hash.toQueryString(value, key); break; + case 'array': + var qs = {}; + value.each(function(val, i){ + qs[i] = val; + }); + result = Hash.toQueryString(qs, key); + break; + default: result = key + '=' + encodeURIComponent(value); + } + if (value != undefined) queryString.push(result); + }); + + return queryString.join('&'); + } + +}); + +Hash.alias({keyOf: 'indexOf', hasValue: 'contains'}); + + +/* +Script: Event.js + Contains the Event Native, to make the event object completely crossbrowser. + +License: + MIT-style license. +*/ + +var Event = new Native({ + + name: 'Event', + + initialize: function(event, win){ + win = win || window; + var doc = win.document; + event = event || win.event; + if (event.$extended) return event; + this.$extended = true; + var type = event.type; + var target = event.target || event.srcElement; + while (target && target.nodeType == 3) target = target.parentNode; + + if (type.test(/key/)){ + var code = event.which || event.keyCode; + var key = Event.Keys.keyOf(code); + if (type == 'keydown'){ + var fKey = code - 111; + if (fKey > 0 && fKey < 13) key = 'f' + fKey; + } + key = key || String.fromCharCode(code).toLowerCase(); + } else if (type.match(/(click|mouse|menu)/i)){ + doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; + var page = { + x: event.pageX || event.clientX + doc.scrollLeft, + y: event.pageY || event.clientY + doc.scrollTop + }; + var client = { + x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX, + y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY + }; + if (type.match(/DOMMouseScroll|mousewheel/)){ + var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3; + } + var rightClick = (event.which == 3) || (event.button == 2); + var related = null; + if (type.match(/over|out/)){ + switch (type){ + case 'mouseover': related = event.relatedTarget || event.fromElement; break; + case 'mouseout': related = event.relatedTarget || event.toElement; + } + if (!(function(){ + while (related && related.nodeType == 3) related = related.parentNode; + return true; + }).create({attempt: Browser.Engine.gecko})()) related = false; + } + } + + return $extend(this, { + event: event, + type: type, + + page: page, + client: client, + rightClick: rightClick, + + wheel: wheel, + + relatedTarget: related, + target: target, + + code: code, + key: key, + + shift: event.shiftKey, + control: event.ctrlKey, + alt: event.altKey, + meta: event.metaKey + }); + } + +}); + +Event.Keys = new Hash({ + 'enter': 13, + 'up': 38, + 'down': 40, + 'left': 37, + 'right': 39, + 'esc': 27, + 'space': 32, + 'backspace': 8, + 'tab': 9, + 'delete': 46 +}); + +Event.implement({ + + stop: function(){ + return this.stopPropagation().preventDefault(); + }, + + stopPropagation: function(){ + if (this.event.stopPropagation) this.event.stopPropagation(); + else this.event.cancelBubble = true; + return this; + }, + + preventDefault: function(){ + if (this.event.preventDefault) this.event.preventDefault(); + else this.event.returnValue = false; + return this; + } + +}); + + +/* +Script: Class.js + Contains the Class Function for easily creating, extending, and implementing reusable Classes. + +License: + MIT-style license. +*/ + +var Class = new Native({ + + name: 'Class', + + initialize: function(properties){ + properties = properties || {}; + var klass = function(){ + for (var key in this){ + if ($type(this[key]) != 'function') this[key] = $unlink(this[key]); + } + this.constructor = klass; + if (Class.prototyping) return this; + var instance = (this.initialize) ? this.initialize.apply(this, arguments) : this; + if (this.options && this.options.initialize) this.options.initialize.call(this); + return instance; + }; + + for (var mutator in Class.Mutators){ + if (!properties[mutator]) continue; + properties = Class.Mutators[mutator](properties, properties[mutator]); + delete properties[mutator]; + } + + $extend(klass, this); + klass.constructor = Class; + klass.prototype = properties; + return klass; + } + +}); + +Class.Mutators = { + + Extends: function(self, klass){ + Class.prototyping = klass.prototype; + var subclass = new klass; + delete subclass.parent; + subclass = Class.inherit(subclass, self); + delete Class.prototyping; + return subclass; + }, + + Implements: function(self, klasses){ + $splat(klasses).each(function(klass){ + Class.prototying = klass; + $extend(self, ($type(klass) == 'class') ? new klass : klass); + delete Class.prototyping; + }); + return self; + } + +}; + +Class.extend({ + + inherit: function(object, properties){ + var caller = arguments.callee.caller; + for (var key in properties){ + var override = properties[key]; + var previous = object[key]; + var type = $type(override); + if (previous && type == 'function'){ + if (override != previous){ + if (caller){ + override.__parent = previous; + object[key] = override; + } else { + Class.override(object, key, override); + } + } + } else if(type == 'object'){ + object[key] = $merge(previous, override); + } else { + object[key] = override; + } + } + + if (caller) object.parent = function(){ + return arguments.callee.caller.__parent.apply(this, arguments); + }; + + return object; + }, + + override: function(object, name, method){ + var parent = Class.prototyping; + if (parent && object[name] != parent[name]) parent = null; + var override = function(){ + var previous = this.parent; + this.parent = parent ? parent[name] : object[name]; + var value = method.apply(this, arguments); + this.parent = previous; + return value; + }; + object[name] = override; + } + +}); + +Class.implement({ + + implement: function(){ + var proto = this.prototype; + $each(arguments, function(properties){ + Class.inherit(proto, properties); + }); + return this; + } + +}); + + +/* +Script: Class.Extras.js + Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. + +License: + MIT-style license. +*/ + +var Chain = new Class({ + + $chain: [], + + chain: function(){ + this.$chain.extend(Array.flatten(arguments)); + return this; + }, + + callChain: function(){ + return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; + }, + + clearChain: function(){ + this.$chain.empty(); + return this; + } + +}); + +var Events = new Class({ + + $events: {}, + + addEvent: function(type, fn, internal){ + type = Events.removeOn(type); + if (fn != $empty){ + this.$events[type] = this.$events[type] || []; + this.$events[type].include(fn); + if (internal) fn.internal = true; + } + return this; + }, + + addEvents: function(events){ + for (var type in events) this.addEvent(type, events[type]); + return this; + }, + + fireEvent: function(type, args, delay){ + type = Events.removeOn(type); + if (!this.$events || !this.$events[type]) return this; + this.$events[type].each(function(fn){ + fn.create({'bind': this, 'delay': delay, 'arguments': args})(); + }, this); + return this; + }, + + removeEvent: function(type, fn){ + type = Events.removeOn(type); + if (!this.$events[type]) return this; + if (!fn.internal) this.$events[type].erase(fn); + return this; + }, + + removeEvents: function(events){ + if ($type(events) == 'object'){ + for (var type in events) this.removeEvent(type, events[type]); + return this; + } + if (events) events = Events.removeOn(events); + for (var type in this.$events){ + if (events && events != type) continue; + var fns = this.$events[type]; + for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]); + } + return this; + } + +}); + +Events.removeOn = function(string){ + return string.replace(/^on([A-Z])/, function(full, first) { + return first.toLowerCase(); + }); +}; + +var Options = new Class({ + + setOptions: function(){ + this.options = $merge.run([this.options].extend(arguments)); + if (!this.addEvent) return this; + for (var option in this.options){ + if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; + this.addEvent(option, this.options[option]); + delete this.options[option]; + } + return this; + } + +}); + + +/* +Script: Element.js + One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, + time-saver methods to let you easily work with HTML Elements. + +License: + MIT-style license. +*/ + +var Element = new Native({ + + name: 'Element', + + legacy: window.Element, + + initialize: function(tag, props){ + var konstructor = Element.Constructors.get(tag); + if (konstructor) return konstructor(props); + if (typeof tag == 'string') return document.newElement(tag, props); + return $(tag).set(props); + }, + + afterImplement: function(key, value){ + Element.Prototype[key] = value; + if (Array[key]) return; + Elements.implement(key, function(){ + var items = [], elements = true; + for (var i = 0, j = this.length; i < j; i++){ + var returns = this[i][key].apply(this[i], arguments); + items.push(returns); + if (elements) elements = ($type(returns) == 'element'); + } + return (elements) ? new Elements(items) : items; + }); + } + +}); + +Element.Prototype = {$family: {name: 'element'}}; + +Element.Constructors = new Hash; + +var IFrame = new Native({ + + name: 'IFrame', + + generics: false, + + initialize: function(){ + var params = Array.link(arguments, {properties: Object.type, iframe: $defined}); + var props = params.properties || {}; + var iframe = $(params.iframe) || false; + var onload = props.onload || $empty; + delete props.onload; + props.id = props.name = $pick(props.id, props.name, iframe.id, iframe.name, 'IFrame_' + $time()); + iframe = new Element(iframe || 'iframe', props); + var onFrameLoad = function(){ + var host = $try(function(){ + return iframe.contentWindow.location.host; + }); + if (host && host == window.location.host){ + var win = new Window(iframe.contentWindow); + new Document(iframe.contentWindow.document); + $extend(win.Element.prototype, Element.Prototype); + } + onload.call(iframe.contentWindow, iframe.contentWindow.document); + }; + (window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad); + return iframe; + } + +}); + +var Elements = new Native({ + + initialize: function(elements, options){ + options = $extend({ddup: true, cash: true}, options); + elements = elements || []; + if (options.ddup || options.cash){ + var uniques = {}, returned = []; + for (var i = 0, l = elements.length; i < l; i++){ + var el = $.element(elements[i], !options.cash); + if (options.ddup){ + if (uniques[el.uid]) continue; + uniques[el.uid] = true; + } + returned.push(el); + } + elements = returned; + } + return (options.cash) ? $extend(elements, this) : elements; + } + +}); + +Elements.implement({ + + filter: function(filter, bind){ + if (!filter) return this; + return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){ + return item.match(filter); + } : filter, bind)); + } + +}); + +Document.implement({ + + newElement: function(tag, props){ + if (Browser.Engine.trident && props){ + ['name', 'type', 'checked'].each(function(attribute){ + if (!props[attribute]) return; + tag += ' ' + attribute + '="' + props[attribute] + '"'; + if (attribute != 'checked') delete props[attribute]; + }); + tag = '<' + tag + '>'; + } + return $.element(this.createElement(tag)).set(props); + }, + + newTextNode: function(text){ + return this.createTextNode(text); + }, + + getDocument: function(){ + return this; + }, + + getWindow: function(){ + return this.window; + } + +}); + +Window.implement({ + + $: function(el, nocash){ + if (el && el.$family && el.uid) return el; + var type = $type(el); + return ($[type]) ? $[type](el, nocash, this.document) : null; + }, + + $$: function(selector){ + if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector); + var elements = []; + var args = Array.flatten(arguments); + for (var i = 0, l = args.length; i < l; i++){ + var item = args[i]; + switch ($type(item)){ + case 'element': elements.push(item); break; + case 'string': elements.extend(this.document.getElements(item, true)); + } + } + return new Elements(elements); + }, + + getDocument: function(){ + return this.document; + }, + + getWindow: function(){ + return this; + } + +}); + +$.string = function(id, nocash, doc){ + id = doc.getElementById(id); + return (id) ? $.element(id, nocash) : null; +}; + +$.element = function(el, nocash){ + $uid(el); + if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){ + var proto = Element.Prototype; + for (var p in proto) el[p] = proto[p]; + }; + return el; +}; + +$.object = function(obj, nocash, doc){ + if (obj.toElement) return $.element(obj.toElement(doc), nocash); + return null; +}; + +$.textnode = $.whitespace = $.window = $.document = $arguments(0); + +Native.implement([Element, Document], { + + getElement: function(selector, nocash){ + return $(this.getElements(selector, true)[0] || null, nocash); + }, + + getElements: function(tags, nocash){ + tags = tags.split(','); + var elements = []; + var ddup = (tags.length > 1); + tags.each(function(tag){ + var partial = this.getElementsByTagName(tag.trim()); + (ddup) ? elements.extend(partial) : elements = partial; + }, this); + return new Elements(elements, {ddup: ddup, cash: !nocash}); + } + +}); + +(function(){ + +var collected = {}, storage = {}; +var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'}; + +var get = function(uid){ + return (storage[uid] || (storage[uid] = {})); +}; + +var clean = function(item, retain){ + if (!item) return; + var uid = item.uid; + if (Browser.Engine.trident){ + if (item.clearAttributes){ + var clone = retain && item.cloneNode(false); + item.clearAttributes(); + if (clone) item.mergeAttributes(clone); + } else if (item.removeEvents){ + item.removeEvents(); + } + if ((/object/i).test(item.tagName)){ + for (var p in item){ + if (typeof item[p] == 'function') item[p] = $empty; + } + Element.dispose(item); + } + } + if (!uid) return; + collected[uid] = storage[uid] = null; +}; + +var purge = function(){ + Hash.each(collected, clean); + if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean); + if (window.CollectGarbage) CollectGarbage(); + collected = storage = null; +}; + +var walk = function(element, walk, start, match, all, nocash){ + var el = element[start || walk]; + var elements = []; + while (el){ + if (el.nodeType == 1 && (!match || Element.match(el, match))){ + if (!all) return $(el, nocash); + elements.push(el); + } + el = el[walk]; + } + return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null; +}; + +var attributes = { + 'html': 'innerHTML', + 'class': 'className', + 'for': 'htmlFor', + 'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent' +}; +var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer']; +var camels = ['value', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap']; + +Hash.extend(attributes, bools.associate(bools)); +Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase))); + +var inserters = { + + before: function(context, element){ + if (element.parentNode) element.parentNode.insertBefore(context, element); + }, + + after: function(context, element){ + if (!element.parentNode) return; + var next = element.nextSibling; + (next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context); + }, + + bottom: function(context, element){ + element.appendChild(context); + }, + + top: function(context, element){ + var first = element.firstChild; + (first) ? element.insertBefore(context, first) : element.appendChild(context); + } + +}; + +inserters.inside = inserters.bottom; + +Hash.each(inserters, function(inserter, where){ + + where = where.capitalize(); + + Element.implement('inject' + where, function(el){ + inserter(this, $(el, true)); + return this; + }); + + Element.implement('grab' + where, function(el){ + inserter($(el, true), this); + return this; + }); + +}); + +Element.implement({ + + set: function(prop, value){ + switch ($type(prop)){ + case 'object': + for (var p in prop) this.set(p, prop[p]); + break; + case 'string': + var property = Element.Properties.get(prop); + (property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value); + } + return this; + }, + + get: function(prop){ + var property = Element.Properties.get(prop); + return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop); + }, + + erase: function(prop){ + var property = Element.Properties.get(prop); + (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); + return this; + }, + + setProperty: function(attribute, value){ + var key = attributes[attribute]; + if (value == undefined) return this.removeProperty(attribute); + if (key && bools[attribute]) value = !!value; + (key) ? this[key] = value : this.setAttribute(attribute, '' + value); + return this; + }, + + setProperties: function(attributes){ + for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); + return this; + }, + + getProperty: function(attribute){ + var key = attributes[attribute]; + var value = (key) ? this[key] : this.getAttribute(attribute, 2); + return (bools[attribute]) ? !!value : (key) ? value : value || null; + }, + + getProperties: function(){ + var args = $A(arguments); + return args.map(this.getProperty, this).associate(args); + }, + + removeProperty: function(attribute){ + var key = attributes[attribute]; + (key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute); + return this; + }, + + removeProperties: function(){ + Array.each(arguments, this.removeProperty, this); + return this; + }, + + hasClass: function(className){ + return this.className.contains(className, ' '); + }, + + addClass: function(className){ + if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean(); + return this; + }, + + removeClass: function(className){ + this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1'); + return this; + }, + + toggleClass: function(className){ + return this.hasClass(className) ? this.removeClass(className) : this.addClass(className); + }, + + adopt: function(){ + Array.flatten(arguments).each(function(element){ + element = $(element, true); + if (element) this.appendChild(element); + }, this); + return this; + }, + + appendText: function(text, where){ + return this.grab(this.getDocument().newTextNode(text), where); + }, + + grab: function(el, where){ + inserters[where || 'bottom']($(el, true), this); + return this; + }, + + inject: function(el, where){ + inserters[where || 'bottom'](this, $(el, true)); + return this; + }, + + replaces: function(el){ + el = $(el, true); + el.parentNode.replaceChild(this, el); + return this; + }, + + wraps: function(el, where){ + el = $(el, true); + return this.replaces(el).grab(el, where); + }, + + getPrevious: function(match, nocash){ + return walk(this, 'previousSibling', null, match, false, nocash); + }, + + getAllPrevious: function(match, nocash){ + return walk(this, 'previousSibling', null, match, true, nocash); + }, + + getNext: function(match, nocash){ + return walk(this, 'nextSibling', null, match, false, nocash); + }, + + getAllNext: function(match, nocash){ + return walk(this, 'nextSibling', null, match, true, nocash); + }, + + getFirst: function(match, nocash){ + return walk(this, 'nextSibling', 'firstChild', match, false, nocash); + }, + + getLast: function(match, nocash){ + return walk(this, 'previousSibling', 'lastChild', match, false, nocash); + }, + + getParent: function(match, nocash){ + return walk(this, 'parentNode', null, match, false, nocash); + }, + + getParents: function(match, nocash){ + return walk(this, 'parentNode', null, match, true, nocash); + }, + + getChildren: function(match, nocash){ + return walk(this, 'nextSibling', 'firstChild', match, true, nocash); + }, + + getWindow: function(){ + return this.ownerDocument.window; + }, + + getDocument: function(){ + return this.ownerDocument; + }, + + getElementById: function(id, nocash){ + var el = this.ownerDocument.getElementById(id); + if (!el) return null; + for (var parent = el.parentNode; parent != this; parent = parent.parentNode){ + if (!parent) return null; + } + return $.element(el, nocash); + }, + + getSelected: function(){ + return new Elements($A(this.options).filter(function(option){ + return option.selected; + })); + }, + + getComputedStyle: function(property){ + if (this.currentStyle) return this.currentStyle[property.camelCase()]; + var computed = this.getDocument().defaultView.getComputedStyle(this, null); + return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null; + }, + + toQueryString: function(){ + var queryString = []; + this.getElements('input, select, textarea', true).each(function(el){ + if (!el.name || el.disabled) return; + var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){ + return opt.value; + }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value; + $splat(value).each(function(val){ + if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val)); + }); + }); + return queryString.join('&'); + }, + + clone: function(contents, keepid){ + contents = contents !== false; + var clone = this.cloneNode(contents); + var clean = function(node, element){ + if (!keepid) node.removeAttribute('id'); + if (Browser.Engine.trident){ + node.clearAttributes(); + node.mergeAttributes(element); + node.removeAttribute('uid'); + if (node.options){ + var no = node.options, eo = element.options; + for (var j = no.length; j--;) no[j].selected = eo[j].selected; + } + } + var prop = props[element.tagName.toLowerCase()]; + if (prop && element[prop]) node[prop] = element[prop]; + }; + + if (contents){ + var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*'); + for (var i = ce.length; i--;) clean(ce[i], te[i]); + } + + clean(clone, this); + return $(clone); + }, + + destroy: function(){ + Element.empty(this); + Element.dispose(this); + clean(this, true); + return null; + }, + + empty: function(){ + $A(this.childNodes).each(function(node){ + Element.destroy(node); + }); + return this; + }, + + dispose: function(){ + return (this.parentNode) ? this.parentNode.removeChild(this) : this; + }, + + hasChild: function(el){ + el = $(el, true); + if (!el) return false; + if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el); + return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16); + }, + + match: function(tag){ + return (!tag || (tag == this) || (Element.get(this, 'tag') == tag)); + } + +}); + +Native.implement([Element, Window, Document], { + + addListener: function(type, fn){ + if (type == 'unload'){ + var old = fn, self = this; + fn = function(){ + self.removeListener('unload', fn); + old(); + }; + } else { + collected[this.uid] = this; + } + if (this.addEventListener) this.addEventListener(type, fn, false); + else this.attachEvent('on' + type, fn); + return this; + }, + + removeListener: function(type, fn){ + if (this.removeEventListener) this.removeEventListener(type, fn, false); + else this.detachEvent('on' + type, fn); + return this; + }, + + retrieve: function(property, dflt){ + var storage = get(this.uid), prop = storage[property]; + if (dflt != undefined && prop == undefined) prop = storage[property] = dflt; + return $pick(prop); + }, + + store: function(property, value){ + var storage = get(this.uid); + storage[property] = value; + return this; + }, + + eliminate: function(property){ + var storage = get(this.uid); + delete storage[property]; + return this; + } + +}); + +window.addListener('unload', purge); + +})(); + +Element.Properties = new Hash; + +Element.Properties.style = { + + set: function(style){ + this.style.cssText = style; + }, + + get: function(){ + return this.style.cssText; + }, + + erase: function(){ + this.style.cssText = ''; + } + +}; + +Element.Properties.tag = { + + get: function(){ + return this.tagName.toLowerCase(); + } + +}; + +Element.Properties.html = (function(){ + var wrapper = document.createElement('div'); + + var translations = { + table: [1, '', '
'], + select: [1, ''], + tbody: [2, '', '
'], + tr: [3, '', '
'] + }; + translations.thead = translations.tfoot = translations.tbody; + + var html = { + set: function(){ + var html = Array.flatten(arguments).join(''); + var wrap = Browser.Engine.trident && translations[this.get('tag')]; + if (wrap){ + var first = wrapper; + first.innerHTML = wrap[1] + html + wrap[2]; + for (var i = wrap[0]; i--;) first = first.firstChild; + this.empty().adopt(first.childNodes); + } else { + this.innerHTML = html; + } + } + }; + + html.erase = html.set; + + return html; +})(); + +if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = { + get: function(){ + if (this.innerText) return this.innerText; + var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body); + var text = temp.innerText; + temp.destroy(); + return text; + } +}; + + +/* +Script: Element.Event.js + Contains Element methods for dealing with events, and custom Events. + +License: + MIT-style license. +*/ + +Element.Properties.events = {set: function(events){ + this.addEvents(events); +}}; + +Native.implement([Element, Window, Document], { + + addEvent: function(type, fn){ + var events = this.retrieve('events', {}); + events[type] = events[type] || {'keys': [], 'values': []}; + if (events[type].keys.contains(fn)) return this; + events[type].keys.push(fn); + var realType = type, custom = Element.Events.get(type), condition = fn, self = this; + if (custom){ + if (custom.onAdd) custom.onAdd.call(this, fn); + if (custom.condition){ + condition = function(event){ + if (custom.condition.call(this, event)) return fn.call(this, event); + return true; + }; + } + realType = custom.base || realType; + } + var defn = function(){ + return fn.call(self); + }; + var nativeEvent = Element.NativeEvents[realType]; + if (nativeEvent){ + if (nativeEvent == 2){ + defn = function(event){ + event = new Event(event, self.getWindow()); + if (condition.call(self, event) === false) event.stop(); + }; + } + this.addListener(realType, defn); + } + events[type].values.push(defn); + return this; + }, + + removeEvent: function(type, fn){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + var pos = events[type].keys.indexOf(fn); + if (pos == -1) return this; + events[type].keys.splice(pos, 1); + var value = events[type].values.splice(pos, 1)[0]; + var custom = Element.Events.get(type); + if (custom){ + if (custom.onRemove) custom.onRemove.call(this, fn); + type = custom.base || type; + } + return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this; + }, + + addEvents: function(events){ + for (var event in events) this.addEvent(event, events[event]); + return this; + }, + + removeEvents: function(events){ + if ($type(events) == 'object'){ + for (var type in events) this.removeEvent(type, events[type]); + return this; + } + var attached = this.retrieve('events'); + if (!attached) return this; + if (!events){ + for (var type in attached) this.removeEvents(type); + this.eliminate('events'); + } else if (attached[events]){ + while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]); + attached[events] = null; + } + return this; + }, + + fireEvent: function(type, args, delay){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + events[type].keys.each(function(fn){ + fn.create({'bind': this, 'delay': delay, 'arguments': args})(); + }, this); + return this; + }, + + cloneEvents: function(from, type){ + from = $(from); + var fevents = from.retrieve('events'); + if (!fevents) return this; + if (!type){ + for (var evType in fevents) this.cloneEvents(from, evType); + } else if (fevents[type]){ + fevents[type].keys.each(function(fn){ + this.addEvent(type, fn); + }, this); + } + return this; + } + +}); + +Element.NativeEvents = { + click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons + mousewheel: 2, DOMMouseScroll: 2, //mouse wheel + mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement + keydown: 2, keypress: 2, keyup: 2, //keyboard + focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements + load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window + error: 1, abort: 1, scroll: 1 //misc +}; + +(function(){ + +var $check = function(event){ + var related = event.relatedTarget; + if (related == undefined) return true; + if (related === false) return false; + return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related)); +}; + +Element.Events = new Hash({ + + mouseenter: { + base: 'mouseover', + condition: $check + }, + + mouseleave: { + base: 'mouseout', + condition: $check + }, + + mousewheel: { + base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel' + } + +}); + +})(); + + +/* +Script: Element.Style.js + Contains methods for interacting with the styles of Elements in a fashionable way. + +License: + MIT-style license. +*/ + +Element.Properties.styles = {set: function(styles){ + this.setStyles(styles); +}}; + +Element.Properties.opacity = { + + set: function(opacity, novisibility){ + if (!novisibility){ + if (opacity == 0){ + if (this.style.visibility != 'hidden') this.style.visibility = 'hidden'; + } else { + if (this.style.visibility != 'visible') this.style.visibility = 'visible'; + } + } + if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1; + if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')'; + this.style.opacity = opacity; + this.store('opacity', opacity); + }, + + get: function(){ + return this.retrieve('opacity', 1); + } + +}; + +Element.implement({ + + setOpacity: function(value){ + return this.set('opacity', value, true); + }, + + getOpacity: function(){ + return this.get('opacity'); + }, + + setStyle: function(property, value){ + switch (property){ + case 'opacity': return this.set('opacity', parseFloat(value)); + case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; + } + property = property.camelCase(); + if ($type(value) != 'string'){ + var map = (Element.Styles.get(property) || '@').split(' '); + value = $splat(value).map(function(val, i){ + if (!map[i]) return ''; + return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; + }).join(' '); + } else if (value == String(Number(value))){ + value = Math.round(value); + } + this.style[property] = value; + return this; + }, + + getStyle: function(property){ + switch (property){ + case 'opacity': return this.get('opacity'); + case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat'; + } + property = property.camelCase(); + var result = this.style[property]; + if (!$chk(result)){ + result = []; + for (var style in Element.ShortStyles){ + if (property != style) continue; + for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s)); + return result.join(' '); + } + result = this.getComputedStyle(property); + } + if (result){ + result = String(result); + var color = result.match(/rgba?\([\d\s,]+\)/); + if (color) result = result.replace(color[0], color[0].rgbToHex()); + } + if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result)))){ + if (property.test(/^(height|width)$/)){ + var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; + values.each(function(value){ + size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); + }, this); + return this['offset' + property.capitalize()] - size + 'px'; + } + if ((Browser.Engine.presto) && String(result).test('px')) return result; + if (property.test(/(border(.+)Width|margin|padding)/)) return '0px'; + } + return result; + }, + + setStyles: function(styles){ + for (var style in styles) this.setStyle(style, styles[style]); + return this; + }, + + getStyles: function(){ + var result = {}; + Array.each(arguments, function(key){ + result[key] = this.getStyle(key); + }, this); + return result; + } + +}); + +Element.Styles = new Hash({ + left: '@px', top: '@px', bottom: '@px', right: '@px', + width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', + backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', + fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', + margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', + borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', + zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@' +}); + +Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; + +['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ + var Short = Element.ShortStyles; + var All = Element.Styles; + ['margin', 'padding'].each(function(style){ + var sd = style + direction; + Short[style][sd] = All[sd] = '@px'; + }); + var bd = 'border' + direction; + Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; + var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; + Short[bd] = {}; + Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; + Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; + Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; +}); + + +/* +Script: Element.Dimensions.js + Contains methods to work with size, scroll, or positioning of Elements and the window object. + +License: + MIT-style license. + +Credits: + - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). + - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). +*/ + +(function(){ + +Element.implement({ + + scrollTo: function(x, y){ + if (isBody(this)){ + this.getWindow().scrollTo(x, y); + } else { + this.scrollLeft = x; + this.scrollTop = y; + } + return this; + }, + + getSize: function(){ + if (isBody(this)) return this.getWindow().getSize(); + return {x: this.offsetWidth, y: this.offsetHeight}; + }, + + getScrollSize: function(){ + if (isBody(this)) return this.getWindow().getScrollSize(); + return {x: this.scrollWidth, y: this.scrollHeight}; + }, + + getScroll: function(){ + if (isBody(this)) return this.getWindow().getScroll(); + return {x: this.scrollLeft, y: this.scrollTop}; + }, + + getScrolls: function(){ + var element = this, position = {x: 0, y: 0}; + while (element && !isBody(element)){ + position.x += element.scrollLeft; + position.y += element.scrollTop; + element = element.parentNode; + } + return position; + }, + + getOffsetParent: function(){ + var element = this; + if (isBody(element)) return null; + if (!Browser.Engine.trident) return element.offsetParent; + while ((element = element.parentNode) && !isBody(element)){ + if (styleString(element, 'position') != 'static') return element; + } + return null; + }, + + getOffsets: function(){ + if (Browser.Engine.trident){ + var bound = this.getBoundingClientRect(), html = this.getDocument().documentElement; + return { + x: bound.left + html.scrollLeft - html.clientLeft, + y: bound.top + html.scrollTop - html.clientTop + }; + } + + var element = this, position = {x: 0, y: 0}; + if (isBody(this)) return position; + + while (element && !isBody(element)){ + position.x += element.offsetLeft; + position.y += element.offsetTop; + + if (Browser.Engine.gecko){ + if (!borderBox(element)){ + position.x += leftBorder(element); + position.y += topBorder(element); + } + var parent = element.parentNode; + if (parent && styleString(parent, 'overflow') != 'visible'){ + position.x += leftBorder(parent); + position.y += topBorder(parent); + } + } else if (element != this && Browser.Engine.webkit){ + position.x += leftBorder(element); + position.y += topBorder(element); + } + + element = element.offsetParent; + } + if (Browser.Engine.gecko && !borderBox(this)){ + position.x -= leftBorder(this); + position.y -= topBorder(this); + } + return position; + }, + + getPosition: function(relative){ + if (isBody(this)) return {x: 0, y: 0}; + var offset = this.getOffsets(), scroll = this.getScrolls(); + var position = {x: offset.x - scroll.x, y: offset.y - scroll.y}; + var relativePosition = (relative && (relative = $(relative))) ? relative.getPosition() : {x: 0, y: 0}; + return {x: position.x - relativePosition.x, y: position.y - relativePosition.y}; + }, + + getCoordinates: function(element){ + if (isBody(this)) return this.getWindow().getCoordinates(); + var position = this.getPosition(element), size = this.getSize(); + var obj = {left: position.x, top: position.y, width: size.x, height: size.y}; + obj.right = obj.left + obj.width; + obj.bottom = obj.top + obj.height; + return obj; + }, + + computePosition: function(obj){ + return {left: obj.x - styleNumber(this, 'margin-left'), top: obj.y - styleNumber(this, 'margin-top')}; + }, + + position: function(obj){ + return this.setStyles(this.computePosition(obj)); + } + +}); + +Native.implement([Document, Window], { + + getSize: function(){ + var win = this.getWindow(); + if (Browser.Engine.presto || Browser.Engine.webkit) return {x: win.innerWidth, y: win.innerHeight}; + var doc = getCompatElement(this); + return {x: doc.clientWidth, y: doc.clientHeight}; + }, + + getScroll: function(){ + var win = this.getWindow(); + var doc = getCompatElement(this); + return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; + }, + + getScrollSize: function(){ + var doc = getCompatElement(this); + var min = this.getSize(); + return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)}; + }, + + getPosition: function(){ + return {x: 0, y: 0}; + }, + + getCoordinates: function(){ + var size = this.getSize(); + return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; + } + +}); + +// private methods + +var styleString = Element.getComputedStyle; + +function styleNumber(element, style){ + return styleString(element, style).toInt() || 0; +}; + +function borderBox(element){ + return styleString(element, '-moz-box-sizing') == 'border-box'; +}; + +function topBorder(element){ + return styleNumber(element, 'border-top-width'); +}; + +function leftBorder(element){ + return styleNumber(element, 'border-left-width'); +}; + +function isBody(element){ + return (/^(?:body|html)$/i).test(element.tagName); +}; + +function getCompatElement(element){ + var doc = element.getDocument(); + return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; +}; + +})(); + +//aliases + +Native.implement([Window, Document, Element], { + + getHeight: function(){ + return this.getSize().y; + }, + + getWidth: function(){ + return this.getSize().x; + }, + + getScrollTop: function(){ + return this.getScroll().y; + }, + + getScrollLeft: function(){ + return this.getScroll().x; + }, + + getScrollHeight: function(){ + return this.getScrollSize().y; + }, + + getScrollWidth: function(){ + return this.getScrollSize().x; + }, + + getTop: function(){ + return this.getPosition().y; + }, + + getLeft: function(){ + return this.getPosition().x; + } + +}); + + +/* +Script: Selectors.js + Adds advanced CSS Querying capabilities for targeting elements. Also includes pseudoselectors support. + +License: + MIT-style license. +*/ + +Native.implement([Document, Element], { + + getElements: function(expression, nocash){ + expression = expression.split(','); + var items, local = {}; + for (var i = 0, l = expression.length; i < l; i++){ + var selector = expression[i], elements = Selectors.Utils.search(this, selector, local); + if (i != 0 && elements.item) elements = $A(elements); + items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements); + } + return new Elements(items, {ddup: (expression.length > 1), cash: !nocash}); + } + +}); + +Element.implement({ + + match: function(selector){ + if (!selector || (selector == this)) return true; + var tagid = Selectors.Utils.parseTagAndID(selector); + var tag = tagid[0], id = tagid[1]; + if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false; + var parsed = Selectors.Utils.parseSelector(selector); + return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true; + } + +}); + +var Selectors = {Cache: {nth: {}, parsed: {}}}; + +Selectors.RegExps = { + id: (/#([\w-]+)/), + tag: (/^(\w+|\*)/), + quick: (/^(\w+|\*)$/), + splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g), + combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g) +}; + +Selectors.Utils = { + + chk: function(item, uniques){ + if (!uniques) return true; + var uid = $uid(item); + if (!uniques[uid]) return uniques[uid] = true; + return false; + }, + + parseNthArgument: function(argument){ + if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument]; + var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/); + if (!parsed) return false; + var inta = parseInt(parsed[1]); + var a = (inta || inta === 0) ? inta : 1; + var special = parsed[2] || false; + var b = parseInt(parsed[3]) || 0; + if (a != 0){ + b--; + while (b < 1) b += a; + while (b >= a) b -= a; + } else { + a = b; + special = 'index'; + } + switch (special){ + case 'n': parsed = {a: a, b: b, special: 'n'}; break; + case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break; + case 'even': parsed = {a: 2, b: 1, special: 'n'}; break; + case 'first': parsed = {a: 0, special: 'index'}; break; + case 'last': parsed = {special: 'last-child'}; break; + case 'only': parsed = {special: 'only-child'}; break; + default: parsed = {a: (a - 1), special: 'index'}; + } + + return Selectors.Cache.nth[argument] = parsed; + }, + + parseSelector: function(selector){ + if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector]; + var m, parsed = {classes: [], pseudos: [], attributes: []}; + while ((m = Selectors.RegExps.combined.exec(selector))){ + var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7]; + if (cn){ + parsed.classes.push(cn); + } else if (pn){ + var parser = Selectors.Pseudo.get(pn); + if (parser) parsed.pseudos.push({parser: parser, argument: pa}); + else parsed.attributes.push({name: pn, operator: '=', value: pa}); + } else if (an){ + parsed.attributes.push({name: an, operator: ao, value: av}); + } + } + if (!parsed.classes.length) delete parsed.classes; + if (!parsed.attributes.length) delete parsed.attributes; + if (!parsed.pseudos.length) delete parsed.pseudos; + if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null; + return Selectors.Cache.parsed[selector] = parsed; + }, + + parseTagAndID: function(selector){ + var tag = selector.match(Selectors.RegExps.tag); + var id = selector.match(Selectors.RegExps.id); + return [(tag) ? tag[1] : '*', (id) ? id[1] : false]; + }, + + filter: function(item, parsed, local){ + var i; + if (parsed.classes){ + for (i = parsed.classes.length; i--; i){ + var cn = parsed.classes[i]; + if (!Selectors.Filters.byClass(item, cn)) return false; + } + } + if (parsed.attributes){ + for (i = parsed.attributes.length; i--; i){ + var att = parsed.attributes[i]; + if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false; + } + } + if (parsed.pseudos){ + for (i = parsed.pseudos.length; i--; i){ + var psd = parsed.pseudos[i]; + if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false; + } + } + return true; + }, + + getByTagAndID: function(ctx, tag, id){ + if (id){ + var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true); + return (item && Selectors.Filters.byTag(item, tag)) ? [item] : []; + } else { + return ctx.getElementsByTagName(tag); + } + }, + + search: function(self, expression, local){ + var splitters = []; + + var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){ + splitters.push(m1); + return ':)' + m2; + }).split(':)'); + + var items, filtered, item; + + for (var i = 0, l = selectors.length; i < l; i++){ + + var selector = selectors[i]; + + if (i == 0 && Selectors.RegExps.quick.test(selector)){ + items = self.getElementsByTagName(selector); + continue; + } + + var splitter = splitters[i - 1]; + + var tagid = Selectors.Utils.parseTagAndID(selector); + var tag = tagid[0], id = tagid[1]; + + if (i == 0){ + items = Selectors.Utils.getByTagAndID(self, tag, id); + } else { + var uniques = {}, found = []; + for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques); + items = found; + } + + var parsed = Selectors.Utils.parseSelector(selector); + + if (parsed){ + filtered = []; + for (var m = 0, n = items.length; m < n; m++){ + item = items[m]; + if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item); + } + items = filtered; + } + + } + + return items; + + } + +}; + +Selectors.Getters = { + + ' ': function(found, self, tag, id, uniques){ + var items = Selectors.Utils.getByTagAndID(self, tag, id); + for (var i = 0, l = items.length; i < l; i++){ + var item = items[i]; + if (Selectors.Utils.chk(item, uniques)) found.push(item); + } + return found; + }, + + '>': function(found, self, tag, id, uniques){ + var children = Selectors.Utils.getByTagAndID(self, tag, id); + for (var i = 0, l = children.length; i < l; i++){ + var child = children[i]; + if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child); + } + return found; + }, + + '+': function(found, self, tag, id, uniques){ + while ((self = self.nextSibling)){ + if (self.nodeType == 1){ + if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); + break; + } + } + return found; + }, + + '~': function(found, self, tag, id, uniques){ + while ((self = self.nextSibling)){ + if (self.nodeType == 1){ + if (!Selectors.Utils.chk(self, uniques)) break; + if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self); + } + } + return found; + } + +}; + +Selectors.Filters = { + + byTag: function(self, tag){ + return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag)); + }, + + byID: function(self, id){ + return (!id || (self.id && self.id == id)); + }, + + byClass: function(self, klass){ + return (self.className && self.className.contains(klass, ' ')); + }, + + byPseudo: function(self, parser, argument, local){ + return parser.call(self, argument, local); + }, + + byAttribute: function(self, name, operator, value){ + var result = Element.prototype.getProperty.call(self, name); + if (!result) return (operator == '!='); + if (!operator || value == undefined) return true; + switch (operator){ + case '=': return (result == value); + case '*=': return (result.contains(value)); + case '^=': return (result.substr(0, value.length) == value); + case '$=': return (result.substr(result.length - value.length) == value); + case '!=': return (result != value); + case '~=': return result.contains(value, ' '); + case '|=': return result.contains(value, '-'); + } + return false; + } + +}; + +Selectors.Pseudo = new Hash({ + + // w3c pseudo selectors + + checked: function(){ + return this.checked; + }, + + empty: function(){ + return !(this.innerText || this.textContent || '').length; + }, + + not: function(selector){ + return !Element.match(this, selector); + }, + + contains: function(text){ + return (this.innerText || this.textContent || '').contains(text); + }, + + 'first-child': function(){ + return Selectors.Pseudo.index.call(this, 0); + }, + + 'last-child': function(){ + var element = this; + while ((element = element.nextSibling)){ + if (element.nodeType == 1) return false; + } + return true; + }, + + 'only-child': function(){ + var prev = this; + while ((prev = prev.previousSibling)){ + if (prev.nodeType == 1) return false; + } + var next = this; + while ((next = next.nextSibling)){ + if (next.nodeType == 1) return false; + } + return true; + }, + + 'nth-child': function(argument, local){ + argument = (argument == undefined) ? 'n' : argument; + var parsed = Selectors.Utils.parseNthArgument(argument); + if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local); + var count = 0; + local.positions = local.positions || {}; + var uid = $uid(this); + if (!local.positions[uid]){ + var self = this; + while ((self = self.previousSibling)){ + if (self.nodeType != 1) continue; + count ++; + var position = local.positions[$uid(self)]; + if (position != undefined){ + count = position + count; + break; + } + } + local.positions[uid] = count; + } + return (local.positions[uid] % parsed.a == parsed.b); + }, + + // custom pseudo selectors + + index: function(index){ + var element = this, count = 0; + while ((element = element.previousSibling)){ + if (element.nodeType == 1 && ++count > index) return false; + } + return (count == index); + }, + + even: function(argument, local){ + return Selectors.Pseudo['nth-child'].call(this, '2n+1', local); + }, + + odd: function(argument, local){ + return Selectors.Pseudo['nth-child'].call(this, '2n', local); + } + +}); + + +/* +Script: Domready.js + Contains the domready custom event. + +License: + MIT-style license. +*/ + +Element.Events.domready = { + + onAdd: function(fn){ + if (Browser.loaded) fn.call(this); + } + +}; + +(function(){ + + var domready = function(){ + if (Browser.loaded) return; + Browser.loaded = true; + window.fireEvent('domready'); + document.fireEvent('domready'); + }; + + if (Browser.Engine.trident){ + var temp = document.createElement('div'); + (function(){ + ($try(function(){ + temp.doScroll('left'); + return $(temp).inject(document.body).set('html', 'temp').dispose(); + })) ? domready() : arguments.callee.delay(50); + })(); + } else if (Browser.Engine.webkit && Browser.Engine.version < 525){ + (function(){ + (['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50); + })(); + } else { + window.addEvent('load', domready); + document.addEvent('DOMContentLoaded', domready); + } + +})(); + + +/* +Script: JSON.js + JSON encoder and decoder. + +License: + MIT-style license. + +See Also: + +*/ + +var JSON = new Hash({ + + $specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'}, + + $replaceChars: function(chr){ + return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16); + }, + + encode: function(obj){ + switch ($type(obj)){ + case 'string': + return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"'; + case 'array': + return '[' + String(obj.map(JSON.encode).filter($defined)) + ']'; + case 'object': case 'hash': + var string = []; + Hash.each(obj, function(value, key){ + var json = JSON.encode(value); + if (json) string.push(JSON.encode(key) + ':' + json); + }); + return '{' + string + '}'; + case 'number': case 'boolean': return String(obj); + case false: return 'null'; + } + return null; + }, + + decode: function(string, secure){ + if ($type(string) != 'string' || !string.length) return null; + if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null; + return eval('(' + string + ')'); + } + +}); + +Native.implement([Hash, Array, String, Number], { + + toJSON: function(){ + return JSON.encode(this); + } + +}); + + +/* +Script: Cookie.js + Class for creating, loading, and saving browser Cookies. + +License: + MIT-style license. + +Credits: + Based on the functions by Peter-Paul Koch (http://quirksmode.org). +*/ + +var Cookie = new Class({ + + Implements: Options, + + options: { + path: false, + domain: false, + duration: false, + secure: false, + document: document + }, + + initialize: function(key, options){ + this.key = key; + this.setOptions(options); + }, + + write: function(value){ + value = encodeURIComponent(value); + if (this.options.domain) value += '; domain=' + this.options.domain; + if (this.options.path) value += '; path=' + this.options.path; + if (this.options.duration){ + var date = new Date(); + date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000); + value += '; expires=' + date.toGMTString(); + } + if (this.options.secure) value += '; secure'; + this.options.document.cookie = this.key + '=' + value; + return this; + }, + + read: function(){ + var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)'); + return (value) ? decodeURIComponent(value[1]) : null; + }, + + dispose: function(){ + new Cookie(this.key, $merge(this.options, {duration: -1})).write(''); + return this; + } + +}); + +Cookie.write = function(key, value, options){ + return new Cookie(key, options).write(value); +}; + +Cookie.read = function(key){ + return new Cookie(key).read(); +}; + +Cookie.dispose = function(key, options){ + return new Cookie(key, options).dispose(); +}; + + +/* +Script: Swiff.js + Wrapper for embedding SWF movies. Supports (and fixes) External Interface Communication. + +License: + MIT-style license. + +Credits: + Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject. +*/ + +var Swiff = new Class({ + + Implements: [Options], + + options: { + id: null, + height: 1, + width: 1, + container: null, + properties: {}, + params: { + quality: 'high', + allowScriptAccess: 'always', + wMode: 'transparent', + swLiveConnect: true + }, + callBacks: {}, + vars: {} + }, + + toElement: function(){ + return this.object; + }, + + initialize: function(path, options){ + this.instance = 'Swiff_' + $time(); + + this.setOptions(options); + options = this.options; + var id = this.id = options.id || this.instance; + var container = $(options.container); + + Swiff.CallBacks[this.instance] = {}; + + var params = options.params, vars = options.vars, callBacks = options.callBacks; + var properties = $extend({height: options.height, width: options.width}, options.properties); + + var self = this; + + for (var callBack in callBacks){ + Swiff.CallBacks[this.instance][callBack] = (function(option){ + return function(){ + return option.apply(self.object, arguments); + }; + })(callBacks[callBack]); + vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack; + } + + params.flashVars = Hash.toQueryString(vars); + if (Browser.Engine.trident){ + properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; + params.movie = path; + } else { + properties.type = 'application/x-shockwave-flash'; + properties.data = path; + } + var build = ''; + } + build += ''; + this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild; + }, + + replaces: function(element){ + element = $(element, true); + element.parentNode.replaceChild(this.toElement(), element); + return this; + }, + + inject: function(element){ + $(element, true).appendChild(this.toElement()); + return this; + }, + + remote: function(){ + return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments)); + } + +}); + +Swiff.CallBacks = {}; + +Swiff.remote = function(obj, fn){ + var rs = obj.CallFunction('' + __flash__argumentsToXML(arguments, 2) + ''); + return eval(rs); +}; + + +/* +Script: Fx.js + Contains the basic animation logic to be extended by all other Fx Classes. + +License: + MIT-style license. +*/ + +var Fx = new Class({ + + Implements: [Chain, Events, Options], + + options: { + /* + onStart: $empty, + onCancel: $empty, + onComplete: $empty, + */ + fps: 50, + unit: false, + duration: 500, + link: 'ignore' + }, + + initialize: function(options){ + this.subject = this.subject || this; + this.setOptions(options); + this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt(); + var wait = this.options.wait; + if (wait === false) this.options.link = 'cancel'; + }, + + getTransition: function(){ + return function(p){ + return -(Math.cos(Math.PI * p) - 1) / 2; + }; + }, + + step: function(){ + var time = $time(); + if (time < this.time + this.options.duration){ + var delta = this.transition((time - this.time) / this.options.duration); + this.set(this.compute(this.from, this.to, delta)); + } else { + this.set(this.compute(this.from, this.to, 1)); + this.complete(); + } + }, + + set: function(now){ + return now; + }, + + compute: function(from, to, delta){ + return Fx.compute(from, to, delta); + }, + + check: function(caller){ + if (!this.timer) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(caller.bind(this, Array.slice(arguments, 1))); return false; + } + return false; + }, + + start: function(from, to){ + if (!this.check(arguments.callee, from, to)) return this; + this.from = from; + this.to = to; + this.time = 0; + this.transition = this.getTransition(); + this.startTimer(); + this.onStart(); + return this; + }, + + complete: function(){ + if (this.stopTimer()) this.onComplete(); + return this; + }, + + cancel: function(){ + if (this.stopTimer()) this.onCancel(); + return this; + }, + + onStart: function(){ + this.fireEvent('start', this.subject); + }, + + onComplete: function(){ + this.fireEvent('complete', this.subject); + if (!this.callChain()) this.fireEvent('chainComplete', this.subject); + }, + + onCancel: function(){ + this.fireEvent('cancel', this.subject).clearChain(); + }, + + pause: function(){ + this.stopTimer(); + return this; + }, + + resume: function(){ + this.startTimer(); + return this; + }, + + stopTimer: function(){ + if (!this.timer) return false; + this.time = $time() - this.time; + this.timer = $clear(this.timer); + return true; + }, + + startTimer: function(){ + if (this.timer) return false; + this.time = $time() - this.time; + this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this); + return true; + } + +}); + +Fx.compute = function(from, to, delta){ + return (to - from) * delta + from; +}; + +Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; + + +/* +Script: Fx.CSS.js + Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements. + +License: + MIT-style license. +*/ + +Fx.CSS = new Class({ + + Extends: Fx, + + //prepares the base from/to object + + prepare: function(element, property, values){ + values = $splat(values); + var values1 = values[1]; + if (!$chk(values1)){ + values[1] = values[0]; + values[0] = element.getStyle(property); + } + var parsed = values.map(this.parse); + return {from: parsed[0], to: parsed[1]}; + }, + + //parses a value into an array + + parse: function(value){ + value = $lambda(value)(); + value = (typeof value == 'string') ? value.split(' ') : $splat(value); + return value.map(function(val){ + val = String(val); + var found = false; + Fx.CSS.Parsers.each(function(parser, key){ + if (found) return; + var parsed = parser.parse(val); + if ($chk(parsed)) found = {value: parsed, parser: parser}; + }); + found = found || {value: val, parser: Fx.CSS.Parsers.String}; + return found; + }); + }, + + //computes by a from and to prepared objects, using their parsers. + + compute: function(from, to, delta){ + var computed = []; + (Math.min(from.length, to.length)).times(function(i){ + computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser}); + }); + computed.$family = {name: 'fx:css:value'}; + return computed; + }, + + //serves the value as settable + + serve: function(value, unit){ + if ($type(value) != 'fx:css:value') value = this.parse(value); + var returned = []; + value.each(function(bit){ + returned = returned.concat(bit.parser.serve(bit.value, unit)); + }); + return returned; + }, + + //renders the change to an element + + render: function(element, property, value, unit){ + element.setStyle(property, this.serve(value, unit)); + }, + + //searches inside the page css to find the values for a selector + + search: function(selector){ + if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector]; + var to = {}; + Array.each(document.styleSheets, function(sheet, j){ + var href = sheet.href; + if (href && href.contains('://') && !href.contains(document.domain)) return; + var rules = sheet.rules || sheet.cssRules; + Array.each(rules, function(rule, i){ + if (!rule.style) return; + var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){ + return m.toLowerCase(); + }) : null; + if (!selectorText || !selectorText.test('^' + selector + '$')) return; + Element.Styles.each(function(value, style){ + if (!rule.style[style] || Element.ShortStyles[style]) return; + value = String(rule.style[style]); + to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value; + }); + }); + }); + return Fx.CSS.Cache[selector] = to; + } + +}); + +Fx.CSS.Cache = {}; + +Fx.CSS.Parsers = new Hash({ + + Color: { + parse: function(value){ + if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true); + return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false; + }, + compute: function(from, to, delta){ + return from.map(function(value, i){ + return Math.round(Fx.compute(from[i], to[i], delta)); + }); + }, + serve: function(value){ + return value.map(Number); + } + }, + + Number: { + parse: parseFloat, + compute: Fx.compute, + serve: function(value, unit){ + return (unit) ? value + unit : value; + } + }, + + String: { + parse: $lambda(false), + compute: $arguments(1), + serve: $arguments(0) + } + +}); + + +/* +Script: Fx.Tween.js + Formerly Fx.Style, effect to transition any CSS property for an element. + +License: + MIT-style license. +*/ + +Fx.Tween = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = $(element); + this.parent(options); + }, + + set: function(property, now){ + if (arguments.length == 1){ + now = property; + property = this.property || this.options.property; + } + this.render(this.element, property, now, this.options.unit); + return this; + }, + + start: function(property, from, to){ + if (!this.check(arguments.callee, property, from, to)) return this; + var args = Array.flatten(arguments); + this.property = this.options.property || args.shift(); + var parsed = this.prepare(this.element, this.property, args); + return this.parent(parsed.from, parsed.to); + } + +}); + +Element.Properties.tween = { + + set: function(options){ + var tween = this.retrieve('tween'); + if (tween) tween.cancel(); + return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options)); + }, + + get: function(options){ + if (options || !this.retrieve('tween')){ + if (options || !this.retrieve('tween:options')) this.set('tween', options); + this.store('tween', new Fx.Tween(this, this.retrieve('tween:options'))); + } + return this.retrieve('tween'); + } + +}; + +Element.implement({ + + tween: function(property, from, to){ + this.get('tween').start(arguments); + return this; + }, + + fade: function(how){ + var fade = this.get('tween'), o = 'opacity', toggle; + how = $pick(how, 'toggle'); + switch (how){ + case 'in': fade.start(o, 1); break; + case 'out': fade.start(o, 0); break; + case 'show': fade.set(o, 1); break; + case 'hide': fade.set(o, 0); break; + case 'toggle': + var flag = this.retrieve('fade:flag', this.get('opacity') == 1); + fade.start(o, (flag) ? 0 : 1); + this.store('fade:flag', !flag); + toggle = true; + break; + default: fade.start(o, arguments); + } + if (!toggle) this.eliminate('fade:flag'); + return this; + }, + + highlight: function(start, end){ + if (!end){ + end = this.retrieve('highlight:original', this.getStyle('background-color')); + end = (end == 'transparent') ? '#fff' : end; + } + var tween = this.get('tween'); + tween.start('background-color', start || '#ffff88', end).chain(function(){ + this.setStyle('background-color', this.retrieve('highlight:original')); + tween.callChain(); + }.bind(this)); + return this; + } + +}); + + +/* +Script: Fx.Morph.js + Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules. + +License: + MIT-style license. +*/ + +Fx.Morph = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = $(element); + this.parent(options); + }, + + set: function(now){ + if (typeof now == 'string') now = this.search(now); + for (var p in now) this.render(this.element, p, now[p], this.options.unit); + return this; + }, + + compute: function(from, to, delta){ + var now = {}; + for (var p in from) now[p] = this.parent(from[p], to[p], delta); + return now; + }, + + start: function(properties){ + if (!this.check(arguments.callee, properties)) return this; + if (typeof properties == 'string') properties = this.search(properties); + var from = {}, to = {}; + for (var p in properties){ + var parsed = this.prepare(this.element, p, properties[p]); + from[p] = parsed.from; + to[p] = parsed.to; + } + return this.parent(from, to); + } + +}); + +Element.Properties.morph = { + + set: function(options){ + var morph = this.retrieve('morph'); + if (morph) morph.cancel(); + return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options)); + }, + + get: function(options){ + if (options || !this.retrieve('morph')){ + if (options || !this.retrieve('morph:options')) this.set('morph', options); + this.store('morph', new Fx.Morph(this, this.retrieve('morph:options'))); + } + return this.retrieve('morph'); + } + +}; + +Element.implement({ + + morph: function(props){ + this.get('morph').start(props); + return this; + } + +}); + + +/* +Script: Fx.Transitions.js + Contains a set of advanced transitions to be used with any of the Fx Classes. + +License: + MIT-style license. + +Credits: + Easing Equations by Robert Penner, , modified and optimized to be used with MooTools. +*/ + +Fx.implement({ + + getTransition: function(){ + var trans = this.options.transition || Fx.Transitions.Sine.easeInOut; + if (typeof trans == 'string'){ + var data = trans.split(':'); + trans = Fx.Transitions; + trans = trans[data[0]] || trans[data[0].capitalize()]; + if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')]; + } + return trans; + } + +}); + +Fx.Transition = function(transition, params){ + params = $splat(params); + return $extend(transition, { + easeIn: function(pos){ + return transition(pos, params); + }, + easeOut: function(pos){ + return 1 - transition(1 - pos, params); + }, + easeInOut: function(pos){ + return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2; + } + }); +}; + +Fx.Transitions = new Hash({ + + linear: $arguments(0) + +}); + +Fx.Transitions.extend = function(transitions){ + for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); +}; + +Fx.Transitions.extend({ + + Pow: function(p, x){ + return Math.pow(p, x[0] || 6); + }, + + Expo: function(p){ + return Math.pow(2, 8 * (p - 1)); + }, + + Circ: function(p){ + return 1 - Math.sin(Math.acos(p)); + }, + + Sine: function(p){ + return 1 - Math.sin((1 - p) * Math.PI / 2); + }, + + Back: function(p, x){ + x = x[0] || 1.618; + return Math.pow(p, 2) * ((x + 1) * p - x); + }, + + Bounce: function(p){ + var value; + for (var a = 0, b = 1; 1; a += b, b /= 2){ + if (p >= (7 - 4 * a) / 11){ + value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); + break; + } + } + return value; + }, + + Elastic: function(p, x){ + return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3); + } + +}); + +['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){ + Fx.Transitions[transition] = new Fx.Transition(function(p){ + return Math.pow(p, [i + 2]); + }); +}); + + +/* +Script: Request.js + Powerful all purpose Request Class. Uses XMLHTTPRequest. + +License: + MIT-style license. +*/ + +var Request = new Class({ + + Implements: [Chain, Events, Options], + + options: {/* + onRequest: $empty, + onComplete: $empty, + onCancel: $empty, + onSuccess: $empty, + onFailure: $empty, + onException: $empty,*/ + url: '', + data: '', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }, + async: true, + format: false, + method: 'post', + link: 'ignore', + isSuccess: null, + emulation: true, + urlEncoded: true, + encoding: 'utf-8', + evalScripts: false, + evalResponse: false + }, + + initialize: function(options){ + this.xhr = new Browser.Request(); + this.setOptions(options); + this.options.isSuccess = this.options.isSuccess || this.isSuccess; + this.headers = new Hash(this.options.headers); + }, + + onStateChange: function(){ + if (this.xhr.readyState != 4 || !this.running) return; + this.running = false; + this.status = 0; + $try(function(){ + this.status = this.xhr.status; + }.bind(this)); + if (this.options.isSuccess.call(this, this.status)){ + this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML}; + this.success(this.response.text, this.response.xml); + } else { + this.response = {text: null, xml: null}; + this.failure(); + } + this.xhr.onreadystatechange = $empty; + }, + + isSuccess: function(){ + return ((this.status >= 200) && (this.status < 300)); + }, + + processScripts: function(text){ + if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text); + return text.stripScripts(this.options.evalScripts); + }, + + success: function(text, xml){ + this.onSuccess(this.processScripts(text), xml); + }, + + onSuccess: function(){ + this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain(); + }, + + failure: function(){ + this.onFailure(); + }, + + onFailure: function(){ + this.fireEvent('complete').fireEvent('failure', this.xhr); + }, + + setHeader: function(name, value){ + this.headers.set(name, value); + return this; + }, + + getHeader: function(name){ + return $try(function(){ + return this.xhr.getResponseHeader(name); + }.bind(this)); + }, + + check: function(caller){ + if (!this.running) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(caller.bind(this, Array.slice(arguments, 1))); return false; + } + return false; + }, + + send: function(options){ + if (!this.check(arguments.callee, options)) return this; + this.running = true; + + var type = $type(options); + if (type == 'string' || type == 'element') options = {data: options}; + + var old = this.options; + options = $extend({data: old.data, url: old.url, method: old.method}, options); + var data = options.data, url = options.url, method = options.method; + + switch ($type(data)){ + case 'element': data = $(data).toQueryString(); break; + case 'object': case 'hash': data = Hash.toQueryString(data); + } + + if (this.options.format){ + var format = 'format=' + this.options.format; + data = (data) ? format + '&' + data : format; + } + + if (this.options.emulation && ['put', 'delete'].contains(method)){ + var _method = '_method=' + method; + data = (data) ? _method + '&' + data : _method; + method = 'post'; + } + + if (this.options.urlEncoded && method == 'post'){ + var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; + this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding); + } + + if (data && method == 'get'){ + url = url + (url.contains('?') ? '&' : '?') + data; + data = null; + } + + this.xhr.open(method.toUpperCase(), url, this.options.async); + + this.xhr.onreadystatechange = this.onStateChange.bind(this); + + this.headers.each(function(value, key){ + try { + this.xhr.setRequestHeader(key, value); + } catch (e){ + this.fireEvent('exception', [key, value]); + } + }, this); + + this.fireEvent('request'); + this.xhr.send(data); + if (!this.options.async) this.onStateChange(); + return this; + }, + + cancel: function(){ + if (!this.running) return this; + this.running = false; + this.xhr.abort(); + this.xhr.onreadystatechange = $empty; + this.xhr = new Browser.Request(); + this.fireEvent('cancel'); + return this; + } + +}); + +(function(){ + +var methods = {}; +['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){ + methods[method] = function(){ + var params = Array.link(arguments, {url: String.type, data: $defined}); + return this.send($extend(params, {method: method.toLowerCase()})); + }; +}); + +Request.implement(methods); + +})(); + +Element.Properties.send = { + + set: function(options){ + var send = this.retrieve('send'); + if (send) send.cancel(); + return this.eliminate('send').store('send:options', $extend({ + data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action') + }, options)); + }, + + get: function(options){ + if (options || !this.retrieve('send')){ + if (options || !this.retrieve('send:options')) this.set('send', options); + this.store('send', new Request(this.retrieve('send:options'))); + } + return this.retrieve('send'); + } + +}; + +Element.implement({ + + send: function(url){ + var sender = this.get('send'); + sender.send({data: this, url: url || sender.options.url}); + return this; + } + +}); + + +/* +Script: Request.HTML.js + Extends the basic Request Class with additional methods for interacting with HTML responses. + +License: + MIT-style license. +*/ + +Request.HTML = new Class({ + + Extends: Request, + + options: { + update: false, + evalScripts: true, + filter: false + }, + + processHTML: function(text){ + var match = text.match(/]*>([\s\S]*?)<\/body>/i); + text = (match) ? match[1] : text; + + var container = new Element('div'); + + return $try(function(){ + var root = '' + text + '', doc; + if (Browser.Engine.trident){ + doc = new ActiveXObject('Microsoft.XMLDOM'); + doc.async = false; + doc.loadXML(root); + } else { + doc = new DOMParser().parseFromString(root, 'text/xml'); + } + root = doc.getElementsByTagName('root')[0]; + for (var i = 0, k = root.childNodes.length; i < k; i++){ + var child = Element.clone(root.childNodes[i], true, true); + if (child) container.grab(child); + } + return container; + }) || container.set('html', text); + }, + + success: function(text){ + var options = this.options, response = this.response; + + response.html = text.stripScripts(function(script){ + response.javascript = script; + }); + + var temp = this.processHTML(response.html); + + response.tree = temp.childNodes; + response.elements = temp.getElements('*'); + + if (options.filter) response.tree = response.elements.filter(options.filter); + if (options.update) $(options.update).empty().set('html', response.html); + if (options.evalScripts) $exec(response.javascript); + + this.onSuccess(response.tree, response.elements, response.html, response.javascript); + } + +}); + +Element.Properties.load = { + + set: function(options){ + var load = this.retrieve('load'); + if (load) load.cancel(); + return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options)); + }, + + get: function(options){ + if (options || ! this.retrieve('load')){ + if (options || !this.retrieve('load:options')) this.set('load', options); + this.store('load', new Request.HTML(this.retrieve('load:options'))); + } + return this.retrieve('load'); + } + +}; + +Element.implement({ + + load: function(){ + this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type})); + return this; + } + +}); + + +/* +Script: Request.JSON.js + Extends the basic Request Class with additional methods for sending and receiving JSON data. + +License: + MIT-style license. +*/ + +Request.JSON = new Class({ + + Extends: Request, + + options: { + secure: true + }, + + initialize: function(options){ + this.parent(options); + this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'}); + }, + + success: function(text){ + this.response.json = JSON.decode(text, this.options.secure); + this.onSuccess(this.response.json, text); + } + +}); diff --git a/static/pics/add.png b/static/pics/add.png new file mode 100644 index 000000000..1aa7f095c Binary files /dev/null and b/static/pics/add.png differ diff --git a/static/pics/admin.png b/static/pics/admin.png new file mode 100644 index 000000000..9460dfc74 Binary files /dev/null and b/static/pics/admin.png differ diff --git a/static/pics/alias.png b/static/pics/alias.png new file mode 100644 index 000000000..de0199b46 Binary files /dev/null and b/static/pics/alias.png differ diff --git a/static/pics/domains.png b/static/pics/domains.png new file mode 100644 index 000000000..aca82fbbb Binary files /dev/null and b/static/pics/domains.png differ diff --git a/static/pics/edit.png b/static/pics/edit.png new file mode 100644 index 000000000..188e1c12b Binary files /dev/null and b/static/pics/edit.png differ diff --git a/static/pics/logout.png b/static/pics/logout.png new file mode 100644 index 000000000..0010931e2 Binary files /dev/null and b/static/pics/logout.png differ diff --git a/static/pics/mailbox.png b/static/pics/mailbox.png new file mode 100644 index 000000000..7c68cb8d3 Binary files /dev/null and b/static/pics/mailbox.png differ diff --git a/static/pics/remove.png b/static/pics/remove.png new file mode 100644 index 000000000..93edaf03f Binary files /dev/null and b/static/pics/remove.png differ diff --git a/static/pics/settings.png b/static/pics/settings.png new file mode 100644 index 000000000..9460dfc74 Binary files /dev/null and b/static/pics/settings.png differ diff --git a/static/pics/simplemail.png b/static/pics/simplemail.png new file mode 100644 index 000000000..e3b2303f3 Binary files /dev/null and b/static/pics/simplemail.png differ diff --git a/static/pics/trash.png b/static/pics/trash.png new file mode 100644 index 000000000..0e0953c73 Binary files /dev/null and b/static/pics/trash.png differ diff --git a/static/pics/undo.png b/static/pics/undo.png new file mode 100644 index 000000000..8b0fef9a8 Binary files /dev/null and b/static/pics/undo.png differ diff --git a/static/stylesheets/SqueezeBox.css b/static/stylesheets/SqueezeBox.css new file mode 100644 index 000000000..54b823c71 --- /dev/null +++ b/static/stylesheets/SqueezeBox.css @@ -0,0 +1,78 @@ +#sbox-window embed, #sbox-window object, #sbox-window select +{ + visibility: visible; +} + +#sbox-overlay +{ + position: absolute; + background-color: #000; +} + +#sbox-window +{ + position: absolute; + background-color: #fff; + text-align: left; + overflow: visible; + padding: 10px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +#sbox-btn-close +{ + position: absolute; + width: 30px; + height: 30px; + right: -15px; + top: -15px; + background: url(closebox.png) no-repeat center; + border: none; +} + +.sbox-window-ie6 #sbox-btn-close +{ + background-image: url(closebox.gif); +} + +.sbox-loading #sbox-content +{ + background-image: url(spinner.gif); + background-repeat: no-repeat; + background-position: center; +} + +#sbox-content +{ + clear: both; + overflow: auto; + background-color: #fff; + height: 100%; + width: 100%; +} + +.sbox-content-image#sbox-content +{ + overflow: visible; +} + +#sbox-image +{ + display: block; +} + +.sbox-content-image img +{ + display: block; +} + +.sbox-content-iframe#sbox-content +{ + overflow: visible; +} + +.body-overlayed embed, .body-overlayed object, .body-overlayed select +{ + visibility: hidden; +} \ No newline at end of file diff --git a/static/stylesheets/basic.css b/static/stylesheets/basic.css new file mode 100644 index 000000000..3631acf52 --- /dev/null +++ b/static/stylesheets/basic.css @@ -0,0 +1,275 @@ +body { + font-family: arial, helvetica, verdana, tahoma, sans-serif; + font-size: small; + background: #153F65; + width: 950px; + margin: 0 auto; + padding: 0; +} + +#header { + width: 100%; + height: 120px; + background: #072342; + margin-top: 20px; + border: 1px solid #333; +} + +#header h1 { + color: white; +} + +#header h2 { + margin-left: 20px; + color: #6AB0C5; +} + +#header h3 { + margin: 0 0 5px 0; + text-align: center; + font-size: small; +} + +#logo { + float: left; + margin-top: 20px; + background: url(logo.png) left no-repeat; + height: 90px; + line-height: 90px; + min-width: 90px; + width: expression(document.body.clientWidth < 1280 ? "90px" : "15%"); +} + +#title { + float: left; + margin-left: 10px; +} + +#info { + float: right; + text-align: center; + background: white; + width: 150px; + margin: 20px 20px; + padding: 10px; + border: 1px solid #333; +} + +#body { + float: left; + width: 100%; + margin: 15px 0; + /*height: 600px;*/ +} + +#loginbox { + margin: 50px auto; + width: 300px; + padding-bottom: 15px; + border: 1px solid #ddd; + background: white; +} + +#loginbox h3 { + font-size: small; + text-align: center; +} + +#leftcol { + float: left; +} + +.leftbox { + border: 1px solid #333; + background: white; +} + +#menubox { + width: 170px; +} + +#userbox { + width: 170px; +} + +.leftbox h3 { + font-size: small; + margin: 2px 0px 2px 0px; + text-align: center; +} + +.leftbox ul { + list-style: none; + padding: 5px 5px; + margin: 0; +} + +.leftbox li { + clear: both; + margin-bottom: 4px; +} + +.leftbox a:link, .leftbox a:visited { + background: transparent; + color: black; + font-weight: bold; + text-decoration: none; +} + +.leftbox a:hover { + color: #6AB0C5; + text-decoration: none; +} + +.leftbox img { + float: left; + margin-right: 2px; +} + +#content { + float: right; + width: 750px; + height: 400px; + background: white; + border: 1px solid #333; + padding: 5px; + margin: 0 0 0 15px; +} + +#menubar { + margin-left: 20px; + display: table; + margin-bottom: 15px; +} + +.menu2 { + font-size: 95%; + white-space: nowrap; +} + +.menu2 ul { + margin: 0; + padding: 0; + list-style: none; + float: left; + width: 100%; +} + +.menu2 li { + float: left; + display: inline; + margin: 2px 5px; +} + +.menu2 li.selected { +/* text-decoration: underline;*/ + background: #ffffcc; +} + +.menu2 a:link, .menu2 a:visited { + background: transparent; + color: black; + font-weight: bold; + text-decoration: none; +} + +.menu2 a:hover { + color: #6AB0C5; +} + +.menu2 img { + float: left; + margin: 1px 3px; +} + +#footer { + border-top: 1px solid #333; + text-align: center; + padding-top: 15px; +} + +.spacer { + clear: both; +} + +.title { + float: left; + width: 100%; + margin-bottom: 5px; +} + +.title img { + float: left; + margin: 1px 3px;; +} + +.title h1 { + float: left; + margin: 5px 10px; + font-size: medium; + font-weight: bold; +} + +#body table { + margin: 0 auto; + width: 100%; + border-collapse: collapse; + border-spacing: 20px; +} + +#body tr.odd { + background: #F7F9Fb; +} + +#body tr.even { + background: white; +} + +#body table th { + background: #CEDBEC; + text-align: left; + padding: 5px; +} + +#body table th.right { + text-align: right; +} + +#body table td { + border-bottom: 1px solid #C8D6E8; + padding: 5px; +} + +#body a, a:visited { + color: #2a4069; + text-decoration: none; +} + +#body a:hover { + text-decoration: underline; +} + +#domcontent { + margin: 10px auto; + width: 90%; +} + + +.button { + margin-left: 35px; +} + +.button img { + float: left; + margin: 0px 3px; + border: 0; +} + +.header { + margin-left: 20px; + margin-bottom: 20px; +} + +.header h1 { + font-size: medium; +} \ No newline at end of file diff --git a/static/stylesheets/box.css b/static/stylesheets/box.css new file mode 100644 index 000000000..2c54b2f20 --- /dev/null +++ b/static/stylesheets/box.css @@ -0,0 +1,74 @@ +body { + font-family: arial, helvetica, verdana, tahoma, sans-serif; + font-size: small; + margin: 0 auto; + padding: 0; +} + +div.row { + clear: both; + padding-top: 10px; + padding-left: 20px; +} + +div.row label { + float: left; + width: 120px; + font-size: small; + padding: 3px 0; +} + +form input { + border: 1px solid #cccccc; + color: inherit; + /*background-color: #e9e9e9;*/ + background-color: #eee; + width: 120px; + padding: 2px; +} + +form input:hover { + color: inherit; + background-color: #ffffcc; +} + +.title { + float: left; + width: 100%; + margin-bottom: 5px; +} + +.title img { + float: left; + margin: 1px 3px;; +} + +.title h1 { + float: left; + margin: 5px 10px; + font-size: medium; + font-weight: bold; +} + +.lastrow { + margin-top: 20px; +} + +.errorlist ul { + float: left; + list-style: none; +} + +.errorlist li { + margin-left: 10px; + float: left; + display: inline; + color: red; +} + +.error { + margin-left: 20px; + color: red; + font-size: small; + font-weight: bold; +} \ No newline at end of file diff --git a/static/stylesheets/closebox.png b/static/stylesheets/closebox.png new file mode 100644 index 000000000..4de4396d4 Binary files /dev/null and b/static/stylesheets/closebox.png differ diff --git a/static/stylesheets/corner-tl.png b/static/stylesheets/corner-tl.png new file mode 100644 index 000000000..3835d2e42 Binary files /dev/null and b/static/stylesheets/corner-tl.png differ diff --git a/static/stylesheets/logo.png b/static/stylesheets/logo.png new file mode 100644 index 000000000..ce411b737 Binary files /dev/null and b/static/stylesheets/logo.png differ diff --git a/static/stylesheets/spinner.gif b/static/stylesheets/spinner.gif new file mode 100644 index 000000000..88fac73dc Binary files /dev/null and b/static/stylesheets/spinner.gif differ diff --git a/templates/admin/aliases.html b/templates/admin/aliases.html new file mode 100644 index 000000000..9c1cc22bd --- /dev/null +++ b/templates/admin/aliases.html @@ -0,0 +1,38 @@ +{% extends "admin/domain_header.html" %} +{% block menu %} +{% load admin_extras %} +{% domain_menu domain.id "aliases" %} +{% endblock %} + +{% block domcontent %} +{% if not aliases %} +

No alias defined.

+{% else %} + + + + + + + +{% for alias in aliases %} + + + + + +{% endfor %} +
ActionsAddressMailboxEnabled
+ {% url admin.views.editalias domain.id alias.id as edit_url %} + + + + + + + {{ alias.address }}@{{ alias.mbox.domain.name }}{{ alias.mbox.address }}@{{ alias.mbox.domain.name }}{{ alias.enabled|yesno }}
+{% endif %} +{% endblock %} diff --git a/templates/admin/domain_header.html b/templates/admin/domain_header.html new file mode 100644 index 000000000..48d191861 --- /dev/null +++ b/templates/admin/domain_header.html @@ -0,0 +1,17 @@ +{% extends "admin/layout.html" %} +{% block html_head %} + +{% endblock %} +{% block mainframe %} +
+

Domain {{ domain.name }}

+
+ + + +
+{% block domcontent %}{% endblock %} +
+{% endblock %} diff --git a/templates/admin/domain_menu.html b/templates/admin/domain_menu.html new file mode 100644 index 000000000..669d53394 --- /dev/null +++ b/templates/admin/domain_menu.html @@ -0,0 +1,27 @@ +{% url admin.views.editdomain domain_id as edit_url %} +{% url admin.views.newmailbox domain_id as new_url %} +{% url admin.views.mailboxes domain_id as mboxes_url %} +{% url full-aliases domain_id as aliases_url %} + diff --git a/templates/admin/domains.html b/templates/admin/domains.html new file mode 100644 index 000000000..e845ff9fe --- /dev/null +++ b/templates/admin/domains.html @@ -0,0 +1,45 @@ +{% extends "admin/layout.html" %} +{% block html_head %} + +{% endblock %} +{% block mainframe %} +
+

Available domains

+
+ +
+ {% if not domains %} + No domain defined. + {% else %} + + + + + + + + + {% for dom in domains %} + + + {% url admin.views.mailboxes dom.id as domurl %} + + + + + + {% endfor %} +
ActionsNameMailboxesQuotaEnabled
+ {% url admin.views.editdomain dom.id as edit_url %} + + + + {% url admin.views.deldomain dom.id as del_url %} + + + + {{ dom.name }}{{ dom.mboxcounter }}{{ dom.quota }} MB{{ dom.enabled|yesno }}
+ {% endif %} +
+{% endblock %} diff --git a/templates/admin/editalias.html b/templates/admin/editalias.html new file mode 100644 index 000000000..da5299440 --- /dev/null +++ b/templates/admin/editalias.html @@ -0,0 +1,15 @@ +{% extends "admin/layout_simple.html" %} +{% block html_head %} + +{% endblock %} +{% block content %} +

Alias {{ alias.address }}

+{% url admin.views.editalias alias.mbox.domain.id alias.id as edit_url %} +
+ {% include "admin/genericform.html" %} +
+
+ +
+
+{% endblock %} diff --git a/templates/admin/editdomain.html b/templates/admin/editdomain.html new file mode 100644 index 000000000..69ac115de --- /dev/null +++ b/templates/admin/editdomain.html @@ -0,0 +1,16 @@ +{% extends "admin/layout_simple.html" %} +{% block html_head %} + +{% endblock %} +{% block content %} +

Domain {{ domain.name }}

+ +{% url admin.views.editdomain domain.id as dom_url %} +
+ {% include "admin/genericform.html" %} + +
+ +
+
+{% endblock %} diff --git a/templates/admin/editmailbox.html b/templates/admin/editmailbox.html new file mode 100644 index 000000000..fea5ccc50 --- /dev/null +++ b/templates/admin/editmailbox.html @@ -0,0 +1,17 @@ +{% extends "admin/layout_simple.html" %} +{% block html_head %} + +{% endblock %} +{% block content %} +

Mailbox {{ mbox.name }}

+ +{% url admin.views.editmailbox mbox.domain_id mbox.id as box_url %} +
+ {% include "admin/genericform.html" %} +
+ +
+ +
+
+{% endblock %} diff --git a/templates/admin/genericform.html b/templates/admin/genericform.html new file mode 100644 index 000000000..728f1c91a --- /dev/null +++ b/templates/admin/genericform.html @@ -0,0 +1,5 @@ +{% for field in form %} +
+ {{ field.label_tag }} {{ field }} {% ifequal noerrors False %}{{ field.errors }}{% endifequal %} +
+{% endfor %} diff --git a/templates/admin/index.html b/templates/admin/index.html new file mode 100644 index 000000000..eecfc9cd3 --- /dev/null +++ b/templates/admin/index.html @@ -0,0 +1,12 @@ +{% extends "admin/layout.html" %} + +{%block pagetitle %}Administration{%endblock %} +{%block breadcrumb %}Domains{%endblock %} +{% block html_head %} + +{% endblock %} + + +{% block content %} +{% include default %} +{% endblock %} diff --git a/templates/admin/layout.html b/templates/admin/layout.html new file mode 100644 index 000000000..522ea9094 --- /dev/null +++ b/templates/admin/layout.html @@ -0,0 +1,48 @@ +{% extends "layout.html" %} +{% block content %} +{% url admin.views.domains as dom_url %} +
+
+
+ {% if user.first_name %} +

{{ user.first_name }} {{ user.last_name }}

+ {% else %} +

{{ user.username }}

+ {% endif %} +
    +
  • + {% url django.contrib.auth.views.logout_then_login as logout_url %} + + Logout +
  • +
+
+
+ +
+
+ {%block mainframe %}{% endblock %} +
+
+{% endblock %} diff --git a/templates/admin/layout_simple.html b/templates/admin/layout_simple.html new file mode 100644 index 000000000..47431d742 --- /dev/null +++ b/templates/admin/layout_simple.html @@ -0,0 +1,18 @@ + + + + + + + + + {%block html_head %}{% endblock %} + + + +
+ {%block content %}{% endblock %} +
+ + diff --git a/templates/admin/mailbox.html b/templates/admin/mailbox.html new file mode 100644 index 000000000..5427ecfbe --- /dev/null +++ b/templates/admin/mailbox.html @@ -0,0 +1,22 @@ +{% extends "admin/layout.html" %} +{% block html_head %}{% endblock %} +{% block content %} +
+

Mailbox {{ mbox.address }}@{{ mbox.domain.name }}

+
+ + + +
+
+ +
+ +

Aliases

+ {% for alias in aliases %} + {{ alias.address }} + {% endfor %} +
+{% endblock %} diff --git a/templates/admin/mailboxes.html b/templates/admin/mailboxes.html new file mode 100644 index 000000000..412de23c9 --- /dev/null +++ b/templates/admin/mailboxes.html @@ -0,0 +1,50 @@ +{% extends "admin/domain_header.html" %} +{% block menu %} +{% load admin_extras %} +{% domain_menu domain.id "mailboxes" %} +{% endblock %} + +{% block domcontent %} +{% if not mailboxes %} +

No mailbox defined.

+{% else %} + + + + + + + + + {% for mb in mailboxes %} + + + + + + + + {% endfor %} +
ActionsNameAddressQuotaEnabled
+ {% url admin.views.editmailbox domain.id mb.id as edit_url %} + + + + {% url admin.views.delmailbox domain.id mb.id as del_url %} + + + + {% url admin.views.newalias domain.id mb.id as alias_url %} + + + + {% url mbox-aliases domain.id mb.id as aliases_url %} + + + + {{ mb.name }}{{ mb.address }}@{{ mb.domain.name }}{{ mb.quota }} MB{{ mb.user.is_active|yesno }}
+{% endif %} +{% endblock %} diff --git a/templates/admin/mbaliases.html b/templates/admin/mbaliases.html new file mode 100644 index 000000000..7aefb2796 --- /dev/null +++ b/templates/admin/mbaliases.html @@ -0,0 +1,14 @@ +{% extends "admin/layout_simple.html" %} +{% block html_head %} + +{% endblock %} +{% block content %} +

Aliases for {{ mbox.address }}

+

+

    + {% for alias in aliases %} +
  • {{alias.address}}
  • + {% endfor %} +
+

+{% endblock %} diff --git a/templates/admin/newalias.html b/templates/admin/newalias.html new file mode 100644 index 000000000..b7ca008e5 --- /dev/null +++ b/templates/admin/newalias.html @@ -0,0 +1,15 @@ +{% extends "admin/layout_simple.html" %} +{% block html_head %} + +{% endblock %} +{% block content %} +

New alias

+{% url admin.views.newalias mbox.domain_id mbox.id as box_url %} +
+ {% include "admin/genericform.html" %} +
+
+ +
+
+{% endblock %} diff --git a/templates/admin/newdomain.html b/templates/admin/newdomain.html new file mode 100644 index 000000000..8a55e0622 --- /dev/null +++ b/templates/admin/newdomain.html @@ -0,0 +1,18 @@ +{% extends "admin/layout_simple.html" %} +{% block html_head %} + +{% endblock %} +{% block content %} +

New domain

+ +{% if error %}
{{ error }}
{% endif %} +{% url admin.views.newdomain as dom_url %} +
+ {% include "admin/genericform.html" %} + +
+ +
+
+{% endblock %} +² diff --git a/templates/admin/newmailbox.html b/templates/admin/newmailbox.html new file mode 100644 index 000000000..f65867340 --- /dev/null +++ b/templates/admin/newmailbox.html @@ -0,0 +1,18 @@ +{% extends "admin/layout_simple.html" %} +{% block html_head %} + +{% endblock %} +{% block content %} +

New mailbox

+ +{% if error %}
{{ error }}
{% endif %} +{% url admin.views.newmailbox domain.id as box_url %} +
+ {% include "admin/genericform.html" %} +
+ +
+ +
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 000000000..39353db47 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,36 @@ + + + + + MailNG - {%block pagetitle %}{% endblock %} + + + + + {%block html_head %}{% endblock %} + + + + + +
+ {%block content %}{% endblock %} +
+ +
+ + + + + diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 000000000..29fd6ce54 --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,29 @@ + + + + + MailNG - {%block pagetitle %}{% endblock %} + + + + + {%block html_head %}{% endblock %} + + + + + {% block content %}{% endblock %} +
+ + + + diff --git a/templates/registration/login.html b/templates/registration/login.html new file mode 100644 index 000000000..041eb2094 --- /dev/null +++ b/templates/registration/login.html @@ -0,0 +1,25 @@ +{% extends "layout.html" %} +{% block html_head %} + +{% endblock %} +{% block content %} +
+

Welcome

+{% if form.errors %} +
+

Your username and password didn't match. Please try again.

+
+{% endif %} +
+ {% for field in form %} +
+ {{ field.label_tag }}{{ field }} +
+ {% endfor %} +
+ +
+ +
+
+{% endblock %} diff --git a/urls.py b/urls.py new file mode 100644 index 000000000..28ebd211b --- /dev/null +++ b/urls.py @@ -0,0 +1,29 @@ +from django.conf.urls.defaults import * +from django.conf import settings + +# Uncomment the next two lines to enable the admin: +# from django.contrib import admin +# admin.autodiscover() + +urlpatterns = patterns('', + # Example: + # (r'^mailng/', include('mailng.foo.urls')), + + # Uncomment the admin/doc line below and add 'django.contrib.admindocs' + # to INSTALLED_APPS to enable admin documentation: + # (r'^admin/doc/', include('django.contrib.admindocs.urls')), + + # Uncomment the next line to enable the admin: + # (r'^admin/(.*)', admin.site.root), + + (r'^mailng/admin/', include('mailng.admin.urls')), + (r'^accounts/login/$', 'django.contrib.auth.views.login'), + (r'^accounts/logout/$', 'django.contrib.auth.views.logout_then_login') +) + +if settings.DEBUG: + urlpatterns += patterns( + '', + (r'^static/(?P.*)$', 'django.views.static.serve', {'document_root': '/home/tonio/projets/mailng/static'}) + ) +