From 18ad148479c5f3b659861b82a4e5c8cd7b4e7302 Mon Sep 17 00:00:00 2001 From: tonio Date: Tue, 24 Mar 2009 16:59:18 +0100 Subject: [PATCH] Initial import --- .hgignore | 2 + COPYING | 13 + INSTALL | 29 + README | 7 + __init__.py | 0 admin/__init__.py | 0 admin/forms.py | 34 + admin/models.py | 30 + admin/templatetags/__init__.py | 0 admin/templatetags/admin_extras.py | 36 + admin/urls.py | 18 + admin/views.py | 222 ++ manage.py | 11 + settings_sample.py | 82 + static/js/SqueezeBox.js | 384 +++ static/js/domains.js | 92 + static/js/form.js | 40 + static/js/mootools.js | 3946 ++++++++++++++++++++++++++++ static/pics/add.png | Bin 0 -> 323 bytes static/pics/admin.png | Bin 0 -> 611 bytes static/pics/alias.png | Bin 0 -> 681 bytes static/pics/domains.png | Bin 0 -> 858 bytes static/pics/edit.png | Bin 0 -> 574 bytes static/pics/logout.png | Bin 0 -> 799 bytes static/pics/mailbox.png | Bin 0 -> 619 bytes static/pics/remove.png | Bin 0 -> 518 bytes static/pics/settings.png | Bin 0 -> 611 bytes static/pics/simplemail.png | Bin 0 -> 8186 bytes static/pics/trash.png | Bin 0 -> 655 bytes static/pics/undo.png | Bin 0 -> 650 bytes static/stylesheets/SqueezeBox.css | 78 + static/stylesheets/basic.css | 275 ++ static/stylesheets/box.css | 74 + static/stylesheets/closebox.png | Bin 0 -> 1910 bytes static/stylesheets/corner-tl.png | Bin 0 -> 495 bytes static/stylesheets/logo.png | Bin 0 -> 6799 bytes static/stylesheets/spinner.gif | Bin 0 -> 1569 bytes templates/admin/aliases.html | 38 + templates/admin/domain_header.html | 17 + templates/admin/domain_menu.html | 27 + templates/admin/domains.html | 45 + templates/admin/editalias.html | 15 + templates/admin/editdomain.html | 16 + templates/admin/editmailbox.html | 17 + templates/admin/genericform.html | 5 + templates/admin/index.html | 12 + templates/admin/layout.html | 48 + templates/admin/layout_simple.html | 18 + templates/admin/mailbox.html | 22 + templates/admin/mailboxes.html | 50 + templates/admin/mbaliases.html | 14 + templates/admin/newalias.html | 15 + templates/admin/newdomain.html | 18 + templates/admin/newmailbox.html | 18 + templates/base.html | 36 + templates/layout.html | 29 + templates/registration/login.html | 25 + urls.py | 29 + 58 files changed, 5887 insertions(+) create mode 100644 .hgignore create mode 100644 COPYING create mode 100644 INSTALL create mode 100644 README create mode 100644 __init__.py create mode 100644 admin/__init__.py create mode 100644 admin/forms.py create mode 100644 admin/models.py create mode 100644 admin/templatetags/__init__.py create mode 100644 admin/templatetags/admin_extras.py create mode 100644 admin/urls.py create mode 100644 admin/views.py create mode 100755 manage.py create mode 100644 settings_sample.py create mode 100644 static/js/SqueezeBox.js create mode 100644 static/js/domains.js create mode 100644 static/js/form.js create mode 100644 static/js/mootools.js create mode 100644 static/pics/add.png create mode 100644 static/pics/admin.png create mode 100644 static/pics/alias.png create mode 100644 static/pics/domains.png create mode 100644 static/pics/edit.png create mode 100644 static/pics/logout.png create mode 100644 static/pics/mailbox.png create mode 100644 static/pics/remove.png create mode 100644 static/pics/settings.png create mode 100644 static/pics/simplemail.png create mode 100644 static/pics/trash.png create mode 100644 static/pics/undo.png create mode 100644 static/stylesheets/SqueezeBox.css create mode 100644 static/stylesheets/basic.css create mode 100644 static/stylesheets/box.css create mode 100644 static/stylesheets/closebox.png create mode 100644 static/stylesheets/corner-tl.png create mode 100644 static/stylesheets/logo.png create mode 100644 static/stylesheets/spinner.gif create mode 100644 templates/admin/aliases.html create mode 100644 templates/admin/domain_header.html create mode 100644 templates/admin/domain_menu.html create mode 100644 templates/admin/domains.html create mode 100644 templates/admin/editalias.html create mode 100644 templates/admin/editdomain.html create mode 100644 templates/admin/editmailbox.html create mode 100644 templates/admin/genericform.html create mode 100644 templates/admin/index.html create mode 100644 templates/admin/layout.html create mode 100644 templates/admin/layout_simple.html create mode 100644 templates/admin/mailbox.html create mode 100644 templates/admin/mailboxes.html create mode 100644 templates/admin/mbaliases.html create mode 100644 templates/admin/newalias.html create mode 100644 templates/admin/newdomain.html create mode 100644 templates/admin/newmailbox.html create mode 100644 templates/base.html create mode 100644 templates/layout.html create mode 100644 templates/registration/login.html create mode 100644 urls.py 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 0000000000000000000000000000000000000000..1aa7f095c6c282262390748ab2e596a3fc15c228 GIT binary patch literal 323 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE7hmi)!cT|6FkAON{jSt(_u!Lq7!B=^4B;IBq%dL`6}O%gpGjO3}J1 z4Y?KfzQ;%l3Rp(0FF*F|EI&io>j^)N99Xu9ZE@HjcqrgTrepSr)S!;v7$d z7#j|r*sm7k|1`qSzwL6X$#3JGkN$s>{Cx9Ec6v|es;&OpMC)hUh7@^ocW)~11Nx1@ M)78&qol`;+0M{XYA^-pY literal 0 HcmV?d00001 diff --git a/static/pics/admin.png b/static/pics/admin.png new file mode 100644 index 0000000000000000000000000000000000000000..9460dfc746be14e4b2a6a94f91d002fa4a56318f GIT binary patch literal 611 zcmV-p0-XJcP)f=aN=#)Y=sMXW9~6k=xULU18eq^Pwm5>f&c14BZf zYe95nK#JnRiH*==SAxmkLmxdrjL^?BWW`;2x>%z19x#ygF@3}wxCj{ps zol&BI3dr$TBKH?fG@Vgwj@VLE`>*x`sHT~#`1-X0&1|$C>5O7D8g(O`Q4IH5!>UCA z#B@f9T4bxM*W2c190hpr@ILWaLI!dGBA^c595S9hn=kAOg|QenCF1t1xZCGP(7 zj844Dzcfmf65S_zD3%MD6HEfSt3fc5It#MB8i9S z5)@_T!Gp3j4LY`s0_ix+K$;4fpa=cEe}5h3zd<3?1224e;QM)>hnE-rQ$B#hBw=<%?P;w&DWxZb@Qg8@5W*WC8u0vE zQ8MWi08&bX5V)?35CSPBN-305Xsxl<;`jTR&b$Y}51^Eyx%mW{bc#SAfVCED?XK7c zYb`#Xk6KXdJ``el;w=>w6=e`M=U_0%ZoT8E{@MT$R)+OE5=)TJW?8N8X}fao#mw%V zvI_I|{KHrS7`%hnWs_*E>y_SCig{DmyLx{)# P00000NkvXXu0mjfCEq#~ literal 0 HcmV?d00001 diff --git a/static/pics/domains.png b/static/pics/domains.png new file mode 100644 index 0000000000000000000000000000000000000000..aca82fbbb0fec9349746a618806ea81fd7dd6096 GIT binary patch literal 858 zcmV-g1Eu_lP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXAz z5eOHz09T^`00P=cL_t(I%XL#ti(FL@J$3Kx`@Xt+dRm-dCKKaF=n!Fof{n|`4{%(G zxbRoF(7k`bg}TX7a3Q#woh}4dBHDtOfKC!I(=$xlulv2%@AbR)R=IGdF`x%(QN^jM z!zqdm4-cOijYhY!EYoS4>Lf{Yk|ZjMB5kczL_}@dhV^>QbzO(Lt~t+huIrko)9HN^ zLbx48(cON3t25jlrdxxpUccY#rfHhRaU2s7nx^G~hlmhD@ZS5@d%t$hwcdL-o6VL*QADQO?dqGOYh&vE z`a;=Mp<_qb$@Zxz7Z4EufYutu7+gSEH?v~82~^;P)b251+BG7(^RW0OLafKs*j(v_;&Igwxd^IjNTA0$k6)fno>$f zQDjWhG<;=nT|Rv49!~Q|xV8HRAi!`q{73JFic1g?hzNCEvvV$}qobpIzBu`-#RN7v z!F2gEybH+ld=vBe9P{~nlP{M`lx0~?CXC$5}wXrlM=J)0k-3pG0*3lhJPD*d4;xZ zMMPi#MUT!W_+$Dd;?yFvJ-ogDF}(MiW@ZKvJvD%tNkpK$Z#nK6`1+@Bkg6L{ zq~N`Wa}Lfqc<IwnbDlcuS$*6Pc5ZkondRpqOy ka%EYD>2$iDOeT}R0Uqa_U9V6V>Hq)$07*qoM6N<$f+>)ZiU0rr literal 0 HcmV?d00001 diff --git a/static/pics/edit.png b/static/pics/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..188e1c12bd2de0029c75eefc6c7c4753b86b7d9b GIT binary patch literal 574 zcmV-E0>S->P)2^qNERY3;<4ZcwkNNp7Zp5;AjKf{B7T6H z7K$;wTN)8fToCQSV$+QqO#Q&sZ0iseT9M6oFhjF#YAe3W{|(Ih&u?blgZ~U+lHy(Z zfEnP}R(g1dys47_mq#z10cH*!ci4`$5?D>W0r2hlIkux$PfFdaJ1vdV-c&$Y6qM6=z7>zUrb8|$BG>39|jmV1jOTU#EGRpi+xRHO?}@de@`-W?HM1Qj5D_ogg}SY zdLN|}^?H57O9w%Kn@Oz|d&_(+O?3c05d+({v28n2?E5}$CdJ@bk;TUuUX_^(h)oH!))dmc3|x7NH+_f7=kKWb2fXhZ zrnAjAMhLXlWV2a*$wSOL4(~qIS$aJMy=iu>tN_|Hp>9hMh9SE@rg`+GoBO#O-SQ{L5?nn?G7$uQNU|ANHWktGp13@enLu*Z~ zRzquzloG8qLI{Kqt=k(B1Emy><7_&0#$#yLz&Ndx##4AnMLxFXBZ(g%HuJ zXi&qdfM(p}?71_;z-a>z1OY)1kR%Cdn$l{uaL%E% z20$r|G3H}0Xl>|O*#nSQ0Qr2LFbv7%a)e>nxm#-iK?Fn;rO}H#*uEVR!P*eOn08KU zEzY@4ZV^E#g$TGTkl0Q5yTko z96r1hp-s`2)|%YZ6svCDq_A@*KN&-G|2`-NL;2>{ay<)mpM1>)Xl?vJ194<}C>UEMz1(#)? z2-^S9HcD2kVCu*bayPCMH=C^7umK&0EG{h2Qi}O%6`Yd}!S|+-h9S4NY{31bkNg24I=4Iksz%a>Tzznj;k653ieJ|1K8_%QpT zsQ-%yp65||Q^JNmza1DLpU)yz5Kkbm-p{$%*@Oz8Th_`>gu&CBehx;5n=4n*f;C^bvfq< dg5VDz{U1L){aup)rDXsB002ovPDHLkV1hluXGs76 literal 0 HcmV?d00001 diff --git a/static/pics/mailbox.png b/static/pics/mailbox.png new file mode 100644 index 0000000000000000000000000000000000000000..7c68cb8d35d52dd168e8e236fcec3a3f8942c2b1 GIT binary patch literal 619 zcmV-x0+juUP)x~Vjlni0r*Ko zK~y-)t&>e?6Hyd}zey&^WXPmVQ`0OWh3ZNaOEGCg5G|%+ih>mde-K@XNWqN=?gepU z5em|cx~cn8LG8-6__Ii2P>g6PSZu1`B4f&%Op?jC5Tip9Q1tHJdFLG7z4tu$$CCb( zXqVkl@b5#C`>|~SW=7H~&*XOG8#k{H0=b`Ox2w+lVxGOpY}V%sM4?;-F>jaoCU+8F zy>jgrXyu?*L>TcQTTTm!k5y`SQ&7#woy6}8c}luYrDB5hl7apLvCVNTX_xAU7sXzby1-Ub)&hq5(9Lvie7#$rW z)YF6CucE4Ps0S&2x`(^;imp(M*+(No$H(r!akLN)_acPJx$_rLRn;+@ucd8lR3|XS zI$eDUR#vBxG|P4vD2l@HaDs<(v+N%^0Duqz!!R%mgTmX_96oXs#jiCrEnCvXp#Z6asU|jc`xahV!Zb}30!`f28abPV zg{Pc2dAbQTtU4DjO|ZDMgkcB=y~QA#$#5v0rd~C%8x5>l6$y!k)nMn2?G)EPcWyz~ zbrQ)vKQ3gSgXjSiRin2rhOX;v4JPkQPhI|tk2uNysP7V2y6Y6gd4d1{002ovPDHLk FV1h5`60`sS literal 0 HcmV?d00001 diff --git a/static/pics/remove.png b/static/pics/remove.png new file mode 100644 index 0000000000000000000000000000000000000000..93edaf03f849940a306db4ff75bb6abc3bb33667 GIT binary patch literal 518 zcmV+h0{Q)kP)dnSF-Q}QlMiV*ii zyO#IQtCM20Ysk8Z)#%Ph=ezfwE1B7R`T_XL9?M$$k=cBzeb;P0RllJvm&^akJ|2$& zXaOpUdM`^TOQnD?0KqxTW&>%uV+v`8{0b!r zk5^YHP3v(2j~5r1g9FTH1RCfaNgyGK zI#hE262r5zZUyfq6PQdo<~42uY&M%Vere*(=P#f3`p&@3AEt*DvUi(Ro&W#<07*qo IM6N<$f?S%`;{X5v literal 0 HcmV?d00001 diff --git a/static/pics/settings.png b/static/pics/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..9460dfc746be14e4b2a6a94f91d002fa4a56318f GIT binary patch literal 611 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 LE#(S`W!V1#>kAE8 literal 0 HcmV?d00001 diff --git a/static/pics/trash.png b/static/pics/trash.png new file mode 100644 index 0000000000000000000000000000000000000000..0e0953c73c60adbbc762d0553f95a586f3054aa6 GIT binary patch literal 655 zcmV;A0&x9_P)Cml&2>S!-Vj|(%DY|xv2rmiUs+--iLl+4{ z?G|A~=Ohg>vv9>N%M}DMW!>HPeV%zbm|I~4O9R8eF!SL%Uk3g&6hQCgwr&w;ys}lf zIX1F%*Xv2Q_jYug8SU>IsAzqBV(1P`&UnCRf8PMZ!;e^5{(|GUxUTch+I8+M7rU?V zW_t34vrC+PH%AZz1VKm`?q)<3MMP26&l(YiA-R=YS&0X*u<(|l`}Zn3VP^W}j)^_# z_6NY#uC7z$Rx^mTh**raXl*fCh+`p13_7uhfHnqW4A$CV1<;djx7Tl^S>Kr5@mDOH z8zq#g#&c^~-&kgAv%q#=W$W-maC!CHfe5XaxietSj|8I1&`<#3&|j$e*lNa`-i+0@!$Xe002ovPDHLkV1g_FA-@0s literal 0 HcmV?d00001 diff --git a/static/pics/undo.png b/static/pics/undo.png new file mode 100644 index 0000000000000000000000000000000000000000..8b0fef9a8319452009247864c41e033694859278 GIT binary patch literal 650 zcmV;50(Jd~P)kSC&LBJ zfVRcM$?V1P3B6PcAX~#mZX7Dr0E-up6>u&Ai1M4+$6px*d6g)50zul{6fOZfD5 zx^(<(+NuITAZTdtF*`7sbB~Mx0Ae$D>fg&k;5u>{h0Ft(%sL2>YF!TkK|`BOGW-0q z$<55rD9F$#2!Mm7ygZedyZydz;2gN<0Fly!zty-=mBaVpcsEVu>hcW{d1#Z{npnGY zBY*fjmK0%?8^t(;YR@+XG=oH^Fxc%lEVS$r0NiT$nf{@pT|4*Ae2zV-96O_3`)`13 k>$j#pJbyuT?P=Bf1qBx3B`dxp6aWAK07*qoM6N<$g68)pi~s-t literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4de4396d4a09677774f79289de2a09511baeea22 GIT binary patch literal 1910 zcmV-+2Z{KJP)z^Q>-8KIssI20AY({UO#lFGm;eBCjsO7iaR2~=Yybd* zy8r;x$N&H_>;M26yq<`~KL7v*>`6pHRCwBAWWWy?p_swJ!GY=X=g*8ke*9qk`ST}Z zdU`qw7Z(@fv}x0Rf#f+jIR5kU^8P2LK7aoF=BZPs zUOIK^)MsF*`~o`QH`D=t(JcoEAauk84FygG1qFf7&``O#bLZLs4PE;m4ZL{q;_rh8 z5B{QyfBg7y1u$e3few}u6cpqKnFTeI3E3im07CaENCB6OjEqoBOpMCLjT`;`{{8y^ zWZ_n$O7C)YSBAZ*T9f@87@wgQ~r-ckkXvV`F1EUVpg2H5Y-eYuxnRM9M39EBU%&nZ#x+Xt8W|aVwYIj#h+{D^u`kKV z$v=Qu3ogdS#`gK?)2F{dW*t0uupVfZDo|VqWEaTS009KEm>Z}`3Ydy*?%%(^5TtnW z(;G*fzk0D zSSo?}K!do)4&2r=HI`6@Kg;DK;Y5@D8S6f$jA!R2#R=y z-@kw3EZXkgz5D;mmoF#|yK?2qKWvtQ08AWYDKNn@LoH?k2q3V5?BoZ76wef+J_SucZ|q}*4Eat0ppwvn;u{u2G>@=;`K8pCnvlB0SF)lXugsH z7X0Rlii*j=;_ekFPSCRwF!6l>MgdBk`uh5!#Cc9m&JXm$1Xy;1vlOsk$^_bB1{9YD zI&JkwlS1w(;^Z^)!@LCEWfM7lr1G>gQO-(Jludi=U<2%PtU4%`gn2LZ7@5Q~G#ZGZrRXD5)S*xA{=fSSvIdGkEBd`SvUNJ#h& zEi#`0(?mH?%mZWxEIR=N5WD~ZWo}@L!d6H~C^;o1WhN}%Kn9R%DX3Tl1uIBDuwk$s zD3%Dsw#WqtKmai#w>W@Fo)wt5IG#Lt!pzOh{RtR^ngIa;EI|DX!1DGl0}&uLHkM`0 znlZR5`~$4;!Ko9NO&EZQ5}bNLd|;LO3|NQX zDk&*B3FO`b6<$F66o}sdF(~nY(#>Ch0AfUH2(Un1!w0oo5@@*$Fu}2#z zG+4x;SOQq;hynAf5U?N-FflO^1=e1m<_rU{W%&_U5xfEBgts6ML*w!V5Wj@tchKm7 z#Vt550|XEwmS!y*Qm}(kyC5{X2|-gN4>0wE+fTqMj{(U315A|Pffy8wAD|Y29Pu8S zm|(&C3%QvK5I~Grnzu|)pR++TIyW=~c%hErLTWoPKn(*K{0(XeEWv#R;_pxkeqBrOP0*Db$8yndX$XyynEc#&m wB;+m&vSk?cJ3s(2G7t}lRr{Y9MF0T?00rpcocj25u>b%707*qoM6N<$g7Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOcB z4j3MdxfU7#000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0003>Nkl!%1QTpl^X=s6Sobm^0BYz-Mao=*FFI z46p^(Qw<KWA++k?a_@mgc&837*J72%#%q-27rs&lm+hw2*!U$1V*sxeey0_FUm8ygztIX` lXoXi=;S_j*7kGh}`~di?BcNWtVvGO)002ovPDHLkV1kBd%d`Lh literal 0 HcmV?d00001 diff --git a/static/stylesheets/logo.png b/static/stylesheets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce411b737d726dd186304d5ef7133f69d3120af8 GIT binary patch literal 6799 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 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'}) + ) +