diff --git a/admin/templatetags/admin_extras.py b/admin/templatetags/admin_extras.py
index b377b3a2c..cdc0a1dec 100644
--- a/admin/templatetags/admin_extras.py
+++ b/admin/templatetags/admin_extras.py
@@ -67,6 +67,20 @@ def settings_menu(selection, perms):
{"selection" : selection, "entries" : entries,
"perms" : perms})
+@register.simple_tag
+def domains_menu(selection, perms):
+ entries = [
+ {"name" : "domains",
+ "url" : reverse(admin.views.domains),
+ "label" : _("Domains"),
+ "img" : "/static/pics/domains.png"}
+ ]
+ entries += events.raiseQueryEvent("AdminMenuDisplay", target="admin_menu_box",
+ perms=perms)
+ return render_to_string('main/menulist.html',
+ {"menu" : entries, "selection" : selection,
+ "perms" : perms})
+
@register.simple_tag
def loadadminextmenu(perms):
menu = events.raiseQueryEvent("AdminMenuDisplay", target="admin_menu_box",
diff --git a/admin/views.py b/admin/views.py
index 7b917765f..5127f1c74 100644
--- a/admin/views.py
+++ b/admin/views.py
@@ -49,7 +49,7 @@ def domains(request):
return mailboxes(request, dom_id=mb.domain.id)
return HttpResponseRedirect(reverse(main.views.index))
-
+
domains = Domain.objects.all()
counters = {}
for dom in domains:
diff --git a/auth/__init__.py b/auth/__init__.py
new file mode 100755
index 000000000..e69de29bb
diff --git a/auth/forms.py b/auth/forms.py
new file mode 100644
index 000000000..909d7d3a6
--- /dev/null
+++ b/auth/forms.py
@@ -0,0 +1,7 @@
+from django import forms
+from django.utils.translation import ugettext as _
+
+class LoginForm(forms.Form):
+ username = forms.CharField(label=_("Username"))
+ password = forms.CharField(label=_("Password"), widget=forms.PasswordInput,
+ help_text=_("Password used to authenticate"))
diff --git a/auth/models.py b/auth/models.py
new file mode 100755
index 000000000..71a836239
--- /dev/null
+++ b/auth/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/auth/tests.py b/auth/tests.py
new file mode 100755
index 000000000..2247054b3
--- /dev/null
+++ b/auth/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/auth/views.py b/auth/views.py
new file mode 100755
index 000000000..7cd567b80
--- /dev/null
+++ b/auth/views.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+from django.contrib.auth import authenticate, login
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _
+import mailng.admin
+from forms import LoginForm
+
+def dologin(request):
+ error = None
+ if request.method == "POST":
+ form = LoginForm(request.POST)
+ user = authenticate(username=request.POST["username"],
+ password=request.POST["password"])
+ if user:
+ if user.is_active:
+ login(request, user)
+ request.session["password"] = request.POST["password"]
+ next = request.POST["next"]
+ if next == "None":
+ next = reverse(mailng.admin.views.domains)
+ return HttpResponseRedirect(next)
+ else:
+ error = _("Account is disabled.")
+ error = _("Your username and password didn't match. Please try again.")
+ else:
+ form = LoginForm()
+ next = request.GET.has_key("next") and request.GET["next"] or None
+ return render_to_response("registration/login.html", {
+ "form" : form, "error" : error, "next" : next
+ })
diff --git a/extensions/amavis_quarantine/main.py b/extensions/amavis_quarantine/main.py
index 3ffd5b9e6..c6f319ce2 100644
--- a/extensions/amavis_quarantine/main.py
+++ b/extensions/amavis_quarantine/main.py
@@ -2,15 +2,19 @@
"""
"""
+from datetime import datetime
+import time
+import email
+import re
+import os
+import lxml.html
+from lxml.html.clean import Cleaner
from django.http import HttpResponseRedirect, HttpResponse
-from django.shortcuts import get_object_or_404, render_to_response
-from django.template.loader import render_to_string
from django.template import Template
from django.utils import simplejson
from django.conf.urls.defaults import *
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
-from django.core.paginator import Paginator
from django.contrib.auth.decorators \
import login_required
from mailng.lib import events, parameters
@@ -18,12 +22,7 @@ from mailng.lib import _render, _ctx_ok, _ctx_ko, decode
from mailng.lib import db
from mailng.admin.models import Mailbox
from lib import AMrelease
-from datetime import datetime
-import email
-import re
-import os
-import lxml.html
-from lxml.html.clean import Cleaner
+from sql_listing import *
attached_map = {}
@@ -53,62 +52,22 @@ def menu(**kwargs):
]
return []
-def lookup(*args):
- filter = ""
- for a in args:
- if not a:
- continue
- if a[0] == "&":
- filter += " AND "
- else:
- filter += " OR "
- filter += a[1:]
- conn = db.getconnection("amavis_quarantine")
- status, cursor = db.execute(conn, """
-SELECT msgs.from_addr, maddr.email, msgs.subject, msgs.content, quarantine.mail_id,
- msgs.time_num, msgs.content
-FROM quarantine, maddr, msgrcpt, msgs
-WHERE quarantine.mail_id=msgrcpt.mail_id
-AND msgrcpt.rid=maddr.id
-AND msgrcpt.mail_id=msgs.mail_id
-AND quarantine.chunk_ind=1
-%s
-ORDER BY msgs.time_num DESC
-""" % filter)
- return cursor
-
-def render_listing(request, cursor):
- emails = []
- rows = cursor.fetchall()
- for row in rows:
- emails.append({"from" : row[0], "to" : row[1],
- "subject" : row[2], "content" : row[3],
- "mailid" : row[4], "time" : datetime.fromtimestamp(row[5]),
- "type" : row[6]})
- paginator = Paginator(emails, 10)
- try:
- pagenum = int(request.GET.get('page', '1'))
- except ValueError:
- pagenum = 1
- page = paginator.page(pagenum)
- return render_to_string("amavis_quarantine/listing.html", {
- "count" : len(rows),
- "emails" : page, "current_page" : pagenum,
- "last_page" : paginator._get_num_pages()
- })
-
@login_required
def index(request, message=None):
+ start = time.time()
if not request.user.is_superuser:
mb = Mailbox.objects.get(user=request.user.id)
- filter = "&maddr.email='%s'" % mb.full_address
+ filter = ["&maddr.email='%s'" % mb.full_address]
else:
filter = None
- cursor = lookup(filter)
-
- return _render(request, 'amavis_quarantine/index.html', {
- "content" : render_listing(request, cursor)
- })
+ try:
+ pageid = request.GET["page"]
+ if pageid == "":
+ pageid = 1
+ except KeyError:
+ pageid = 1
+ lst = SQLlisting(filter)
+ return lst.render(request, pageid=int(pageid), start=start)
@login_required
def viewmail_plain(request, content):
@@ -311,7 +270,19 @@ def search(request):
filter += " OR "
filter += "msgs.%s LIKE '%%%s%%'" % (c, pattern)
filter = "&(%s)" % filter
- cursor = lookup(filter)
- ctx = {"status" : "ok", "content" : render_listing(request, cursor)}
- return HttpResponse(simplejson.dumps(ctx),
- mimetype="application/json")
+ lst = SQLlisting([filter])
+ try:
+ pageid = request.GET["page"]
+ if pageid == "":
+ pageid = 1
+ except KeyError:
+ pageid = 1
+ page = lst.paginator.getpage(pageid)
+ if page:
+ content = lst.fetch(page.id_start, page.id_stop).render(request)
+ navbar = lst.render_navbar(page)
+ else:
+ content = "Empty quarantine"
+ navbar = ""
+ ctx = {"status" : "ok", "listing" : content, "navbar" : navbar}
+ return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
diff --git a/extensions/amavis_quarantine/sql_listing.py b/extensions/amavis_quarantine/sql_listing.py
new file mode 100644
index 000000000..f4744adef
--- /dev/null
+++ b/extensions/amavis_quarantine/sql_listing.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+from datetime import datetime
+from django.utils.translation import ugettext as _
+from mailng.lib import tables, db
+from mailng.lib.email_listing import MBconnector, EmailListing
+
+class Qtable(tables.Table):
+ idkey = "mailid"
+ selection = tables.SelectionColumn("selection", first=True)
+ type = tables.Column("type", label=_("Type"))
+ from_ = tables.Column("from", label=_("From"))
+ subject = tables.Column("subject", label=_("Subject"))
+ time = tables.Column("date", label=_("Date"))
+
+ def parse_date(self, value):
+ return datetime.fromtimestamp(value)
+
+class SQLconnector(MBconnector):
+ def __init__(self, filter=None):
+ self.conn = db.getconnection("amavis_quarantine")
+ self.filter = ""
+ if filter:
+ for a in filter:
+ if not a:
+ continue
+ if a[0] == "&":
+ self.filter += " AND "
+ else:
+ self.filter += " OR "
+ self.filter += a[1:]
+ query = self._get_query()
+ status, cursor = db.execute(self.conn, """
+SELECT count(quarantine.mail_id) AS total
+%s
+""" % query)
+ if not status:
+ print cursor
+ self.count = 0
+ else:
+ self.count = int(cursor.fetchone()[0])
+
+ def _get_query(self):
+ return """
+FROM quarantine, maddr, msgrcpt, msgs
+WHERE quarantine.mail_id=msgrcpt.mail_id
+AND msgrcpt.rid=maddr.id
+AND msgrcpt.mail_id=msgs.mail_id
+AND quarantine.chunk_ind=1
+%s
+ORDER BY msgs.time_num DESC
+""" % self.filter
+
+ def messages_count(self, folder=None):
+ return self.count
+
+ def fetch(self, start=None, stop=None, **kwargs):
+ query = self._get_query()
+ status, cursor = db.execute(self.conn, """
+SELECT msgs.from_addr, maddr.email, msgs.subject, msgs.content, quarantine.mail_id,
+ msgs.time_num, msgs.content
+%s
+LIMIT %d,%d
+""" % (query, start - 1, stop - 1))
+ if not status:
+ print cursor
+ return []
+ emails = []
+ rows = cursor.fetchall()
+ for row in rows:
+ emails.append({"from" : row[0], "to" : row[1],
+ "subject" : row[2], "content" : row[3],
+ "mailid" : row[4], "date" : row[5],
+ "type" : row[6]})
+ return emails
+
+class SQLlisting(EmailListing):
+ tpl = "amavis_quarantine/index.html"
+
+ def __init__(self, filter, **kwargs):
+ self.mbc = SQLconnector(filter)
+ EmailListing.__init__(self, **kwargs)
+
+ def fetch(self, id_start, id_stop):
+ return Qtable(self.mbc.fetch(start=id_start, stop=id_stop))
diff --git a/extensions/amavis_quarantine/templates/index.html b/extensions/amavis_quarantine/templates/index.html
index c7da1ca4c..3d780c5d7 100644
--- a/extensions/amavis_quarantine/templates/index.html
+++ b/extensions/amavis_quarantine/templates/index.html
@@ -1,49 +1,11 @@
-{% extends "layout.html" %}
+{% extends "common/email_listing.html" %}
{% load i18n %}
{% load amextras %}
-{% block html_head %}
-
-
-
-
+$$("a[name=release]").addEvent("click", function(event) {
+ if (!checkSelection("selection", "{% trans "Release this selection?" %}")) {
+ event.stop();
+ return;
+ }
+ mysubmit(event, "release");
+});
+
+
+var myFx = new Fx.Slide($("searchparams"));
+myFx.hide();
+$("searchopts").addEvent("click", function(event) {
+ myFx.cancel();
+ myFx.toggle();
+});
+$$("input[name=scriteria]").addEvent("click", function(event) {
+ myFx.cancel();
+ myFx.toggle();
+});
+$("searchfield").addEvent("change", function(event) {
+ var criteria = "";
+ $$("input[name=scriteria]:checked").each(function(obj) {
+ criteria = obj.value;
+ });
+ {% url extensions.amavis_quarantine.main.search as s_url %}
+ new Request.JSON({url: "{{ s_url }}", onSuccess: function(resp) {
+ $("listing").set("html", resp.listing);
+ $("navbar").set("html", resp.navbar);
+ {% url extensions.amavis_quarantine.main.viewmail as view_url %}
+ bindRows("{{ view_url }}", "{{ current_page }}");
+ }}).get({"pattern" : $("searchfield").value, "criteria" : criteria});
+});
+$("searchfield").addEvent("click", function(event) {
+ $("searchfield").select();
+});
+
+$("searchfield").addEvent("blur", function(event) {
+ if ($("searchfield").value == "") {
+ $("searchfield").set("value", "{% trans "Search..." %}");
+ }
+});
+{% url extensions.amavis_quarantine.main.viewmail as view_url %}
+bindRows("{{ view_url }}", "{{ current_page }}");
{% endblock %}
{% block middletitle %}{% trans "Quarantine" %}{% endblock %}
{% block menu %}
@@ -162,15 +101,3 @@ window.addEvent("domready", function() {
{% endblock %}
-{% block content %}
-
-{% endblock %}
diff --git a/extensions/amavis_quarantine/templates/listing.html b/extensions/amavis_quarantine/templates/listing.html
deleted file mode 100644
index 9ce78ec9c..000000000
--- a/extensions/amavis_quarantine/templates/listing.html
+++ /dev/null
@@ -1,51 +0,0 @@
-{% load i18n %}
-
-{% if not count %}
-{% trans "No messages in quarantine" %}
-{% else %}
-
-
-
-
-{% endif %}
diff --git a/extensions/amavis_quarantine/templates/viewmail.html b/extensions/amavis_quarantine/templates/viewmail.html
index b0f59790e..d1f9c0539 100644
--- a/extensions/amavis_quarantine/templates/viewmail.html
+++ b/extensions/amavis_quarantine/templates/viewmail.html
@@ -1,11 +1,11 @@
-{% extends "layout.html" %}
+{% extends "nlayout.html" %}
{% load i18n %}
{% block html_head %}
-
+
+
+
{% endblock %}
{% block middletitle %}{% trans "Mail content" %}{% endblock %}
{% block menu %}
diff --git a/extensions/amavis_quarantine/urls.py b/extensions/amavis_quarantine/urls.py
index 515bd274a..06806aa1f 100644
--- a/extensions/amavis_quarantine/urls.py
+++ b/extensions/amavis_quarantine/urls.py
@@ -2,7 +2,9 @@ from django.conf.urls.defaults import *
urlpatterns = patterns('mailng.extensions.amavis_quarantine.main',
(r'^$', 'index'),
- (r'^viewmail/(?P[\w\-\+]+)/$', 'viewmail'),
+ url(r'^viewmail/(?P[\w\-\+]+)/$', 'viewmail',
+ name="viewmail_std"),
+ url(r'^viewmail/$', 'viewmail', name="viewmail_null"),
(r'^getmailcontent/(?P[\w\-\+]+)/$', 'getmailcontent'),
(r'^viewheaders/(?P[\w\-\+]+)/$', 'viewheaders'),
(r'^process/$', 'process'),
diff --git a/extensions/webmail/__init__.py b/extensions/webmail/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/extensions/webmail/imap_listing.py b/extensions/webmail/imap_listing.py
new file mode 100644
index 000000000..b6ff4ad19
--- /dev/null
+++ b/extensions/webmail/imap_listing.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+from datetime import datetime, timedelta
+from email.header import decode_header
+import email.utils
+from django.utils.translation import ugettext as _
+from mailng.lib import decode, tables, imap_utf7
+from mailng.lib.email_listing import MBconnector, EmailListing
+from mailng.lib import tables, imap_utf7, parameters
+
+class WMtable(tables.Table):
+ idkey = "imapid"
+ selection = tables.SelectionColumn("selection", first=True)
+ subject = tables.Column("subject", label=_("Subject"))
+ from_ = tables.Column("from", label=_("From"))
+ date = tables.Column("date", label=_("Date"))
+ from_exp = re.compile("([^<]+)<([^>]+)>")
+ name_exp = re.compile("\"(.+)\"")
+
+ def parse_from(self, value):
+ m = self.from_exp.match(value)
+ if m:
+ name = m.group(1)
+ name = name.strip()
+ m2 = self.name_exp.match(name)
+ if m2:
+ return m2.group(1)
+ return m.group(1)
+ return value
+
+ def parse_date(self, value):
+ ndate = datetime(*(email.utils.parsedate_tz(value))[:7])
+ now = datetime.now()
+ if now - ndate > timedelta(7):
+ return ndate.strftime("%d.%m.%Y %H:%M")
+ return ndate.strftime("%a %H:%M")
+
+ def parse_subject(self, value):
+ res = ""
+ dcd = decode_header(value)
+ for part in dcd:
+ if res != "":
+ res += " "
+ res += part[0].strip(" ")
+ return decode(res)
+
+
+class IMAPconnector(MBconnector):
+ def login(self, user, passwd):
+ import imaplib
+
+ try:
+ self.m = imaplib.IMAP4_SSL(self.address)
+ self.m.login(user, passwd)
+ except imaplib.IMAP4.error, test:
+ return False, test
+ return True, None
+
+ def _encodefolder(self, folder):
+ if not folder:
+ return "INBOX"
+ return folder.encode("imap4-utf-7")
+
+ def messages_count(self, folder=None):
+ (status, data) = self.m.select(self._encodefolder(folder))
+ return int(data[0])
+
+ def listfolders(self, topfolder='INBOX', md_folders=[]):
+ (status, data) = self.m.list()
+ result = []
+ for mb in data:
+ attrs, truc, name = mb.split()
+ name = name.strip('"').decode("imap4-utf-7")
+ present = False
+ for mdf in md_folders:
+ if mdf["name"] == name:
+ present = True
+ break
+ if not present:
+ result += [name]
+ return sorted(result)
+
+ def fetch(self, start=None, stop=None, folder=None):
+ if not start or not stop:
+ return []
+ result = []
+ self.m.select(self._encodefolder(folder))
+ range = "%d:%d" % (start, stop)
+ typ, data = self.m.fetch(range, '(BODY[HEADER.FIELDS (DATE FROM SUBJECT)])')
+ imapid = start
+ for response_part in data:
+ if isinstance(response_part, tuple):
+ msg = email.message_from_string(response_part[1])
+ msg["imapid"] = imapid
+ result += [msg]
+ imapid += 1
+ return result
+
+class ImapListing(EmailListing):
+ tpl = "webmail/index.html"
+
+ def __init__(self, user, password, **kwargs):
+ self.mbc = IMAPconnector(parameters.get("webmail", "SERVER_ADDRESS"), 143)
+ status, text = self.mbc.login(user, password)
+ if not status:
+ print "Login error: %s" % text
+ EmailListing.__init__(self, **kwargs)
+
+ def getfolders(self):
+ md_folders = [{"name" : "INBOX", "icon" : "overview.png"},
+ {"name" : 'Drafts'},
+ {"name" : 'Junk'},
+ {"name" : 'Sent'},
+ {"name" : 'Trash', "icon" : "trash.png"}]
+ folders = self.mbc.listfolders(md_folders=md_folders)
+ for fd in folders:
+ md_folders += [{"name" : fd}]
+ return md_folders
+
+ def fetch(self, id_start, id_stop):
+ return WMtable(self.mbc.fetch(start=id_start, stop=id_stop,
+ folder=self.folder))
diff --git a/extensions/webmail/main.py b/extensions/webmail/main.py
new file mode 100644
index 000000000..684587404
--- /dev/null
+++ b/extensions/webmail/main.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+import time
+from django.http import HttpResponse
+from django.utils import simplejson
+from django.utils.translation import ugettext as _
+from django.core.urlresolvers import reverse
+from django.conf.urls.defaults import include
+from django.contrib.auth.decorators import login_required
+from mailng.admin.models import Mailbox
+from mailng.lib import events, parameters
+from mailng.lib import _render
+from imap_listing import *
+
+def init():
+ events.register("UserMenuDisplay", menu)
+ parameters.register("webmail", "SERVER_ADDRESS", "string", "127.0.0.1",
+ help=_("Mailboxes server address"))
+
+def urls():
+ return (r'^mailng/webmail/',
+ include('mailng.extensions.webmail.urls'))
+
+def menu(**kwargs):
+ if kwargs["target"] != "user_menu_box":
+ return []
+ return [
+ {"name" : _("Webmail"),
+ "url" : reverse(index),
+ "img" : "/static/pics/webmail.png"}
+ ]
+
+@login_required
+def index(request):
+ start = time.time()
+ lst = ImapListing(request.user.username, request.session["password"])
+ try:
+ pageid = request.GET["page"]
+ except KeyError:
+ pageid = 1
+ return lst.render(request, pageid=int(pageid), start=start)
+
+@login_required
+def folder(request, folder):
+ lst = ImapListing(folder=folder)
+ try:
+ pageid = request.GET["page"]
+ except KeyError:
+ pageid = 1
+ page = lst.paginator.getpage(pageid)
+ if page:
+ content = lst.fetch(page.id_start, page.id_stop).render(request)
+ navbar = lst.render_navbar(page)
+ else:
+ content = "Empty folder"
+ navbar = ""
+ ctx = {"status" : "ok", "listing" : content, "navbar" : navbar}
+ return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
diff --git a/extensions/webmail/templates/index.html b/extensions/webmail/templates/index.html
new file mode 100644
index 000000000..af4971081
--- /dev/null
+++ b/extensions/webmail/templates/index.html
@@ -0,0 +1,85 @@
+{% extends "common/email_listing.html" %}
+{% load i18n %}
+{% block css %}
+#leftcol {
+ float: left;
+ width: 150px;
+}
+
+.box h3 {
+ font-size: small;
+ margin: 2px 0px 2px 0px;
+ text-align: center;
+}
+
+.box ul {
+ list-style: none;
+ padding: 5px 5px;
+ margin: 0;
+}
+
+.box li {
+ clear: both;
+ margin-bottom: 4px;
+}
+
+.box li.selected {
+ background: #ffffcc;
+}
+
+.box a:link, .box a:visited {
+ background: transparent;
+ color: black;
+ font-weight: bold;
+ text-decoration: none;
+}
+
+.box a:hover {
+ color: #6AB0C5;
+ text-decoration: none;
+}
+
+.box img {
+ float: left;
+ margin-right: 2px;
+ border: 0;
+}
+{% endblock %}
+{% block js %}
+ $$("a[name=loadfolder]").addEvent("click", function(event) {
+ event.stop();
+ $$("a[name=loadfolder]").each(function(item) {
+ item.setStyle("background", "white");
+ });
+ event.target.setStyle("background", "#FFFFCC");
+ new Request.JSON({url: event.target.get("href"), onSuccess: function(resp) {
+ $("listing").set("html", resp.listing);
+ $("navbar").set("html", resp.navbar);
+ }}).get();
+ });
+{% endblock %}
+{% block middletitle %}{% trans "Webmail" %}{% endblock %}
+{% block menu %}
+
+{% endblock %}
+{% block leftcol %}
+
+
{% trans "Folders" %}
+
+
+{% endblock %}
diff --git a/extensions/webmail/urls.py b/extensions/webmail/urls.py
new file mode 100644
index 000000000..c61787ee9
--- /dev/null
+++ b/extensions/webmail/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('mailng.extensions.webmail.main',
+ (r'^$', "index"),
+ (r'^(?P.+)/$', "folder"),
+ )
diff --git a/lib/__init__.py b/lib/__init__.py
index ff9fab9b6..25b629862 100644
--- a/lib/__init__.py
+++ b/lib/__init__.py
@@ -63,7 +63,7 @@ def _ctx_ok(url):
def _ctx_ko(tpl, ctx):
return {"status" : "ko", "content" : render_to_string(tpl, ctx)}
-def decode(s, encodings=('ascii', 'utf8', 'latin1')):
+def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')):
for encoding in encodings:
try:
return s.decode(encoding)
diff --git a/lib/email_listing.py b/lib/email_listing.py
new file mode 100644
index 000000000..e56867631
--- /dev/null
+++ b/lib/email_listing.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+import time
+from django.template.loader import render_to_string
+from mailng.lib import _render
+
+class MBconnector(object):
+ def __init__(self, address, port):
+ self.address = address
+ self.port = port
+
+class Page(object):
+ def __init__(self, pageid, id_start, id_stop, items,
+ items_per_page, has_previous, has_next):
+ self.pageid = pageid
+ self.id_start = id_start
+ self.id_stop = id_stop
+ self.items = items
+ self.items_per_page = items_per_page
+ self.has_previous = has_previous
+ self.has_next = has_next
+
+ def previous_page(self):
+ if not self.has_previous:
+ return False
+ return self.pageid - 1
+
+ def next_page(self):
+ if not self.has_next:
+ return False
+ return self.pageid + 1
+
+ def last_page(self):
+ lid = self.items / self.items_per_page
+ if not lid:
+ return 1
+ if self.items % self.items_per_page:
+ lid += 1
+ return lid
+
+class Paginator(object):
+ def __init__(self, total, elems_per_page):
+ self.total = total
+ self.elems_per_page = elems_per_page
+
+ def _indexes(self, page):
+ id_start = self.elems_per_page * page + 1
+ id_stop = id_start + self.elems_per_page - 1
+ return (id_start, id_stop)
+
+ def getpage(self, page):
+ if page < 1:
+ return None
+ id_start, id_stop = self._indexes(page - 1)
+ has_previous = has_next = False
+ if id_start < self.total:
+ if page > 1:
+ has_previous = True
+ if id_stop < self.total:
+ has_next = True
+ else:
+ id_stop = self.total
+ else:
+ return None
+ return Page(page, id_start, id_stop, self.total,
+ self.elems_per_page, has_previous, has_next)
+
+class EmailListing:
+ def __init__(self, folder=None, elems_per_page=40, **kwargs):
+ self.folder = folder
+ self.elems_per_page = elems_per_page
+ self.paginator = Paginator(self.mbc.messages_count(self.folder),
+ elems_per_page)
+
+ def render_navbar(self, page):
+ return render_to_string("common/navbar.html", {
+ "page" : page
+ })
+
+ def getfolders(self):
+ return None
+
+ def render(self, request, pageid=1, **kwargs):
+ page = self.paginator.getpage(pageid)
+ if not page:
+ listing = "Empty folder"
+ else:
+ listing = self.fetch(page.id_start, page.id_stop).render(request)
+ elapsed = kwargs.has_key("start") and time.time() - kwargs["start"] or 0
+ return _render(request, self.tpl, {
+ "listing" : listing, "elapsed" : elapsed,
+ "navbar" : self.render_navbar(page),
+ "folders" : self.getfolders(),
+ "selection" : self.folder,
+ "current_page" : pageid
+ })
diff --git a/lib/imap_utf7.py b/lib/imap_utf7.py
new file mode 100644
index 000000000..516a45d7f
--- /dev/null
+++ b/lib/imap_utf7.py
@@ -0,0 +1,210 @@
+# -*- coding: latin-1 -*-
+
+"""
+Imap folder names are encoded using a special version of utf-7 as defined in RFC
+2060 section 5.1.3.
+
+5.1.3. Mailbox International Naming Convention
+
+ By convention, international mailbox names are specified using a
+ modified version of the UTF-7 encoding described in [UTF-7]. The
+ purpose of these modifications is to correct the following problems
+ with UTF-7:
+
+ 1) UTF-7 uses the "+" character for shifting; this conflicts with
+ the common use of "+" in mailbox names, in particular USENET
+ newsgroup names.
+
+ 2) UTF-7's encoding is BASE64 which uses the "/" character; this
+ conflicts with the use of "/" as a popular hierarchy delimiter.
+
+ 3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
+ the use of "\" as a popular hierarchy delimiter.
+
+ 4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
+ the use of "~" in some servers as a home directory indicator.
+
+ 5) UTF-7 permits multiple alternate forms to represent the same
+ string; in particular, printable US-ASCII chararacters can be
+ represented in encoded form.
+
+ In modified UTF-7, printable US-ASCII characters except for "&"
+ represent themselves; that is, characters with octet values 0x20-0x25
+ and 0x27-0x7e. The character "&" (0x26) is represented by the two-
+ octet sequence "&-".
+
+ All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
+ Unicode 16-bit octets) are represented in modified BASE64, with a
+ further modification from [UTF-7] that "," is used instead of "/".
+ Modified BASE64 MUST NOT be used to represent any printing US-ASCII
+ character which can represent itself.
+
+ "&" is used to shift to modified BASE64 and "-" to shift back to US-
+ ASCII. All names start in US-ASCII, and MUST end in US-ASCII (that
+ is, a name that ends with a Unicode 16-bit octet MUST end with a "-
+ ").
+
+ For example, here is a mailbox name which mixes English, Japanese,
+ and Chinese text: ~peter/mail/&ZeVnLIqe-/&U,BTFw-
+
+Found here:
+http://svn.plone.org/svn/collective/mxmImapClient/trunk/imapUTF7.py
+
+"""
+import binascii
+import codecs
+
+# encoding
+
+def modified_base64(s):
+ s = s.encode('utf-16be')
+ return binascii.b2a_base64(s).rstrip('\n=').replace('/', ',')
+
+def doB64(_in, r):
+ if _in:
+ r.append('&%s-' % modified_base64(''.join(_in)))
+ del _in[:]
+
+def encoder(s):
+ r = []
+ _in = []
+ for c in s:
+ ordC = ord(c)
+ if 0x20 <= ordC <= 0x25 or 0x27 <= ordC <= 0x7e:
+ doB64(_in, r)
+ r.append(c)
+ elif c == '&':
+ doB64(_in, r)
+ r.append('&-')
+ else:
+ _in.append(c)
+ doB64(_in, r)
+ return (str(''.join(r)), len(s))
+
+
+# decoding
+
+def modified_unbase64(s):
+ b = binascii.a2b_base64(s.replace(',', '/') + '===')
+ return unicode(b, 'utf-16be')
+
+
+def decoder(s):
+ r = []
+ decode = []
+ for c in s:
+ if c == '&' and not decode:
+ decode.append('&')
+ elif c == '-' and decode:
+ if len(decode) == 1:
+ r.append('&')
+ else:
+ r.append(modified_unbase64(''.join(decode[1:])))
+ decode = []
+ elif decode:
+ decode.append(c)
+ else:
+ r.append(c)
+ if decode:
+ r.append(modified_unbase64(''.join(decode[1:])))
+ bin_str = ''.join(r)
+ return (bin_str, len(s))
+
+
+class StreamReader(codecs.StreamReader):
+ def decode(self, s, errors='strict'):
+ return decoder(s)
+
+
+class StreamWriter(codecs.StreamWriter):
+ def decode(self, s, errors='strict'):
+ return encoder(s)
+
+
+def imap4_utf_7(name):
+ if name == 'imap4-utf-7':
+ return (encoder, decoder, StreamReader, StreamWriter)
+codecs.register(imap4_utf_7)
+
+
+## testing methods
+
+def imapUTF7Encode(ust):
+ "Returns imap utf-7 encoded version of string"
+ return ust.encode('imap4-utf-7')
+
+def imapUTF7EncodeSequence(seq):
+ "Returns imap utf-7 encoded version of strings in sequence"
+ return [imapUTF7Encode(itm) for itm in seq]
+
+
+def imapUTF7Decode(st):
+ "Returns utf7 encoded version of imap utf-7 string"
+ return st.decode('imap4-utf-7')
+
+def imapUTF7DecodeSequence(seq):
+ "Returns utf7 encoded version of imap utf-7 strings in sequence"
+ return [imapUTF7Decode(itm) for itm in seq]
+
+
+def utf8Decode(st):
+ "Returns utf7 encoded version of imap utf-7 string"
+ return st.decode('utf-8')
+
+
+def utf7SequenceToUTF8(seq):
+ "Returns utf7 encoded version of imap utf-7 strings in sequence"
+ return [itm.decode('imap4-utf-7').encode('utf-8') for itm in seq]
+
+
+__all__ = ['imapUTF7Encode', 'imapUTF7Decode', ]
+
+if __name__=='__main__':
+
+## print u'bøx'.encode('imap4-utf-7')
+## print 'expected b&APg-x'
+##
+## print u'båx'.encode('imap4-utf-7')
+## print 'expected b&AOU-x'
+##
+ print '#######'
+ print 'bøx'
+ e = imapUTF7Encode(u'bøx')
+ print e
+ print imapUTF7Decode(e).encode('latin-1')
+
+ print '#######'
+ print 'båx'
+ e = imapUTF7Encode(u'båx')
+ print e
+ print imapUTF7Decode(e).encode('latin-1')
+
+ print '#######'
+ print '~/bågø'
+ e = imapUTF7Encode(u'~/bågø')
+ print e
+ print imapUTF7Decode(e).encode('latin-1')
+
+ print '#######'
+ print 'Ting & Såger'
+ e = imapUTF7Encode(u'Ting & Såger')
+ print e
+ print imapUTF7Decode(e).encode('latin-1')
+
+## e = imapUTF7Decode('b&AOU-x')
+## print e.encode('latin-1')
+##
+## e = imapUTF7Decode('b&APg-x')
+## print e.encode('latin-1')
+##
+ print '#######'
+ print '~/Følder/mailbåx & stuff + more'
+ n = u'~/Følder/mailbåx & stuff + more'
+ e = imapUTF7Encode(n)
+ print e
+ print imapUTF7Decode(e).encode('latin-1')
+
+ print '#######'
+ print '~peter/mail/&ZeVnLIqe-/&U,BTFw-'
+ print imapUTF7Decode('~peter/mail/&ZeVnLIqe-/&U,BTFw-').encode('utf-8')
+
diff --git a/lib/parameters.py b/lib/parameters.py
index dfaf50f27..e7b1e484f 100644
--- a/lib/parameters.py
+++ b/lib/parameters.py
@@ -24,14 +24,14 @@ class NotDefined(Exception):
return "Application '%s' and/or parameter '%s' not defined" % (self.app,
self.name)
-def register(app, name, type="string", default=None, help=None):
+def register(app, name, type="string", deflt=None, help=None):
"""Register a new parameter.
app corresponds to a core component (admin, main) or an extension.
"""
if not app in _params.keys():
_params[app] = {}
- _params[app][name] = {"type" : type, "default" : default, "help" : help}
+ _params[app][name] = {"type" : type, "default" : deflt, "help" : help}
def save(app, name, value):
if not app in _params.keys() or not name in _params[app].keys():
@@ -53,5 +53,5 @@ def get(app, name):
try:
p = Parameter.objects.get(name="%s.%s" % (app, name))
except Parameter.DoesNotExist:
- return _params[app][name]["defaut"]
+ return _params[app][name]["default"]
return p.value
diff --git a/lib/tables.py b/lib/tables.py
new file mode 100644
index 000000000..a83b806a3
--- /dev/null
+++ b/lib/tables.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+import inspect
+import lxml
+from django.template.loader import render_to_string
+
+class Column:
+ def __init__(self, name, **kwargs):
+ self.name = name
+ for k, v in kwargs.iteritems():
+ setattr(self, k, v)
+
+ def __str__(self):
+ try:
+ label = getattr(self, "label")
+ except KeyError:
+ label = getattr(self, "name")
+ return label
+
+class SelectionColumn(Column):
+ def __str__(self):
+ return ""
+
+ def render(self, value):
+ return "" \
+ % (self.name, value)
+
+class Table(object):
+ def __init__(self, rows={}):
+ self.columns = []
+ for m in inspect.getmembers(self):
+ if isinstance(m[1], Column):
+ try:
+ if getattr(m[1], "first"):
+ self.columns = [m[1]] + self.columns
+ except AttributeError:
+ self.columns += [m[1]]
+ self.rows = []
+ for row in rows:
+ nrow = {"id" : row[self.idkey], "cols" : []}
+ for c in self.columns:
+ if isinstance(c, SelectionColumn):
+ nrow["cols"] += [{"value" : c.render(row[self.idkey]), "safe" : True}]
+ continue
+ if row.has_key(c.name):
+ try:
+ nrow["cols"] += [{"value" : getattr(self, "parse_%s" % c.name)(row[c.name])}]
+ except AttributeError:
+ nrow["cols"] += [{"value" : row[c.name]}]
+ self.rows += [nrow]
+
+ def render(self, request):
+ return render_to_string("common/tables.html", {
+ "table" : self
+ })
+
+
diff --git a/main/forms.py b/main/forms.py
index f267eb72e..e038c46e6 100644
--- a/main/forms.py
+++ b/main/forms.py
@@ -6,7 +6,7 @@ from django.utils.translation import ugettext as _
class ARmessageForm(forms.ModelForm):
class Meta:
model = ARmessage
- fields = ('subject', 'content', 'enabled')
+ fields = ('subject', 'content', 'enabled', 'untildate')
def __init__(self, *args, **kwargs):
super(ARmessageForm, self).__init__(*args, **kwargs)
diff --git a/main/models.py b/main/models.py
index 4f4348057..a3e5329f0 100644
--- a/main/models.py
+++ b/main/models.py
@@ -7,6 +7,7 @@ class ARmessage(models.Model):
subject = models.CharField(_('subject'), max_length=255)
content = models.TextField(_('content'))
enabled = models.BooleanField(_('enabled'))
+# untildate = models.DateTimeField(_('until'))
class ARhistoric(models.Model):
armessage = models.ForeignKey(ARmessage)
diff --git a/static/js/common.js b/static/js/common.js
index 4f1e387e4..17bcf706a 100644
--- a/static/js/common.js
+++ b/static/js/common.js
@@ -1,6 +1,14 @@
window.addEvent('domready', function(){
- if ($('menubar')) {
- $('menubar').getElements('li.dropdown').each( function( elem ){
+ parse_menubar('topmenubar');
+ parse_menubar('menubar');
+ SqueezeBox.assign($$('a.boxed'), {
+ parse: 'rel',
+ });
+});
+
+parse_menubar = function(id) {
+ if ($(id)) {
+ $(id).getElements('li.dropdown').each( function( elem ){
var list = elem.getElement('ul.links');
var myFx = new Fx.Slide(list).hide();
elem.addEvents({
@@ -15,10 +23,7 @@ window.addEvent('domready', function(){
});
});
}
- SqueezeBox.assign($$('a.boxed'), {
- parse: 'rel',
- });
-});
+}
get_iframe_body = function(id) {
var saf = navigator.userAgent.match(/Safari/i);
@@ -65,3 +70,38 @@ confirmation = function(question, action, callback) {
});
SqueezeBox.open("/mailng/main/confirm/?question=" + question);
}
+
+setContentSize = function(cellsize) {
+ var contentsize = $(document.body).getSize().y - $("topmenubar").getSize().y
+ - $("header").getSize().y;
+
+ contentsize -= $("navbar").getSize().y * 2;
+ contentsize -= contentsize % cellsize;
+ $("listing").setStyle("height", contentsize + "px");
+}
+
+bindRows = function(rooturl, page) {
+ $$("tr").addEvent("click", function(event) {
+ if (event.target.parentNode.id) {
+ var url = rooturl + event.target.parentNode.id + "?page=" + page;
+ event.target.parentNode.setStyle("background", "#ffffcc");
+ location.href = url;
+ }
+ });
+}
+
+toggleSelection = function(id, value) {
+ $$("input[name=" + id + "]").each(function(obj) {
+ obj.checked = value;
+ });
+}
+
+checkSelection = function(id, text) {
+ var getout = $$("input[name=" + id + "]").every(function(item, index) {
+ return item.checked == false;
+ });
+ if (getout) {
+ return false;
+ }
+ return confirm(text);
+}
\ No newline at end of file
diff --git a/static/js/domains.js b/static/js/domains.js
index e59eafc99..286515bac 100644
--- a/static/js/domains.js
+++ b/static/js/domains.js
@@ -40,7 +40,7 @@ window.addEvent('domready', function() {
});
- $('body').addEvent('click', function(event) {
+ $(document.body).addEvent('click', function(event) {
target = event.target;
if (target.get('tag') == 'a') {
name = target.get('name');
@@ -54,7 +54,7 @@ window.addEvent('domready', function() {
}
});
- $('body').addEvent('submit', function(event) {
+ $(document.body).addEvent('submit', function(event) {
target = event.target;
if (target.get('tag') == 'form') {
name = target.get('name');
diff --git a/static/js/main.js b/static/js/main.js
index c89c7f9ba..97c3c0340 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -2,4 +2,9 @@ window.addEvent('domready', function() {
SqueezeBox.assign($$('a.boxed'), {
parse: 'rel'
});
+
+ new DatePicker("#untildate", {
+ pickerClass: "datepicker_vista",
+ allowEmpty: true
+ });
});
diff --git a/static/js/mootools-more.js b/static/js/mootools-more.js
index 2337e8bba..6dce4406c 100644
--- a/static/js/mootools-more.js
+++ b/static/js/mootools-more.js
@@ -182,6 +182,101 @@ Element.implement({
});
+/*
+---
+
+script: Scroller.js
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Events
+- core:1.2.4/Options
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Dimensions
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ area: 20,
+ velocity: 1,
+ onChange: function(x, y){
+ this.element.scrollTo(x, y);
+ },
+ fps: 50
+ },
+
+ initialize: function(element, options){
+ this.setOptions(options);
+ this.element = document.id(element);
+ this.listener = ($type(this.element) != 'element') ? document.id(this.element.getDocument().body) : this.element;
+ this.timer = null;
+ this.bound = {
+ attach: this.attach.bind(this),
+ detach: this.detach.bind(this),
+ getCoords: this.getCoords.bind(this)
+ };
+ },
+
+ start: function(){
+ this.listener.addEvents({
+ mouseover: this.bound.attach,
+ mouseout: this.bound.detach
+ });
+ },
+
+ stop: function(){
+ this.listener.removeEvents({
+ mouseover: this.bound.attach,
+ mouseout: this.bound.detach
+ });
+ this.detach();
+ this.timer = $clear(this.timer);
+ },
+
+ attach: function(){
+ this.listener.addEvent('mousemove', this.bound.getCoords);
+ },
+
+ detach: function(){
+ this.listener.removeEvent('mousemove', this.bound.getCoords);
+ this.timer = $clear(this.timer);
+ },
+
+ getCoords: function(event){
+ this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+ if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+ },
+
+ scroll: function(){
+ var size = this.element.getSize(),
+ scroll = this.element.getScroll(),
+ pos = this.element.getOffsets(),
+ scrollSize = this.element.getScrollSize(),
+ change = {x: 0, y: 0};
+ for (var z in this.page){
+ if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0)
+ change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
+ else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z])
+ change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
+ }
+ if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+ }
+
+});
+
/*
---
@@ -211,167 +306,159 @@ provides: [Tips]
(function(){
var read = function(option, element){
- return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
+ return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
};
this.Tips = new Class({
- Implements: [Events, Options],
+ Implements: [Events, Options],
- options: {
- /*
- onAttach: $empty(element),
- onDetach: $empty(element),
- */
- onShow: function(){
- this.tip.setStyle('display', 'block');
- },
- onHide: function(){
- this.tip.setStyle('display', 'none');
- },
- title: 'title',
- text: function(element){
- return element.get('rel') || element.get('href');
- },
- showDelay: 100,
- hideDelay: 100,
- className: 'tip-wrap',
- offset: {x: 16, y: 16},
- fixed: false
- },
+ options: {
+ /*
+ onAttach: $empty(element),
+ onDetach: $empty(element),
+ */
+ onShow: function(){
+ this.tip.setStyle('display', 'block');
+ },
+ onHide: function(){
+ this.tip.setStyle('display', 'none');
+ },
+ title: 'title',
+ text: function(element){
+ return element.get('rel') || element.get('href');
+ },
+ showDelay: 100,
+ hideDelay: 100,
+ className: 'tip-wrap',
+ offset: {x: 16, y: 16},
+ fixed: false
+ },
- initialize: function(){
- var params = Array.link(arguments, {options: Object.type, elements: $defined});
- this.setOptions(params.options);
- document.id(this);
-
- if (params.elements) this.attach(params.elements);
- },
+ initialize: function(){
+ var params = Array.link(arguments, {options: Object.type, elements: $defined});
+ this.setOptions(params.options);
+ document.id(this);
+
+ if (params.elements) this.attach(params.elements);
+ },
- toElement: function(){
- if (this.tip) return this.tip;
-
- this.container = new Element('div', {'class': 'tip'});
- return this.tip = new Element('div', {
- 'class': this.options.className,
- styles: {
- position: 'absolute',
- top: 0,
- left: 0,
- visibility: 'hidden',
- display: 'none'
- }
- }).adopt(
- new Element('div', {'class': 'tip-top'}),
- this.container,
- new Element('div', {'class': 'tip-bottom'})
- ).inject(document.body);
- },
-
- attach: function(elements){
- $$(elements).each(function(element){
- var title = read(this.options.title, element),
- text = read(this.options.text, element);
-
- element.erase('title').store('tip:native', title).retrieve('tip:title', title);
- element.retrieve('tip:text', text);
- this.fireEvent('attach', [element]);
-
- var events = ['enter', 'leave'];
- if (!this.options.fixed) events.push('move');
+ toElement: function(){
+ if (this.tip) return this.tip;
+
+ this.container = new Element('div', {'class': 'tip'});
+ return this.tip = new Element('div', {
+ 'class': this.options.className,
+ styles: {
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }
+ }).adopt(
+ new Element('div', {'class': 'tip-top'}),
+ this.container,
+ new Element('div', {'class': 'tip-bottom'})
+ ).inject(document.body);
+ },
- events.each(function(value){
- var event = element.retrieve('tip:' + value);
- if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);
- element.store('tip:' + value, event).addEvent('mouse' + value, event);
- }, this);
-
- element.addEvent('mouseout', this.hide.bind(this, element)); // firefox doesn't handle mouseleave correct
-
- }, this);
-
- return this;
- },
-
- detach: function(elements){
- $$(elements).each(function(element){
- ['enter', 'leave', 'move'].each(function(value){
- element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
- });
-
- this.fireEvent('detach', [element]);
-
- if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
- var original = element.retrieve('tip:native');
- if (original) element.set('title', original);
- }
- }, this);
-
- return this;
- },
-
- elementEnter: function(event, element){
- this.container.empty();
-
- ['title', 'text'].each(function(value){
- var content = element.retrieve('tip:' + value);
- if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
- }, this);
-
- $clear(this.timer);
- this.timer = this.show.delay(this.options.showDelay, this, [element, event]);
- },
-
- elementLeave: function(event, element){
- this.tip.setStyle('visibility', 'hidden');
- $clear(this.timer);
- this.timer = this.hide.delay(this.options.hideDelay, this, element);
- this.fireForParent(event, element);
- },
-
- fireForParent: function(event, element){
- var element = $(element);
- if (!element) return;
- parentNode = element.getParent();
- if (parentNode == document.body) return;
- if (parentNode.retrieve('tip:enter')) parentNode.fireEvent('mouseenter', event);
- else this.fireForParent(event, parentNode);
- },
-
- elementMove: function(event, element) {
- this.position(event);
- },
+ attach: function(elements){
+ $$(elements).each(function(element){
+ var title = read(this.options.title, element),
+ text = read(this.options.text, element);
+
+ element.erase('title').store('tip:native', title).retrieve('tip:title', title);
+ element.retrieve('tip:text', text);
+ this.fireEvent('attach', [element]);
+
+ var events = ['enter', 'leave'];
+ if (!this.options.fixed) events.push('move');
+
+ events.each(function(value){
+ var event = element.retrieve('tip:' + value);
+ if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);
+
+ element.store('tip:' + value, event).addEvent('mouse' + value, event);
+ }, this);
+ }, this);
+
+ return this;
+ },
+
+ detach: function(elements){
+ $$(elements).each(function(element){
+ ['enter', 'leave', 'move'].each(function(value){
+ element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+ });
+
+ this.fireEvent('detach', [element]);
+
+ if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+ var original = element.retrieve('tip:native');
+ if (original) element.set('title', original);
+ }
+ }, this);
+
+ return this;
+ },
+
+ elementEnter: function(event, element){
+ this.container.empty();
+
+ ['title', 'text'].each(function(value){
+ var content = element.retrieve('tip:' + value);
+ if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
+ }, this);
+
+ $clear(this.timer);
+ this.timer = this.show.delay(this.options.showDelay, this, element);
+ this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+ },
+
+ elementLeave: function(event, element){
+ $clear(this.timer);
+ this.timer = this.hide.delay(this.options.hideDelay, this, element);
+ this.fireForParent(event, element);
+ },
+
+ fireForParent: function(event, element){
+ if (!element) return;
+ parentNode = element.getParent();
+ if (parentNode == document.body) return;
+ if (parentNode.retrieve('tip:enter')) parentNode.fireEvent('mouseenter', event);
+ else this.fireForParent(parentNode, event);
+ },
+
+ elementMove: function(event, element){
+ this.position(event);
+ },
+
+ position: function(event){
+ var size = window.getSize(), scroll = window.getScroll(),
+ tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+ props = {x: 'left', y: 'top'},
+ obj = {};
+
+ for (var z in props){
+ obj[props[z]] = event.page[z] + this.options.offset[z];
+ if ((obj[props[z]] + tip[z] - scroll[z]) > size[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+ }
+
+ this.tip.setStyles(obj);
+ },
+
+ fill: function(element, contents){
+ if(typeof contents == 'string') element.set('html', contents);
+ else element.adopt(contents);
+ },
+
+ show: function(element){
+ this.fireEvent('show', [this.tip, element]);
+ },
+
+ hide: function(element){
+ this.fireEvent('hide', [this.tip, element]);
+ }
- position: function(event){
- if (this.tip.getStyle('display') != 'block') return;
- var size = window.getSize(), scroll = window.getScroll(),
- tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
- props = {x: 'left', y: 'top'},
- obj = {};
-
- for (var z in props){
- obj[props[z]] = event.page[z] + this.options.offset[z];
- if ((obj[props[z]] + tip[z] - scroll[z]) > size[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
- }
-
- this.tip.setStyles(obj);
- this.tip.setStyle('visibility', 'visible');
- },
-
- fill: function(element, contents){
- if(typeof contents == 'string') element.set('html', contents);
- else element.adopt(contents);
- },
-
- show: function(element, event) {
- this.fireEvent('show', [this.tip, element]);
- this.position((this.options.fixed) ? {page: element.getPosition()} : event);
- },
-
- hide: function(element){
- this.fireEvent('hide', [this.tip, element]);
- }
-
});
})();
\ No newline at end of file
diff --git a/static/pics/folder.png b/static/pics/folder.png
new file mode 100644
index 000000000..901edc962
Binary files /dev/null and b/static/pics/folder.png differ
diff --git a/static/pics/overview.png b/static/pics/overview.png
new file mode 100644
index 000000000..13d2c00d0
Binary files /dev/null and b/static/pics/overview.png differ
diff --git a/static/pics/webmail.png b/static/pics/webmail.png
new file mode 100644
index 000000000..859251fe0
Binary files /dev/null and b/static/pics/webmail.png differ
diff --git a/static/stylesheets/basic.css b/static/stylesheets/basic.css
index 6529c8112..58de59d91 100644
--- a/static/stylesheets/basic.css
+++ b/static/stylesheets/basic.css
@@ -2,6 +2,7 @@ body {
font-family: arial, helvetica, verdana, tahoma, sans-serif;
font-size: small;
background: #153F65;
+ margin: 0;
}
#first-container {
@@ -342,3 +343,13 @@ body {
margin: 20px;
font-weight: bold;
}
+
+/* nouveau style */
+#topmenubar {
+ width: 100%;
+ margin: 0 auto;
+ padding: 0;
+ background: #072342;
+ border-bottom: 2px solid #333;
+ height: 30px;
+}
diff --git a/static/stylesheets/default.css b/static/stylesheets/default.css
new file mode 100644
index 000000000..84ef58b6c
--- /dev/null
+++ b/static/stylesheets/default.css
@@ -0,0 +1,254 @@
+html, body {
+ font-family: arial, helvetica, verdana, tahoma, sans-serif;
+ font-size: small;
+ background: #153F65;
+ margin: 0;
+ padding: 0;
+
+}
+
+#topmenubar {
+ width: 100%;
+ margin: 0 auto;
+ padding: 0;
+ background: #072342;
+ border-bottom: 2px solid #333;
+ height: 30px;
+}
+
+#menubar {
+ margin: 5px;
+ display: table;
+ width: 700px;
+ margin-top: 35px;
+ /*position: absolute;*/
+}
+
+#topmenubar a, #topmenubar a:visited {
+ color: white;
+ font-weight: bold;
+}
+
+#topmenubar label {
+ color: white;
+ font-weight: bold;
+}
+
+#menubar a, #menubar a:visited {
+ color: black;
+ font-weight: bold;
+}
+
+#menubar label {
+ color: black;
+ font-weight: bold;
+}
+
+#page {
+ border: 2px solid #333;
+ background: white;
+ min-height: 92%;
+ margin: 0 15px;
+}
+
+#content {
+ overflow: auto;
+ padding: 5px;
+}
+
+#header {
+ width: 100%;
+ padding: 5px 0;
+}
+
+#header h1 {
+ font-size: medium;
+ width: 50%;
+ margin: 0 0 0 5px;
+}
+
+#loginbox {
+ margin: 100px auto;
+ width: 610px;
+ padding-bottom: 15px;
+ border: 2px solid #333;
+ background: white;
+}
+
+#loginbox h2 {
+ margin: 30px 10px 0 10px;
+ font-size: small;
+}
+
+#loginbox h3 {
+ font-size: small;
+ text-align: center;
+}
+
+#logo {
+ float: left;
+ text-align: center;
+ width: 300px;
+ margin-top: 10px;
+ border-right: 1px solid #ccc;
+}
+
+#logo img {
+ height: 90px;
+}
+
+#test {
+ float: right;
+ width: 300px;
+}
+
+.menubar a, .menubar a:visited {
+ text-decoration: none;
+}
+
+.menubar a:hover {
+ text-decoration: underline;
+}
+
+.menubar ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ width: 100%;
+ color: #2a4069;
+}
+
+.menubar li {
+ float: left;
+ display: inline;
+ margin: 5px 5px;
+}
+
+.menubar li.selected {
+ background: #ffff77;
+}
+
+.menubar img {
+ float: left;
+ margin: 0px 3px;
+ border: 0;
+}
+
+.menubar .title {
+ color: white;
+}
+
+.left {
+ text-align: left;
+}
+
+.right {
+ float: right;
+}
+
+.logo {
+ float: right;
+ /*margin: 5px 0 0 5px;*/
+ height: 90px;
+}
+
+.dropdown {
+ position: absolute;
+}
+
+.dropdown ul {
+ display: block;
+ background: #ffffcc;
+ width: 150px;
+ height: 90px;
+ margin: 0px;
+ padding: 0px;
+ padding-top: 10px;
+}
+
+.dropdown a, .dropdown a:visited {
+ color: black !important;
+}
+
+.spacer {
+ clear: both;
+}
+
+/* Mootools tooltips */
+.tip {
+ color: #fff;
+ width: 139px;
+ z-index: 13000;
+ border: 1px solid #cccccc;
+}
+
+.tip-title {
+ font-weight: bold;
+ font-size: 11px;
+ margin: 0;
+ color: #9FD4FF;
+ padding: 8px 8px 4px;
+ background: #eee;
+}
+
+.tip-text {
+ font-size: 11px;
+ padding: 4px 8px 8px;
+ background: #eee;
+ color: black;
+}
+
+table {
+ margin: 0 auto;
+ width: 100%;
+ border-collapse: collapse;
+ /*border-spacing: 20px;*/
+}
+
+tr.odd {
+ background: white;
+}
+
+tr.even {
+ background: #F4F6F8;
+}
+
+th, td {
+ height: 20px;
+}
+
+table th {
+ background: #CEDBEC;
+ text-align: left;
+ padding: 5px;
+}
+
+table th.right {
+ text-align: right;
+}
+
+table td {
+ border-bottom: 1px solid #C8D6E8;
+ /*padding: 5px;*/
+}
+
+input {
+ border: 1px solid #cccccc;
+ color: inherit;
+ /*background-color: #e9e9e9;*/
+ background-color: #eee;
+ padding: 2px;
+}
+
+input:hover {
+ color: inherit;
+ background-color: #ffffcc;
+}
+
+.hr td.odd {
+ background: #F7F9Fb;
+}
+
+.hr td.even {
+
+}
\ No newline at end of file
diff --git a/static/stylesheets/login.css b/static/stylesheets/login.css
new file mode 100644
index 000000000..11060e5c0
--- /dev/null
+++ b/static/stylesheets/login.css
@@ -0,0 +1,70 @@
+html, body {
+ font-family: arial, helvetica, verdana, tahoma, sans-serif;
+ font-size: small;
+ background: #153F65;
+ margin: 0;
+ padding: 0;
+}
+
+#loginbox {
+ margin: 100px auto;
+ width: 610px;
+ padding-bottom: 15px;
+ border: 5px solid #333;
+ background: white;
+}
+
+#loginbox h2 {
+ margin: 30px 10px 0 10px;
+ font-size: small;
+}
+
+#loginbox h3 {
+ font-size: small;
+ text-align: center;
+}
+
+#logo {
+ float: left;
+ text-align: center;
+ width: 300px;
+ margin-top: 10px;
+ border-right: 1px solid #ccc;
+}
+
+#logo img {
+ height: 90px;
+}
+
+#test {
+ float: right;
+ width: 300px;
+}
+
+.spacer {
+ clear: both;
+}
+
+/* Mootools tooltips */
+.tip {
+ color: #fff;
+ width: 139px;
+ z-index: 13000;
+ border: 1px solid #cccccc;
+}
+
+.tip-title {
+ font-weight: bold;
+ font-size: 11px;
+ margin: 0;
+ color: #9FD4FF;
+ padding: 8px 8px 4px;
+ background: #eee;
+}
+
+.tip-text {
+ font-size: 11px;
+ padding: 4px 8px 8px;
+ background: #eee;
+ color: black;
+}
diff --git a/static/stylesheets/mailng.png b/static/stylesheets/mailng.png
new file mode 100644
index 000000000..f12629da5
Binary files /dev/null and b/static/stylesheets/mailng.png differ
diff --git a/templates/admin/addpermission.html b/templates/admin/addpermission.html
index dfbbee887..c5108058d 100644
--- a/templates/admin/addpermission.html
+++ b/templates/admin/addpermission.html
@@ -26,7 +26,7 @@ window.addEvent("domready", function() {
{% if error %}{{ error }}
{% endif %}
{% url admin.views.addpermission as addperm_url %}