mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Webmail improvements. (drap&drop of messages for moving and deleting, empty trash, etc.)
This commit is contained in:
@@ -7,7 +7,7 @@ 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 import decode, tables, imap_utf7, Singleton
|
||||
from mailng.lib.email_listing import MBconnector, EmailListing
|
||||
from mailng.lib import tables, imap_utf7, parameters
|
||||
|
||||
@@ -17,7 +17,8 @@ class WMtable(tables.Table):
|
||||
idkey = "imapid"
|
||||
selection = tables.SelectionColumn("selection", width="20px", first=True)
|
||||
_1_flags = tables.ImgColumn("flags", width="40px")
|
||||
_2_subject = tables.Column("subject", width="50%", label=_("Subject"))
|
||||
_2_subject = tables.Column("subject", width="50%", label=_("Subject"),
|
||||
cssclass="draggable")
|
||||
_3_from_ = tables.Column("from", width="20%", label=_("From"))
|
||||
_4_date = tables.Column("date", width="20%", label=_("Date"))
|
||||
|
||||
@@ -83,11 +84,23 @@ class IMAPheader(object):
|
||||
res += " "
|
||||
res += part[0].strip(" ")
|
||||
return decode(res)
|
||||
|
||||
class IMAPconnector(MBconnector):
|
||||
|
||||
class IMAPconnector(object):
|
||||
__metaclass__ = Singleton
|
||||
|
||||
def __init__(self, request=None, user=None, password=None):
|
||||
self.address = parameters.get("webmail", "IMAP_SERVER")
|
||||
if request:
|
||||
status, msg = self.login(request.user.username,
|
||||
request.session["password"])
|
||||
else:
|
||||
status, msg = self.login(user, password)
|
||||
if not status:
|
||||
# Gestion d'erreur
|
||||
pass
|
||||
|
||||
def login(self, user, passwd):
|
||||
import imaplib
|
||||
|
||||
try:
|
||||
self.m = imaplib.IMAP4_SSL(self.address)
|
||||
self.m.login(user, passwd)
|
||||
@@ -125,6 +138,18 @@ class IMAPconnector(MBconnector):
|
||||
folder, id = imapid.split("/")
|
||||
self.m.store(id, "+FLAGS", "\\Seen")
|
||||
|
||||
def move(self, msgset, oldfolder, newfolder):
|
||||
self.m.select(self._encodefolder(oldfolder), True)
|
||||
self.m.copy(msgset, newfolder)
|
||||
self.m.store(msgset, "+FLAGS", "\\Deleted")
|
||||
|
||||
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", "\\Deleted")
|
||||
self.m.expunge()
|
||||
|
||||
def fetch(self, start=None, stop=None, folder=None, all=False):
|
||||
if not start and not stop:
|
||||
return []
|
||||
@@ -164,10 +189,7 @@ class ImapListing(EmailListing):
|
||||
tbltype = WMtable
|
||||
|
||||
def __init__(self, user, password, baseurl=None, **kwargs):
|
||||
self.mbc = IMAPconnector(parameters.get("webmail", "IMAP_SERVER"), 143)
|
||||
status, text = self.mbc.login(user, password)
|
||||
if not status:
|
||||
print "Login error: %s" % text
|
||||
self.mbc = IMAPconnector(user=user, password=password)
|
||||
EmailListing.__init__(self, **kwargs)
|
||||
if baseurl:
|
||||
self.paginator.baseurl = baseurl
|
||||
|
||||
+20
-11
@@ -15,8 +15,6 @@ from imap_listing import *
|
||||
from forms import *
|
||||
from templatetags.webextras import *
|
||||
|
||||
mbc = None
|
||||
|
||||
def init():
|
||||
events.register("UserMenuDisplay", menu)
|
||||
events.register("UserLogin", userlogin)
|
||||
@@ -66,7 +64,7 @@ def folder(request, name, updatenav=True):
|
||||
content = "Empty folder"
|
||||
navbar = ""
|
||||
ctx = getctx("ok", listing=content, navbar=navbar,
|
||||
menu=listing_menu("", request.user.get_all_permissions()))
|
||||
menu=listing_menu("", name, request.user.get_all_permissions()))
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
@@ -76,11 +74,7 @@ def index(request):
|
||||
return lst.render(request, empty=True)
|
||||
|
||||
def fetchmail(request, folder, mail_id, all=False):
|
||||
global mbc
|
||||
if not mbc:
|
||||
mbc = IMAPconnector(parameters.get("webmail", "IMAP_SERVER"), 143)
|
||||
mbc.login(request.user.username, request.session["password"])
|
||||
res = mbc.fetch(start=mail_id, folder=folder, all=all)
|
||||
res = IMAPconnector(request).fetch(start=mail_id, folder=folder, all=all)
|
||||
if len(res):
|
||||
return res[0]
|
||||
return None
|
||||
@@ -106,9 +100,7 @@ def getmailcontent(request, folder, mail_id):
|
||||
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
if "class" in msg.keys() and msg["class"] == "unseen":
|
||||
global mbc
|
||||
mbc.msgseen(msg["imapid"])
|
||||
|
||||
IMAPconnector(request).msgseen(msg["imapid"])
|
||||
email = Email(msg, mode="html", links="1")
|
||||
return email.render(request)
|
||||
|
||||
@@ -139,3 +131,20 @@ def compose(request):
|
||||
})
|
||||
ctx = getctx("ok", menu=menu, listing=content)
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def move(request):
|
||||
for arg in ["msgset", "to"]:
|
||||
if not request.GET.has_key(arg):
|
||||
return
|
||||
folder, id = request.GET["msgset"].split("/")
|
||||
mbc = IMAPconnector(request)
|
||||
mbc.move(id, folder, request.GET["to"])
|
||||
return HttpResponse(simplejson.dumps({"status" : "ok"}),
|
||||
mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def empty(request, name):
|
||||
mbc = IMAPconnector(request)
|
||||
mbc.empty(name)
|
||||
return folder(request, name, False)
|
||||
|
||||
@@ -45,8 +45,20 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
tr.table-tr-selected {
|
||||
background: red;
|
||||
td {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
background: #6AB0C5;
|
||||
}
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
@@ -64,6 +76,22 @@ tr.table-tr-selected {
|
||||
$("navbar").getChildren("a").addEvent("click", navbarcallback);
|
||||
}
|
||||
$$("a[name=compose]").addEvent("click", loadCompose);
|
||||
$$("a[name=empty]").addEvent("click", empty);
|
||||
}
|
||||
empty = function(event) {
|
||||
event.stop();
|
||||
new Request.JSON({url : event.target.get("href"), onSuccess : function(resp) {
|
||||
updatelisting(resp);
|
||||
}}).get();
|
||||
}
|
||||
|
||||
select = function(obj) {
|
||||
$$("a[name=loadfolder]").each(function(item) {
|
||||
item.getParent().removeClass("selected");
|
||||
item.getParent().addClass("droppable");
|
||||
});
|
||||
obj.getParent().removeClass("droppable");
|
||||
obj.getParent().addClass("selected");
|
||||
}
|
||||
|
||||
{% url extensions.webmail.main.folder as view_url %}
|
||||
@@ -110,20 +138,62 @@ tr.table-tr-selected {
|
||||
}
|
||||
register_callback("folder", function(resp) {
|
||||
updatelisting(resp, "auto");
|
||||
var tbl = new HtmlTable($("emails"), {
|
||||
selectable: true
|
||||
});
|
||||
var drag_group = new Drag.Group({
|
||||
});
|
||||
tbl.addEvent("rowFocus", function(obj) {
|
||||
var clone = obj.clone()
|
||||
.setStyles(obj.getCoordinates())
|
||||
.setStyles({"opacity": 0.7, "position": "absolute"})
|
||||
.inject(document.body);
|
||||
drag_group.add(clone, {
|
||||
/*var drag_group = new Drag.Group({
|
||||
filter: function(item) { return item.hasClass("active"); },
|
||||
drag: {
|
||||
onComplete: function(item) {
|
||||
item.dispose();
|
||||
delete(item);
|
||||
},
|
||||
onEnter: function(item, droppable) {
|
||||
alert(droppable);
|
||||
}
|
||||
}
|
||||
});*/
|
||||
$$("td[class*=draggable]").each(function(item) {
|
||||
item.makeDraggable({
|
||||
droppables: ".droppable",
|
||||
}).addEvents({
|
||||
"enter" : function(item, droppable) {
|
||||
droppable.addClass("dragging");
|
||||
},
|
||||
"leave" : function(item, droppable) {
|
||||
droppable.removeClass("dragging");
|
||||
},
|
||||
"drop" : function(item, droppable, event) {
|
||||
item.dispose();
|
||||
delete(item);
|
||||
if (!droppable) {
|
||||
this.element = this.oldelement;
|
||||
return;
|
||||
}
|
||||
droppable.removeClass("dragging");
|
||||
this.oldelement.getParent().dispose();
|
||||
to = droppable.getFirst("a").get("href");
|
||||
{% url extensions.webmail.main.move as moveurl %}
|
||||
new Request.JSON({url: "{{ moveurl }}", onSuccess: function(resp) {
|
||||
|
||||
}}).get({msgset : this.oldelement.getParent().get("id"),
|
||||
to : to});
|
||||
},
|
||||
"beforeStart": function(item) {
|
||||
var clone = item.clone()
|
||||
.setStyles({"position" : "absolute",
|
||||
"opacity" : 0.6,
|
||||
"width" : "300px",
|
||||
"border" : "1px solid #333",
|
||||
"background" : "white"})
|
||||
.setStyles(item.getCoordinates())
|
||||
.inject(document.body);
|
||||
this.oldelement = this.element;
|
||||
this.element = clone;
|
||||
},
|
||||
"cancel": function(item) {
|
||||
if ($defined(this.oldelement)) {
|
||||
item.dispose();
|
||||
delete(item);
|
||||
this.element = this.oldelement;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -134,13 +204,6 @@ tr.table-tr-selected {
|
||||
loadFolder(event, event.target.getParent());
|
||||
}
|
||||
|
||||
select = function(obj) {
|
||||
$$("a[name=loadfolder]").each(function(item) {
|
||||
item.getParent().setStyle("background", "white");
|
||||
});
|
||||
obj.getParent().setStyle("background", "#FFFFCC");
|
||||
}
|
||||
|
||||
foldercallback = function(event) {
|
||||
select(event.target);
|
||||
loadFolder(event, event.target);
|
||||
@@ -154,7 +217,7 @@ tr.table-tr-selected {
|
||||
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
|
||||
{% block menu %}
|
||||
{% load webextras %}
|
||||
{% listing_menu "" perms %}
|
||||
{% listing_menu "" "INBOX" perms %}
|
||||
{% endblock %}
|
||||
{% block leftcol %}
|
||||
<div id="leftcol" class="box">
|
||||
@@ -162,13 +225,14 @@ tr.table-tr-selected {
|
||||
<ul>
|
||||
{% for fd in folders %}
|
||||
{% url extensions.webmail.main.folder fd.name as fdurl %}
|
||||
<a href="{{ fd.name }}" name="loadfolder">
|
||||
<li {% ifequal selection fd.name %}class="selected"{% endifequal %}>
|
||||
<li class="droppable">
|
||||
<a href="{{ fd.name }}" name="loadfolder">
|
||||
{% if fd.icon %}<img src="/static/pics/{{ fd.icon }}" />
|
||||
{% else %}<img src="/static/pics/folder.png" />
|
||||
{% endif %}
|
||||
{{ fd.name }}</li>
|
||||
</a>
|
||||
{{ fd.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -37,13 +37,20 @@ def compose_menu(selection, folder, page_id, perms):
|
||||
"perms" : perms})
|
||||
|
||||
@register.simple_tag
|
||||
def listing_menu(selection, perms):
|
||||
def listing_menu(selection, folder, perms):
|
||||
entries = [
|
||||
{"name" : "compose",
|
||||
"url" : reverse(webmail.main.compose),
|
||||
"img" : "/static/pics/edit.png",
|
||||
"label" : _("New message")}
|
||||
]
|
||||
if folder in ["Trash"]:
|
||||
entries += [
|
||||
{"name" : "empty",
|
||||
"url" : reverse(webmail.main.empty, args=[folder]),
|
||||
"img" : "/static/pics/clear.png",
|
||||
"label" : _("Empty trash")}
|
||||
]
|
||||
|
||||
return render_to_string("main/menu.html",
|
||||
{"selection" : selection, "entries" : entries,
|
||||
|
||||
@@ -3,6 +3,8 @@ from django.conf.urls.defaults import *
|
||||
urlpatterns = patterns('mailng.extensions.webmail.main',
|
||||
(r'^$', "index"),
|
||||
(r'^compose/$', "compose"),
|
||||
(r'^move/$', "move"),
|
||||
(r'^empty/(?P<name>.+)/', "empty"),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/content/$', 'getmailcontent'),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
|
||||
(r'^(?P<name>.+)/$', "folder"),
|
||||
|
||||
@@ -78,3 +78,14 @@ def getoption(name, default=None):
|
||||
except AttributeError:
|
||||
res = default
|
||||
return res
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
def __init__(cls, name, bases, dict):
|
||||
super(Singleton, cls).__init__(name, bases, dict)
|
||||
cls.instance = None
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls.instance is None:
|
||||
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls.instance
|
||||
|
||||
+5
-3
@@ -58,10 +58,12 @@ class Table(object):
|
||||
nrow["cols"] += ["."]
|
||||
continue
|
||||
if row.has_key(c.name):
|
||||
if "class" in row.keys():
|
||||
cssclass = row["class"]
|
||||
else:
|
||||
try:
|
||||
cssclass = getattr(c, "cssclass")
|
||||
except AttributeError:
|
||||
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}]
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 773 B |
Reference in New Issue
Block a user