wip: Big webmail refactoring and performance improvment

* Fixed some issues with the navigation plugin
* Compose form is reachable
This commit is contained in:
Antoine Nguyen
2011-11-29 11:52:26 +01:00
parent 39d105890a
commit b9061b7f4a
8 changed files with 256 additions and 123 deletions
+42 -18
View File
@@ -230,7 +230,7 @@ class IMAPconnector(object):
@capability('LIST-EXTENDED', '_listfolders_simple')
def _listfolders(self, topmailbox='', md_mailboxes=[]):
pattern = ("%s.%%" % topmailbox) if len(topmailbox) else "%"
pattern = ("%s.%%" % topmailbox.encode("imap4-utf-7")) if len(topmailbox) else "%"
resp = self._cmd("LSUB", "", pattern, "RETURN", "(CHILDREN)")
result = []
for mb in resp:
@@ -275,16 +275,32 @@ class IMAPconnector(object):
fd["unseen"] = count
return md_mailboxes
def _add_flag(self, folder, mail_id, flag):
self.m.select(self._encodefolder(folder))
self.m.store(mail_id, "+FLAGS", flag)
def _add_flag(self, mbox, msgset, flag):
"""Add flag(s) to a messages set
def msg_unread(self, folder, msgset):
self.m.select(self._encodefolder(folder))
self.m.store(msgset, "-FLAGS", r'(\Seen)')
:param mbox: the mailbox containing the messages
:param msgset: messages set (uid)
:param flag: the flag to add
"""
self.select_mailbox(mbox, False)
self._cmd("STORE", msgset, "+FLAGS", flag)
def msg_read(self, folder, msgset):
self._add_flag(folder, msgset, r'(\Seen)')
def mark_messages_unread(self, mbox, msgset):
"""Mark a set of messages as unread
:param mbox: the mailbox containing the messages
:param msgset: messages set (uid)
"""
self.select_mailbox(mbox, False)
self._cmd("STORE", msgset, "-FLAGS", r'(\Seen)')
def mark_messages_read(self, mbox, msgset):
"""Mark a set of messages as unread
:param mbox: the mailbox containing the messages
:param msgset: messages set (uid)
"""
self._add_flag(mbox, msgset, r'(\Seen)')
def msgforwarded(self, folder, imapid):
self._add_flag(folder, imapid, '($Forwarded)')
@@ -304,16 +320,24 @@ class IMAPconnector(object):
now = imaplib.Time2Internaldate(time.time())
self.m.append(self._encodefolder(folder), r'(\Seen)', now, str(msg))
def empty(self, folder):
self.m.select(self._encodefolder(folder))
typ, data = self.m.search(None, 'ALL')
for num in data[0].split():
self.m.store(num, "+FLAGS", r'(\Deleted)')
self.m.expunge()
def empty(self, mbox):
self.select_mailbox(mbox, False)
resp = self._cmd("SEARCH", "ALL")
print resp
# for num in data[0].split():
# self.m.store(num, "+FLAGS", r'(\Deleted)')
# self.m.expunge()
def compact(self, folder):
self.m.select(self._encodefolder(folder))
self.m.expunge()
def compact(self, mbox):
"""Compact a specific mailbox
Issue an EXPUNGE command for the specified mailbox.
:param mbox: the mailbox's name
"""
self.select_mailbox(mbox, False)
self._cmd("EXPUNGE")
def create_folder(self, name, parent=None):
if parent is not None:
+2 -1
View File
@@ -110,8 +110,9 @@ class ImapListing(EmailListing):
if kwargs.has_key("pattern"):
self.parse_search_parameters(kwargs["criteria"],
kwargs["pattern"])
if kwargs.has_key("reset"):
else:
self.mbc.criterions = []
EmailListing.__init__(self, **kwargs)
self.extravars["refreshrate"] = \
int(parameters.get_user(user, "REFRESH_INTERVAL")) * 1000
+25 -9
View File
@@ -42,6 +42,7 @@ var Webmail = new Class({
*/
init_menubar: function() {
searchbox_init();
$$("a[name=compose]").addEvent("click", this.compose_loader);
$$("a[name=mark-read]").addEvent("click", this.send_mark_request.bind(this));
$$("a[name=mark-unread]").addEvent("click", this.send_mark_request.bind(this));
$$("a[name=fdaction]").addEvent("click", function(evt) {
@@ -58,11 +59,21 @@ var Webmail = new Class({
var ids = new Array();
this.emails_table._selectedRows.each(function(item, index) {
ids.include(item.get("id"));
ids.include(item.get("id"));
});
this.simple_request(evt.target.get("href"), {
ids : ids.join()
});
}, function(response) {
if (response.action == "read") {
this.emails_table._selectedRows.removeClass("unseen");
} else {
this.emails_table._selectedRows.addClass("unseen");
}
/*
* MAJ de l'affichage du nombre de messages non lus
* (faire fonction à part)
*/
}.bind(this));
},
/*
@@ -461,7 +472,7 @@ var Webmail = new Class({
*/
compose_loader: function(event) {
event.stop();
current_anchor.baseurl("compose").update();
current_anchor.reset().setparam("action", this.get("href")).update();
},
/*
@@ -505,7 +516,8 @@ var Webmail = new Class({
* It is also shared with other similar actions : reply, forward.
*/
compose_callback: function(resp) {
wm_updatelisting(resp);
//wm_updatelisting(resp);
updatelisting(resp);
window.addEvent("resize", this.resize_window_callback.bind(this));
@@ -534,9 +546,9 @@ var Webmail = new Class({
});
});
$$("a[name=back]")
/*$$("a[name=back]")
.removeEvents("click")
.addEvent("click", this._listmailbox_loader);
.addEvent("click", this._listmailbox_loader);*/
$$("a[name=sendmail]")
.store("editormode", editormode)
.removeEvents("click")
@@ -583,7 +595,7 @@ var Webmail = new Class({
* encoded answer. The infobox is cleared by the request callback
* function.
*/
simple_request: function(url, params) {
simple_request: function(url, params, callback) {
infobox.show(gettext("Waiting..."), {
profile: "gray",
spinner: true
@@ -593,8 +605,12 @@ var Webmail = new Class({
url : url,
onSuccess : function(response) {
if (response.status == "ok") {
if (!$defined(response.next)) {
current_anchor.get_callback("listmailbox")(response);
if (response.next == undefined) {
if (callback != undefined) {
callback(response);
} else {
current_anchor.get_callback("listmailbox")(response);
}
}
infobox.info(gettext("Done"));
if ($defined(response.next)) {
+2 -2
View File
@@ -56,7 +56,7 @@ def viewm_menu(selection, backurl, folder, mail_id, user):
def compose_menu(selection, backurl, user):
entries = [
{"name" : "back",
"url" : backurl,
"url" : "javascript:history.go(-2);",#backurl,
"img" : static_url("pics/back.png"),
"label" : _("Back")},
{"name" : "sendmail",
@@ -72,7 +72,7 @@ def compose_menu(selection, backurl, user):
def listmailbox_menu(selection, folder, user):
entries = [
{"name" : "compose",
"url" : reverse(webmail.views.compose),
"url" : "compose",
"img" : static_url("pics/edit.png"),
"label" : _("New message")},
{"name" : "mark",
+1 -1
View File
@@ -10,7 +10,7 @@ urlpatterns = patterns('modoboa.extensions.webmail.views',
(r'^$', "newindex"),
(r'^submailboxes', "submailboxes"),
(r'^compose/$', "compose"),
# (r'^compose/$', "compose"),
(r'^move/$', "move"),
(r'^mark/(?P<name>.+)/$', "mark"),
(r'^empty/(?P<name>.+)/$', "empty"),
+157 -70
View File
@@ -230,87 +230,89 @@ def delete(request, fdname, mail_id):
@login_required
@is_not_localadmin()
def mark(request, name):
if not request.GET.has_key("status") or not request.GET.has_key("ids"):
return
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
status = request.GET.get("status", None)
ids = request.GET.get("ids", None)
if status is None or ids is None:
raise WebmailError(_("Invalid request"))
imapc = get_imapconnector(request)
try:
getattr(mbc, "msg_%s" % request.GET["status"])(name, request.GET["ids"])
getattr(imapc, "mark_messages_%s" % status)(name, ids)
except AttributeError:
pass
return folder(request, name, False)
raise WebmailError(_("Unknown action"))
return ajax_simple_response(
dict(status="ok", action=status, mbox=name,
unseen=imapc.unseen_messages(name))
)
@login_required
@is_not_localadmin()
def empty(request, name):
if name == parameters.get_user(request.user, "TRASH_FOLDER"):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
mbc.empty(name)
return folder(request, name, False)
get_imapconnector(request).empty(name)
return ajax_simple_response(dict(status="ok"))
@login_required
@is_not_localadmin()
def compact(request, name):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
mbc.compact(name)
return folder(request, name, False)
imapc = get_imapconnector(request)
imapc.compact(name)
return ajax_simple_response(dict(status="ok"))
def render_compose(request, form, posturl, email=None, insert_signature=False):
menu = compose_menu("", get_current_url(request),
request.user.get_all_permissions())
editor = parameters.get_user(request.user, "EDITOR")
if email is None:
body = ""
textheader = ""
else:
body = email.body
textheader = email.textheader
if insert_signature:
signature = EmailSignature(request.user)
body += str(signature)
randid = None
if not request.GET.has_key("id"):
if request.session.has_key("compose_mail"):
clean_attachments(request.session["compose_mail"]["attachments"])
randid = set_compose_session(request)
elif not request.session.has_key("compose_mail") \
or request.session["compose_mail"]["id"] != request.GET["id"]:
randid = set_compose_session(request)
# def render_compose(request, form, posturl, email=None, insert_signature=False):
# menu = compose_menu("", get_current_url(request),
# request.user.get_all_permissions())
# editor = parameters.get_user(request.user, "EDITOR")
# if email is None:
# body = ""
# textheader = ""
# else:
# body = email.body
# textheader = email.textheader
# if insert_signature:
# signature = EmailSignature(request.user)
# body += str(signature)
# randid = None
# if not request.GET.has_key("id"):
# if request.session.has_key("compose_mail"):
# clean_attachments(request.session["compose_mail"]["attachments"])
# randid = set_compose_session(request)
# elif not request.session.has_key("compose_mail") \
# or request.session["compose_mail"]["id"] != request.GET["id"]:
# randid = set_compose_session(request)
attachments = request.session["compose_mail"]["attachments"]
if len(attachments):
short_att_list = "(%s)" \
% ", ".join(map(lambda att: att["fname"],
attachments[:2] + [{"fname" : "..."}] \
if len(attachments) > 2 else attachments))
else:
short_att_list = ""
content = _render_to_string(request, "webmail/compose.html", {
"form" : form, "bodyheader" : textheader,
"body" : body, "posturl" : posturl,
"attachments" : attachments, "short_att_list" : short_att_list
})
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
ctx = getctx("ok", level=2, editor=editor,
**__render_common_components(request, request.session["folder"],
menu=menu, content=content))
if randid is not None:
ctx["id"] = randid
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
# attachments = request.session["compose_mail"]["attachments"]
# if len(attachments):
# short_att_list = "(%s)" \
# % ", ".join(map(lambda att: att["fname"],
# attachments[:2] + [{"fname" : "..."}] \
# if len(attachments) > 2 else attachments))
# else:
# short_att_list = ""
# content = _render_to_string(request, "webmail/compose.html", {
# "form" : form, "bodyheader" : textheader,
# "body" : body, "posturl" : posturl,
# "attachments" : attachments, "short_att_list" : short_att_list
# })
# mbc = IMAPconnector(user=request.user.username,
# password=request.session["password"])
# ctx = getctx("ok", level=2, editor=editor,
# **__render_common_components(request, request.session["folder"],
# menu=menu, content=content))
# if randid is not None:
# ctx["id"] = randid
# return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
@is_not_localadmin()
def compose(request):
if request.method == "POST":
status, resp = send_mail(request, posturl=reverse(compose))
return resp
# @login_required
# @is_not_localadmin()
# def compose(request):
# if request.method == "POST":
# status, resp = send_mail(request, posturl=reverse(compose))
# return resp
form = ComposeMailForm()
form.fields["from_"].initial = request.user.username
return render_compose(request, form, reverse(compose), insert_signature=True)
# form = ComposeMailForm()
# form.fields["from_"].initial = request.user.username
# return render_compose(request, form, reverse(compose), insert_signature=True)
@login_required
@is_not_localadmin()
@@ -503,6 +505,12 @@ def delattachment(request):
# NEW CODE STARTS HERE
#
def render_mboxes_list(request, imapc):
"""Return the HTML representation of a mailboxes list
:param request: a ``Request`` object
:param imap: an ``IMAPconnector` object
:return: a string
"""
curmbox = request.session.get("mbox", "INBOX")
return _render_to_string(request, "webmail/folders.html", {
"titlebar" : True,
@@ -518,19 +526,83 @@ def set_nav_params(request):
request.session["pageid"] = \
int(request.GET.get("page", 1))
if not request.GET.get("order", False):
if not request.session["navparams"].has_key("order"):
request.session["navparams"]["order"] = "-date"
else:
request.session["navparams"]["order"] = request.GET.get("order")
for p in ["pattern", "criteria"]:
if request.GET.get(p, False):
request.session["navparams"][p] = request.GET[p]
elif request.session["navparams"].has_key(p):
del request.session["navparams"][p]
def listmailbox(request, defmailbox="INBOX"):
"""Mailbox content listing
Return a list of messages contained in the specified mailbox. The
number of elements returned depends on the ``MESSAGES_PER_PAGE``
parameter. (user preferences)
:param request: a ``Request`` object
:param defmailbox: the default mailbox (when not present inside request arguments)
:return: a dictionnary
"""
mbox = request.GET.get("name", defmailbox)
request.session["mbox"] = mbox
set_nav_params(request)
lst = ImapListing(request.user, request.session["password"],
baseurl="?action=listmailbox&name=%s&" % mbox,
folder=mbox, order="-date")
folder=mbox, **request.session["navparams"])
return lst.render(request, request.session["pageid"])
def render_compose(request, form, posturl, email=None, insert_signature=False):
editor = parameters.get_user(request.user, "EDITOR")
if email is None:
body = ""
textheader = ""
else:
body = email.body
textheader = email.textheader
if insert_signature:
signature = EmailSignature(request.user)
body += str(signature)
randid = None
if not request.GET.has_key("id"):
if request.session.has_key("compose_mail"):
clean_attachments(request.session["compose_mail"]["attachments"])
randid = set_compose_session(request)
elif not request.session.has_key("compose_mail") \
or request.session["compose_mail"]["id"] != request.GET["id"]:
randid = set_compose_session(request)
attachments = request.session["compose_mail"]["attachments"]
if len(attachments):
short_att_list = "(%s)" \
% ", ".join(map(lambda att: att["fname"],
attachments[:2] + [{"fname" : "..."}] \
if len(attachments) > 2 else attachments))
else:
short_att_list = ""
content = _render_to_string(request, "webmail/compose.html", {
"form" : form, "bodyheader" : textheader,
"body" : body, "posturl" : posturl,
"attachments" : attachments, "short_att_list" : short_att_list
})
ctx = dict(listing=content, editor=editor)
if randid is not None:
ctx["id"] = randid
return ctx
def compose(request):
form = ComposeMailForm()
form.fields["from_"].initial = request.user.username
return render_compose(request, form, "compose", insert_signature=True)
@login_required
@is_not_localadmin()
def submailboxes(request):
@@ -556,6 +628,11 @@ def newindex(request):
faudrait pouvoir envoyer le menu par la même occasion, le souci
étant de savoir lequel...
Une solution possible : il suffirait de déplacer le menu vers la
droite pour l'aligner avec le contenu, remonter la liste des
dossiers (même hauteur que le menu) et renvoyer le menu en même
temps que le contenu. Le rendu sera plus uniforme je pense.
"""
action = request.GET.get("action", None)
json = request.GET.get("json", False)
@@ -571,8 +648,8 @@ def newindex(request):
response = dict(deflocation="?action=listmailbox",
defcallback="wm_updatelisting")
curmbox = request.session.get("mbox", "INBOX")
if not json:
curmbox = request.session.get("mbox", "INBOX")
imapc = get_imapconnector(request)
response["refreshrate"] = \
int(parameters.get_user(request.user, "REFRESH_INTERVAL")) * 1000
@@ -580,9 +657,19 @@ def newindex(request):
try:
menufunc = globals()["%s_menu" % action]
except KeyError:
menufunc = globals()["listmailbox_menu"]
if request.session.has_key("lastaction"):
menufunc = globals()["%s_menu" % request.session["lastaction"]]
else:
menufunc = globals()["listmailbox_menu"]
response["menu"] = menufunc("", curmbox, request.user)
return _render(request, "webmail/index.html", response)
if request.session.get("lastaction", None) != action:
request.session["lastaction"] = action
try:
response["menu"] = globals()["%s_menu" % action]("", curmbox, request.user)
except KeyError:
pass
response.update(status="ok", callback=action)
return ajax_simple_response(response)
+11 -11
View File
@@ -53,11 +53,13 @@ var AnchorNavigation = new Class({
if (reset) {
this.reset();
}
this.base = (splits[0] == "") ? this.deflocation : splits[0];
this.base = splits[0];
var re = new RegExp("/$");
if (!this.base.match(re)) {
this.base += '/';
if (this.base != "") {
var re = new RegExp("/$");
if (!this.base.match(re)) {
this.base += '/';
}
}
if (splits.length > 1) {
var params = splits[1].split('&');
@@ -183,16 +185,14 @@ var AnchorNavigation = new Class({
},
check_anchor: function() {
if (this.serialized == location.hash
&& !$defined(this.force)) {
if (this.serialized == location.hash && this.force == undefined) {
return;
}
delete(this.force);
this.from_string(location.hash);
if (!this.serialized) {
location.hash = this.deflocation;
return;
}
this.from_string((location.hash != "") ? location.hash : "#" + this.deflocation);
location.hash = this.serialized;
if (!this.updatenext) {
this.updatenext = true;
return;
+16 -11
View File
@@ -153,8 +153,10 @@ checkTableSelection = function(text) {
return confirm(text);
};
searchbox_init = function() {
function searchbox_init() {
var myFx = new Fx.Slide($("searchparams"));
var $searchfield = $("searchfield");
myFx.hide();
$("searchopts").addEvent("click", function(event) {
myFx.cancel();
@@ -164,28 +166,31 @@ searchbox_init = function() {
myFx.cancel();
myFx.toggle();
});
$("searchfield").addEvent("change", function(event) {
$searchfield.addEvent("change", function(event) {
var criteria = "";
var pattern = this.get("value");
$$("input[name=scriteria]:checked").each(function(obj) {
criteria = obj.value;
});
var pattern = $("searchfield").get("value");
if (pattern != "") {
var params = "pattern=" + pattern + "&criteria=" + criteria;
current_anchor.updateparams(params);
current_anchor.setparams({
pattern: pattern,
criteria: criteria
});
} else {
current_anchor.deleteParam("pattern");
current_anchor.deleteParam("criteria");
}
current_anchor.update();
});
$("searchfield").addEvent("click", function(event) {
$("searchfield").select();
$searchfield.addEvent("click", function(event) {
$searchfield.select();
});
$("searchfield").addEvent("blur", function(event) {
if ($("searchfield").value == "") {
$("searchfield").set("value", gettext("Search..."));
$searchfield.addEvent("blur", function(event) {
if ($searchfield.value == "") {
$searchfield.set("value", gettext("Search..."));
}
});
$("searchbox").setStyle("float", "left");
@@ -193,7 +198,7 @@ searchbox_init = function() {
if (frag.indexOf('?') != -1) {
var params = frag.substring(frag.indexOf('?') + 1).parseQueryString();
if ($defined(params.pattern)) {
$("searchfield").set("value", params.pattern);
$searchfield.set("value", params.pattern);
}
if ($defined(params.criteria)) {
$("crit_" + params.criteria).checked = true;