wip: global UI refactoring

* More work on the webmail

see #204
This commit is contained in:
Antoine Nguyen
2012-03-30 11:15:34 +02:00
parent e10bba2d4e
commit 28e8e736ee
21 changed files with 142 additions and 191 deletions
+12 -6
View File
@@ -76,7 +76,8 @@ def newdomain(request):
events.raiseEvent("CreateDomain", user, domain)
commonctx = {"title" : _("New domain"),
"submit_label" : _("Create"),
"action_label" : _("Create"),
"action_classes" : "submit",
"action" : reverse(newdomain),
"formid" : "domform"}
if request.method == "POST":
@@ -96,7 +97,8 @@ def editdomain(request, dom_id, tplname="admin/domainform.html"):
raise PermDeniedException(_("You can't edit this domain"))
commonctx = {"title" : _("Domain editing"),
"submit_label" : _("Update"),
"action_label" : _("Update"),
"action_classes" : "submit",
"action" : reverse(editdomain, args=[dom_id]),
"formid" : "domform"}
if request.method == "POST":
@@ -186,7 +188,8 @@ def _validate_dlist(request, form, successmsg, tplname, commonctx, callback=None
def newdlist(request, tplname="common/generic_modal_form.html"):
events.raiseEvent("CanCreate", request.user, "mailbox_aliases")
commonctx = {"title" : _("New distribution list"),
"submit_label" : _("Create"),
"action_label" : _("Create"),
"action_classes" : "submit",
"action" : reverse(newdlist),
"formid" : "dlistform"}
@@ -210,7 +213,8 @@ def editdlist(request, dlist_id, tplname="admin/dlistform.html"):
if not request.user.is_owner(dlist.domain):
raise PermDeniedException(_("You do not have access to this domain"))
commonctx = {"title" : dlist.full_address,
"submit_label" : _("Update"),
"action_label" : _("Update"),
"action_classes" : "submit",
"action" : reverse(editdlist, args=[dlist.id]),
"formid" : "dlistform"}
if request.method == "POST":
@@ -521,7 +525,8 @@ def import_identities(request):
ctx = dict(
title=_("Import identities"),
submit_label=_("Import"),
action_label=_("Import"),
action_classes="submit",
action=reverse(import_identities),
formid="importform",
enctype="multipart/form-data",
@@ -538,7 +543,8 @@ def import_domains(request):
ctx = dict(
title=_("Import domains"),
submit_label=_("Import"),
action_label=_("Import"),
action_classes="submit",
action=reverse(import_domains),
formid="importform",
enctype="multipart/form-data",
+2 -2
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# coding: utf-8
from datetime import datetime
from django.utils.translation import ugettext as _, ugettext_noop
@@ -11,6 +11,7 @@ from models import *
class Qtable(tables.Table):
tableid = "emails"
styles = "table-condensed"
idkey = "mailid"
type = tables.Column("type", align="center", width="30px",
@@ -81,7 +82,6 @@ class SQLlisting(EmailListing):
deflocation = "listing/"
defcallback = "updatelisting"
reset_wm_url = True
table_styles = "table-condensed"
def __init__(self, user, msgs, filter, **kwargs):
if user.group == 'SimpleUsers':
+1 -1
View File
@@ -94,7 +94,7 @@ def _listing(request):
return empty_quarantine(request)
content = lst.fetch(request, page.id_start, page.id_stop)
navbar = lst.render_navbar(page, "listing/")
navbar = lst.render_navbar(page, "listing/?")
ctx = getctx("ok", listing=content, navbar=navbar,
menu=quar_menu("", request.user))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
+4 -2
View File
@@ -5,8 +5,10 @@
"""
import re
from django.utils.translation import ugettext as _
from modoboa.lib.exceptions import ModoboaException
class WebmailError(Exception):
class WebmailError(ModoboaException):
errorexpr = re.compile("\[([^\]]+)\]\s*([^\.]+)")
def __init__(self, reason, ajax=False):
@@ -20,7 +22,7 @@ class WebmailError(Exception):
def __str__(self):
return self.reason
class ImapError(Exception):
class ImapError(ModoboaException):
def __init__(self, reason):
self.reason = reason
+3 -3
View File
@@ -3,11 +3,11 @@ from django.utils.translation import ugettext_noop as _
class ComposeMailForm(forms.Form):
from_ = forms.CharField(label=_("From"))
to = forms.CharField(label=_("To"), widget=forms.Textarea())
cc = forms.CharField(label=_("Cc"), widget=forms.Textarea(), required=False)
to = forms.CharField(label=_("To"))
cc = forms.CharField(label=_("Cc"), required=False)
subject = forms.CharField(label=_("Subject"), max_length=255, required=False)
origmsgid = forms.CharField(widget=forms.HiddenInput(), required=False)
origmsgid = forms.CharField(label="", widget=forms.HiddenInput(), required=False)
class FolderForm(forms.Form):
name = forms.CharField()
+14 -7
View File
@@ -135,7 +135,7 @@ class IMAPconnector(object):
if not status:
raise Exception(msg)
def _cmd(self, name, *args):
def _cmd(self, name, *args, **kwargs):
"""IMAP command wrapper
To simplify errors handling, this wrapper calls the
@@ -166,9 +166,16 @@ class IMAPconnector(object):
raise ImapError(e)
if typ == "NO":
raise ImapError(data)
if not name in self.m.untagged_responses:
return None
return self.m.untagged_responses.pop(name)
if not kwargs.has_key('responses'):
if not name in self.m.untagged_responses:
return None
return self.m.untagged_responses.pop(name)
res = []
for r in kwargs['responses']:
if not r in self.m.untagged_responses:
return None
res.append(self.m.untagged_responses.pop(r))
return res
def __find_content_in_bodystruct(self, bodystruct, mtype, stype, prefix=""):
"""Retrieve the number (index) of a specific part
@@ -358,7 +365,6 @@ class IMAPconnector(object):
if r'\HasChildren' in flags:
descr["path"] = name
descr["sub"] = []
descr["class"] = "subfolders"
result += [descr]
from operator import itemgetter
return sorted(result, key=itemgetter("name"))
@@ -411,7 +417,7 @@ class IMAPconnector(object):
"""
self._add_flag(mbox, msgset, r'(\Seen)')
def msgforwarded(self, mailbox, mailid):
def msg_forwarded(self, mailbox, mailid):
self._add_flag(mailbox, mailid, '($Forwarded)')
def msg_answered(self, mailbox, mailid):
@@ -475,7 +481,8 @@ class IMAPconnector(object):
self.quota_limit = self.quota_actual = None
return
data = self._cmd("GETQUOTAROOT", self._encodefolder(mailbox))
data = self._cmd("GETQUOTAROOT", self._encodefolder(mailbox),
responses=["QUOTAROOT", "QUOTA"])
if data is None:
self.quota_limit = self.quota_actual = None
return
+9 -6
View File
@@ -26,15 +26,18 @@ from exceptions import *
class WMtable(tables.Table):
tableid = "emails"
styles = "table-condensed"
idkey = "imapid"
select = tables.ImgColumn("select", cssclass="draggable left", width="1%",
defvalue="%spics/grippy.png" % settings.MEDIA_URL,
header="<input type='checkbox' name='toggleselect' id='toggleselect' />")
select = tables.ImgColumn(
"select", cssclass="draggable left", width="1%",
defvalue="%spics/grippy.png" % settings.MEDIA_URL,
header="<input type='checkbox' name='toggleselect' id='toggleselect' />"
)
flags = tables.ImgColumn("flags", width="2%")
withatts = tables.ImgColumn("withatts", width="2%")
subject = tables.Column("subject", label=__("Subject"), width="50%")
from_ = tables.Column("from", width="20%", label=__("From"))
date = tables.Column("date", width="20%", label=__("Date"))
subject = tables.Column("subject", label=__("Subject"), width="50%", limit=60)
from_ = tables.Column("from", width="20%", label=__("From"), limit=30)
date = tables.Column("date", width="10%", label=__("Date"))
cols_order = ["select", "withatts", "flags", "subject", "from_", "date"]
@@ -1,115 +1,24 @@
{% extends "layout_simple.html" %}
{% extends "common/generic_modal_form.html" %}
{% load i18n %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}webmail/css/attachments.css" />
<script src="{{ MEDIA_URL }}webmail/js/webmail.js" type="text/javascript"></script>
<script type="text/javascript">
function del_attachment(evt) {
evt.stop();
new Request.JSON.mdb({
url: this.get("href"),
onSuccess: function(resp) {
this.getParent().dispose();
}.bind(this)
}).get();
}
function upload_cb(fname, tmpname) {
$("upload_status").setStyle("display", "none");
$("submit").set("disabled", false);
$("error").set("html", "");
var ndiv = new Element("div");
var delbtn = new Element("a")
.set("name", "delattachment")
.set("href", "{% url modoboa.extensions.webmail.views.delattachment %}?name=" + tmpname)
.set("html", "<img src='{{MEDIA_URL}}pics/remove.png' />")
.inject(ndiv);
var label = new Element("label").set("html", fname).inject(ndiv);
delbtn.addEvent("click", del_attachment);
$("id_attachment").set("value", "");
ndiv.inject($("attachment_list"));
}
function upload_error(msg) {
$("upload_status").setStyle("display", "none");
$("error").set("html", msg);
$("submit").set("disabled", false);
}
window.addEvent("domready", function() {
$("uploadfile").addEvent("submit", function(evt) {
if ($("id_attachment").value == "") {
evt.stop();
return;
}
$("upload_status").setStyle("display", "block");
$("submit").set("disabled", true);
});
$$("a[name=delattachment]").addEvent("click", del_attachment);
$("done").addEvent("click", function(evt) {
var nfiles = 0;
var shortlist = "";
var html;
$$("a[name=delattachment]").each(function(item) {
var label = item.getNext();
nfiles++;
if (nfiles <= 2) {
if (shortlist != "") {
shortlist += ", ";
} else {
shortlist = "(";
}
shortlist += label.get("html");
return;
}
if (nfiles == 3) {
shortlist += ", ...";
}
});
if (shortlist != "") {
shortlist += ")";
}
html = interpolate(ngettext("%s file", "%s files", nfiles), [nfiles]);
html += " <span class='shortlist'>" + shortlist + "</span>";
parent.document.getElementById("list")
.set("html", html);
parent.SqueezeBox.close();
});
});
</script>
{% endblock %}
{% block content %}
<div class="title"><h1>{% trans "Attachments" %}</h1></div>
<div id="error" class="error">{{ error }}</div>
<form enctype="multipart/form-data" method="post" name="uploadfile" id="uploadfile"
target="upload_target"
action="{% url modoboa.extensions.webmail.views.attachments %}">
{% include "common/genericform.html" %}
<div class="row">
<input type="submit" id="submit" name="submit" value="{% trans 'Attach' %}" />
</div>
</form>
{% block modalbody %}
{{ block.super }}
<div id="test" style="text-align: center">
<input type="button" class="btn btn-primary" id="submit" name="submit" value="{% trans 'Attach' %}" />
</div>
<div id="upload_status" style="display:none">
<img src="{{MEDIA_URL}}css/spinner.gif" /> {% trans "Uploading.." %}
<img src="{{ MEDIA_URL }}css/spinner.gif" /> {% trans "Uploading.." %}
</div>
<iframe id="upload_target" name="upload_target" src="#"
style="width:0;height:0;border:0px solid #fff;"></iframe>
<div id="attachment_list">
<div class="well" id="attachment_list">
{% for att in attachments %}
<div>
<div class="row-fluid">
<a href="{% url modoboa.extensions.webmail.views.delattachment %}?name={{ att.tmpname }}"
name="delattachment">
<img src="{{ MEDIA_URL }}pics/remove.png" />
<i class="icon-remove"></i>
</a>
<label>{{ att.fname }}</label>
</div>
{% endfor %}
</div>
<div id="done_container">
<input type="button" id="done" name="done" value="{% trans 'Done' %}" />
</div>
{% endblock %}
@@ -1,7 +1,7 @@
{% load i18n libextras %}
<form method="POST" action="{{ posturl }}" id="composemail">
<form class="form-horizontal" method="POST" action="{{ posturl }}" id="composemail">
<div id="compose-container">
<div id="mailheader">
<div id="mailheader" class="well">
{% render_form form %}
<div id="attachments" name="{% url modoboa.extensions.webmail.views.attachments %}">
<div>
@@ -14,7 +14,7 @@
</div>
</div>
</div>
<div class="spacer"></div>
<div id="body_container">
{% if body %}
<textarea id="id_body" name="id_body" class="editor">{% if bodyheader %}{{ bodyheader }}{% endif %}
@@ -5,9 +5,9 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
{% if status == "ok" %}
parent.upload_cb("{{ fname }}", "{{ tmpname }}");
parent.webmail.upload_success("{{ fname }}", "{{ tmpname }}");
{% else %}
parent.upload_error("{{ error }}");
parent.webmail.upload_error("{{ error }}");
{% endif %}
</script>
</head>
+11 -12
View File
@@ -15,7 +15,7 @@ def viewmail_menu(selection, folder, user, mail_id=None):
entries = [
{"name" : "back",
"url" : "javascript:history.go(-1);",
"img" : static_url("pics/back.png"),
"img" : "icon-arrow-left",
"label" : _("Back")},
{"name" : "reply",
"url" : "action=reply&mbox=%s&mailid=%s" % (folder, mail_id),
@@ -58,12 +58,12 @@ def viewmail_menu(selection, folder, user, mail_id=None):
def compose_menu(selection, backurl, user):
entries = [
{"name" : "back",
"url" : "javascript:history.go(-2);",#backurl,
"img" : static_url("pics/back.png"),
"url" : "javascript:history.go(-2);",
"img" : "icon-arrow-left",
"label" : _("Back")},
{"name" : "sendmail",
"url" : "",
"img" : static_url("pics/send-receive.png"),
"img" : "icon-envelope",
"label" : _("Send")},
]
return render_to_string('common/buttons_list.html',
@@ -109,14 +109,13 @@ def listmailbox_menu(selection, folder, user):
"img" : static_url("pics/clear.png"),
"url" : reverse(webmail.views.empty, args=[folder])}
]
menu = render_to_string("common/buttons_list.html",
{"selection" : selection, "entries" : entries,
"user" : user, "css" : "nav"})
# searchbar = render_to_string("common/email_searchbar.html", {
# "MEDIA_URL" : settings.MEDIA_URL
# })
# return menu + searchbar
return menu
searchbar = render_to_string('common/email_searchbar.html', {
"MEDIA_URL" : settings.MEDIA_URL
})
return render_to_string('common/buttons_list.html', dict(
selection=selection, entries=entries, extracontent=searchbar,
user=user, css="nav"
))
@register.simple_tag
def print_folders(folders, selected=None, withunseen=False, withmenu=False):
+18 -4
View File
@@ -56,7 +56,7 @@ def __render_common_components(request, folder_name, lst=None, content=None, men
else:
navbar = ""
if lst:
content = "<div class='info'>%s</div>" \
content = "<div class='alert alert-info'>%s</div>" \
% _("This folder contains no messages")
ret = {
@@ -307,7 +307,7 @@ def delfolder(request):
@login_required
@needs_mailbox()
def attachments(request, tplname="webmail/attachments.html"):
def attachments(request, tplname="webmail/attachments2.html"):
if request.method == "POST":
csuploader = AttachmentUploadHandler()
request.upload_handlers.insert(0, csuploader)
@@ -336,8 +336,15 @@ def attachments(request, tplname="webmail/attachments.html"):
return _render(request, "webmail/upload_done.html", {
"status" : "ko", "error" : error
});
ctx = {"form" : AttachmentForm(),
"attachments" : request.session["compose_mail"]["attachments"]}
ctx = {
"title" : _("Attachments"),
"formid" : "uploadfile",
"target" : "upload_target",
"enctype" : "multipart/form-data",
"form" : AttachmentForm(),
"action" : reverse(attachments),
"attachments" : request.session["compose_mail"]["attachments"]
}
return _render(request, tplname, ctx)
@login_required
@@ -578,10 +585,17 @@ def newindex(request):
if not request.is_ajax():
request.session["lastaction"] = None
imapc = get_imapconnector(request)
imapc.getquota(curmbox)
response["refreshrate"] = \
int(parameters.get_user(request.user, "REFRESH_INTERVAL")) * 1000
response["mboxes"] = render_mboxes_list(request, imapc)
response["quota"] = ImapListing.computequota(imapc)
if response["quota"] < 50:
response["quotalevel"] = "success"
elif response["quotalevel"] < 80:
response["quotalevel"] = "warning"
else:
response["quotalevel"] = "danger"
return _render(request, "webmail/index2.html", response)
if action in ["reply", "forward"]:
+1 -3
View File
@@ -86,7 +86,6 @@ class Paginator(object):
return p
class EmailListing(object):
table_styles = "table-striped table-condensed"
def __init__(self, baseurl=None, folder=None, elems_per_page=40,
navparams={}, **kwargs):
@@ -125,8 +124,7 @@ class EmailListing(object):
</form>""")
return tpl.render(
Context(
{"table" : table.render(styles=self.table_styles,
withheader=False)}
{"table" : table.render(withheader=False)}
)
)
+7 -2
View File
@@ -188,8 +188,13 @@ class Table(object):
def __str__(self):
return self.render()
def render(self, styles="table-striped table-bordered", withheader=True):
def render(self, withheader=True):
if len(self.rows):
try:
styles = self.styles
except AttributeError:
styles = "table-striped table-bordered"
t = Template("""
<table id="{{ tableid }}" class="table {{ styles }}">
{% if withheader %}{% include "common/table_head.html" %}{% endif %}
@@ -201,7 +206,7 @@ class Table(object):
"withheader" : withheader
}))
t = Template("""
<div class="info">%s</div>
<div class="alert alert-info">%s</div>
""" % _("No entries to display"))
return t.render(Context())
+1
View File
@@ -15,6 +15,7 @@ body {
left: 0;
right: 0;
bottom: 0;
margin-bottom: 0;
}
#pagination_bar {
+7 -5
View File
@@ -61,6 +61,7 @@ History.prototype = {
? value.substring(1).split('?') : value.split('?');
if (splits.length == 0) {
this.base = this.options.deflocation;
return this;
}
if (reset === undefined) {
@@ -69,11 +70,12 @@ History.prototype = {
if (reset) {
this.reset();
}
this.base = (splits[0] == "") ? this.options.deflocation : splits[0];
var re = new RegExp("/$");
if (!this.base.match(re)) {
this.base += '/';
if ((this.base = splits[0]) != "") {
var re = new RegExp("/$");
if (!this.base.match(re)) {
this.base += '/';
}
}
if (splits.length > 1) {
var params = splits[1].split('&');
@@ -101,7 +103,7 @@ History.prototype = {
},
update: function(force, noupdate) {
$(window).trigger("pageRefresh");
/*$(window).trigger("pageRefresh");*/
location.hash = this.serialize();
if (noupdate === undefined) {
+8 -4
View File
@@ -2,10 +2,10 @@
* Collection of functions that are accessible to every page.
*/
function modalbox(e, css) {
function modalbox(e, css, defhref, defcb) {
e.preventDefault();
var href = $(this).attr('href');
var modalcb = $(this).attr('modalcb');
var href = (defhref != undefined) ? defhref : $(this).attr('href');
var modalcb = (defcb != undefined) ? defcb : $(this).attr('modalcb');
if (href.indexOf('#') == 0) {
$(href).modal('open');
@@ -22,7 +22,11 @@ function modalbox(e, css) {
.modal()
.one('shown', function() {
if (modalcb != undefined) {
eval(modalcb + '()');
if (typeof modalcb === "function") {
modalcb();
} else {
eval(modalcb + '()');
}
}
})
.on('hidden', function() {
+1 -1
View File
@@ -1,5 +1,5 @@
<div class="control-group {% if field.errors %}error{% endif %}">
<label class="control-label" for="id_{{ field.html_name }}">{{ field.label }}</label>
{% if field.label %}<label class="control-label" for="id_{{ field.html_name }}">{{ field.label }}</label>{% endif %}
<div class="controls spaced">
{{ field }}{% if field.errors %}<p class="help-block">{% for error in field.errors %}{{ error|safe }}{% endfor %}</p>{% endif %}
</div>
+13 -20
View File
@@ -1,20 +1,13 @@
{% load i18n libextras %}
<div class="modal-header">
<a class="close" data-dismiss="modal">x</a>
<h3>{{ title }}</h3>
</div>
<div class="modal-body">
{% if error %}<div class="alert alert-error">{{ error }}</div>{% endif %}
<form class="form-horizontal" action="{{ action }}" enctype="{{ enctype }}"
method="POST" id="{{ formid }}" name="{{ formid }}"
target="{{ target }}">
{% block formbody %}
{% render_form form %}
{% block extrafields %}{% endblock %}
{% endblock %}
</form>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-primary submit">{{ submit_label }}</a>
<a class="btn" data-dismiss="modal">{% trans "Close" %}</a>
</div>
{% extends "common/generic_modal.html" %}
{% load libextras %}
{% block modalbody %}
{% if error %}<div class="alert alert-error">{{ error }}</div>{% endif %}
<form class="form-horizontal" action="{{ action }}" enctype="{{ enctype }}"
method="POST" id="{{ formid }}" name="{{ formid }}"
target="{{ target }}">
{% block formbody %}
{% render_form form %}
{% block extrafields %}{% endblock %}
{% endblock %}
</form>
{% endblock %}
+4 -4
View File
@@ -1,21 +1,21 @@
<div id="pagination_bar" class="simplepagination pull-right padded5">
<ul>
<li class="{% if not page.has_previous %}disabled{% endif %}">
<a href="{{ baseurl }}?page=1"><i class="icon-white icon-fast-backward"></i></a>
<a href="{{ baseurl }}page=1"><i class="icon-white icon-fast-backward"></i></a>
</li>
<li class="{% if not page.has_previous %}disabled{% endif %}">
<a href="{{ baseurl }}?page={{ page.previous_page_number }}">
<a href="{{ baseurl }}page={{ page.previous_page_number }}">
<i class="icon-step-backward icon-white"></i></a>
</li>
<li class="disabled">
{{ page.number }}/{{ page.paginator.num_pages }}
</li>
<li class="{% if not page.has_next %}disabled{% endif %}">
<a href="{{ baseurl }}?page={{ page.next_page_number }}">
<a href="{{ baseurl }}page={{ page.next_page_number }}">
<i class="icon-step-forward icon-white"></i></a>
</li>
<li class="{% if not page.has_next %}disabled{% endif %}">
<a href="{{ baseurl }}?page={{ page.paginator.num_pages }}">
<a href="{{ baseurl }}page={{ page.paginator.num_pages }}">
<i class="icon-fast-forward icon-white"></i></a>
</li>
</ul>
+11 -3
View File
@@ -42,11 +42,19 @@
</div>
</div>
{% block content %}{% endblock %}
<div id="bottom-bar">
<div id="bottom-bar" class="navbar">
<div class="navbar-inner">
<div class="container-fluid">
<div id="bottom-bar-in" class="nav-collapse">
{% block footer %}{% endblock %}
<div class="row">
<div id="bottom-bar-left" class="pull-left">
{% block footerleft %}{% endblock %}
</div>
<div id="bottom-bar-middle" class="">
{% block footermiddle %}{% endblock %}
</div>
<div id="bottom-bar-right" class="pull-right nav-collapse">
{% block footerright %}{% endblock %}
</div>
</div>
</div>
</div>