wip: global UI refactoring.

see #204
This commit is contained in:
Antoine Nguyen
2012-04-16 09:48:15 +02:00
parent daf81ec8fd
commit c201d25e09
17 changed files with 228 additions and 336 deletions
+3 -9
View File
@@ -103,11 +103,9 @@ def extra_static_content(user):
return []
tpl = Template("""<script type="text/javascript">
function check_nb_requests() {
$.ajax({
url: "{{ url }}",
dataType: 'json',
success: function(data) {
$(document).ready(function() {
var poller = new Poller("{{ url }}", {
success_cb: function(data) {
var $link = $("#nbrequests");
if (data.requests > 0) {
$link.html(data.requests + " " + gettext("pending requests"));
@@ -117,10 +115,6 @@ function check_nb_requests() {
}
}
});
}
$(document).ready(function() {
setInterval(check_nb_requests, 2000);
});
</script>""")
url = reverse("modoboa.extensions.amavis_quarantine.views.nbrequests")
+1 -1
View File
@@ -303,4 +303,4 @@ def process(request):
@user_passes_test(lambda u: u.group != 'SimpleUsers')
def nbrequests(request):
nbrequests = get_nb_requests(request.user)
return ajax_simple_response(dict(requests=nbrequests))
return ajax_simple_response(dict(status="ok", requests=nbrequests))
+12 -9
View File
@@ -150,23 +150,26 @@ class FilterForm(forms.Form):
def _build_fileinto_field(self, request, value):
self._build_action_field(request, "fileinto", value)
def __build_folders_list(self, folders):
def __build_folders_list(self, folders, user, imapc, parentmb=None):
ret = []
for fd in folders:
value = fd["path"] if fd.has_key("path") else fd["name"]
ret += [(value, fd["name"])]
if fd.has_key("sub") and len(fd["sub"]):
ret += self.__build_folders_list(fd["sub"])
if parentmb:
ret += [(value, fd["name"].replace("%s." % parentmb, ""))]
else:
ret += [(value, fd["name"])]
if fd.has_key("sub"):
submboxes = imapc.getmboxes(user, value, unseen_messages=False)
ret += self.__build_folders_list(submboxes, user, imapc, value)
return ret
def userfolders(self, request):
from modoboa.extensions.webmail.lib import IMAPconnector
from modoboa.extensions.webmail.imaputils import get_imapconnector
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
ret = mbc.getfolders(request.user, unseen_messages=False)
mbc = get_imapconnector(request)
ret = mbc.getmboxes(request.user, unseen_messages=False)
folders = self.__build_folders_list(ret)
folders = self.__build_folders_list(ret, request.user, mbc)
return folders
def tofilter(self):
@@ -1,208 +0,0 @@
var DynamicCondition = new Class({
Extends: DynamicTextInput,
options: {
defaultcond: "Subject",
templates: []
},
loadoperators: function(container, tpl) {
container.empty();
for (var ocpt = 0; ocpt < tpl["operators"].length; ocpt++) {
var opt = new Element("option", {
value: tpl["operators"][ocpt][0],
html: tpl["operators"][ocpt][1]
});
opt.inject(container);
}
},
onTargetChange: function(event) {
var parts = event.target.get("name").split("_");
var container = $("id_cond_operator_" + parts[2]);
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt];
if (tpl["name"] != event.target.get("value")) {
continue;
}
this.loadoperators(container, tpl);
break;
}
},
createfield: function() {
var div = new Element("div", {"class": "item"});
var condtarget = new Element("select", {
id: "id_cond_target_" + this.nextid,
name: "cond_target_" + this.nextid
});
var condoperator = new Element("select", {
id: "id_cond_operator_" + this.nextid,
name: "cond_operator_" + this.nextid
});
var condvalue = new Element("input", {
id: "id_cond_value_" + this.nextid,
name: "cond_value_" + this.nextid,
type: "text"
});
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt];
var opt = new Element("option", {
value: tpl["name"],
html: tpl["label"]
});
opt.inject(condtarget);
if (tpl["name"] != this.options.defaultcond) {
continue;
}
this.loadoperators(condoperator, tpl);
}
condtarget.inject(div);
condtarget.addEvent("change", this.onTargetChange.bind(this));
condoperator.inject(div);
condvalue.inject(div);
return [div, condvalue];
},
removeelement: function(event) {
var id = (event.target.get("id").split("_"))[1];
event.stop();
$("id_cond_target_" + id).destroy();
$("id_cond_operator_" + id).destroy();
$("id_cond_value_" + id).destroy();
event.target.destroy();
this.fields_cnt--;
}
});
var DynamicAction = new Class({
Extends: DynamicTextInput,
options: {
defaultaction: "fileinto",
templates: [],
ufolders_url: null
},
initialize: function(targets, options) {
this.parent(targets, options);
$("id_action_arg_0_0").getElements("option").each(function(item) {
var cpt = 1;
var start = 0;
var value = item.get("value");
while (true) {
var id = value.indexOf(".", start);
if (id == -1) {
break;
}
item.setStyle("margin-left", (cpt * 20) + "px");
cpt++;
start = id + 1;
}
});
this.fdoptions = $("id_action_arg_0_0").getElements("option");
},
loadargs: function(id, tpl) {
var res = new Elements();
for (var acpt = 0; acpt < tpl["args"].length; acpt++) {
var argname = "action_arg_" + id + "_" + acpt;
var child = null;
if (tpl["args"][acpt]["type"] == "list") {
child = new Element("select", {id: "id_" + argname, name: argname});
if ($defined(tpl["args"][acpt]["vloader"])) {
this[tpl["args"][acpt]["vloader"]](child);
}
} else if (tpl["args"][acpt]["type"] == "string") {
child = new Element("input", {type: "text",
id: "id_" + argname, name: argname});
}
res.push(child);
}
return res;
},
deletePrevArgs: function(id) {
for (var cpt = 0; true; cpt++) {
var arg = $("id_action_arg_" + id + "_" + cpt);
if (!$defined(arg)) {
break;
}
arg.destroy();
}
},
onNameChange: function(event) {
var parts = event.target.get("name").split("_");
var container = event.target.getParent();
event.stop();
this.deletePrevArgs(parts[2]);
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt];
if (tpl["name"] != event.target.get("value")) {
continue;
}
var args = this.loadargs(parts[2], tpl);
args.inject(container);
}
},
createfield: function() {
var div = new Element("div", {"class": "item"});
var actionname = new Element("select", {
id: "id_action_name_" + this.nextid,
name: "action_name_" + this.nextid
});
var args = null;
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt];
var opt = new Element("option", {
value: tpl["name"],
html: tpl["label"]
});
opt.inject(actionname);
if (tpl["name"] != this.options.defaultaction) {
continue;
}
args = this.loadargs(this.nextid, tpl);
}
actionname.inject(div);
actionname.addEvent("change", this.onNameChange.bind(this));
args.inject(div);
return [div, actionname];
},
removeelement: function(event) {
var id = (event.target.get("id").split("_"))[1];
event.stop();
$("id_action_name_" + id).destroy();
for (var cpt = 0; true; cpt++) {
var arg = $("id_action_arg_" + id + "_" + cpt);
if (!$defined(arg)) {
break;
}
arg.destroy();
}
event.target.destroy();
this.fields_cnt--;
},
userfolders: function(container) {
this.fdoptions.each(function(item) {
item.clone().inject(container);
});
}
});
@@ -54,7 +54,7 @@
return $('<a />', {
href: '#',
id: 'condition_' + this.nextid + '_rmbtn',
html: '<i class="icon-remove"></i>'
html: '<i class="icon-trash"></i>'
}).click($.proxy(this.removefields, this));
},
@@ -250,7 +250,7 @@
return $('<a />', {
href: '#',
id: 'action_' + this.nextid + '_rmbtn',
html: '<i class="icon-remove"></i>'
html: '<i class="icon-trash"></i>'
}).click($.proxy(this.removefields, this));
},
@@ -322,7 +322,6 @@
$.fn.dynaction.defaults = {
defaultaction: "fileinto",
templates: [],
ufolders_url: null
templates: []
};
})(jQuery);
@@ -22,8 +22,7 @@ function filterform_cb() {
templates: cond_templates
}).data('dyncondition');
var $da = $("#action_0").dynaction({
templates: action_templates,
ufolders_url: "{% url modoboa.extensions.sievefilters.views.get_user_folders %}"
templates: action_templates
}).data('dynaction');
$("input[name=match_type]").click(function(e) {
@@ -53,7 +53,7 @@ def fset_menu(mode, setname):
"url" : reverse("modoboa.extensions.sievefilters.views.activate_filters_set",
args=[setname])},
{"name" : "removefs",
"img" : "icon-remove",
"img" : "icon-trash",
"label" : _("Remove filters set"),
"url" : reverse("modoboa.extensions.sievefilters.views.remove_filters_set",
args=[setname])},
@@ -81,7 +81,7 @@ def filter_actions(setname, f, position, islast):
{"name" : "removefilter",
"url" : reverse("modoboa.extensions.sievefilters.views.removefilter",
args=[setname, f["name"]]),
"img" : "icon-remove",
"img" : "icon-trash",
"title" : _("Remove this filter")}
]
if position != 1:
-1
View File
@@ -8,7 +8,6 @@ urlpatterns = patterns('modoboa.extensions.sievefilters.views',
(r'^activatefs/(?P<name>.+)/$', 'activate_filters_set'),
(r'^downloadfs/(?P<name>.+)/$', 'download_filters_set'),
(r'^templates/(?P<ftype>\w+)/$', 'get_templates'),
(r'^userfolders/$', 'get_user_folders'),
(r'^(?P<setname>.+)/newfilter/$', 'newfilter'),
(r'^(?P<setname>.+)/editfilter/(?P<fname>.+)/$', 'editfilter'),
(r'^(?P<setname>.+)/removefilter/(?P<fname>.+)/$', 'removefilter'),
-10
View File
@@ -48,16 +48,6 @@ def get_templates(request, ftype):
return ajax_simple_response(FilterForm([], [], request).cond_templates)
return ajax_simple_response(FilterForm([], [], request).action_templates)
@login_required
@needs_mailbox()
def get_user_folders(request):
from modoboa.extensions.webmail.lib import IMAPconnector
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
ret = mbc.getfolders(request.user, unseen_messages=False)
return ajax_simple_response(ret)
@login_required
@needs_mailbox()
def getfs(request, name):
+66 -46
View File
@@ -149,7 +149,7 @@ class IMAPconnector(object):
:param name: the command's name
:return: the command's result
"""
if name in ['FETCH', 'SORT', 'STORE', 'COPY']:
if name in ['FETCH', 'SORT', 'STORE', 'COPY', 'SEARCH']:
try:
typ, data = self.m.uid(name, *args)
except imaplib.IMAP4.error, e:
@@ -211,12 +211,15 @@ class IMAPconnector(object):
"""
if self.m is not None:
try:
self.m.select()
self._cmd("CHECK")
except ImapError, e:
if hasattr(self, "current_mailbox"):
del self.current_mailbox
else:
return
except imaplib.IMAP4.error, error:
print error
print self.login(user, password)
status, msg = self.login(user, password)
if not status:
raise ImapError(msg)
def login(self, user, passwd):
import socket
@@ -263,14 +266,18 @@ class IMAPconnector(object):
else:
criterion = "REVERSE DATE"
folder = kwargs.has_key("folder") and kwargs["folder"] or None
self.select_mailbox(folder, False)
# FIXME: pourquoi suis je obligé de faire un SELECT ici? un
# EXAMINE plante mais je pense que c'est du à une mauvaise
# lecture des réponses de ma part...
self.select_mailbox(folder, readonly=False)
data = self._cmd("SORT", "(%s)" % criterion, "UTF-8", "(NOT DELETED)",
*self.criterions)
self.messages = data[0].split()
self.getquota(folder)
return len(self.messages)
def select_mailbox(self, name, readonly=True):
def select_mailbox(self, name, readonly=True, force=False):
"""Issue a SELECT/EXAMINE command to the server
The given name is first 'imap-utf7' encoded.
@@ -278,11 +285,16 @@ class IMAPconnector(object):
:param name: mailbox's name
:param readonly:
"""
if hasattr(self, "current_mailbox"):
if self.current_mailbox == name and not force:
return
self.current_mailbox = name
name = name.encode("imap4-utf-7")
if readonly:
self._cmd("EXAMINE", name)
else:
self._cmd("SELECT", name)
self.m.state = "SELECTED"
def unseen_messages(self, mailbox):
"""Return the number of unseen messages
@@ -290,21 +302,18 @@ class IMAPconnector(object):
:param mailbox: the mailbox's name
:return: an integer
"""
# self.select_mailbox(mailbox)
# data = self._cmd("SEARCH", "UTF-8", "(NOT DELETED UNSEEN)")
# return len(data[0].split())
data = self._cmd("STATUS", mailbox.encode("imap4-utf-7"), "(UNSEEN)")
m = self.unseen_pattern.match(data[0])
if m is None:
return 0
return int(m.group(1))
def _encodefolder(self, folder):
def _encode_mbox_name(self, folder):
if not folder:
return "INBOX"
return folder.encode("imap4-utf-7")
def _parse_folder_name(self, descr, prefix, delimiter, parts):
def _parse_mailbox_name(self, descr, prefix, delimiter, parts):
if not len(parts):
return False
path = "%s%s%s" % (prefix, delimiter, parts[0])
@@ -316,11 +325,11 @@ class IMAPconnector(object):
if sdescr is None:
sdescr = {"name" : parts[0], "path" : path, "sub" : []}
descr += [sdescr]
if self._parse_folder_name(sdescr["sub"], path, delimiter, parts[1:]):
if self._parse_mailbox_name(sdescr["sub"], path, delimiter, parts[1:]):
sdescr["class"] = "subfolders"
return True
def _listfolders_simple(self, topmailbox='INBOX', md_mailboxes=[]):
def _listmboxes_simple(self, topmailbox='INBOX', md_mailboxes=[]):
(status, data) = self.m.list()
result = []
for mb in data:
@@ -331,8 +340,8 @@ class IMAPconnector(object):
if not descr.has_key("path"):
descr["path"] = parts[0]
descr["sub"] = []
if self._parse_folder_name(descr["sub"], parts[0], delimiter,
parts[1:]):
if self._parse_mailbox_name(descr["sub"], parts[0], delimiter,
parts[1:]):
descr["class"] = "subfolders"
continue
present = False
@@ -347,31 +356,35 @@ class IMAPconnector(object):
return sorted(result, key=itemgetter("name"))
@capability('LIST-EXTENDED', '_listfolders_simple')
def _listfolders(self, topmailbox='', md_mailboxes=[]):
def _listmboxes(self, topmailbox='', mailboxes=[]):
pattern = ("%s.%%" % topmailbox.encode("imap4-utf-7")) if len(topmailbox) else "%"
resp = self._cmd("LIST", "", pattern, "RETURN", "(CHILDREN)")
result = []
newmboxes = []
for mb in resp:
flags, delimiter, name, childinfo = \
self.listextended_response_pattern.match(mb).groups()
flags = flags.split(' ')
name = name.decode("imap4-utf-7")
present = False
for mdm in md_mailboxes:
pos = -1
for idx, mdm in enumerate(mailboxes):
if mdm["name"] == name:
present = True
pos = idx
break
if present:
continue
descr = dict(name=name)
if pos == -1:
descr = dict(name=name)
newmboxes += [descr]
else:
descr = mailboxes[idx]
if r'\Marked' in flags or not r'\UnMarked' in flags:
descr["send_status"] = True
if r'\HasChildren' in flags:
descr["path"] = name
descr["sub"] = []
result += [descr]
from operator import itemgetter
return sorted(result, key=itemgetter("name"))
mailboxes += sorted(newmboxes, key=itemgetter("name"))
def getfolders(self, user, topmailbox='', unseen_messages=True):
def getmboxes(self, user, topmailbox='', unseen_messages=True):
if len(topmailbox):
md_mailboxes = []
else:
@@ -382,14 +395,18 @@ class IMAPconnector(object):
{"name" : parameters.get_user(user, "SENT_FOLDER")},
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
"class" : "icon-trash"}]
md_mailboxes += self._listfolders(topmailbox, md_mailboxes)
self._listmboxes(topmailbox, md_mailboxes)
if unseen_messages:
for fd in md_mailboxes:
key = fd.has_key("path") and "path" or "name"
count = self.unseen_messages(fd[key])
for mb in md_mailboxes:
if not mb.has_key("send_status"):
continue
del mb["send_status"]
key = mb.has_key("path") and "path" or "name"
count = self.unseen_messages(mb[key])
if count == 0:
continue
fd["unseen"] = count
mb["unseen"] = count
return md_mailboxes
def _add_flag(self, mbox, msgset, flag):
@@ -432,20 +449,18 @@ class IMAPconnector(object):
"""
self.select_mailbox(oldmailbox, False)
self._cmd("COPY", msgset, newmailbox.encode("imap4-utf-7"))
self._cmd("STORE", msgset, "+FLAGS", r'(\Deleted)')
self._cmd("STORE", msgset, "+FLAGS", r'(\Deleted \Seen)')
def push_mail(self, folder, msg):
now = imaplib.Time2Internaldate(time.time())
self.m.append(self._encodefolder(folder), r'(\Seen)', now, str(msg))
self.m.append(self._encode_mbox_name(folder), r'(\Seen)', now, str(msg))
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()
seq = ",".join(resp[0].split())
self._cmd("STORE", seq, "+FLAGS", r'(\Deleted)')
self._cmd("EXPUNGE")
def compact(self, mbox):
"""Compact a specific mailbox
@@ -460,20 +475,20 @@ class IMAPconnector(object):
def create_folder(self, name, parent=None):
if parent is not None:
name = "%s.%s" % (parent, name)
typ, data = self.m.create(self._encodefolder(name))
typ, data = self.m.create(self._encode_mbox_name(name))
if typ == "NO":
raise WebmailError(data[0])
return True
def rename_folder(self, oldname, newname):
typ, data = self.m.rename(self._encodefolder(oldname),
self._encodefolder(newname))
typ, data = self.m.rename(self._encode_mbox_name(oldname),
self._encode_mbox_name(newname))
if typ == "NO":
raise WebmailError(data[0], ajax=True)
return True
def delete_folder(self, name):
typ, data = self.m.delete(self._encodefolder(name))
typ, data = self.m.delete(self._encode_mbox_name(name))
if typ == "NO":
raise WebmailError(data[0])
return True
@@ -483,7 +498,7 @@ class IMAPconnector(object):
self.quota_limit = self.quota_actual = None
return
data = self._cmd("GETQUOTAROOT", self._encodefolder(mailbox),
data = self._cmd("GETQUOTAROOT", self._encode_mbox_name(mailbox),
responses=["QUOTAROOT", "QUOTA"])
if data is None:
self.quota_limit = self.quota_actual = None
@@ -526,7 +541,7 @@ class IMAPconnector(object):
msg = email.message_from_string(data[int(uid)]['BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)]'])
msg['imapid'] = uid
if not r'\Seen' in data[int(uid)]['FLAGS']:
msg['class'] = 'unseen'
msg['style'] = 'unseen'
if r'\Answered' in data[int(uid)]['FLAGS']:
msg['img_flags'] = static_url('pics/answered.png')
if r'$Forwarded' in data[int(uid)]['FLAGS']:
@@ -551,6 +566,7 @@ class IMAPconnector(object):
:param readonly:
:param extraheaders:
"""
print mbox
self.select_mailbox(mbox, readonly)
if headers is None:
headers = ['DATE', 'FROM', 'TO', 'CC', 'SUBJECT']
@@ -562,6 +578,10 @@ class IMAPconnector(object):
return data[int(mailid)]
def get_imapconnector(request):
"""Simple shortcut to create a connector
:param request: a ``Request`` object
"""
imapc = IMAPconnector(user=request.user.username,
password=request.session["password"])
return imapc
+4 -2
View File
@@ -143,16 +143,18 @@ def print_folders(folders, selected=None, withunseen=False, selectonly=False):
result += "<div class='clickbox %s'></div>" % div_state
cssclass = "block"
extra_attrs = ""
if withunseen and fd.has_key("unseen"):
label += " (%d)" % fd["unseen"]
cssclass += " unseen"
extra_attrs = ' data-toggle="%d"' % fd["unseen"]
iclass = fd["class"] if fd.has_key("class") else "icon-folder-close"
result += """<a href='%s' class='%s' name='%s'>
result += """<a href='%s' class='%s' name='%s'%s>
<i class="%s"></i>
%s
</a>
""" % (fd.has_key("path") and fd["path"] or fd["name"], cssclass,
'selectfolder' if selectonly else 'loadfolder', iclass, label)
'selectfolder' if selectonly else 'loadfolder', extra_attrs, iclass, label)
if fd.has_key("sub") and len(fd["sub"]):
result += "<ul name='%s' class='%s'>" % (fd["path"], ul_state) \
+ print_folders(fd["sub"], selected, withunseen, selectonly) + "</ul>\n"
+1
View File
@@ -11,6 +11,7 @@ urlpatterns = patterns(
(r'^$', "newindex"),
(r'^submailboxes', "submailboxes"),
(r'^getmailcontent', 'getmailcontent'),
(r'^unseenmsgs', 'check_unseen_messages'),
# (r'^compose/$', "compose"),
(r'^delete/$', 'delete'),
+67 -39
View File
@@ -63,7 +63,7 @@ def __render_common_components(request, folder_name, lst=None, content=None, men
"folders" : _render_to_string(request, "webmail/folders.html", {
"titlebar" : True,
"selected" : request.session["folder"],
"folders" : mbc.getfolders(request.user),
"folders" : mbc.getmboxes(request.user),
"withunseen" : True
}),
"menu" : menu,
@@ -125,38 +125,50 @@ def index(request):
@login_required
@needs_mailbox()
def getattachment(request, folder, mail_id):
if request.GET.has_key("partnumber"):
headers = {"Content-Type" : "text/plain",
"Content-Transfer-Encoding" : None}
icon = IMAPconnector(user=request.user.username,
password=request.session["password"])
part = icon.fetchpart(mail_id, folder, request.GET["partnumber"])
if part is not None:
if part.get_content_maintype() == "message":
payload = part.get_payload(0)
else:
payload = part.get_payload(decode=True)
resp = HttpResponse(payload)
for hdr, default in headers.iteritems():
if not part.has_key(hdr):
if default is not None:
resp[hdr] = default
else:
continue
resp[hdr] = re.sub("\s", "", part[hdr])
# I would add this part into the previous loop if I was
# able to use functions as default values... but I'm a bit
# lazy :p
if part.has_key("Content-Disposition"):
resp["Content-Disposition"] = \
re.sub("\s", "", part["Content-Disposition"])
else:
resp["Content-Disposition"] = \
"attachment; filename=%s" % request.GET["fname"]
resp["Content-Length"] = len(payload)
return resp
raise Http404
"""Fetch a message attachment
Problème lié aux optimisations apportées : la méthode fetchpart du
client IMAP ne renvoie que le payload. Pour obtenir les headers,
deux solutions :
* Conserver le bodystructure parsé précédemment (nécessite du caching)
* Reparser le bodystructure :p
"""
pnum = request.GET.get("partnumber", None)
if not pnum:
raise Http404
headers = {"Content-Type" : "text/plain",
"Content-Transfer-Encoding" : None}
imapc = get_imapconnector(request)
part = imapc.fetchpart(mail_id, folder, pnum)
if part is None:
raise Http404
if part.get_content_maintype() == "message":
payload = part.get_payload(0)
else:
payload = part.get_payload(decode=True)
resp = HttpResponse(payload)
for hdr, default in headers.iteritems():
if not part.has_key(hdr):
if default is not None:
resp[hdr] = default
else:
continue
resp[hdr] = re.sub("\s", "", part[hdr])
# I would add this part into the previous loop if I was
# able to use functions as default values... but I'm a bit
# lazy :p
if part.has_key("Content-Disposition"):
resp["Content-Disposition"] = \
re.sub("\s", "", part["Content-Disposition"])
else:
resp["Content-Disposition"] = \
"attachment; filename=%s" % request.GET["fname"]
resp["Content-Length"] = len(payload)
return resp
@login_required
@needs_mailbox()
def move(request):
@@ -204,7 +216,10 @@ def mark(request, name):
def empty(request, name):
if name == parameters.get_user(request.user, "TRASH_FOLDER"):
get_imapconnector(request).empty(name)
return ajax_simple_response(dict(status="ok"))
content = "<div class='alert alert-info'>%s</div>" % _("Empty mailbox")
return ajax_simple_response(dict(
status="ok", listing=content
))
@login_required
@needs_mailbox()
@@ -239,7 +254,7 @@ def newfolder(request, tplname="webmail/folder2.html"):
"withunseen" : False,
"selectonly" : True}
ctx["folders"] = mbc.getfolders(request.user)
ctx["folders"] = mbc.getmboxes(request.user)
if request.method == "POST":
form = FolderForm(request.POST)
if form.is_valid():
@@ -267,7 +282,7 @@ def editfolder(request, tplname="webmail/folder2.html"):
"action_classes" : "submit",
"withunseen" : False,
"selectonly" : True}
ctx["folders"] = mbc.getfolders(request.user)
ctx["folders"] = mbc.getmboxes(request.user)
if request.method == "POST":
form = FolderForm(request.POST)
if form.is_valid():
@@ -388,7 +403,7 @@ def render_mboxes_list(request, imapc):
curmbox = request.session.get("mbox", "INBOX")
return _render_to_string(request, "webmail/folders.html", {
"selected" : curmbox,
"folders" : imapc.getfolders(request.user),
"folders" : imapc.getmboxes(request.user),
"withunseen" : True
})
@@ -544,9 +559,22 @@ def viewmail(request):
@needs_mailbox()
def submailboxes(request):
topmailbox = request.GET.get('topmailbox', '')
mboxes = get_imapconnector(request).getfolders(request.user, topmailbox)
mboxes = get_imapconnector(request).getmboxes(request.user, topmailbox)
return ajax_simple_response(dict(status="ok", mboxes=mboxes))
@login_required
@needs_mailbox()
def check_unseen_messages(request):
mboxes = request.GET.get("mboxes", None)
if not mboxes:
raise WebmailError(_("Invalid request"))
mboxes = mboxes.split(",")
counters = dict()
imapc = get_imapconnector(request)
for mb in mboxes:
counters[mb] = imapc.unseen_messages(mb)
return ajax_simple_response(dict(status="ok", counters=counters))
@login_required
@needs_mailbox()
def newindex(request):
@@ -587,10 +615,10 @@ def newindex(request):
if not request.is_ajax():
request.session["lastaction"] = None
imapc = get_imapconnector(request)
response["mboxes"] = render_mboxes_list(request, imapc)
imapc.getquota(curmbox)
response["refreshrate"] = \
int(parameters.get_user(request.user, "REFRESH_INTERVAL")) * 1000
response["mboxes"] = render_mboxes_list(request, imapc)
int(parameters.get_user(request.user, "REFRESH_INTERVAL"))
response["quota"] = ImapListing.computequota(imapc)
if response["quota"] < 50:
response["quotalevel"] = "success"
+1 -1
View File
@@ -155,7 +155,7 @@ class EmailListing(object):
def render(self, request, pageid=1, **kwargs):
page = self.paginator.getpage(pageid)
if not page:
listing = _("This folder contains no message")
listing = "<div class='alert alert-info'>%s</div>" % _("Empty mailbox")
else:
listing = self.fetch(request, page.id_start, page.id_stop)
return dict(listing=listing, navbar=self.render_navbar(page, self.baseurl))
+4 -2
View File
@@ -120,8 +120,10 @@ class Table(object):
trcpt = 0
for row in rows:
nrow = {"id" : row[self.idkey], "cols" : [], "trcpt" : trcpt}
if row.has_key("options"):
nrow["options"] = row["options"]
for name in ["style", "options"]:
if row.has_key(name):
nrow[name] = row[name]
for c in self.columns:
newcol = {"name" : c.name}
for key in ["width", "align", "cssclass"]:
+62
View File
@@ -0,0 +1,62 @@
/*
* Simple poller plugin.
*
* Sends a request at a given frequency.
*/
var Poller = function(url, options) {
this.initialize(url, options);
};
Poller.prototype = {
constructor: Poller,
defaults: {
interval: 2000,
success_cb: null,
error_cb: null
},
initialize: function(url, options) {
this.url = url;
this.options = $.extend({}, this.defaults, options);
this.running_request = false;
this.reset();
},
reset: function() {
setTimeout($.proxy(this.send_request, this), this.options.interval);
},
send_request: function() {
var poller = this;
var args = "";
if (this.options.args != undefined) {
args = "?";
if (typeof this.options.args === "function") {
args += this.options.args();
} else {
args += this.options.args;
}
}
this.running_request = true;
$.ajax({
url: this.url + args,
dataType: 'json',
success: function(data) {
poller.running_request = false;
if (data.status == "ok") {
if (poller.options.success_cb) {
poller.options.success_cb(data);
}
poller.reset();
} else {
$("body").notify("error", data.respmsg);
if (poller.options.error_cb) {
poller.options.error_cb(data);
}
}
}
});
}
};
+1
View File
@@ -16,6 +16,7 @@
<script src="{{ MEDIA_URL }}js/common.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/confirmation.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/notify.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/poller.js" type="text/javascript"></script>
{% url django.views.i18n.javascript_catalog as catalog_url %}
<script type="text/javascript" src="{{ catalog_url }}"></script>
<script type="text/javascript">