From 0252cebb4e701575ccdc0d05cddfaa6d1705b7e5 Mon Sep 17 00:00:00 2001 From: tonio Date: Fri, 2 Apr 2010 19:07:37 +0200 Subject: [PATCH] Quarantine plugin refactoring. It is now working like the webmail plugin, ie with an anchor based ajax navigation. What is broken for the moment: * search functionality, * headers do not appear while displaying a mail. New message system. (see infobox.js) --- extensions/amavis_quarantine/main.py | 170 ++++++++------ extensions/amavis_quarantine/sql_listing.py | 3 + .../amavis_quarantine/templates/index.html | 207 +++++++++++------- .../amavis_quarantine/templates/viewmail.html | 2 + .../templatetags/amextras.py | 30 ++- extensions/amavis_quarantine/urls.py | 7 +- extensions/webmail/imap_listing.py | 8 +- extensions/webmail/main.py | 20 +- extensions/webmail/templates/index.html | 61 ++---- extensions/webmail/templatetags/webextras.py | 11 + lib/__init__.py | 11 + lib/email_listing.py | 24 +- lib/tables.py | 24 +- static/js/common.js | 36 ++- static/js/infobox.js | 105 +++++++++ static/stylesheets/default.css | 4 +- templates/common/email_listing.html | 50 +++-- templates/common/email_searchbar.html | 10 + .../common}/viewmail.html | 0 templates/nlayout.html | 1 + 20 files changed, 530 insertions(+), 254 deletions(-) create mode 100644 static/js/infobox.js create mode 100644 templates/common/email_searchbar.html rename {extensions/webmail/templates => templates/common}/viewmail.html (100%) diff --git a/extensions/amavis_quarantine/main.py b/extensions/amavis_quarantine/main.py index 79758e3dd..f2ba4d2d4 100644 --- a/extensions/amavis_quarantine/main.py +++ b/extensions/amavis_quarantine/main.py @@ -8,6 +8,7 @@ import email import re import os from django.http import HttpResponseRedirect, HttpResponse +from django.template import Template, Context from django.utils import simplejson from django.conf.urls.defaults import * from django.utils.translation import ugettext as _, ungettext @@ -15,10 +16,11 @@ from django.core.urlresolvers import reverse from django.contrib.auth.decorators \ import login_required from mailng.lib import events, parameters -from mailng.lib import _render, _ctx_ok, _ctx_ko, decode +from mailng.lib import _render, _ctx_ok, _ctx_ko, decode, getctx from mailng.lib import db from mailng.admin.models import Mailbox from lib import AMrelease +from templatetags.amextras import * from sql_listing import * def init(): @@ -40,21 +42,71 @@ def menu(**kwargs): return [] @login_required -def index(request, message=None): - start = time.time() +def _listing(request, internal=False, filter=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] + if filter is None: + filter = ["&maddr.email='%s'" % mb.full_address] + else: + filter += ["&maddr.email='%s'" % mb.full_address] + pageid = request.GET.has_key("page") and int(request.GET["page"]) or 1 + request.session["page"] = pageid # ?? nécessaire + lst = SQLlisting(filter, baseurl="listing/", empty=internal) + if internal: + return lst.render(request, pageid=int(pageid)) + page = lst.paginator.getpage(pageid) + if page: + content = lst.fetch(request, page.id_start, page.id_stop) + navbar = lst.render_navbar(page) else: - filter = None - 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) + content = _("Empty quarantine") + navbar = "" + ctx = getctx("ok", listing=content, navbar=navbar, + menu=quar_menu("", request.user.get_all_permissions())) + return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") + +@login_required +def index(request, message=None): + return _listing(request, True) + +# @login_required +# def getmailcontent(request, mail_id): +# from mailng.lib.email_listing import Email + +# conn = db.getconnection("amavis_quarantine") +# status, cursor = db.execute(conn, """ +# SELECT mail_text +# FROM quarantine +# WHERE quarantine.mail_id='%s' +# """ % mail_id) +# content = "" +# for part in cursor.fetchall(): +# content += part[0] +# msg = email.message_from_string(content) +# links = request.GET.has_key("links") and request.GET["links"] or "0" +# mode = request.GET.has_key("mode") and request.GET["mode"] or "plain" +# mail = Email(msg, mode, links) +# return mail.render(request) + +# @login_required +# def viewmail(request, mail_id): +# conn = db.getconnection("amavis_quarantine") +# status, cursor = db.execute(conn, """ +# SELECT mail_text +# FROM quarantine +# WHERE quarantine.mail_id='%s' +# """ % mail_id) +# content = "" +# for part in cursor.fetchall(): +# content += part[0] +# msg = email.message_from_string(content) +# ctx = getctx("ok", ) +# return _render(request, 'amavis_quarantine/viewmail.html', { +# "headers" : msg, "alert" : msg.get("X-Amavis-Alert"), +# "mail_id" : mail_id, "page_id" : request.session["page"], +# "params" : request.GET +# }) @login_required def getmailcontent(request, mail_id): @@ -73,25 +125,25 @@ WHERE quarantine.mail_id='%s' links = request.GET.has_key("links") and request.GET["links"] or "0" mode = request.GET.has_key("mode") and request.GET["mode"] or "plain" mail = Email(msg, mode, links) - return mail.render(request) - + return _render(request, "common/viewmail.html", { + "headers" : "", + "mailbody" : mail.body, + "pre" : mail.pre + }) + @login_required def viewmail(request, mail_id): - conn = db.getconnection("amavis_quarantine") - status, cursor = db.execute(conn, """ -SELECT mail_text -FROM quarantine -WHERE quarantine.mail_id='%s' -""" % mail_id) - content = "" - for part in cursor.fetchall(): - content += part[0] - msg = email.message_from_string(content) - return _render(request, 'amavis_quarantine/viewmail.html', { - "headers" : msg, "alert" : msg.get("X-Amavis-Alert"), - "mail_id" : mail_id, "page_id" : request.GET["page"], - "params" : request.GET - }) + args = "" + for kw in ["mode", "links"]: + if kw in request.GET.keys(): + args += args != "" and "&" or "?" + args += "%s=%s" % (kw, request.GET[kw]) + content = Template(""" + +""").render(Context({"url" : reverse(getmailcontent, args=[mail_id]) + args})) + menu = viewm_menu("", request.session, mail_id, request.user.get_all_permissions()) + ctx = getctx("ok", menu=menu, listing=content) + return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") @login_required def viewheaders(request, mail_id): @@ -129,7 +181,9 @@ def delete(request, mail_id, count=1): count) % {"count" : count} else: message = error - return _redirect_to_index(request, message, count) + ctx = getctx("ok", url="?page=%s" % request.session["page"], message=message) + return HttpResponse(simplejson.dumps(ctx), + mimetype="application/json") @login_required def release(request, mail_id, count=1): @@ -152,13 +206,6 @@ WHERE msgrcpt.mail_id=msgs.mail_id AND msgrcpt.rid=maddr.id AND msgs.mail_id IN error = None for k, values in emails.iteritems(): result = amr.sendreq(k, values["secret"], *values["rcpts"]) -# cmd = "/tmp/amavisd-release " -# cmd += "%s %s" % (k, values["secret"]) -# for rcpt in values["rcpts"]: -# cmd += " %s" % rcpt -# cmd += "\n" -# result = os.system(cmd) -# if re.search("250 [^\s]+ Ok", result): if result: count += 1 else: @@ -170,7 +217,9 @@ WHERE msgrcpt.mail_id=msgs.mail_id AND msgrcpt.rid=maddr.id AND msgs.mail_id IN count) % {"count" : count} else: message = error - return _redirect_to_index(request, message, count) + ctx = getctx("ok", url="?page=%s" % request.session["page"], message=message) + return HttpResponse(simplejson.dumps(ctx), + mimetype="application/json") @login_required def process(request): @@ -182,19 +231,13 @@ def process(request): ids += "'%s'" % id if ids == "": return HttpResponseRedirect(reverse(index)) + + print "Ok c pas vide" if request.POST["action"] == "release": - release(request, ids, count) + return release(request, ids, count) if request.POST["action"] == "delete": - delete(request, ids, count) - - try: - pagenum = int(request.POST.get('pagenum', '1')) - except ValueError: - pagenum = 1 - ctx = _ctx_ok(reverse(index) + "?page=%s" % pagenum) - return HttpResponse(simplejson.dumps(ctx), - mimetype="application/json") + return delete(request, ids, count) @login_required def search(request): @@ -211,19 +254,16 @@ def search(request): filter += " OR " filter += "msgs.%s LIKE '%%%s%%'" % (c, pattern) filter = "&(%s)" % filter - 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") + return _listing(request, filter=filter) + +# lst = SQLlisting([filter]) + +# page = lst.paginator.getpage(1) +# 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 index fe24843a8..d7f83f1d0 100644 --- a/extensions/amavis_quarantine/sql_listing.py +++ b/extensions/amavis_quarantine/sql_listing.py @@ -6,6 +6,7 @@ from mailng.lib import tables, db from mailng.lib.email_listing import MBconnector, EmailListing class Qtable(tables.Table): + tableid = "emails" idkey = "mailid" selection = tables.SelectionColumn("selection", first=True) type = tables.Column("type", label=_("Type")) @@ -83,6 +84,8 @@ LIMIT %d,%d class SQLlisting(EmailListing): tpl = "amavis_quarantine/index.html" tbltype = Qtable + deflocation = "listing/" + defcallback = "updatelisting" def __init__(self, filter, **kwargs): self.mbc = SQLconnector(filter) diff --git a/extensions/amavis_quarantine/templates/index.html b/extensions/amavis_quarantine/templates/index.html index 3d780c5d7..bd95702b6 100644 --- a/extensions/amavis_quarantine/templates/index.html +++ b/extensions/amavis_quarantine/templates/index.html @@ -3,9 +3,10 @@ {% load amextras %} {% block css %} #searchbox { - float: left; + /*float: left;*/ /* Marche pas ici?! obligé de le faire en JS sous FF */ margin-right: 10px; - width: 250px; + z-index: 2; + position: relative; } #searchbox img { @@ -17,87 +18,137 @@ #searchparams { background: white; padding: 5px; - width: 150px; + width: 90%; + border-bottom: 5px solid #CCCCCC; + border-left: 5px solid #CCCCCC; + border-right: 5px solid #CCCCCC; +} + +thead tr { + position: relative; + z-index: 1; } {% endblock %} {% block js %} -mysubmit = function(event, name) { - event.stop(); - new Element('input', { - 'type' : 'hidden', 'name' : "action", style: "display:none", 'value' : name - }).inject($("listingform"), "top"); - {% url extensions.amavis_quarantine.main.process as actionurl %} - $("listingform").set("action", "{{ actionurl }}"); - $("listingform").set("send", { + mysubmit = function(event, name) { + event.stop(); + new Element('input', { + 'type' : 'hidden', 'name' : "action", style: "display:none", 'value' : name + }).inject($("listingform"), "top"); + {% url extensions.amavis_quarantine.main.process as actionurl %} + $("listingform").set("action", "{{ actionurl }}"); + $("listingform").set("send", { onSuccess : function(res) { - response = JSON.decode(res); - location.href = response.url; + response = JSON.decode(res); + actioncallback(response); + } + }); + $("listingform").send(); + } + + updatehash = function(evt) { + evt.stop(); + current_anchor.parse_string(evt.target.get("href"), true).setparams(navparams); + current_anchor.update(); + } + + appendhash = function(evt) { + evt.stop(); + location.hash += evt.target.get("href"); + } + + updateparams = function(evt) { + evt.stop(); + current_anchor.updateparams(evt.target.get("href")); + current_anchor.update(); + } + + register_callback("_listing", function(resp) { + updatelisting(resp); + $$("a[name=release]").addEvent("click", function(event) { + if (!checkSelection("selection", "{% trans "Release this selection?" %}")) { + event.stop(); + return; + } + mysubmit(event, "release"); + }); + $$("a[name=delete]").addEvent("click", function(event) { + if (!checkSelection("selection", "{% trans "Delete this selection?" %}")) { + event.stop(); + return; + } + mysubmit(event, "delete"); + }); + + 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; + }); + var url = "search" + + "?pattern=" + $("searchfield").value + + "&criteria=" + criteria; + current_anchor.parse_string(url, true).setparams(navparams); + current_anchor.update(); + + /*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..." %}"); + } + }); + $("searchbox").setStyle("float", "left"); + $$("tr").addEvent("dblclick", viewmail); + }); + + actioncallback = function(resp) { + $("listing").set("html", ""); + $("menubar").set("html", ""); + $("navbar").set("html", ""); + if (resp.status == "ok") { + infobox.show(resp.message, "green", 1); + } else { + infobox.show(resp.message, "red", 1); } + current_anchor.parse_string(resp.url, true).setparams(navparams); + current_anchor.update(1); + } + + actionclick = function(evt) { + evt.stop(); + new Request.JSON({ + url: evt.target.get("href"), + onSuccess: actioncallback + }).get(); + } + + register_callback("viewmail", function(resp) { + updatelisting(resp); + $$("a[name=back]").addEvent("click", updatehash); + $$("a[name=delete]").addEvent("click", actionclick); + $$("a[name=release]").addEvent("click", actionclick); + $$("a[name=viewmode]").addEvent("click", updateparams); + setDivHeight("mailcontent", 5, 0); }); - $("listingform").send(); -} - -$$("a[name=delete]").addEvent("click", function(event) { - if (!checkSelection("selection", "{% trans "Delete this selection?" %}")) { - event.stop(); - return; - } - mysubmit(event, "delete"); -}); -$$("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 %} - {% quar_menu "" perms %} - -{% endblock %} diff --git a/extensions/amavis_quarantine/templates/viewmail.html b/extensions/amavis_quarantine/templates/viewmail.html index d1f9c0539..351418ee3 100644 --- a/extensions/amavis_quarantine/templates/viewmail.html +++ b/extensions/amavis_quarantine/templates/viewmail.html @@ -26,6 +26,8 @@ window.addEvent("domready", function() { var iheight = spage.y - stable.y - slogo.y; $("mailcontent").setStyle("height", iheight + "px"); + parse_menubar("menubar"); + }); diff --git a/extensions/amavis_quarantine/templatetags/amextras.py b/extensions/amavis_quarantine/templatetags/amextras.py index 5a4aaf1f9..62cf4b7e1 100644 --- a/extensions/amavis_quarantine/templatetags/amextras.py +++ b/extensions/amavis_quarantine/templatetags/amextras.py @@ -7,25 +7,22 @@ from mailng.extensions import amavis_quarantine register = template.Library() @register.simple_tag -def viewm_menu(selection, mail_id, page_id, perms): +def viewm_menu(selection, session, mail_id, perms): options_menu = [ - {"name" : "plain", + {"name" : "viewmode", "label" : _("View as plain text"), - "url" : reverse(amavis_quarantine.main.viewmail, args=[mail_id]) - + "?page=%s&mode=plain" % page_id}, - {"name" : "html", + "url" : "?mode=plain&links=0"}, + {"name" : "viewmode", "label" : _("View as HTML"), - "url" : reverse(amavis_quarantine.main.viewmail, args=[mail_id]) - + "?page=%s&mode=html" % page_id}, - {"name" : "links", + "url" : "?mode=html&links=0"}, + {"name" : "viewmode", "label" : _("Activate links"), - "url" : reverse(amavis_quarantine.main.viewmail, args=[mail_id]) - + "?page=%s&mode=html&links=1" % page_id} + "url" : "?mode=html&links=1"} ] entries = [ {"name" : "back", - "url" : reverse(amavis_quarantine.main.index) + "?page=%s" % page_id, + "url" : "?page=%s" % session["page"], "img" : "/static/pics/back.png", "label" : _("Back to list")}, {"name" : "headers", @@ -35,20 +32,19 @@ def viewm_menu(selection, mail_id, page_id, perms): "class" : "boxed", "rel" : "{handler:'iframe',size:{x:600,y:500}}"}, {"name" : "release", - "url" : reverse(amavis_quarantine.main.release, args=[mail_id]) - + "?page=%s" % page_id, + "url" : reverse(amavis_quarantine.main.release, args=[mail_id]), "img" : "/static/pics/release.png", "label" : _("Release"), "confirm" : _("Release this message?")}, {"name" : "delete", - "url" : reverse(amavis_quarantine.main.delete, args=[mail_id]) - + "?page=%s" % page_id, + "url" : reverse(amavis_quarantine.main.delete, args=[mail_id]), "img" : "/static/pics/remove.png", "label" : _("Delete"), "confirm" : _("Delete this message?")}, {"name" : "options", "label" : _("Options"), "img" : "/static/pics/settings.png", + "class" : "menubardropdown", "menu" : options_menu} ] @@ -69,6 +65,8 @@ def quar_menu(selection, perms): "label" : _("Delete")}, ] - return render_to_string('main/menu.html', + menu = render_to_string('main/menu.html', {"selection" : selection, "entries" : entries, "perms" : perms}) + searchbar = render_to_string('common/email_searchbar.html', {}) + return menu + searchbar diff --git a/extensions/amavis_quarantine/urls.py b/extensions/amavis_quarantine/urls.py index 06806aa1f..303885bda 100644 --- a/extensions/amavis_quarantine/urls.py +++ b/extensions/amavis_quarantine/urls.py @@ -2,13 +2,12 @@ from django.conf.urls.defaults import * urlpatterns = patterns('mailng.extensions.amavis_quarantine.main', (r'^$', 'index'), - url(r'^viewmail/(?P[\w\-\+]+)/$', 'viewmail', - name="viewmail_std"), - url(r'^viewmail/$', 'viewmail', name="viewmail_null"), + (r'^listing/$', '_listing'), (r'^getmailcontent/(?P[\w\-\+]+)/$', 'getmailcontent'), - (r'^viewheaders/(?P[\w\-\+]+)/$', 'viewheaders'), + (r'^(?P[\w\-\+]+)/headers/$', 'viewheaders'), (r'^process/$', 'process'), (r'^delete/(?P[\w\-\+]+)/$', 'delete'), (r'^release/(?P[\w\-\+]+)/$', 'release'), (r'^search/', 'search'), + (r'^(?P[\w\-\+]+)/$', 'viewmail'), ) diff --git a/extensions/webmail/imap_listing.py b/extensions/webmail/imap_listing.py index a09c55aa8..311c8b47a 100644 --- a/extensions/webmail/imap_listing.py +++ b/extensions/webmail/imap_listing.py @@ -248,12 +248,12 @@ class IMAPconnector(object): class ImapListing(EmailListing): tpl = "webmail/index.html" tbltype = WMtable + deflocation = "INBOX/" + defcallback = "wm_updatelisting" - def __init__(self, user, password, baseurl=None, **kwargs): + def __init__(self, user, password, **kwargs): self.mbc = IMAPconnector(user=user, password=password) - EmailListing.__init__(self, **kwargs) - if baseurl: - self.paginator.baseurl = baseurl + EmailListing.__init__(self, **kwargs) def getfolders(self): md_folders = [{"name" : "INBOX", "icon" : "overview.png"}, diff --git a/extensions/webmail/main.py b/extensions/webmail/main.py index bb1a4158c..4ad2eb26c 100644 --- a/extensions/webmail/main.py +++ b/extensions/webmail/main.py @@ -10,7 +10,7 @@ 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, _render, _render_error +from mailng.lib import events, parameters, _render, _render_error, getctx from imap_listing import * from forms import * from templatetags.webextras import * @@ -51,13 +51,6 @@ def menu(**kwargs): def userlogin(**kwargs): kwargs["request"].session["password"] = kwargs["password"] -def getctx(status, level=1, **kwargs): - callername = sys._getframe(level).f_code.co_name - ctx = {"status" : status, "callback" : callername} - for kw, v in kwargs.iteritems(): - ctx[kw] = v - return ctx - @login_required def folder(request, name, updatenav=True): if not name: @@ -76,7 +69,7 @@ def folder(request, name, updatenav=True): order = request.session["navparams"]["order"] lst = ImapListing(request.user.username, request.session["password"], - name, folder=name, order=order) + baseurl=name, folder=name, order=order) page = lst.paginator.getpage(request.session["page"]) if page: content = lst.fetch(request, page.id_start, page.id_stop) @@ -94,10 +87,11 @@ def index(request): navp = request.session.has_key("navparams") \ and request.session["navparams"] or {} lst = ImapListing(request.user.username, request.session["password"], - "INBOX", navparams=navp, folder="INBOX") + baseurl="INBOX", navparams=navp, folder="INBOX", + empty=True) except Exception as exp: return _render_error(request, {"error" : exp}) - return lst.render(request, empty=True) + return lst.render(request) def fetchmail(request, folder, mail_id, all=False): res = IMAPconnector(request).fetch(start=mail_id, folder=folder, all=all) @@ -128,7 +122,7 @@ def getmailcontent(request, folder, mail_id): except KeyError: pageid = "1" folder, imapid = msg["imapid"].split("/") - return _render(request, "webmail/viewmail.html", { + return _render(request, "common/viewmail.html", { "headers" : email.render_headers(folder, mail_id), "folder" : folder, "imapid" : imapid, "mailbody" : email.body, "pre" : email.pre @@ -270,7 +264,7 @@ def reply(request, folder, mail_id): else: form.fields["subject"].initial = "Re: %s" % email.Subject textheader = "%s %s" % (email.From, _("wrote:")) - return render_compose(request, form, reverse(reply, args[folder, mail_id]), + return render_compose(request, form, reverse(reply, args=[folder, mail_id]), textheader, body) @login_required diff --git a/extensions/webmail/templates/index.html b/extensions/webmail/templates/index.html index 9bbf339a4..3818c77c7 100644 --- a/extensions/webmail/templates/index.html +++ b/extensions/webmail/templates/index.html @@ -1,6 +1,20 @@ {% extends "common/email_listing.html" %} {% load i18n %} {% block css %} +#listing { + position: absolute; + left: 155px; + right: 0px; + top: 130px; + height: expression(document.body.clientHeight - 35 + "px"); + overflow: auto; +} + +html > body #listing { + position: fixed; + bottom: 35px; +} + #leftcol { float: left; width: 150px; @@ -97,22 +111,9 @@ tr.selected { {% endblock %} {% block js %} - navparams = new Hash({ - {% for k, v in navparams.items %} - {{ k }} : "{{ v }}", - {% endfor %} - }); + wm_updatelisting = function(resp) { + updatelisting(resp); - updatelisting = function(resp) { - if ($defined(resp.menu)) { - $("menubar").set("html", resp.menu); - parse_menubar("menubar"); - } - $("listing").set("html", resp.listing); - if ($defined(resp.navbar)) { - $("navbar").set("html", resp.navbar); - $("navbar").getChildren("a").addEvent("click", navbarcallback); - } $$("a[name=compose]").addEvent("click", loadCompose); $$("a[name=empty]").addEvent("click", empty); if (!checkSelection()) { @@ -123,7 +124,7 @@ tr.selected { empty = function(event) { event.stop(); new Request.JSON({url : event.target.get("href"), onSuccess : function(resp) { - updatelisting(resp); + wm_updatelisting(resp); }}).get(); } @@ -147,17 +148,8 @@ tr.selected { obj.getParent().addClass("selected"); } - {% url extensions.webmail.main.folder as view_url %} - viewmail = function(event) { - event.stop(); - if (event.target.parentNode.id) { - var url = event.target.parentNode.id + "/"; - event.target.parentNode.setStyle("background", "#ffffcc"); - current_anchor.baseurl(url).update(); - } - } register_callback("viewmail", function(resp) { - updatelisting(resp, "hidden"); + wm_updatelisting(resp, "hidden"); $$("a[name=back]").addEvent("click", loadFolder); $$("a[name=reply]").addEvent("click", reply); $$("a[name=replyall]").addEvent("click", replyall); @@ -173,7 +165,7 @@ tr.selected { }); compose_callback = function(resp) { - updatelisting(resp); + wm_updatelisting(resp); window.addEvent("resize", function(evt) { setDivHeight("id_body", $("mailheader").getSize().y, 0); }); @@ -184,7 +176,7 @@ tr.selected { onSuccess: function(resp) { resp = JSON.decode(resp); if (resp.status == "ko") { - updatelisting(resp); + wm_updatelisting(resp); window.fireEvent("resize"); return; } @@ -228,7 +220,7 @@ tr.selected { current_anchor.update(); } register_callback("folder", function(resp) { - updatelisting(resp); + wm_updatelisting(resp); if (navparams.has("order")) { src = navparams.get("order").charAt(0) == "-" ? "down_arrow.png" : "up_arrow.png"; th = $$("th[id=" + navparams.get("order").substring(1) + "]"); @@ -341,18 +333,10 @@ tr.selected { }); }); - navbarcallback = function(event) { - event.stop(); - current_anchor.setparams(navparams); - current_anchor.parse_string(event.target.getParent().get("href")).update(); - } - foldercallback = function(event) { select(event.target); loadFolder(event, event.target); } - - ajaxListener("INBOX/", updatelisting); $$("a[name=loadfolder]").addEvent("click", foldercallback); {% endblock %} {% block middletitle %}{% trans "Webmail" %}{% endblock %} @@ -374,3 +358,6 @@ tr.selected { {% endblock %} +{% block menu %} + +{% endblock %} diff --git a/extensions/webmail/templatetags/webextras.py b/extensions/webmail/templatetags/webextras.py index f36817730..f15d3488b 100644 --- a/extensions/webmail/templatetags/webextras.py +++ b/extensions/webmail/templatetags/webextras.py @@ -75,6 +75,17 @@ def listing_menu(selection, folder, perms): "url" : reverse(webmail.main.compose), "img" : "/static/pics/edit.png", "label" : _("New message")}, + {"name" : "actions", + "img" : "/static/pics/domains.png", + "label" : _("Mark messages"), + "class" : "menubardropdown", + "menu" : [ + {"name" : "mark-read", + "label" : _("As read")}, + {"name" : "mark-unread", + "label" : _("As unread")} + ] + } ] if folder in ["Trash"]: entries += [ diff --git a/lib/__init__.py b/lib/__init__.py index c2582c4bd..581f964d3 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import popen2 import os +import sys import time from django.conf import settings from django.template import RequestContext @@ -67,6 +68,16 @@ def _ctx_ok(url): def _ctx_ko(tpl, ctx): return {"status" : "ko", "content" : render_to_string(tpl, ctx)} +def getctx(status, level=1, callback=None, **kwargs): + if not callback: + callername = sys._getframe(level).f_code.co_name + else: + callername = callback + ctx = {"status" : status, "callback" : callername} + for kw, v in kwargs.iteritems(): + ctx[kw] = v + return ctx + def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')): for encoding in encodings: try: diff --git a/lib/email_listing.py b/lib/email_listing.py index 5d77853f6..0d8b337a0 100644 --- a/lib/email_listing.py +++ b/lib/email_listing.py @@ -83,16 +83,23 @@ class Paginator(object): return p class EmailListing: - def __init__(self, folder=None, elems_per_page=40, navparams={}, **kwargs): + def __init__(self, baseurl=None, folder=None, elems_per_page=40, + navparams={}, **kwargs): self.folder = folder self.elems_per_page = elems_per_page self.navparams = navparams order = "order" in kwargs.keys() and kwargs["order"] or None - self.paginator = Paginator(self.mbc.messages_count(folder=self.folder, - order=order), - elems_per_page) + self.empty = "empty" in kwargs.keys() and kwargs["empty"] or False + if not self.empty: + self.paginator = Paginator(self.mbc.messages_count(folder=self.folder, + order=order), + elems_per_page) + if baseurl: + self.paginator.baseurl = baseurl def render_navbar(self, page): + if page is None: + return "" return render_to_string("common/navbar.html", { "page" : page }) @@ -110,9 +117,10 @@ class EmailListing: return tpl.render(Context({"table" : table.render(request)})) def render(self, request, pageid=1, **kwargs): - page = self.paginator.getpage(pageid) - thead = listing = "" - if "empty" in kwargs.keys() and not kwargs["empty"]: + listing = "" + page = None + if not self.empty: + page = self.paginator.getpage(pageid) if not page: listing = "Empty folder" else: @@ -125,7 +133,7 @@ class EmailListing: "folders" : self.getfolders(), "selection" : self.folder, "navparams" : self.navparams, - "current_page" : pageid + "deflocation" : self.deflocation, "defcallback" : self.defcallback }) class Email(object): diff --git a/lib/tables.py b/lib/tables.py index 3a3e07f33..6d64b8bb2 100644 --- a/lib/tables.py +++ b/lib/tables.py @@ -55,16 +55,21 @@ class Table(object): for row in rows: nrow = {"id" : row[self.idkey], "cols" : []} for c in self.columns: + newcol = {} + try: + newcol["width"] = getattr(c, "width") + except AttributeError: + pass if isinstance(c, SelectionColumn): - nrow["cols"] += [{"value" : c.render(row[self.idkey]), "safe" : True}] - continue + newcol["value"] = c.render(row[self.idkey]) + newcol["safe"] = True + if isinstance(c, ImgColumn): key = "img_%s" % c.name if key in row.keys(): - nrow["cols"] += [{"value" : c.render(row[key]), "safe" : True, "width" : c.width}] - else: - nrow["cols"] += [{"width" : c.width}] - continue + newcol["value"] = c.render(row[key]) + newcol["safe"] = True + if row.has_key(c.name): try: cssclass = getattr(c, "cssclass") @@ -72,10 +77,9 @@ class Table(object): cssclass = "" if "class" in row.keys(): cssclass += (cssclass != "") and ", %s" % row["class"] or row["class"] - - nrow["cols"] += [{"value" : self.parse(c.name, row[c.name]), - "class" : cssclass, - "width" : c.width}] + newcol["value"] = self.parse(c.name, row[c.name]) + newcol["class"] = cssclass + nrow["cols"] += [newcol] self.rows += [nrow] def render(self, request): diff --git a/static/js/common.js b/static/js/common.js index 2f64d6e4e..4f9be35e5 100644 --- a/static/js/common.js +++ b/static/js/common.js @@ -3,6 +3,12 @@ window.addEvent('domready', function(){ SqueezeBox.assign($$('a.boxed'), { parse: 'rel', }); + + + infobox = new InfoBox({ + parent : $("menubar"), + message : "Loading" + }); }); callbacks = {}; @@ -18,18 +24,23 @@ get_callback = function(name) { current_anchor = null; check_anchor = function() { - if (current_anchor.serialized == location.hash) { + if (current_anchor.serialized == location.hash + && !$defined(current_anchor.force)) { return; } + delete(current_anchor.force); current_anchor.from_string(location.hash); if (!current_anchor.serialized) { query = current_anchor.deflocation; } else { query = current_anchor.serialized.substring(1); } + infobox.show("Loading...", "gray"); new Request.JSON({url: query, onSuccess: function(resp) { callback = ($defined(resp.callback)) ? resp.callback : "default"; callbacks[callback](resp); + infobox.notice("Done"); + infobox.hide(1); }}).get(); } @@ -96,8 +107,26 @@ function HashWrapper(deflocation) { return res; }; - this.update = function() { + this.update = function(force) { location.hash = this.serialize(); + if ($defined(force)) { + this.force = force; + } + }; + + this.updateparams = function(str) { + if (str.charAt(0) == '?') { + str = str.substring(1); + } + var elems = str.split('&'); + for (var i = 0; i < elems.length; i++) { + this.setparamfromstring(elems[i]); + } + }; + + this.setparamfromstring = function(str) { + var def = str.split('='); + this.params.set(def[0], def[1]); }; this.setparam = function(name, value) { @@ -158,6 +187,9 @@ parse_menubar = function(id) { } }); }); + SqueezeBox.assign($$('a.boxed'), { + parse: 'rel', + }); } } diff --git a/static/js/infobox.js b/static/js/infobox.js new file mode 100644 index 000000000..9852af96b --- /dev/null +++ b/static/js/infobox.js @@ -0,0 +1,105 @@ +var InfoBox = new Class({ + Implements: [Options, Events], + options: { + parent : null, + message : "This is my info box!", + width : 200, + }, + + profiles: { + gray : {"background" : "#EEEEEE", "border" : "#CCCCCC"}, + green : {"background" : "#DAF8DA", "border" : "#166D14"}, + red : {"background" : "#F9D2D2", "border" : "#FF2A00"}, + yellow : {"background" : "#FDE6B5", "border" : "#FF7F00"} + }, + + initialize: function(options) { + this.setOptions(options); + + var boxleft = document.body.getSize().x / 2 - this.options.width / 2; + this.box = new Element("div", {"id" : "infobox"}); + this.box.setStyles({ + "font-size" : "normal", + "font-weight" : "bold", + "text-align" : "left", + "width" : this.options. width + "px", + "padding" : "10px", + "position" : "absolute", + "top" : "0px", + "left" : boxleft + "px", + "display" : "none" + }); + this.setProfile("gray"); + this.spinner = new Element("img", { + "src" : "/static/stylesheets/spinner.gif" + }); + this.spinner.setStyle("float", "left"); + this.spinner.inject(this.box); + + this.label = new Element("label").set("html", this.options.message); + this.label.setStyles({ + "margin-left" : "10px" + }); + this.label.inject(this.box); + this.box.inject(document.body); + }, + + setMessage: function(text) { + this.label.set("html", text); + }, + + setProfile: function(color) { + this.box.setStyles({ + "background" : this.profiles[color].background, + "border" : "2px solid " + this.profiles[color].border + }); + }, + + notice: function(message) { + this.setProfile("green"); + this.setMessage(message); + }, + + error: function(message) { + this.setProfile("red"); + this.setMessage(message); + }, + + warning: function(message) { + this.setProfile("yellow"); + this.setMessage(message); + }, + + show: function(message, prof, delay) { + if ($defined(this.start)) { + if ($time() - this.start > this.delay) { + $clear(this.showtimer); + delete(this.start); + delete(this.delay); + } else { + this.showtimer = this.show.delay(100, this, [message, prof]); + return; + } + } + if ($defined(message)) { + this.setMessage(message); + } + if ($defined(prof)) { + this.setProfile(prof); + } + if ($defined(delay)) { + this.start = $time(); + this.delay = delay * 1000; + } + this.box.setStyle("display", "block"); + }, + + hide: function(timer) { + if ($defined(timer)) { + this.timer = this.hide.periodical(timer * 1000, this); + return; + } + $clear(this.timer); + this.box.setStyle("display", "none"); + }, +}); \ No newline at end of file diff --git a/static/stylesheets/default.css b/static/stylesheets/default.css index 809492b69..61e1b46e2 100644 --- a/static/stylesheets/default.css +++ b/static/stylesheets/default.css @@ -177,11 +177,11 @@ div#footer { border-left: 5px solid #ccc; } -.left { +div.left { float: left; } -.right { +div.right { float: right; } diff --git a/templates/common/email_listing.html b/templates/common/email_listing.html index e0c19ef14..10f1abd1e 100644 --- a/templates/common/email_listing.html +++ b/templates/common/email_listing.html @@ -2,20 +2,6 @@ {% load i18n %} {% block html_head %} {% endblock %} @@ -55,5 +75,5 @@ window.addEvent("domready", function() {
{{ listing }}
{% endblock %} -{% block footer %}{% endblock %} +{% block footer %}{% endblock %} diff --git a/templates/common/email_searchbar.html b/templates/common/email_searchbar.html new file mode 100644 index 000000000..99b7e9f9f --- /dev/null +++ b/templates/common/email_searchbar.html @@ -0,0 +1,10 @@ +{% load i18n %} + diff --git a/extensions/webmail/templates/viewmail.html b/templates/common/viewmail.html similarity index 100% rename from extensions/webmail/templates/viewmail.html rename to templates/common/viewmail.html diff --git a/templates/nlayout.html b/templates/nlayout.html index 683576397..ad3092f79 100644 --- a/templates/nlayout.html +++ b/templates/nlayout.html @@ -13,6 +13,7 @@ + {% url django.views.i18n.javascript_catalog as catalog_url %}