wip: Global user interface improvment

* The quarantine manager is done
* The webmail is not yet completed
* Cleanup
* More...
This commit is contained in:
Antoine Nguyen
2012-03-27 12:02:46 +02:00
parent 4dae056d87
commit e10bba2d4e
38 changed files with 746 additions and 461 deletions
+1 -1
View File
@@ -246,11 +246,11 @@ class AccountFormGeneral(forms.ModelForm):
role = self.cleaned_data["role"]
else:
role = "SimpleUsers"
account.groups.clear()
if role == "SuperAdmins":
account.is_superuser = True
else:
account.is_superuser = False
account.groups.clear()
account.groups.add(Group.objects.get(name=role))
account.save()
return account
+1 -1
View File
@@ -167,7 +167,7 @@ class User(DUser):
@property
def group(self):
if self.is_superuser:
return "SuperAdmin"
return "SuperAdmins"
try:
return self.groups.all()[0].name
except IndexError:
+3 -2
View File
@@ -76,14 +76,15 @@ class SQLconnector(MBconnector):
return emails
class SQLlisting(EmailListing):
tpl = "amavis_quarantine/index.html"
tpl = "amavis_quarantine/index2.html"
tbltype = Qtable
deflocation = "listing/"
defcallback = "updatelisting"
reset_wm_url = True
table_styles = "table-condensed"
def __init__(self, user, msgs, filter, **kwargs):
if user.belongs_to_group('SimpleUsers'):
if user.group == 'SimpleUsers':
Qtable.cols_order = ['type', 'rstatus', 'from_', 'subject', 'time']
else:
Qtable.cols_order = ['type', 'rstatus', 'to', 'from_', 'subject', 'time']
@@ -1,8 +1,29 @@
#menubar {
position: fixed;
top: 60px;
padding-left: 15px;
}
#listing {
position: fixed;
padding: 0 15px;
right: 0;
left: 0;
}
#mailcontent {
border: 0;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
thead tr {
position: relative;
z-index: 1;
}
tr.table-tr-selected {
background: #ffffcc;
}
td.unseen {
font-weight: bold;
}
@@ -1,136 +1,180 @@
function updatehash(evt) {
evt.stop();
var url = $defined(this.get("href")) ? this.get("href") : "";
current_anchor.parse_string(url, true).setparams(navparams);
current_anchor.update();
}
var Quarantine = function(options) {
this.initialize(options);
};
function appendhash(evt) {
evt.stop();
location.hash += evt.target.get("href");
}
Quarantine.prototype = {
constructor: Quarantine,
function updateparams(evt) {
evt.stop();
current_anchor.updateparams(evt.target.get("href"));
current_anchor.update();
}
defaults: {
deflocation: "listing",
defcallback: "listing_cb"
},
function actioncallback(resp) {
$("listing").set("html", "");
$("menubar").set("html", "");
$("navbar").set("html", "");
if (resp.status == "ok") {
infobox.info(resp.respmsg);
current_anchor.parse_string(resp.url, true).setparams(navparams);
current_anchor.update(1);
} else {
infobox.error(resp.respmsg);
}
}
initialize: function(options) {
this.options = $.extend({}, this.defaults, options);
this.navobj = new History(this.options);
function actionclick(evt) {
new Request.JSON.mdb({
url: evt.target.get("href"),
onSuccess: actioncallback
}).get();
}
this.register_navcallbacks();
this.listen();
},
function deleteclick(evt) {
evt.stop();
if (!confirm(gettext("Delete this message?"))) {
return false;
update_page: function(data) {
if (data.menu != undefined) {
$("#menubar").html(data.menu);
$("#searchfield").searchbar({navobj: this.navobj});
}
if (data.navbar) {
$("#bottom-bar-in").html(data.navbar);
}
if (data.listing != undefined) {
$("#listing").html(data.listing);
$("#listing").css({
top: $("#menubar").outerHeight() + 60 + "px",
bottom: $("#bottom-bar").outerHeight() + "px",
overflow: "auto"
});
}
},
listen: function() {
$(document).on("dblclick", "tbody>tr", $.proxy(this.viewmail_loader, this));
$(document).on("click", "a[name=selectmsgs]", $.proxy(this.selectmsgs, this));
$(document).on("click", "a[name=release-multi]",
$.proxy(this.release_selection, this));
$(document).on("click", "a[name=delete-multi]",
$.proxy(this.delete_selection, this));
$(document).on("click", "#bottom-bar a", $.proxy(this.load_page, this));
$(document).on("click", "a[name=release]", $.proxy(this.release, this));
$(document).on("click", "a[name=delete]", $.proxy(this.delete, this));
$(document).on("click", "a[name=viewmode]", $.proxy(this.update_params, this));
},
load_page: function(e) {
e.preventDefault();
var $link = $(e.target).parent();
this.navobj.deleteParam("rcpt");
this.navobj.from_string($link.attr("href")).update();
},
viewmail_loader: function(e) {
e.preventDefault();
var $tr = $(e.target).parent();
var $to = $tr.find("td[name=to]");
this.navobj.baseurl($tr.attr("id"));
if ($to.length) {
this.navobj.setparam("rcpt", $to.html().trim());
}
this.navobj.update();
},
selectmsgs: function(e) {
e.preventDefault();
var type = $(e.target).attr("href");
if (type == "") {
$("#emails").htmltable("clear_selection");
return;
}
$("td[name=type]").each(function() {
var $this = $(this);
if ($this.html().trim() == type) {
$("#emails").htmltable("select_row", $this.parent());
}
});
},
_send_selection: function(e, name, message) {
var $link = $(e.target);
e.preventDefault();
if (!this.htmltable.current_selection().length ||
!confirm(message)) {
return;
}
var selection = [];
this.htmltable.current_selection().each(function() {
var $tr = $(this);
var $to = $tr.find("td[name=to]");
if ($to.length) {
selection.push($to.html().trim() + " " + $tr.attr("id"));
} else {
selection.push($tr.attr("id"));
}
});
$.ajax({
url: $link.attr("href"),
data: "action=" + name + "&selection=" + selection.join(","),
type: 'POST',
dataType: 'json',
success: $.proxy(this.action_cb, this)
});
},
release_selection: function(e) {
this._send_selection(e, "release", gettext("Release this selection?"));
},
delete_selection: function(e) {
this._send_selection(e, "delete", gettext("Delete this selection?"));
},
_send_action: function(e, message) {
var $link = $(e.target);
e.preventDefault();
if (!confirm(message)) {
return;
}
$.ajax({
url: $link.attr("href"),
dataType: 'json',
success: $.proxy(this.action_cb, this)
});
},
release: function(e) {
this._send_action(e, gettext("Release this message?"));
},
delete: function(e) {
this._send_action(e, gettext("Delete this message?"));
},
update_params: function(e) {
var $link = $(e.target);
e.preventDefault();
this.navobj.updateparams($link.attr("href")).update();
},
register_navcallbacks: function() {
this.navobj.register_callback("_listing",
$.proxy(this.listing_cb, this));
this.navobj.register_callback("viewmail",
$.proxy(this.viewmail_cb, this));
},
listing_cb: function(data) {
this.update_page(data);
$("#emails").htmltable();
this.htmltable = $("#emails").data("htmltable");
},
viewmail_cb: function(data) {
this.update_page(data);
$("#listing").css("overflow", "hidden");
},
action_cb: function(data) {
if (data.status == "ok") {
this.navobj.parse_string(data.url, true).update(true);
$("body").notify("success", data.respmsg, 2000);
} else {
$("body").notify("error", data.respmsg);
}
}
actionclick(evt);
}
function releaseclick(evt) {
evt.stop();
if (!confirm(gettext("Release this message?"))) {
return false;
}
actionclick(evt);
}
function viewmail_cs(evt) {
evt.stop();
var to = this.getFirst("td[name=to]");
current_anchor.baseurl(this.get("id"));
if ($defined(to)) {
current_anchor.setparam("rcpt", to.get("html").trim());
}
current_anchor.update();
}
/*
* Callback of the 'listing' action (the main screen)
*/
function listing_cb(resp) {
updatelisting(resp);
var tbl = new HtmlTable($("emails"), {
useKeyboard: false,
selectable: true
});
if ($("toggleselect")) {
$("toggleselect").addEvent("click", function(evt) {
if ($$("tr[class*=table-tr-selected]").length) {
tbl.selectNone();
} else {
tbl.selectAll();
}
});
}
$$("a[name=selectmsgs]").addEvent("click", function(evt) {
evt.stop();
var type = evt.target.get("href");
if (!$chk(type)) {
$$("tr[class*=table-tr-selected]").each(function(item) {
item.removeClass("table-tr-selected");
});
if ($("toggleselect").checked) {
$("toggleselect").checked = false;
tbl.selectNone();
}
return;
}
$$("td[name=type]").each(function(item) {
if (item.get("html").trim() == type) {
item.getParent().addClass("table-tr-selected");
}
});
});
$$("a[name=release]").addEvent("click", function(event) {
if (!checkTableSelection(gettext("Release this selection?"))) {
event.stop();
return;
}
mysubmit(event, "release");
});
$$("a[name=delete]").addEvent("click", function(event) {
if (!checkTableSelection(gettext("Delete this selection?"))) {
event.stop();
return;
}
mysubmit(event, "delete");
});
searchbox_init();
init_sortable_columns();
update_sortable_column();
$$("tbody>tr").addEvent("dblclick", viewmail_cs);
}
/*
* Callback of the 'viewmail' action
*/
function viewmail_cb(resp) {
updatelisting(resp);
$$("a[name=back]").addEvent("click", updatehash);
$$("a[name=delete]").addEvent("click", deleteclick);
$$("a[name=release]").addEvent("click", releaseclick);
$$("a[name=viewmode]").addEvent("click", updateparams);
setDivHeight("mailcontent", 5, 0);
}
};
@@ -1,42 +1,22 @@
{% extends "common/email_listing.html" %}
{% load i18n %}
{% extends "fixed.html" %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}amavis_quarantine/css/quarantine.css" />
<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.htmltable.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/history.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/searchbar.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}amavis_quarantine/js/quarantine.js"></script>
{{ block.super }}
<script type="text/javascript">
mysubmit = function(event, name) {
event.stop();
var selection = new Array();
$$("tr[class*=table-tr-selected]").each(function(item) {
var content = "";
var to = item.getFirst("td[name=to]");
if ($defined(to)) {
content = to.get("html").trim() + " ";
}
selection.push(content + item.get("id"));
$(document).ready(function() {
var qm = new Quarantine({
deflocation: "{{ deflocation }}",
defcallback: "{{ defcallback }}"
});
new Element("input", {
"type" : "hidden", "name" : "selection", "value" : selection
}).inject($("listingform"), "top");
new Element('input', {
'type' : 'hidden', 'name' : "action", style: "display:none", 'value' : name
}).inject($("listingform"), "top");
{% url modoboa.extensions.amavis_quarantine.views.process as actionurl %}
$("listingform").set("action", "{{ actionurl }}");
$("listingform").set("send", {
onSuccess : function(res) {
response = JSON.decode(res);
actioncallback(response);
}
});
$("listingform").send();
}
});
</script>
{% endblock %}
{% block js %}
current_anchor.register_callback("_listing", listing_cb);
current_anchor.register_callback("viewmail", viewmail_cb);
{% block fixed_content %}
<div id="menubar"></div>
<div id="listing"></div>
{% endblock %}
{% block footer %}
{% endblock %}
{% block middletitle %}{% trans "Quarantine" %}{% endblock %}
@@ -1,43 +1,11 @@
{% extends "layout_simple.html" %}
{% extends "common/generic_modal.html" %}
{% load i18n %}
{% block html_head %}
<style type="text/css">
table {
margin: 0 auto;
width: 100%;
border-collapse: collapse;
border-spacing: 20px;
}
table th {
background: #CEDBEC;
text-align: left;
padding: 5px;
}
table td {
border-bottom: 1px solid #C8D6E8;
padding: 5px;
}
td.odd {
background: #F7F9Fb;
}
td {
font-size: smaller;
height: 40px;
}
</style>
{% endblock %}
{% block content %}
<table border="0">
<tr><th colspan="2">{% trans "Message headers" %}</th></tr>
{% block modalbody %}
<table class="table table-condensed table-striped">
{% for k, v in headers %}
<tr>
<td class="odd">{{ k }}</td><td>{{ v }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% endblock %}
@@ -24,46 +24,44 @@ def viewm_menu(selection, backurl, mail_id, rcpt, perms):
entries = [
{"name" : "back",
"url" : backurl,
"img" : static_url("pics/back.png"),
"img" : "icon-arrow-left",
"url" : "javascript:history.go(-1);",
"label" : _("Back to list")},
{"name" : "headers",
"url" : reverse(amavis_quarantine.views.viewheaders, args=[mail_id]),
"img" : static_url("pics/add.png"),
"label" : _("View full headers"),
"class" : "boxed",
"rel" : "{handler:'iframe',size:{x:600,y:500}}"},
"modal" : True,
"autowidth" : True},
{"name" : "release",
"img" : "icon-ok",
"url" : reverse(amavis_quarantine.views.release, args=[mail_id]) \
+ "?rcpt=%s" % rcpt,
"img" : static_url("pics/release.png"),
"label" : _("Release")},
{"name" : "delete",
"img" : "icon-remove",
"url" : reverse(amavis_quarantine.views.delete, args=[mail_id]) \
+ "?rcpt=%s" % rcpt,
"img" : static_url("pics/remove.png"),
"label" : _("Delete")},
{"name" : "options",
"label" : _("Options"),
"img" : static_url("pics/settings.png"),
"class" : "menubardropdown",
"img" : "icon-cog",
"menu" : options_menu}
]
return render_to_string('common/menu.html',
return render_to_string('common/buttons_list.html',
{"selection" : selection, "entries" : entries,
"perms" : perms})
@register.simple_tag
def quar_menu(selection, user):
entries = [
{"name" : "release",
"url" : "",
"img" : static_url("pics/release.png"),
{"name" : "release-multi",
"url" : reverse(amavis_quarantine.views.process),
"img" : "icon-ok",
"label" : _("Release")},
{"name" : "delete",
"url" : "",
"img" : static_url("pics/remove.png"),
{"name" : "delete-multi",
"img" : "icon-remove",
"url" : reverse(amavis_quarantine.views.process),
"label" : _("Delete")},
{"name" : "select",
"url" : "",
@@ -87,9 +85,7 @@ def quar_menu(selection, user):
}
]
menu = render_to_string('common/menu.html',
{"selection" : selection, "entries" : entries})
if not user.belongs_to_group('SimpleUsers'):
if user.group != 'SimpleUsers':
extraopts = [{"name" : "to", "label" : _("To")}]
else:
extraopts = []
@@ -97,4 +93,7 @@ def quar_menu(selection, user):
"MEDIA_URL" : settings.MEDIA_URL,
"extraopts" : extraopts
})
return menu + searchbar
return render_to_string('common/buttons_list.html', dict(
selection=selection, entries=entries, extracontent=searchbar
))
+16 -14
View File
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# coding: utf-8
import email
from django.http import HttpResponseRedirect, HttpResponse
from django.template import Template, Context
@@ -29,14 +28,14 @@ def __get_current_url(request):
return res
def empty_quarantine(request):
content = "<div class='info'>%s</div>" % _("Empty quarantine")
content = "<div class='alert alert-info'>%s</div>" % _("Empty quarantine")
ctx = getctx("ok", level=2, listing=content, navbar="",
menu=quar_menu("", request.user))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
def _listing(request):
filter = None
flt = None
rcptfilter = None
msgs = None
@@ -60,11 +59,11 @@ def _listing(request):
continue
else:
raise Exception("unsupported search criteria %s" % c)
filter = nfilter if filter is None else filter | nfilter
flt = nfilter if flt is None else flt | nfilter
q = ~Q(rs='D')
if not request.user.is_superuser:
if not request.user.belongs_to_group('SimpleUsers'):
if request.user.group != 'SimpleUsers':
doms = request.user.get_domains()
if not len(doms):
return empty_quarantine(request)
@@ -73,7 +72,7 @@ def _listing(request):
else:
q &= Q(rid__email=request.user.email)
if not request.user.belongs_to_group('SimpleUsers') \
if request.user.group != 'SimpleUsers' \
and rcptfilter is not None:
q &= Q(rid__email__contains=rcptfilter)
msgs = Msgrcpt.objects.filter(q).values("mail_id")
@@ -86,7 +85,7 @@ def _listing(request):
del request.session["page"]
pageid = 1
lst = SQLlisting(request.user, msgs, filter, baseurl="listing/",
lst = SQLlisting(request.user, msgs, flt,
navparams=request.session["navparams"],
elems_per_page=int(parameters.get_user(request.user,
"MESSAGES_PER_PAGE")))
@@ -95,14 +94,16 @@ def _listing(request):
return empty_quarantine(request)
content = lst.fetch(request, page.id_start, page.id_stop)
navbar = lst.render_navbar(page)
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")
@login_required
def index(request):
return SQLlisting(request.user, None, None, empty=True).render(request)
return _render(request, "amavis_quarantine/index.html", dict(
deflocation="listing/", defcallback="listing_cb", selection="quarantine"
))
@login_required
def getmailcontent(request, mail_id):
@@ -123,7 +124,7 @@ def getmailcontent(request, mail_id):
@login_required
def viewmail(request, mail_id):
if not request.user.belongs_to_group('SimpleUsers'):
if request.user.group != 'SimpleUsers':
rcpt = request.GET["rcpt"]
else:
mb = Mailbox.objects.get(user=request.user)
@@ -137,7 +138,7 @@ def viewmail(request, mail_id):
args += ["%s=%s" % (kw, request.GET[kw])]
content = Template("""
<iframe width="100%" frameBorder="0" src="{{ url }}" id="mailcontent"></iframe>
<iframe src="{{ url }}" id="mailcontent"></iframe>
""").render(Context({"url" : reverse(getmailcontent, args=[mail_id]) \
+ "?%s" % "&".join(args)}))
menu = viewm_menu("", __get_current_url(request), mail_id, rcpt,
@@ -152,6 +153,7 @@ def viewheaders(request, mail_id):
content += qm.mail_text
msg = email.message_from_string(content)
return _render(request, 'amavis_quarantine/viewheader.html', {
"title" : _("Message headers"),
"headers" : msg.items()
})
@@ -166,7 +168,7 @@ def check_mail_id(request, mail_id):
@login_required
def delete(request, mail_id):
mail_id = check_mail_id(request, mail_id)
if request.user.belongs_to_group('SimpleUsers'):
if request.user.group == 'SimpleUsers':
mb = Mailbox.objects.get(user=request.user)
msgrcpts = Msgrcpt.objects.filter(mail__in=mail_id, rid__email=mb.full_address)
msgrcpts.update(rs='D')
@@ -186,7 +188,7 @@ def delete(request, mail_id):
@login_required
def release(request, mail_id):
mail_id = check_mail_id(request, mail_id)
if request.user.belongs_to_group('SimpleUsers'):
if request.user.group == 'SimpleUsers':
mb = Mailbox.objects.get(user=request.user)
msgrcpts = Msgrcpt.objects.filter(mail__in=mail_id, rid__email=mb.full_address)
else:
+7 -4
View File
@@ -367,13 +367,13 @@ class IMAPconnector(object):
if len(topmailbox):
md_mailboxes = []
else:
md_mailboxes = [{"name" : "INBOX", "class" : "inbox"},
md_mailboxes = [{"name" : "INBOX", "class" : "icon-home"},
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
"class" : "drafts"},
"class" : "icon-file"},
{"name" : 'Junk'},
{"name" : parameters.get_user(user, "SENT_FOLDER")},
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
"class" : "trash"}]
"class" : "icon-trash"}]
md_mailboxes += self._listfolders(topmailbox, md_mailboxes)
if unseen_messages:
for fd in md_mailboxes:
@@ -476,7 +476,10 @@ class IMAPconnector(object):
return
data = self._cmd("GETQUOTAROOT", self._encodefolder(mailbox))
print data
if data is None:
self.quota_limit = self.quota_actual = None
return
quotadef = data[1][0]
m = re.match("[^\s]+ \(STORAGE (\d+) (\d+)\)", quotadef)
if not m:
@@ -1,8 +1,8 @@
{% load i18n %}
{% load i18n libextras %}
<form method="POST" action="{{ posturl }}" id="composemail">
<div id="compose-container">
<div id="mailheader">
{% include "common/genericform.html" %}
{% render_form form %}
<div id="attachments" name="{% url modoboa.extensions.webmail.views.attachments %}">
<div>
<img src="{{ MEDIA_URL }}pics/attachment.png" />
@@ -1,16 +1,12 @@
{% load i18n %}
{% load webextras %}
{% if titlebar %}
<div class="boxtitle">
<a href="{% url modoboa.extensions.webmail.views.newfolder %}" class="boxed"
rel="{handler: 'iframe', size: {x: 350, y: 400}, closeBtn: true}">
<img src="{{MEDIA_URL}}pics/add.png"
alt="{% trans 'Add a new folder' %}"/>
</a>
<label class="aligned">{% trans "Folders" %}</label>
<div class="spacer"></div>
</div>
{% endif %}
<ul>
<ul class="nav nav-list">
{% if titlebar %}
<li class="nav-header">
<a href="{% url modoboa.extensions.webmail.views.newfolder %}">
<i class="icon-plus"></i>
{% trans "Folders" %}
</a>
{% endif %}
{% print_folders folders selected withunseen withmenu %}
</ul>
@@ -1,6 +1,5 @@
{% extends "common/mailheaders.html" %}
{% load i18n %}
{% block extraheaders %}
{% if attachments %}
<tr>
+27 -23
View File
@@ -50,7 +50,7 @@ def viewmail_menu(selection, folder, user, mail_id=None):
}
]
return render_to_string('common/menu.html',
return render_to_string('common/buttons_list.html',
{"selection" : selection, "entries" : entries,
"user" : user})
@@ -66,7 +66,7 @@ def compose_menu(selection, backurl, user):
"img" : static_url("pics/send-receive.png"),
"label" : _("Send")},
]
return render_to_string('common/menu.html',
return render_to_string('common/buttons_list.html',
{"selection" : selection, "entries" : entries,
"user" : user})
@@ -74,13 +74,13 @@ def compose_menu(selection, backurl, user):
def listmailbox_menu(selection, folder, user):
entries = [
{"name" : "compose",
"url" : "compose",
"img" : static_url("pics/edit.png"),
"label" : _("New message")},
"url" : "compose",
"label" : _("New message"),
"class" : "btn"},
{"name" : "mark",
"img" : static_url("pics/domains.png"),
"label" : _("Mark messages"),
"class" : "menubardropdown",
"class" : "btn",
"menu" : [
{"name" : "mark-read",
"label" : _("As read"),
@@ -93,8 +93,7 @@ def listmailbox_menu(selection, folder, user):
{"name" : "actions",
"img" : static_url("pics/settings.png"),
"label" : _("Actions"),
"class" : "menubardropdown",
"width" : "150",
"class" : "btn",
"menu" : [
{"name" : "fdaction",
"label" : _("Compress folder"),
@@ -110,24 +109,25 @@ def listmailbox_menu(selection, folder, user):
"img" : static_url("pics/clear.png"),
"url" : reverse(webmail.views.empty, args=[folder])}
]
menu = render_to_string("common/menu.html",
menu = render_to_string("common/buttons_list.html",
{"selection" : selection, "entries" : entries,
"user" : user})
searchbar = render_to_string("common/email_searchbar.html", {
"MEDIA_URL" : settings.MEDIA_URL
})
return menu + searchbar
"user" : user, "css" : "nav"})
# searchbar = render_to_string("common/email_searchbar.html", {
# "MEDIA_URL" : settings.MEDIA_URL
# })
# return menu + searchbar
return menu
@register.simple_tag
def print_folders(folders, selected=None, withunseen=False, withmenu=False):
result = ""
for fd in folders:
cssclass = fd["class"] if fd.has_key("class") else "folder"
cssclass = ""
name = fd["path"] if fd.has_key("sub") else fd["name"]
label = fd["name"]
if selected == name:
cssclass += " selected"
cssclass = "active"
result += "<li name='%s' class='droppable %s'>\n" % (name, cssclass)
if fd.has_key("sub"):
if selected is not None and selected != name and selected.count(name):
@@ -136,17 +136,21 @@ def print_folders(folders, selected=None, withunseen=False, withmenu=False):
else:
ul_state = "hidden"
div_state = "collapsed"
result += "<div class='clickbox %s'>&nbsp;</div>" % div_state
if withmenu:
result += "<img src='%spics/go-down.png' class='footer' />" \
% settings.MEDIA_URL
result += "<div class='clickbox %s'></div>" % div_state
# if withmenu:
# result += "<img src='%spics/go-down.png' class='footer' />" \
# % settings.MEDIA_URL
cssclass = "block"
if withunseen and fd.has_key("unseen"):
label += " (%d)" % fd["unseen"]
cssclass += " unseen"
result += "<a href='%s' class='%s' name='loadfolder'>%s</a>\n" \
% (fd.has_key("path") and fd["path"] or fd["name"], cssclass, label)
iclass = fd["class"] if fd.has_key("class") else "icon-folder-close"
result += """<a href='%s' class='%s' name='loadfolder'>
<i class="%s"></i>
%s
</a>
""" % (fd.has_key("path") and fd["path"] or fd["name"], cssclass, 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, withmenu) + "</ul>\n"
+3 -3
View File
@@ -526,7 +526,7 @@ def viewmail(request):
url = reverse(getmailcontent) + "?mbox=%s&mailid=%s&links=%d" % \
(request.session["mbox"], mailid, links)
content = Template("""
<iframe width="100%" frameBorder="0" src="{{ url }}" id="mailcontent"></iframe>
<iframe src="{{ url }}" id="mailcontent"></iframe>
""").render(Context({"url" : url}))
return dict(listing=content, menuargs=dict(mail_id=mailid))
@@ -571,7 +571,7 @@ def newindex(request):
else:
if request.is_ajax():
raise WebmailError(_("Bad request"))
response = dict(deflocation="?action=listmailbox",
response = dict(selection="webmail", deflocation="?action=listmailbox",
defcallback="wm_updatelisting")
curmbox = request.session.get("mbox", "INBOX")
@@ -582,7 +582,7 @@ def newindex(request):
int(parameters.get_user(request.user, "REFRESH_INTERVAL")) * 1000
response["mboxes"] = render_mboxes_list(request, imapc)
response["quota"] = ImapListing.computequota(imapc)
return _render(request, "webmail/index.html", response)
return _render(request, "webmail/index2.html", response)
if action in ["reply", "forward"]:
action = "compose"
+22 -15
View File
@@ -26,7 +26,7 @@ class Page(object):
def __init__(self, pageid, id_start, id_stop, items,
items_per_page, has_previous, has_next,
baseurl=None):
self.pageid = pageid
self.number = pageid
self.id_start = id_start
self.id_stop = id_stop
self.items = items
@@ -35,15 +35,15 @@ class Page(object):
self.has_next = has_next
self.baseurl = baseurl
def previous_page(self):
def previous_page_number(self):
if not self.has_previous:
return False
return self.pageid - 1
return self.number - 1
def next_page(self):
def next_page_number(self):
if not self.has_next:
return False
return self.pageid + 1
return self.number + 1
def last_page(self):
lid = self.items / self.items_per_page
@@ -57,7 +57,9 @@ class Paginator(object):
def __init__(self, total, elems_per_page):
self.total = total
self.elems_per_page = elems_per_page
self.baseurl = None
self.num_pages = total / elems_per_page
if total % elems_per_page:
self.num_pages += 1
def _indexes(self, page):
id_start = self.elems_per_page * page + 1
@@ -80,11 +82,12 @@ class Paginator(object):
return None
p = Page(page, id_start, id_stop, self.total,
self.elems_per_page, has_previous, has_next)
if self.baseurl:
p.baseurl = self.baseurl
p.paginator = self
return p
class EmailListing(object):
table_styles = "table-striped table-condensed"
def __init__(self, baseurl=None, folder=None, elems_per_page=40,
navparams={}, **kwargs):
self.folder = folder
@@ -102,15 +105,14 @@ class EmailListing(object):
self.paginator = Paginator(self.mbc.messages_count(folder=self.folder,
order=order),
elems_per_page)
if baseurl:
self.paginator.baseurl = baseurl
self.baseurl = baseurl
@staticmethod
def render_navbar(page):
def render_navbar(page, baseurl=None):
if page is None:
return ""
context = {"page" : page, "MEDIA_URL" : settings.MEDIA_URL}
return render_to_string("common/navbar.html", context)
context = {"page" : page, "MEDIA_URL" : settings.MEDIA_URL, "baseurl" : baseurl}
return render_to_string("common/pagination_bar.html", context)
def fetch(self, request, id_start, id_stop):
table = self.tbltype(request,
@@ -121,7 +123,12 @@ class EmailListing(object):
<form method="POST" id="listingform">
{{ table }}
</form>""")
return tpl.render(Context({"table" : table.render()}))
return tpl.render(
Context(
{"table" : table.render(styles=self.table_styles,
withheader=False)}
)
)
# def render(self, request, pageid=1, **kwargs):
# listing = ""
@@ -153,7 +160,7 @@ class EmailListing(object):
listing = _("This folder contains no message")
else:
listing = self.fetch(request, page.id_start, page.id_stop)
return dict(listing=listing, navbar=self.render_navbar(page))
return dict(listing=listing, navbar=self.render_navbar(page, self.baseurl))
def parse_search_parameters(request):
if request.GET.has_key("pattern"):
+5 -4
View File
@@ -188,16 +188,17 @@ class Table(object):
def __str__(self):
return self.render()
def render(self):
def render(self, styles="table-striped table-bordered", withheader=True):
if len(self.rows):
t = Template("""
<table id="{{ tableid }}" class="table table-striped table-bordered">
{% include "common/table_head.html" %}
<table id="{{ tableid }}" class="table {{ styles }}">
{% if withheader %}{% include "common/table_head.html" %}{% endif %}
{% include "common/table_body.html" %}
</table>
""")
return t.render(RequestContext(self.request, {
"table" : self, "tableid" : self.tableid
"table" : self, "tableid" : self.tableid, "styles" : styles,
"withheader" : withheader
}))
t = Template("""
<div class="info">%s</div>
+13
View File
@@ -3,6 +3,7 @@ import os
from django.conf import settings
from django import template
from django.contrib import messages
from django.template import Template, Context
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
@@ -116,3 +117,15 @@ def alert(msg, typ):
<a class="close" data-dismiss="alert">×</a>
%s
</div>""" % (typ, msg)
@register.simple_tag
def render_link(linkdef, mdclass=None):
t = Template("""<a href="{{ link.url }}" name="{{ link.name }}"
{% if link.modal %}data-toggle="ajaxmodal{% if link.autowidth %}-autowidth{% endif %}"{% endif %}
{% if link.modalcb %}modalcb="{{ link.modalcb }}"{% endif %}
class="{{ mdclass }}{% if link.class %} {{ link.class }}{% endif %}"
{% if link.confirm %} onclick="return confirm('{{ link.confirm }}')"{% endif %}>
{% if link.img %}<i class="{{ link.img }}"></i>{% endif %}
{{ link.label }}
</a>""")
return t.render(Context(dict(link=linkdef, mdclass=mdclass)))
+25
View File
@@ -2,6 +2,14 @@ body {
padding-top: 60px;
}
#fixed_content {
position: absolute;
top: 60px;
right: 0;
left: 0;
overflow: hidden;
}
#bottom-bar {
position: fixed;
left: 0;
@@ -46,6 +54,19 @@ body {
margin-bottom: 8px;
}
.dropdown-menu .radio {
padding: 3px 33px;
}
.no-margin {
margin: 0;
}
.dropdown-menu label:hover {
background-color: #0088CC;
color: white;
}
.spaced select,
.spaced input[type="text"] {
margin-bottom: 8px;
@@ -62,3 +83,7 @@ body {
.buttons_list .active {
display: block;
}
.tr-selected {
background-color: #F5F5F5;
}
-15
View File
@@ -233,21 +233,6 @@ function searchbox_init() {
}
};
// The following code prevents a bug under IE7 because fullpath is
// returned instead of a relative one. (even if mootools uses
// getAttribute("href", 2), this is not working for AJAX requests)
gethref = function(obj) {
var url = obj.get("href");
var re = new RegExp("^(https?):");
var scheme = re.exec(url);
if (scheme != null) {
var baseurl = scheme[0] + "://" + location.host + location.pathname;
return url.replace(baseurl, "");
}
return url;
};
/*function saveparams(id, message) {
$("#update").click(function(evt) {
evt.preventDefault();
+8 -8
View File
@@ -12,20 +12,20 @@ function navbarcallback(event) {
}
function updatelisting(resp) {
if ($defined(resp.url)) {
current_anchor.baseurl(resp.url).update();
if (resp.url != undefined) {
history.baseurl(resp.url).update();
return;
}
if ($defined(resp.menu)) {
/*if (resp.menu != undefined) {
$("menubar").set("html", resp.menu);
parse_menubar("menubar");
}*/
if (resp.listing != undefined) {
$("#listing").html(resp.listing);
}
if ($defined(resp.listing)) {
$("listing").set("html", resp.listing);
}
if ($defined(resp.navbar)) {
/*if ($defined(resp.navbar)) {
$("navbar").set("html", resp.navbar);
$$("#navbar a").addEvent("click", navbarcallback);
}
}*/
}
+35 -15
View File
@@ -1,16 +1,5 @@
var History = function(options) {
this.options = $.extend({}, this.defaults, options);
this.callbacks = {};
this.params = {};
this.base = '';
this.serialized = null;
this.updatenext = true;
this.reset_loading_infos();
if (this.options.defcallback) {
this.register_callback("default", this.options.defcallback);
this.check_id =
setInterval($.proxy(this.check, this), this.options.checkinterval);
}
this.initialize(options);
};
History.prototype = {
@@ -24,6 +13,34 @@ History.prototype = {
defcallback: null
},
initialize: function(options) {
this.options = $.extend({}, this.defaults, options);
this.callbacks = {};
this.base = '';
this.serialized = null;
this.updatenext = true;
this.load_params();
this.reset_loading_infos();
if (this.options.defcallback) {
this.register_callback("default", this.options.defcallback);
this.check_id =
setInterval($.proxy(this.check, this), this.options.checkinterval);
}
},
load_params: function() {
var rawqs = window.location.hash.substr(1);
if (rawqs.indexOf('?')) {
this.params = {};
return;
}
var tmp = rawqs.split('?');
rawqs = (tmp.length == 1) ? tmp[0] : tmp[1];
this.params = parse_qs(rawqs.split('&'));
},
paramslength: function() {
return Object.keys(this.params).length;
},
@@ -34,7 +51,7 @@ History.prototype = {
},
reset: function() {
this.base = null;
this.base = "";
this.params = {};
return this;
},
@@ -100,7 +117,9 @@ History.prototype = {
},
deleteParam: function(str) {
this.params.erase(str);
if (this.params[str] != undefined) {
delete this.params[str];
}
},
updateparams: function(str) {
@@ -111,6 +130,7 @@ History.prototype = {
for (var i = 0; i < elems.length; i++) {
this.setparamfromstring(elems[i]);
}
return this;
},
setparamfromstring: function(str) {
@@ -124,7 +144,7 @@ History.prototype = {
},
setparams: function(params) {
this.params.extend(params);
this.params = $.extend({}, this.params, params);
return this;
},
+127
View File
@@ -0,0 +1,127 @@
(function($) {
var HtmlTable = function(element, options) {
this.initialize(element, options);
};
HtmlTable.prototype = {
constructor: HtmlTable,
initialize: function(element, options) {
this.$element = $(element);
this.options = $.extend({}, $.fn.htmltable.defaults, options);
this.shift_pressed = false;
this.last_selection = null;
this.prev_dir = null;
this.listen();
this.$element.css({
"-moz-user-select": "-moz-none",
"-khtml-user-select": "none",
"-webkit-user-select": "none",
"-ms-user-select": "none",
"user-select": "none"
});
this.$element.attr("unselectable", "on");
},
listen: function() {
$(document).on("click", "tbody>tr",
$.proxy(this.toggle_select, this));
$(document).on("keydown", "body", $.proxy(this.keydown, this));
$(document).on("keyup", "body", $.proxy(this.keyup, this));
},
_toggle_select: function($tr) {
this.last_selection = $tr;
if ($tr.hasClass(this.options.tr_selected_class)) {
$tr.removeClass(this.options.tr_selected_class);
} else {
$tr.addClass(this.options.tr_selected_class);
}
},
toggle_select: function(e) {
e.preventDefault();
var $tr = $(e.target).parent();
if (this.shift_pressed && this.last_selection) {
var itfunc = ($tr.index() >= this.last_selection.index())
? "next" : "prev";
var $curtr = null;
if (this.prev_dir && itfunc != this.prev_dir) {
$curtr = this.last_selection;
} else {
$curtr = this.last_selection[itfunc]("tr");
}
for (; $curtr.length; $curtr = $curtr[itfunc]("tr")) {
this._toggle_select($curtr);
if ($tr.attr("id") == $curtr.attr("id")) {
break;
}
}
this.prev_dir = itfunc;
return;
}
this._toggle_select($tr);
},
current_selection: function() {
return $("tr[class*=" + this.options.tr_selected_class + "]");
},
/*
* Cancel the current selection
*/
clear_selection: function() {
this.current_selection().removeClass(this.options.tr_selected_class);
},
/*
* Manually select a table row
*/
select_row: function($row) {
$row.addClass(this.options.tr_selected_class);
},
keydown: function(e) {
switch (e.which) {
case 16:
e.preventDefault();
this.shift_pressed = true;
break;
}
},
keyup: function(e) {
switch (e.which) {
case 16:
e.preventDefault();
this.shift_pressed = false;
break;
}
}
};
$.fn.htmltable = function(method) {
var args = arguments;
return this.each(function() {
var $this = $(this),
data = $this.data('htmltable'),
options = typeof method === "object" && method;
if (!data) {
$this.data('htmltable', new HtmlTable(this, options));
data = $this.data('htmltable');
}
if (typeof method === "string") {
data[method].apply(data, Array.prototype.slice.call(args, 1));
}
});
};
$.fn.htmltable.defaults = {
tr_selected_class: 'tr-selected'
};
})(jQuery);
+68
View File
@@ -0,0 +1,68 @@
(function($) {
var SearchBar = function(element, options) {
this.initialize(element, options);
};
SearchBar.prototype = {
constructor: SearchBar,
initialize: function(element, options) {
this.$element = $(element);
this.options = $.extend({}, $.fn.searchbar.defaults, options);
this.$element.change($.proxy(this.pattern_change, this));
this.$element.click(function(e) {
$(this).select();
});
this.$element.blur(function(e) {
var $this = $(this);
if ($this.attr("value") == "") {
$this.attr("value", gettext("Search..."));
}
});
if (this.options.navobj.params.pattern != undefined) {
this.$element.attr("value", this.options.navobj.params.pattern);
}
if (this.options.navobj.params.criteria != undefined) {
$("#crit_" + this.options.navobj.params.criteria).attr("checked", "checked");
}
},
pattern_change: function(e) {
var $input = $(e.target);
var criteria = $("input[name=scriteria]:checked").attr("value");
var pattern = $input.attr("value");
if (pattern != "") {
this.options.navobj.setparams({
pattern: pattern,
criteria: criteria
});
} else {
this.options.navobj.deleteParam("pattern");
this.options.navobj.deleteParam("criteria");
this.options.navobj.deleteParam("page");
}
this.options.navobj.update();
}
};
$.fn.searchbar = function(method) {
return this.each(function() {
var $this = $(this),
data = $this.data('searchbar'),
options = typeof method === "object" && method;
if (!data) {
$this.data('searchbar', new SearchBar(this, options));
}
if (typeof method === "string") {
data[method]();
}
});
};
$.fn.searchbar.defaults = {
navobj: null
};
})(jQuery);
+36
View File
@@ -123,6 +123,42 @@ function simple_ajax_form_post(e, options) {
});
}
/*
* The following code prevents a bug under IE7 because fullpath is
* returned instead of a relative one. (even if mootools uses
* getAttribute("href", 2), this is not working for AJAX requests)
*/
function gethref(obj) {
var url = $(obj).attr("href");
var re = new RegExp("^(https?):");
var scheme = re.exec(url);
if (scheme != null) {
var baseurl = scheme[0] + "://" + location.host + location.pathname;
return url.replace(baseurl, "");
}
return url;
};
/*
* Extract the current URL parameters into a dictionnary.
*
* Ref:
* http://stackoverflow.com/questions/901115/get-query-string-values-in-javascript
*/
function parse_qs(raw) {
if (raw == "") return {};
var res = {};
for (var i = 0; i < raw.length; i++) {
var p = raw[i].split('=');
if (p.length != 2) continue;
res[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
}
return res;
}
/*
* Simple wrapper around Request.JSON. We just ensure that
* redirections are correctly catched. (on session timeout for
+11
View File
@@ -0,0 +1,11 @@
{% load libextras %}
<div class="btn-toolbar">{% for entry in entries %}
<div class="btn-group">{% if entry.menu %}
<a class="btn dropdown-toggle" data-toggle="dropdown">{{ entry.label }}<span class="caret"></span></a>
<ul class="dropdown-menu">{% for sentry in entry.menu %}
<li>{% render_link sentry %}</li>
{% endfor %}</ul>
{% else %}{% render_link entry "btn" %}{% endif %}
</div>
{% endfor %}{{ extracontent }}</div>
+9 -9
View File
@@ -1,13 +1,13 @@
{% load i18n %}
<div id="searchbox">
<img src="{{ MEDIA_URL }}pics/search.png" id="searchopts" />
<input type="text" name="searchfield" id="searchfield" value="{% trans "Search..." %}" />
<div id="searchparams">
<input type="radio" id="crit_from_addr" name="scriteria" value="from_addr" checked>{% trans "From address" %}<br/>
<input type="radio" id="crit_subject" name="scriteria" value="subject">{% trans "Subject" %}<br/>
<div id="searchbox" class="btn-group">
<a href="#" class="btn dropdown-toggle" data-toggle="dropdown"><i class="icon-search" id="searchopts"></i></a>
<ul class="dropdown-menu" id="searchparams">
<li><label class="radio"><input type="radio" id="crit_from_addr" name="scriteria" value="from_addr" checked>{% trans "From address" %}</label></li>
<li><label class="radio"><input type="radio" id="crit_subject" name="scriteria" value="subject">{% trans "Subject" %}</label></li>
{% for opt in extraopts %}
<input type="radio" id="crit_{{ opt.name }}" name="scriteria" value="{{ opt.name }}">{{ opt.label }}</br>
<li><label class="radio"><input type="radio" id="crit_{{ opt.name }}" name="scriteria" value="{{ opt.name }}">{{ opt.label }}</label></li>
{% endfor %}
<input type="radio" id="crit_both" name="scriteria" value="both">{% trans "Both" %}<br/>
</div>
<li><label class="radio"><input type="radio" id="crit_both" name="scriteria" value="both">{% trans "Both" %}</label></li>
</ul>
<input type="text" class="pull-right no-margin" name="searchfield" id="searchfield" value="{% trans "Search..." %}" />
</div>
+12
View File
@@ -0,0 +1,12 @@
{% load i18n %}
<div class="modal-header">
<a class="close" data-dismiss="modal">x</a>
<h3>{{ title }}</h3>
</div>
<div class="modal-body">
{% block modalbody %}{% endblock %}
</div>
<div class="modal-footer">
{% if action_label %}<a href="#" class="btn btn-primary {{ action_classes }}">{{ action_label }}</a>{% endif %}
<a class="btn" data-dismiss="modal">{% trans "Close" %}</a>
</div>
+1 -1
View File
@@ -1,5 +1,5 @@
{% load i18n %}
<table id="emailheaders">
<table id="emailheaders" class="table table-striped table-bordered table-condensed">
{% for hdr in headers %}
<tr><th width="130px">{% trans hdr.name %}</th><td>{{ hdr.value }}</td></tr>
{% endfor %}
+3 -23
View File
@@ -1,4 +1,4 @@
{% load i18n %}
{% load i18n libextras %}
{% for entry in entries %}
{% if entry.separator %}
<li class="divider">&nbsp;</li>
@@ -12,33 +12,13 @@
<ul class="dropdown-menu"
{% if entry.width %}style="width: {{ entry.width }}px"{% endif %}>
{% for sentry in entry.menu %}
<li>
<a href="{{ sentry.url }}" name="{{ sentry.name }}"
{% if sentry.modal %}data-toggle="ajaxmodal{% if sentry.autowidth %}-autowidth{% endif %}"{% endif %}
{% if sentry.modalcb %}modalcb="{{ entry.modalcb }}"{% endif %}
{% if sentry.class %}class={{ sentry.class }}{% endif %}
{% if sentry.rel %}rel={{ sentry.rel }}{% endif %}
{% if sentry.confirm %} onclick="return confirm('{{ sentry.confirm }}')"{% endif %}>
{% if sentry.img %}<i class="{{ sentry.img }}"></i>{% endif %}
{{ sentry.label }}
</a>
</li>
<li>{% render_link sentry %}</li>
{% endfor %}
</ul>
</li>
{% else %}
<li {% if selection == entry.name %}class="active"{% endif %}>
{% if not entry.disabled %}
<a href="{{ entry.url }}" name="{{ entry.name }}"
{% if entry.modal %}data-toggle="ajaxmodal{% if entry.autowidth %}-autowidth{% endif %}"{% endif %}
{% if entry.modalcb %}modalcb="{{ entry.modalcb }}"{% endif %}
{% if entry.class %}class={{ entry.class }}{% endif %}
{% if entry.rel %}rel={{ entry.rel }}{% endif %}
{% if entry.confirm %} onclick="return confirm('{{ entry.confirm }}')"{% endif %}>
{% endif %}
{% if entry.img %}<i class="{{ entry.img }}"></i>{% endif %}
{{ entry.label }}
{% if not entry.disabled %}</a>{% endif %}
{% render_link entry %}
</li>
{% endif %}
{% endif %}
+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="?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="?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="?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="?page={{ page.next_page_number }}">
<a href="{{ baseurl }}?page={{ page.paginator.num_pages }}">
<i class="icon-fast-forward icon-white"></i></a>
</li>
</ul>
+1 -2
View File
@@ -1,7 +1,6 @@
<tbody>
{% for row in table.rows %}
<tr id="{{ row.id }}" class="{% cycle 'odd' 'even' %} {{ row.style }}" name="{{ row.trcpt }}"
rel="{{ row.options }}">
<tr id="{{ row.id }}" class="{{ row.style }}" name="{{ row.trcpt }}">
{% for cell in row.cols %}
<td name="{{ cell.name }}" width="{{ cell.width }}" align="{{ cell.align }}" class="{{ cell.cssclass }}">
{% if cell.safe %}{{ cell.value|safe }}{% else %}{{ cell.value }}{% endif %}
+2 -19
View File
@@ -4,7 +4,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head profile="http://www.w3.org/2005/10/profile">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/box.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/bootstrap.css" />
<style type="text/css">
body {
@@ -22,24 +22,7 @@
border: 0px transparent none;
margin: 0;
}
table#emailheaders {
width: 100%;
border-collapse: collapse;
margin-bottom: 10px;
}
#emailheaders td,
#emailheaders th {
height: 22px;
text-align: left;
padding-left: 10px;
border: 1px solid #ccc;
}
#emailheaders td {
font-size: 1em;
}
#emailheaders th {
background: #F7F9Fb;
}
#emailheaders a, #emailheaders a:visited {
color: blue;
text-decoration: none;
+4
View File
@@ -0,0 +1,4 @@
{% extends "nlayout.html" %}
{% block content %}
<div id="fixed_content">{% block fixed_content %}{% endblock %}</div>
{% endblock %}
+8
View File
@@ -0,0 +1,8 @@
{% extends "nlayout.html" %}
{% block content %}
<div class="container-fluid">
<div class="row-fluid">
{% block rowcontent %}{% endblock %}
</div>
</div>
{% endblock %}
+45 -56
View File
@@ -1,66 +1,55 @@
<!DOCTYPE html>
{% load i18n admin_extras uprefs_extras libextras %}
<html xmlns="http://www.w3.org/1999/xhtml">
<head profile="http://www.w3.org/2005/10/profile">
<meta charset="utf-8" />
<title>Modoboa - {% block pagetitle %}{% endblock %}</title>
<link rel="icon" type="image/png" href="{{ MEDIA_URL }}css/logo-icon.png" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/bootstrap.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/bootstrap-responsive.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/datepicker.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/custom.css" />
<script src="{{ MEDIA_URL }}js/jquery.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/bootstrap.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/bootstrap-datepicker.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/utils.js" type="text/javascript"></script>
<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>
{% url django.views.i18n.javascript_catalog as catalog_url %}
<script type="text/javascript" src="{{ catalog_url }}"></script>
<script type="text/javascript">
set_media_url("{{ MEDIA_URL }}");
</script>
{% block html_head %}{% endblock %}
</head>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Modoboa - {% block pagetitle %}{% endblock %}</title>
<link rel="icon" type="image/png" href="{{ MEDIA_URL }}css/logo-icon.png" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/bootstrap.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/bootstrap-responsive.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/datepicker.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/custom.css" />
<script src="{{ MEDIA_URL }}js/jquery.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/bootstrap.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/bootstrap-datepicker.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/utils.js" type="text/javascript"></script>
<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>
{% url django.views.i18n.javascript_catalog as catalog_url %}
<script type="text/javascript" src="{{ catalog_url }}"></script>
<script type="text/javascript">
set_media_url("{{ MEDIA_URL }}");
</script>
{% block html_head %}{% endblock %}
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<div class="nav-collapse">
<ul class="nav">
{% loadextmenu user %}
{% if selection %}{% admin_menu selection user %}{% else %}{% admin_menu "" user %}{% endif %}
{% load_optionalmenu user %}
</ul>
<ul class="nav pull-right">
{% user_menu user %}
</ul>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<div class="nav-collapse">
<ul class="nav">{% if selection %}
{% loadextmenu selection user %}{% admin_menu selection user %}
{% else %}{% loadextmenu "" user %}{% admin_menu "" user %}{% endif %}
{% load_optionalmenu user %}
</ul>
<ul class="nav pull-right">
{% user_menu user %}
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
</div>
{% block content %}{% endblock %}
</div>
</div>
<div id="bottom-bar">
<div class="navbar-inner">
<div class="container-fluid">
<div class="nav-collapse">
{% block footer %}{% endblock %}
<div id="bottom-bar">
<div class="navbar-inner">
<div class="container-fluid">
<div id="bottom-bar-in" class="nav-collapse">
{% block footer %}{% endblock %}
</div>
</div>
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</div>
</footer>
</div>
</body>
</body>
</html>
+2 -2
View File
@@ -1,6 +1,6 @@
{% extends "nlayout.html" %}
{% extends "fluid.html" %}
{% load libextras %}
{% block content %}
{% block rowcontent %}
<div class="span2">{% block leftcol %}{% endblock %}</div>
<div class="span10">
{% if messages %}{% display_messages messages %}{% endif %}
+2 -2
View File
@@ -65,10 +65,10 @@ def user_menu(user):
{"entries" : entries, "user" : user})
@register.simple_tag
def loadextmenu(user):
def loadextmenu(selection, user):
menu = events.raiseQueryEvent("UserMenuDisplay", "top_menu", user)
return render_to_string('common/menulist.html',
{"entries" : menu, "user" : user})
{"selection" : selection, "entries" : menu, "user" : user})
@register.simple_tag
def extra_head_content():