mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Big webmail update:
* Dragging multiple messages is now possible * Better email displaying (headers and body are now displayed at the same time...) * Bugfixes about dropdown menus * Misc. enhancements.
This commit is contained in:
@@ -4,5 +4,6 @@ from django.utils.translation import ugettext as _
|
||||
class ComposeMailForm(forms.Form):
|
||||
from_ = forms.CharField(label=_("From"))
|
||||
to = forms.CharField(label=_("To"), widget=forms.Textarea())
|
||||
cc = forms.CharField(label=_("Cc"), widget=forms.Textarea())
|
||||
subject = forms.CharField(label=_("Subject"), max_length=255)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from email.header import decode_header
|
||||
import email.utils
|
||||
from django.utils.translation import ugettext as _
|
||||
from mailng.lib import decode, tables, imap_utf7, Singleton
|
||||
from mailng.lib.email_listing import MBconnector, EmailListing
|
||||
from mailng.lib.email_listing import MBconnector, EmailListing, Email
|
||||
from mailng.lib import tables, imap_utf7, parameters
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ class WMtable(tables.Table):
|
||||
tableid = "emails"
|
||||
idkey = "imapid"
|
||||
# selection = tables.SelectionColumn("selection", width="20px", first=True)
|
||||
_1_flags = tables.ImgColumn("flags", width="6%")
|
||||
_1_flags = tables.ImgColumn("flags", width="4%",
|
||||
header="<input type='checkbox' name='toggleselect' id='toggleselect' />")
|
||||
_2_subject = tables.Column("subject", label=_("Subject"),
|
||||
cssclass="draggable", width="50%")
|
||||
_3_from_ = tables.Column("from", width="20%", label=_("From"))
|
||||
@@ -226,3 +227,19 @@ class ImapListing(EmailListing):
|
||||
for fd in folders:
|
||||
md_folders += [{"name" : fd}]
|
||||
return md_folders
|
||||
|
||||
class ImapEmail(Email):
|
||||
def __init__(self, msg, **kwargs):
|
||||
Email.__init__(self, msg, **kwargs)
|
||||
|
||||
fields = ["Subject", "From", "To", "Cc", "Date"]
|
||||
for f in fields:
|
||||
label = f
|
||||
if not f in msg.keys():
|
||||
f = f.upper()
|
||||
if not f in msg.keys():
|
||||
continue
|
||||
try:
|
||||
self.headers += [{"name" : label, "value" : getattr(IMAPheader, "parse_%s" % f.lower())(msg[f])}]
|
||||
except AttributeError:
|
||||
self.headers += [{"name" : label, "value" : msg[f]}]
|
||||
|
||||
+35
-28
@@ -2,6 +2,7 @@
|
||||
import time
|
||||
import sys
|
||||
from django.http import HttpResponse
|
||||
from django.template import Template, Context
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import simplejson
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -10,10 +11,10 @@ from django.conf.urls.defaults import include
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from mailng.admin.models import Mailbox
|
||||
from mailng.lib import events, parameters, _render, _render_error
|
||||
from mailng.lib.email_listing import Email
|
||||
from imap_listing import *
|
||||
from forms import *
|
||||
from templatetags.webextras import *
|
||||
from imap_listing import ImapEmail
|
||||
|
||||
def init():
|
||||
events.register("UserMenuDisplay", menu)
|
||||
@@ -65,6 +66,9 @@ def folder(request, name, updatenav=True):
|
||||
request.session["page"] = pageid
|
||||
if order:
|
||||
request.session["navparams"]["order"] = order
|
||||
else:
|
||||
# Internal usage
|
||||
order = request.session["navparams"]["order"]
|
||||
|
||||
lst = ImapListing(request.user.username, request.session["password"],
|
||||
name, folder=name, order=order)
|
||||
@@ -98,42 +102,45 @@ def fetchmail(request, folder, mail_id, all=False):
|
||||
|
||||
@login_required
|
||||
def viewmail(request, folder, mail_id=None):
|
||||
header = fetchmail(request, folder, mail_id)
|
||||
if header:
|
||||
from templatetags.webextras import viewm_menu
|
||||
try:
|
||||
pageid = request.session["page"]
|
||||
except KeyError:
|
||||
pageid = "1"
|
||||
menu = viewm_menu("", request.session, mail_id,
|
||||
request.user.get_all_permissions())
|
||||
folder, imapid = header["imapid"].split("/")
|
||||
mailcontent = render_to_string("webmail/viewmail.html", {
|
||||
"header" : header, "folder" : folder, "imapid" : imapid
|
||||
})
|
||||
ctx = getctx("ok", menu=menu, listing=mailcontent)
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
from templatetags.webextras import viewm_menu
|
||||
|
||||
content = Template("""
|
||||
<iframe width="100%" frameBorder="0" src="{{ url }}" id="mailcontent"></iframe>
|
||||
""").render(Context({"url" : reverse(getmailcontent, args=[folder, mail_id])}))
|
||||
menu = viewm_menu("", request.session, mail_id,
|
||||
request.user.get_all_permissions())
|
||||
ctx = getctx("ok", menu=menu, listing=content)
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def getmailcontent(request, folder, mail_id):
|
||||
from mailng.lib.email_listing import Email
|
||||
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
if "class" in msg.keys() and msg["class"] == "unseen":
|
||||
IMAPconnector(request).msgseen(msg["imapid"])
|
||||
email = Email(msg, mode="html", links="1")
|
||||
return email.render(request)
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
if "class" in msg.keys() and msg["class"] == "unseen":
|
||||
IMAPconnector(request).msgseen(msg["imapid"])
|
||||
email = ImapEmail(msg, mode="html", links="1")
|
||||
try:
|
||||
pageid = request.session["page"]
|
||||
except KeyError:
|
||||
pageid = "1"
|
||||
folder, imapid = msg["imapid"].split("/")
|
||||
return _render(request, "webmail/viewmail.html", {
|
||||
"headers" : email.render_headers(), "folder" : folder,
|
||||
"imapid" : imapid, "mailbody" : email.body, "pre" : email.pre
|
||||
})
|
||||
|
||||
@login_required
|
||||
def move(request):
|
||||
for arg in ["msgset", "to"]:
|
||||
if not request.GET.has_key(arg):
|
||||
return
|
||||
folder, id = request.GET["msgset"].split("/")
|
||||
msgset = []
|
||||
fdname = ""
|
||||
for item in request.GET["msgset"].split(","):
|
||||
fdname, id = item.split("/")
|
||||
msgset += [id]
|
||||
mbc = IMAPconnector(request)
|
||||
mbc.move(id, folder, request.GET["to"])
|
||||
return HttpResponse(simplejson.dumps({"status" : "ok"}),
|
||||
mimetype="application/json")
|
||||
mbc.move(",".join(msgset), fdname, request.GET["to"])
|
||||
return folder(request, fdname, False)
|
||||
|
||||
@login_required
|
||||
def empty(request, name):
|
||||
@@ -176,7 +183,7 @@ def reply(request, folder, mail_id):
|
||||
if request.method == "POST":
|
||||
return
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
email = Email(msg)
|
||||
email = ImapEmail(msg)
|
||||
lines = email.body.split('\n')
|
||||
body = ""
|
||||
for l in lines:
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<style type="text/css">
|
||||
#compose-container {
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
width: 100%;
|
||||
}
|
||||
div.row {
|
||||
margin: 3px;
|
||||
clear: both;
|
||||
padding-top: 5px;
|
||||
}
|
||||
div.row label {
|
||||
float: left;
|
||||
@@ -18,7 +18,7 @@
|
||||
border: 1px solid #cccccc;
|
||||
background-color: #eee;
|
||||
width: 80%;
|
||||
height: 40px;
|
||||
height: 30px;
|
||||
font-size: medium;
|
||||
}
|
||||
textarea:hover {
|
||||
@@ -26,14 +26,14 @@
|
||||
}
|
||||
#id_body {
|
||||
background: white;
|
||||
width: 100%;
|
||||
width: 98%;
|
||||
}
|
||||
input {
|
||||
width: 80%;
|
||||
padding: 0;
|
||||
}
|
||||
#mailheader {
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<table id="headers">
|
||||
<table id="emailheaders">
|
||||
{% for hdr in headers %}
|
||||
<tr><th width="150px">{{ hdr.name }}</th><td>{{ hdr.value }}</td></tr>
|
||||
<tr><th width="130px">{{ hdr.name }}</th><td>{{ hdr.value }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
@@ -23,7 +23,7 @@ table#emails > tbody {
|
||||
}
|
||||
|
||||
#emails th {
|
||||
text-align: center;
|
||||
/*text-align: center;*/
|
||||
}
|
||||
|
||||
#emails tr {
|
||||
@@ -69,20 +69,16 @@ table#emails > tbody {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
td {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
th {
|
||||
-moz-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;
|
||||
-webkit-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;
|
||||
/*-moz-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;
|
||||
-webkit-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;*/
|
||||
}
|
||||
|
||||
th.selected {
|
||||
background: #EEEEEE;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
.draggable {2
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
@@ -91,8 +87,14 @@ th.selected {
|
||||
}
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #C8D6E8;
|
||||
}
|
||||
|
||||
tr.selected {
|
||||
background: #ffffcc;
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
navparams = new Hash({
|
||||
@@ -101,21 +103,21 @@ tr {
|
||||
{% endfor %}
|
||||
});
|
||||
|
||||
updatelisting = function(resp, overflow) {
|
||||
/*if (!$defined(overflow)) {
|
||||
overflow = "auto";
|
||||
}*/
|
||||
updatelisting = function(resp) {
|
||||
if ($defined(resp.menu)) {
|
||||
$("menubar").set("html", resp.menu);
|
||||
parse_menubar("menubar");
|
||||
}
|
||||
$("listing").set("html", resp.listing);
|
||||
/*if ($defined(resp.navbar)) {
|
||||
if ($defined(resp.navbar)) {
|
||||
$("navbar").set("html", resp.navbar);
|
||||
$("navbar").setStyle("display", "block");
|
||||
$("navbar").getChildren("a").addEvent("click", navbarcallback);
|
||||
}*/
|
||||
}
|
||||
$$("a[name=compose]").addEvent("click", loadCompose);
|
||||
$$("a[name=empty]").addEvent("click", empty);
|
||||
if (!checkSelection()) {
|
||||
select($$("a[href=INBOX]"));
|
||||
}
|
||||
}
|
||||
|
||||
empty = function(event) {
|
||||
@@ -125,6 +127,17 @@ tr {
|
||||
}}).get();
|
||||
}
|
||||
|
||||
checkSelection = function() {
|
||||
var selection = false;
|
||||
|
||||
$$("a[name=loadfolder]").each(function(item) {
|
||||
if (!selection) {
|
||||
selection = item.getParent().hasClass("selected");
|
||||
}
|
||||
});
|
||||
return selection;
|
||||
}
|
||||
|
||||
select = function(obj) {
|
||||
$$("a[name=loadfolder]").each(function(item) {
|
||||
item.getParent().removeClass("selected");
|
||||
@@ -147,23 +160,27 @@ tr {
|
||||
updatelisting(resp, "hidden");
|
||||
$$("a[name=back]").addEvent("click", loadFolder);
|
||||
$$("a[name=reply]").addEvent("click", reply);
|
||||
setDivHeight("mailcontent", $("table-container").getSize().y, 0);
|
||||
setDivHeight("mailcontent", /*$("table-container").getSize().y*/0, 0);
|
||||
});
|
||||
|
||||
compose_callback = function(resp) {
|
||||
updatelisting(resp, "hidden");
|
||||
updatelisting(resp);
|
||||
window.addEvent("resize", function(evt) {
|
||||
var bodysize = $("listing").getSize().y - $("mailheader").getSize().y;
|
||||
$("id_body").setStyle("height", bodysize + "px");
|
||||
});
|
||||
$$("a[name=back]").addEvent("click", loadFolder);
|
||||
$$("a[name=sendmail]").addEvent("click", function(evt) {
|
||||
evt.stop();
|
||||
$("composemail").set("send", {
|
||||
onSuccess: function(res) {
|
||||
updatelisting(JSON.decode(res));
|
||||
$$("tr").addEvent("click", viewmail);
|
||||
$$("tr").addEvent("dblclick", viewmail);
|
||||
}
|
||||
});
|
||||
$("composemail").send();
|
||||
});
|
||||
setDivHeight("id_body", $("mailheader").getSize().y + 20, 0);
|
||||
window.fireEvent("resize");
|
||||
}
|
||||
|
||||
loadCompose = function(event) {
|
||||
@@ -187,17 +204,60 @@ tr {
|
||||
current_anchor.update();
|
||||
}
|
||||
register_callback("folder", function(resp) {
|
||||
updatelisting(resp, "auto");
|
||||
updatelisting(resp);
|
||||
if (navparams.has("order")) {
|
||||
src = navparams.get("order").charAt(0) == "-" ? "down_arrow.png" : "up_arrow.png";
|
||||
th = $$("th[id=" + navparams.get("order").substring(1) + "]");
|
||||
th.getElement("img").set("src", "/static/pics/" + src);
|
||||
th.addClass("selected");
|
||||
}
|
||||
/*$$("td[class*=draggable]").each(function(item) {
|
||||
item.makeDraggable({
|
||||
var selectedRows = new Array();
|
||||
$$("tr").addEvent("click", function(event) {
|
||||
var tr = event.target.getParent();
|
||||
if (!tr.hasClass("selected")) {
|
||||
tr.addClass("selected");
|
||||
selectedRows.include(tr);
|
||||
} else {
|
||||
tr.removeClass("selected");
|
||||
selectedRows.erase(tr);
|
||||
}
|
||||
});
|
||||
|
||||
$$("tr").each(function(item) {
|
||||
new Drag.Move(item, {
|
||||
droppables: ".droppable",
|
||||
}).addEvents({
|
||||
"start" : function(item) {
|
||||
if (!item.hasClass("selected")) {
|
||||
selectedRows.each(function(item, index) {
|
||||
item.removeClass("selected");
|
||||
});
|
||||
selectedRows.empty();
|
||||
item.addClass("selected");
|
||||
selectedRows.include(item);
|
||||
}
|
||||
var block = new Element("div", {id: "draggeditems"})
|
||||
.setStyles({"position" : "absolute",
|
||||
"opacity" : 0.8,
|
||||
"width" : "200px",
|
||||
"padding" : "5px",
|
||||
"text-align" : "center",
|
||||
"height" : "25px",
|
||||
"border" : "1px solid #333",
|
||||
"background" : "white"})
|
||||
.setStyles(item.getPosition())
|
||||
.inject(document.body);
|
||||
block.set("html", "Moving " + selectedRows.length + " messages");
|
||||
this.oldelement = this.element;
|
||||
this.element = block;
|
||||
},
|
||||
"cancel" : function(item) {
|
||||
if ($defined(this.oldelement)) {
|
||||
item.dispose();
|
||||
delete(item);
|
||||
this.element = this.oldelement;
|
||||
}
|
||||
},
|
||||
"enter" : function(item, droppable) {
|
||||
droppable.addClass("dragging");
|
||||
},
|
||||
@@ -212,35 +272,32 @@ tr {
|
||||
return;
|
||||
}
|
||||
droppable.removeClass("dragging");
|
||||
this.oldelement.getParent().dispose();
|
||||
to = droppable.getFirst("a").get("href");
|
||||
dest = droppable.getFirst("a").get("href");
|
||||
msgset = new Array();
|
||||
selectedRows.each(function(item, index) {
|
||||
msgset.include(item.get("id"));
|
||||
});
|
||||
{% url extensions.webmail.main.move as moveurl %}
|
||||
new Request.JSON({url: "{{ moveurl }}", onSuccess: function(resp) {
|
||||
|
||||
}}).get({msgset : this.oldelement.getParent().get("id"),
|
||||
to : to});
|
||||
new Request.JSON({url: "{{ moveurl }}",
|
||||
onSuccess: get_callback("folder")}
|
||||
).get({msgset : msgset.join(), to : dest});
|
||||
},
|
||||
"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;
|
||||
}
|
||||
}
|
||||
});
|
||||
});*/
|
||||
});
|
||||
$("toggleselect").addEvent("click", function(evt) {
|
||||
var checked = evt.target.get("checked");
|
||||
if (checked) {
|
||||
$$("tbody>tr").each(function(item) {
|
||||
item.addClass("selected");
|
||||
selectedRows.include(item);
|
||||
});
|
||||
} else {
|
||||
$$("tr").each(function(item) { item.removeClass("selected"); });
|
||||
selectedRows.empty();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$$("tr").addEvent("dblclick", viewmail);
|
||||
$$("th[class*=sortable]").addEvent("click", function(evt) {
|
||||
var tmp = "?";
|
||||
@@ -260,11 +317,11 @@ tr {
|
||||
});
|
||||
});
|
||||
|
||||
/*navbarcallback = function(event) {
|
||||
navbarcallback = function(event) {
|
||||
event.stop();
|
||||
current_anchor.setparams(navparams);
|
||||
current_anchor.parse_string(event.target.getParent().get("href")).update();
|
||||
}*/
|
||||
}
|
||||
|
||||
foldercallback = function(event) {
|
||||
select(event.target);
|
||||
@@ -273,8 +330,6 @@ tr {
|
||||
|
||||
ajaxListener("INBOX/", updatelisting);
|
||||
$$("a[name=loadfolder]").addEvent("click", foldercallback);
|
||||
/*$("navbar").getChildren("a").addEvent("click", navbarcallback);*/
|
||||
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
|
||||
{% block leftcol %}
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
{% load i18n %}
|
||||
{% load webextras %}
|
||||
<style type="text/css">
|
||||
#mailcontent {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
td {
|
||||
font-size: 1em;
|
||||
height: 20px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #F7F9Fb;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="table-container">
|
||||
{% render_headers header %}
|
||||
</div>
|
||||
|
||||
{% url extensions.webmail.main.getmailcontent folder imapid as geturl %}
|
||||
<iframe id="mailcontent" src="{{ geturl }}"></iframe>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<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="/static/stylesheets/box.css" />
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 5px;
|
||||
}
|
||||
#mailcontent {
|
||||
width: 100%;
|
||||
border: 0px transparent none;
|
||||
margin: 0;
|
||||
}
|
||||
table#emailheaders {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#emailheaders td,
|
||||
#emailheaders th {
|
||||
height: 20px;
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
#emailheaders td {
|
||||
font-size: 1em;
|
||||
}
|
||||
#emailheaders th {
|
||||
background: #F7F9Fb;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="table-container">
|
||||
{{ headers }}
|
||||
</div>
|
||||
{% if pre %}<pre>{% endif %}{{ mailbody }}{% if pre %}</pre>{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -70,7 +70,7 @@ def listing_menu(selection, folder, perms):
|
||||
{"name" : "compose",
|
||||
"url" : reverse(webmail.main.compose),
|
||||
"img" : "/static/pics/edit.png",
|
||||
"label" : _("New message")}
|
||||
"label" : _("New message")},
|
||||
]
|
||||
if folder in ["Trash"]:
|
||||
entries += [
|
||||
@@ -83,19 +83,3 @@ def listing_menu(selection, folder, perms):
|
||||
return render_to_string("main/menu.html",
|
||||
{"selection" : selection, "entries" : entries,
|
||||
"perms" : perms})
|
||||
|
||||
@register.simple_tag
|
||||
def render_headers(headers):
|
||||
fields = ["Subject", "From", "To", "Cc", "Date"]
|
||||
result = []
|
||||
for f in fields:
|
||||
if not f in headers.keys():
|
||||
continue
|
||||
try:
|
||||
result += [{"name" : f, "value" : getattr(IMAPheader, "parse_%s" % f.lower())(headers[f])}]
|
||||
except AttributeError:
|
||||
result += [{"name" : f, "value" : headers[f]}]
|
||||
return render_to_string("webmail/headers.html", {
|
||||
"headers" : result
|
||||
})
|
||||
|
||||
|
||||
+13
-1
@@ -3,6 +3,7 @@ import time
|
||||
import re
|
||||
import lxml
|
||||
import lxml.html
|
||||
from lxml import etree
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import Template, Context
|
||||
from mailng.lib import _render, decode
|
||||
@@ -124,6 +125,7 @@ class Email(object):
|
||||
def __init__(self, msg, mode="plain", links="0"):
|
||||
self.attached_map = {}
|
||||
contents = {"html" : "", "plain" : ""}
|
||||
self.headers = []
|
||||
for part in msg.walk():
|
||||
if part.get_content_maintype() == 'multipart':
|
||||
continue
|
||||
@@ -166,6 +168,11 @@ class Email(object):
|
||||
return map[m.group(1)]
|
||||
return url
|
||||
|
||||
def render_headers(self):
|
||||
return render_to_string("webmail/headers.html", {
|
||||
"headers" : self.headers
|
||||
})
|
||||
|
||||
def render(self, request):
|
||||
return _render(request, 'common/getmailcontent.html', {
|
||||
"body" : self.body, "pre" : self.pre
|
||||
@@ -182,6 +189,11 @@ class Email(object):
|
||||
html.rewrite_links(lambda x: None)
|
||||
else:
|
||||
html.rewrite_links(self.map_cid)
|
||||
body = lxml.html.tostring(html)
|
||||
body = html.find("body")
|
||||
if not body:
|
||||
body = lxml.html.tostring(html)
|
||||
else:
|
||||
body = lxml.html.tostring(body)
|
||||
body = re.sub("<(/?)body", lambda m: "<%sdiv" % m.group(1), body)
|
||||
body = Template(decode(body)).render({})
|
||||
return (False, body)
|
||||
|
||||
+8
-1
@@ -5,6 +5,8 @@ from django.template import Template, Context
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
class Column:
|
||||
sortable = True
|
||||
|
||||
def __init__(self, name, **kwargs):
|
||||
self.name = name
|
||||
for k, v in kwargs.iteritems():
|
||||
@@ -26,8 +28,13 @@ class SelectionColumn(Column):
|
||||
% (self.name, value)
|
||||
|
||||
class ImgColumn(Column):
|
||||
sortable = False
|
||||
|
||||
def __str__(self):
|
||||
return ""
|
||||
try:
|
||||
return getattr(self, "header")
|
||||
except AttributeError:
|
||||
return ""
|
||||
|
||||
def render(self, value):
|
||||
return "<img src='%s' />" % value
|
||||
|
||||
+15
-5
@@ -1,9 +1,8 @@
|
||||
window.addEvent('domready', function(){
|
||||
parse_menubar('topmenubar');
|
||||
parse_menubar('menubar');
|
||||
/*SqueezeBox.assign($$('a.boxed'), {
|
||||
SqueezeBox.assign($$('a.boxed'), {
|
||||
parse: 'rel',
|
||||
});*/
|
||||
});
|
||||
});
|
||||
|
||||
callbacks = {};
|
||||
@@ -12,6 +11,10 @@ register_callback = function(name, callback) {
|
||||
callbacks[name] = callback;
|
||||
}
|
||||
|
||||
get_callback = function(name) {
|
||||
return callbacks[name];
|
||||
}
|
||||
|
||||
current_anchor = null;
|
||||
|
||||
check_anchor = function() {
|
||||
@@ -134,9 +137,16 @@ function HashWrapper(deflocation) {
|
||||
|
||||
parse_menubar = function(id) {
|
||||
if ($(id)) {
|
||||
$(id).getElements('li.dropdown').each(function( elem ){
|
||||
$(id).getElements('li.dropdown').each(function(elem){
|
||||
var list = elem.getElement('ul.links');
|
||||
var myFx = new Fx.Slide(list).hide();
|
||||
var myFx = new Fx.Slide(list, {
|
||||
duration: 200
|
||||
}).hide();
|
||||
|
||||
myFx.addEvent("start", function() {
|
||||
this.wrapper.setStyle("clear", "both");
|
||||
});
|
||||
|
||||
elem.addEvents({
|
||||
'mouseenter' : function(){
|
||||
myFx.cancel();
|
||||
|
||||
+216
-124
@@ -23,8 +23,8 @@ provides: [MooTools.More]
|
||||
*/
|
||||
|
||||
MooTools.More = {
|
||||
'version': '1.2.4.2',
|
||||
'build': 'bd5a93c0913cce25917c48cbdacde568e15e02ef'
|
||||
'version': '1.2.4.4',
|
||||
'build': '6f6057dc645fdb7547689183b2311063bd653ddf'
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -207,7 +207,8 @@ provides: [Element.Delegation]
|
||||
|
||||
...
|
||||
*/
|
||||
(function(){
|
||||
|
||||
(function(addEvent, removeEvent){
|
||||
|
||||
var match = /(.*?):relay\(([^)]+)\)$/,
|
||||
combinators = /[+>~\s]/,
|
||||
@@ -234,9 +235,6 @@ provides: [Element.Delegation]
|
||||
return null;
|
||||
};
|
||||
|
||||
var oldAddEvent = Element.prototype.addEvent,
|
||||
oldRemoveEvent = Element.prototype.removeEvent;
|
||||
|
||||
Element.implement({
|
||||
|
||||
addEvent: function(type, fn){
|
||||
@@ -249,10 +247,10 @@ provides: [Element.Delegation]
|
||||
if (el) this.fireEvent(type, [e, el], 0, el);
|
||||
}.bind(this);
|
||||
monitors[type] = monitor;
|
||||
oldAddEvent.call(this, splitted.event, monitor);
|
||||
addEvent.call(this, splitted.event, monitor);
|
||||
}
|
||||
}
|
||||
return oldAddEvent.apply(this, arguments);
|
||||
return addEvent.apply(this, arguments);
|
||||
},
|
||||
|
||||
removeEvent: function(type, fn){
|
||||
@@ -261,19 +259,18 @@ provides: [Element.Delegation]
|
||||
var events = this.retrieve('events');
|
||||
if (!events || !events[type] || (fn && !events[type].keys.contains(fn))) return this;
|
||||
|
||||
if (fn) oldRemoveEvent.apply(this, [type, fn]);
|
||||
else oldRemoveEvent.apply(this, type);
|
||||
if (fn) removeEvent.apply(this, [type, fn]);
|
||||
else removeEvent.apply(this, type);
|
||||
|
||||
events = this.retrieve('events');
|
||||
if (events && events[type] && events[type].length == 0){
|
||||
if (events && events[type] && events[type].keys.length == 0){
|
||||
var monitors = this.retrieve('$moo:delegateMonitors', {});
|
||||
oldRemoveEvent.apply(this, [splitted.event, monitors[type]]);
|
||||
removeEvent.apply(this, [splitted.event, monitors[type]]);
|
||||
delete monitors[type];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
return oldRemoveEvent.apply(this, arguments);
|
||||
return removeEvent.apply(this, arguments);
|
||||
},
|
||||
|
||||
fireEvent: function(type, args, delay, bind){
|
||||
@@ -287,7 +284,7 @@ provides: [Element.Delegation]
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
})(Element.prototype.addEvent, Element.prototype.removeEvent);
|
||||
|
||||
/*
|
||||
---
|
||||
@@ -373,7 +370,7 @@ provides: [Fx.Accordion]
|
||||
...
|
||||
*/
|
||||
|
||||
var Accordion = Fx.Accordion = new Class({
|
||||
Fx.Accordion = new Class({
|
||||
|
||||
Extends: Fx.Elements,
|
||||
|
||||
@@ -395,10 +392,14 @@ var Accordion = Fx.Accordion = new Class({
|
||||
},
|
||||
|
||||
initialize: function(){
|
||||
var params = Array.link(arguments, {'container': Element.type, 'options': Object.type, 'togglers': $defined, 'elements': $defined});
|
||||
var params = Array.link(arguments, {
|
||||
'container': Element.type, //deprecated
|
||||
'options': Object.type,
|
||||
'togglers': $defined,
|
||||
'elements': $defined
|
||||
});
|
||||
this.parent(params.elements, params.options);
|
||||
this.togglers = $$(params.togglers);
|
||||
this.container = document.id(params.container);
|
||||
this.previous = -1;
|
||||
this.internalChain = new Chain();
|
||||
if (this.options.alwaysHide) this.options.wait = true;
|
||||
@@ -422,7 +423,8 @@ var Accordion = Fx.Accordion = new Class({
|
||||
for (var fx in this.effects) el.setStyle(fx, 0);
|
||||
}
|
||||
}, this);
|
||||
if ($chk(this.options.display)) this.display(this.options.display, this.options.initialDisplayFx);
|
||||
if ($chk(this.options.display) || this.options.initialDisplayFx === false) this.display(this.options.display, this.options.initialDisplayFx);
|
||||
if (this.options.fixedHeight !== false) this.options.returnHeightToAuto = false;
|
||||
this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
|
||||
},
|
||||
|
||||
@@ -492,6 +494,37 @@ var Accordion = Fx.Accordion = new Class({
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
Compatibility with 1.2.0
|
||||
*/
|
||||
var Accordion = new Class({
|
||||
|
||||
Extends: Fx.Accordion,
|
||||
|
||||
initialize: function(){
|
||||
this.parent.apply(this, arguments);
|
||||
var params = Array.link(arguments, {'container': Element.type});
|
||||
this.container = params.container;
|
||||
},
|
||||
|
||||
addSection: function(toggler, element, pos){
|
||||
toggler = document.id(toggler);
|
||||
element = document.id(element);
|
||||
var test = this.togglers.contains(toggler);
|
||||
var len = this.togglers.length;
|
||||
if (len && (!test || pos)){
|
||||
pos = $pick(pos, len - 1);
|
||||
toggler.inject(this.togglers[pos], 'before');
|
||||
element.inject(toggler, 'after');
|
||||
} else if (this.container && !test){
|
||||
toggler.inject(this.container);
|
||||
element.inject(this.container);
|
||||
}
|
||||
return this.parent.apply(this, arguments);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
---
|
||||
|
||||
@@ -519,12 +552,14 @@ Fx.Slide = new Class({
|
||||
|
||||
options: {
|
||||
mode: 'vertical',
|
||||
wrapper: false,
|
||||
hideOverflow: true
|
||||
},
|
||||
|
||||
initialize: function(element, options){
|
||||
this.addEvent('complete', function(){
|
||||
this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
|
||||
if (this.open) this.wrapper.setStyle('height', '');
|
||||
if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
|
||||
}, true);
|
||||
this.element = this.subject = document.id(element);
|
||||
@@ -532,6 +567,7 @@ Fx.Slide = new Class({
|
||||
var wrapper = this.element.retrieve('wrapper');
|
||||
var styles = this.element.getStyles('margin', 'position', 'overflow');
|
||||
if (this.options.hideOverflow) styles = $extend(styles, {overflow: 'hidden'});
|
||||
if (this.options.wrapper) wrapper = document.id(this.options.wrapper).setStyles(styles);
|
||||
this.wrapper = wrapper || new Element('div', {
|
||||
styles: styles
|
||||
}).wraps(this.element);
|
||||
@@ -741,7 +777,8 @@ var Drag = new Class({
|
||||
if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
|
||||
else this.value.now[z] = this.element[this.options.modifiers[z]];
|
||||
if (this.options.invert) this.value.now[z] *= -1;
|
||||
this.mouse.pos[z] = event.page[z] - this.value.now[z];
|
||||
/*this.mouse.pos[z] = event.page[z] - this.value.now[z];*/
|
||||
this.mouse.pos[z] = this.value.now[z];
|
||||
if (limit && limit[z]){
|
||||
for (var i = 2; i--; i){
|
||||
if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
|
||||
@@ -871,7 +908,7 @@ Drag.Move = new Class({
|
||||
if (this.container && $type(this.container) != 'element')
|
||||
this.container = document.id(this.container.getDocument().body);
|
||||
|
||||
var styles = element.getStyles('left', 'right', 'position');
|
||||
var styles = element.getStyles('left', 'top', 'position');
|
||||
if (styles.left == 'auto' || styles.top == 'auto')
|
||||
element.setPosition(element.getPosition(element.getOffsetParent()));
|
||||
|
||||
@@ -1096,12 +1133,20 @@ var Sortables = new Class({
|
||||
getClone: function(event, element){
|
||||
if (!this.options.clone) return new Element('div').inject(document.body);
|
||||
if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
|
||||
return element.clone(true).setStyles({
|
||||
var clone = element.clone(true).setStyles({
|
||||
margin: '0px',
|
||||
position: 'absolute',
|
||||
visibility: 'hidden',
|
||||
'width': element.getStyle('width')
|
||||
}).inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
|
||||
});
|
||||
//prevent the duplicated radio inputs from unchecking the real one
|
||||
if (clone.get('html').test('radio')) {
|
||||
clone.getElements('input[type=radio]').each(function(input, i) {
|
||||
input.set('name', 'clone_' + i);
|
||||
});
|
||||
}
|
||||
|
||||
return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
|
||||
},
|
||||
|
||||
getDroppables: function(){
|
||||
@@ -1269,34 +1314,38 @@ var HtmlTable = new Class({
|
||||
return this;
|
||||
},
|
||||
|
||||
set: function(what, items) {
|
||||
var target = (what == 'headers') ? 'tHead' : 'tFoot';
|
||||
this[target.toLowerCase()] = (document.id(this.element[target]) || new Element(target.toLowerCase()).inject(this.element, 'top')).empty();
|
||||
var data = this.push(items, {}, this[target.toLowerCase()], what == 'headers' ? 'th' : 'td');
|
||||
if (what == 'headers') this.head = document.id(this.thead.rows[0]);
|
||||
else this.foot = document.id(this.thead.rows[0]);
|
||||
return data;
|
||||
},
|
||||
|
||||
setHeaders: function(headers){
|
||||
this.thead = (document.id(this.element.tHead) || new Element('thead').inject(this.element, 'top')).empty();
|
||||
this.push(headers, this.thead, 'th');
|
||||
this.head = document.id(this.thead.rows[0]);
|
||||
this.set('headers', headers);
|
||||
return this;
|
||||
},
|
||||
|
||||
setFooters: function(footers){
|
||||
this.tfoot = (document.id(this.element.tFoot) || new Element('tfoot').inject(this.element, 'top')).empty();
|
||||
this.push(footers, this.tfoot);
|
||||
this.foot = document.id(this.thead.rows[0]);
|
||||
this.set('footers', footers);
|
||||
return this;
|
||||
},
|
||||
|
||||
push: function(row, target, tag){
|
||||
push: function(row, rowProperties, target, tag){
|
||||
var tds = row.map(function(data){
|
||||
var td = new Element(tag || 'td', data.properties),
|
||||
type = data.content || data || '',
|
||||
element = document.id(type);
|
||||
|
||||
if(element) td.adopt(element);
|
||||
if($type(type) != 'string' && element) td.adopt(element);
|
||||
else td.set('html', type);
|
||||
|
||||
return td;
|
||||
});
|
||||
|
||||
return {
|
||||
tr: new Element('tr').inject(target || this.body).adopt(tds),
|
||||
tr: new Element('tr', rowProperties).inject(target || this.body).adopt(tds),
|
||||
tds: tds
|
||||
};
|
||||
}
|
||||
@@ -1331,8 +1380,8 @@ provides: [HtmlTable.Select]
|
||||
HtmlTable = Class.refactor(HtmlTable, {
|
||||
|
||||
options: {
|
||||
/*onRowSelect: $empty,
|
||||
onRowUnselect: $empty,*/
|
||||
/*onRowFocus: $empty,
|
||||
onRowUnfocus: $empty,*/
|
||||
useKeyboard: true,
|
||||
classRowSelected: 'table-tr-selected',
|
||||
classRowHovered: 'table-tr-hovered',
|
||||
@@ -1481,6 +1530,7 @@ HtmlTable = Class.refactor(HtmlTable, {
|
||||
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
---
|
||||
|
||||
@@ -1507,37 +1557,8 @@ provides: [Keyboard]
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
var parsed = {};
|
||||
var modifiers = ['shift', 'control', 'alt', 'meta'];
|
||||
var regex = /^(?:shift|control|ctrl|alt|meta)$/;
|
||||
|
||||
var parse = function(type, eventType){
|
||||
type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
|
||||
eventType = $1;
|
||||
return '';
|
||||
});
|
||||
|
||||
if (!parsed[type]){
|
||||
var key = '', mods = {};
|
||||
type.split('+').each(function(part){
|
||||
if (regex.test(part)) mods[part] = true;
|
||||
else key = part;
|
||||
});
|
||||
|
||||
mods.control = mods.control || mods.ctrl; // allow both control and ctrl
|
||||
var match = '';
|
||||
modifiers.each(function(mod){
|
||||
if (mods[mod]) match += mod + '+';
|
||||
});
|
||||
|
||||
parsed[type] = match + key;
|
||||
}
|
||||
|
||||
return eventType + ':' + parsed[type];
|
||||
};
|
||||
|
||||
this.Keyboard = new Class({
|
||||
var Keyboard = this.Keyboard = new Class({
|
||||
|
||||
Extends: Events,
|
||||
|
||||
@@ -1550,24 +1571,24 @@ provides: [Keyboard]
|
||||
*/
|
||||
defaultEventType: 'keydown',
|
||||
active: false,
|
||||
events: {}
|
||||
events: {},
|
||||
nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
|
||||
},
|
||||
|
||||
initialize: function(options){
|
||||
this.setOptions(options);
|
||||
//if this is the root manager, nothing manages it
|
||||
if (Keyboard.manager) Keyboard.manager.manage(this);
|
||||
this.setup();
|
||||
},
|
||||
|
||||
},
|
||||
setup: function(){
|
||||
this.addEvents(this.options.events);
|
||||
//if this is the root manager, nothing manages it
|
||||
if (Keyboard.manager && !this.manager) Keyboard.manager.manage(this);
|
||||
if (this.options.active) this.activate();
|
||||
},
|
||||
|
||||
handle: function(event, type){
|
||||
//Keyboard.stop(event) prevents key propagation
|
||||
if (!this.active || event.preventKeyboardPropagation) return;
|
||||
if (event.preventKeyboardPropagation) return;
|
||||
|
||||
var bubbles = !!this.manager;
|
||||
if (bubbles && this.activeKB){
|
||||
@@ -1579,59 +1600,64 @@ provides: [Keyboard]
|
||||
if (!bubbles && this.activeKB) this.activeKB.handle(event, type);
|
||||
},
|
||||
|
||||
addEvent: function(type, fn, internal) {
|
||||
return this.parent(parse(type, this.options.defaultEventType), fn, internal);
|
||||
addEvent: function(type, fn, internal){
|
||||
return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
|
||||
},
|
||||
|
||||
removeEvent: function(type, fn) {
|
||||
return this.parent(parse(type, this.options.defaultEventType), fn);
|
||||
},
|
||||
|
||||
activate: function(){
|
||||
this.active = true;
|
||||
return this.enable();
|
||||
},
|
||||
|
||||
deactivate: function(){
|
||||
this.active = false;
|
||||
return this.fireEvent('deactivate');
|
||||
removeEvent: function(type, fn){
|
||||
return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
|
||||
},
|
||||
|
||||
toggleActive: function(){
|
||||
return this[this.active ? 'deactivate' : 'activate']();
|
||||
},
|
||||
|
||||
enable: function(instance){
|
||||
activate: function(instance){
|
||||
if (instance) {
|
||||
//if we're stealing focus, store the last keyboard to have it so the relenquish command works
|
||||
if (instance != this.activeKB) this.previous = this.activeKB;
|
||||
//if we're enabling a child, assign it so that events are now passed to it
|
||||
this.activeKB = instance.fireEvent('activate');
|
||||
Keyboard.manager.fireEvent('changed');
|
||||
} else if (this.manager) {
|
||||
//else we're enabling ourselves, we must ask our parent to do it for us
|
||||
this.manager.enable(this);
|
||||
this.manager.activate(this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
deactivate: function(instance){
|
||||
if (instance) {
|
||||
if(instance === this.activeKB) {
|
||||
this.activeKB = null;
|
||||
instance.fireEvent('deactivate');
|
||||
Keyboard.manager.fireEvent('changed');
|
||||
}
|
||||
}
|
||||
else if (this.manager) {
|
||||
this.manager.deactivate(this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
relenquish: function(){
|
||||
if (this.previous) this.enable(this.previous);
|
||||
if (this.previous) this.activate(this.previous);
|
||||
},
|
||||
|
||||
//management logic
|
||||
manage: function(instance) {
|
||||
manage: function(instance){
|
||||
if (instance.manager) instance.manager.drop(instance);
|
||||
this.instances.push(instance);
|
||||
instance.manager = this;
|
||||
if (!this.activeKB) this.enable(instance);
|
||||
if (!this.activeKB) this.activate(instance);
|
||||
else this._disable(instance);
|
||||
},
|
||||
|
||||
_disable: function(instance) {
|
||||
_disable: function(instance){
|
||||
if (this.activeKB == instance) this.activeKB = null;
|
||||
},
|
||||
|
||||
drop: function(instance) {
|
||||
drop: function(instance){
|
||||
this._disable(instance);
|
||||
this.instances.erase(instance);
|
||||
},
|
||||
@@ -1639,35 +1665,81 @@ provides: [Keyboard]
|
||||
instances: [],
|
||||
|
||||
trace: function(){
|
||||
this.enableLog();
|
||||
var item = this;
|
||||
this.log('the following items have focus: ');
|
||||
while (item) {
|
||||
this.log(document.id(item.widget) || item.widget || item, 'active: ' + this.active);
|
||||
item = item.activeKB;
|
||||
}
|
||||
Keyboard.trace(this);
|
||||
},
|
||||
|
||||
each: function(fn){
|
||||
Keyboard.each(this, fn);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var parsed = {};
|
||||
var modifiers = ['shift', 'control', 'alt', 'meta'];
|
||||
var regex = /^(?:shift|control|ctrl|alt|meta)$/;
|
||||
|
||||
Keyboard.parse = function(type, eventType, ignore){
|
||||
if (ignore && ignore.contains(type.toLowerCase())) return type;
|
||||
|
||||
type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
|
||||
eventType = $1;
|
||||
return '';
|
||||
});
|
||||
|
||||
Keyboard.stop = function(event) {
|
||||
if (!parsed[type]){
|
||||
var key, mods = {};
|
||||
type.split('+').each(function(part){
|
||||
if (regex.test(part)) mods[part] = true;
|
||||
else key = part;
|
||||
});
|
||||
|
||||
mods.control = mods.control || mods.ctrl; // allow both control and ctrl
|
||||
|
||||
var keys = [];
|
||||
modifiers.each(function(mod){
|
||||
if (mods[mod]) keys.push(mod);
|
||||
});
|
||||
|
||||
if (key) keys.push(key);
|
||||
parsed[type] = keys.join('+');
|
||||
}
|
||||
|
||||
return eventType + ':' + parsed[type];
|
||||
};
|
||||
|
||||
Keyboard.each = function(keyboard, fn){
|
||||
var current = keyboard || Keyboard.manager;
|
||||
while (current){
|
||||
fn.run(current);
|
||||
current = current.activeKB;
|
||||
}
|
||||
};
|
||||
|
||||
Keyboard.stop = function(event){
|
||||
event.preventKeyboardPropagation = true;
|
||||
};
|
||||
|
||||
Keyboard.manager = new this.Keyboard({
|
||||
Keyboard.manager = new Keyboard({
|
||||
active: true
|
||||
});
|
||||
|
||||
Keyboard.trace = function(){
|
||||
Keyboard.manager.trace();
|
||||
Keyboard.trace = function(keyboard){
|
||||
keyboard = keyboard || Keyboard.manager;
|
||||
keyboard.enableLog();
|
||||
keyboard.log('the following items have focus: ');
|
||||
Keyboard.each(keyboard, function(current){
|
||||
keyboard.log(document.id(current.widget) || current.wiget || current);
|
||||
});
|
||||
};
|
||||
|
||||
var handler = function(event){
|
||||
var mods = '';
|
||||
var keys = [];
|
||||
modifiers.each(function(mod){
|
||||
if (event[mod]) mods += mod + '+';
|
||||
if (event[mod]) keys.push(mod);
|
||||
});
|
||||
Keyboard.manager.handle(event, event.type + ':' + mods + event.key);
|
||||
|
||||
if (!regex.test(event.key)) keys.push(event.key);
|
||||
Keyboard.manager.handle(event, event.type + ':' + keys.join('+'));
|
||||
};
|
||||
|
||||
document.addEvents({
|
||||
@@ -1676,13 +1748,27 @@ provides: [Keyboard]
|
||||
});
|
||||
|
||||
Event.Keys.extend({
|
||||
'shift': 16,
|
||||
'control': 17,
|
||||
'alt': 18,
|
||||
'capslock': 20,
|
||||
'pageup': 33,
|
||||
'pagedown': 34,
|
||||
'end': 35,
|
||||
'home': 36,
|
||||
'capslock': 20,
|
||||
'numlock': 144,
|
||||
'scrolllock': 145
|
||||
'scrolllock': 145,
|
||||
';': 186,
|
||||
'=': 187,
|
||||
',': 188,
|
||||
'-': Browser.Engine.Gecko ? 109 : 189,
|
||||
'.': 190,
|
||||
'/': 191,
|
||||
'`': 192,
|
||||
'[': 219,
|
||||
'\\': 220,
|
||||
']': 221,
|
||||
"'": 222
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -1727,7 +1813,8 @@ var Scroller = new Class({
|
||||
initialize: function(element, options){
|
||||
this.setOptions(options);
|
||||
this.element = document.id(element);
|
||||
this.listener = ($type(this.element) != 'element') ? document.id(this.element.getDocument().body) : this.element;
|
||||
this.docBody = document.id(this.element.getDocument().body);
|
||||
this.listener = ($type(this.element) != 'element') ? this.docBody : this.element;
|
||||
this.timer = null;
|
||||
this.bound = {
|
||||
attach: this.attach.bind(this),
|
||||
@@ -1769,14 +1856,15 @@ var Scroller = new Class({
|
||||
scroll: function(){
|
||||
var size = this.element.getSize(),
|
||||
scroll = this.element.getScroll(),
|
||||
pos = this.element.getOffsets(),
|
||||
pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0},
|
||||
scrollSize = this.element.getScrollSize(),
|
||||
change = {x: 0, y: 0};
|
||||
for (var z in this.page){
|
||||
if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0)
|
||||
if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0) {
|
||||
change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
|
||||
else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z])
|
||||
} else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]) {
|
||||
change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
|
||||
}
|
||||
}
|
||||
if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
|
||||
}
|
||||
@@ -1838,21 +1926,20 @@ this.Tips = new Class({
|
||||
hideDelay: 100,
|
||||
className: 'tip-wrap',
|
||||
offset: {x: 16, y: 16},
|
||||
windowPadding: {x:0, y:0},
|
||||
fixed: false
|
||||
},
|
||||
|
||||
initialize: function(){
|
||||
var params = Array.link(arguments, {options: Object.type, elements: $defined});
|
||||
this.setOptions(params.options);
|
||||
document.id(this);
|
||||
|
||||
if (params.elements) this.attach(params.elements);
|
||||
this.container = new Element('div', {'class': 'tip'});
|
||||
},
|
||||
|
||||
toElement: function(){
|
||||
if (this.tip) return this.tip;
|
||||
|
||||
this.container = new Element('div', {'class': 'tip'});
|
||||
|
||||
return this.tip = new Element('div', {
|
||||
'class': this.options.className,
|
||||
styles: {
|
||||
@@ -1916,8 +2003,10 @@ this.Tips = new Class({
|
||||
}, this);
|
||||
|
||||
$clear(this.timer);
|
||||
this.timer = this.show.delay(this.options.showDelay, this, element);
|
||||
this.position((this.options.fixed) ? {page: element.getPosition()} : event);
|
||||
this.timer = (function(){
|
||||
this.show(this, element);
|
||||
this.position((this.options.fixed) ? {page: element.getPosition()} : event);
|
||||
}).delay(this.options.showDelay, this);
|
||||
},
|
||||
|
||||
elementLeave: function(event, element){
|
||||
@@ -1927,11 +2016,10 @@ this.Tips = new Class({
|
||||
},
|
||||
|
||||
fireForParent: function(event, element){
|
||||
if (!element) return;
|
||||
parentNode = element.getParent();
|
||||
if (parentNode == document.body) return;
|
||||
if (parentNode.retrieve('tip:enter')) parentNode.fireEvent('mouseenter', event);
|
||||
else this.fireForParent(parentNode, event);
|
||||
element = element.getParent();
|
||||
if (!element || element == document.body) return;
|
||||
if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
|
||||
else this.fireForParent(event, element);
|
||||
},
|
||||
|
||||
elementMove: function(event, element){
|
||||
@@ -1939,6 +2027,8 @@ this.Tips = new Class({
|
||||
},
|
||||
|
||||
position: function(event){
|
||||
if (!this.tip) document.id(this);
|
||||
|
||||
var size = window.getSize(), scroll = window.getScroll(),
|
||||
tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
|
||||
props = {x: 'left', y: 'top'},
|
||||
@@ -1946,7 +2036,7 @@ this.Tips = new Class({
|
||||
|
||||
for (var z in props){
|
||||
obj[props[z]] = event.page[z] + this.options.offset[z];
|
||||
if ((obj[props[z]] + tip[z] - scroll[z]) > size[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
|
||||
if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
|
||||
}
|
||||
|
||||
this.tip.setStyles(obj);
|
||||
@@ -1958,10 +2048,12 @@ this.Tips = new Class({
|
||||
},
|
||||
|
||||
show: function(element){
|
||||
if (!this.tip) document.id(this);
|
||||
this.fireEvent('show', [this.tip, element]);
|
||||
},
|
||||
|
||||
hide: function(element){
|
||||
if (!this.tip) document.id(this);
|
||||
this.fireEvent('hide', [this.tip, element]);
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +169,14 @@ div#footer {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.menubardropdown ul {
|
||||
background: white;
|
||||
width: 120px;
|
||||
border-bottom: 5px solid #ccc;
|
||||
border-right: 5px solid #ccc;
|
||||
border-left: 5px solid #ccc;
|
||||
}
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
@@ -185,20 +193,32 @@ div#footer {
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
clear: both;
|
||||
z-index: 2;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.dropdown li {
|
||||
padding: 5px;
|
||||
width: 170px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown ul {
|
||||
display: block;
|
||||
background: #ffffcc;
|
||||
width: 150px;
|
||||
height: 90px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
padding-top: 10px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.dropdown a, .dropdown a:visited {
|
||||
color: black !important;
|
||||
.topdropdown ul {
|
||||
background: #153F65;
|
||||
border-bottom: 5px solid #333;
|
||||
border-right: 5px solid #333;
|
||||
border-left: 5px solid #333;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.dropdown ul li {
|
||||
display: block;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
@@ -248,17 +268,17 @@ th, td {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
table th {
|
||||
th {
|
||||
background: #CEDBEC;
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
table th.right {
|
||||
th.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table td {
|
||||
td {
|
||||
border-bottom: 1px solid #C8D6E8;
|
||||
/*padding: 5px;*/
|
||||
}
|
||||
|
||||
@@ -17,22 +17,19 @@ html > body #listing {
|
||||
}
|
||||
|
||||
#navbar {
|
||||
float: right;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: white;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
/*border-top: 1px solid #C8D6E8;*/
|
||||
display: none;
|
||||
}
|
||||
|
||||
#navbar label {
|
||||
color: white;
|
||||
margin-right: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#navbar img {
|
||||
#navbar img{
|
||||
margin-top: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
@@ -57,5 +54,6 @@ window.addEvent("domready", function() {
|
||||
{% block leftcol %}{% endblock %}
|
||||
<div id="listing">{{ listing }}</div>
|
||||
<div class="spacer"></div>
|
||||
<div id="navbar"></div>
|
||||
{% endblock %}
|
||||
{% block footer %}<div id="navbar"></div>{% endblock %}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<thead>
|
||||
<tr>
|
||||
{% for c in table.columns %}
|
||||
{% if c.sortable %}
|
||||
<th id="{{ c.name }}" class="sortable" width="{{ c.width }}">
|
||||
{% else %}
|
||||
<th id="{{ c.name }}" width="{{ c.width }}">
|
||||
{% endif %}
|
||||
{{ c|safe }}
|
||||
<span style="float: right"><img src="" style="display:none" /></span>
|
||||
<span style="float: right"><img src=""/></span>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
<ul>
|
||||
{% for entry in entries %}
|
||||
{% if entry.menu %}
|
||||
<li class="dropdown">
|
||||
<li class="dropdown {{ entry.class }}">
|
||||
<img src="{{ entry.img }}" border="0" />
|
||||
<label>{{ entry.label }}</label>
|
||||
<ul class="links">
|
||||
{% for sentry in entry.menu %}
|
||||
<a href="{{ sentry.url }}" name="{{ sentry.name }}">
|
||||
<li>{{ sentry.label }}</li>
|
||||
</a>
|
||||
<li>
|
||||
<a href="{{ sentry.url }}" name="{{ sentry.name }}">{{ sentry.label }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
+12
-11
@@ -31,21 +31,21 @@
|
||||
</li>
|
||||
{% loadextmenu perms %}
|
||||
{% if user.is_superuser or perms.admin.view_domains %}
|
||||
<li class="dropdown">
|
||||
<li class="dropdown topdropdown">
|
||||
<img src="/static/pics/settings.png" border="0" />
|
||||
<label>{% trans "Admin" %}</label>
|
||||
<ul class="links">
|
||||
{% url admin.views.domains as dom_url %}
|
||||
<a href="{{ dom_url }}">
|
||||
<li><img src="/static/pics/domains.png" />{% trans "Domains" %}</li>
|
||||
</a>
|
||||
{% url admin.views.settings as settings_url %}
|
||||
<a href="{{ settings_url }}">
|
||||
<li>
|
||||
<li>
|
||||
<a href="{% url admin.views.domains %}">
|
||||
<img src="/static/pics/domains.png" />{% trans "Domains" %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url admin.views.settings %}">
|
||||
<img src="/static/pics/settings.png" border="0"/>
|
||||
{% trans "Settings" %}
|
||||
</li>
|
||||
</a>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -84,8 +84,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" class="uibar menubar">
|
||||
<div id="footer" class="uibar">
|
||||
|
||||
{% block footer %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user