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:
tonio
2010-03-04 17:55:28 +01:00
parent 720e10109b
commit cb1f62883b
17 changed files with 524 additions and 297 deletions
+1
View File
@@ -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)
+19 -2
View File
@@ -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
View File
@@ -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:
+5 -5
View File
@@ -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>
+2 -2
View File
@@ -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>
+108 -53
View File
@@ -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 %}
+43 -24
View File
@@ -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>
+1 -17
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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]);
}
+32 -12
View File
@@ -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;*/
}
+5 -7
View File
@@ -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 %}
+5 -1
View File
@@ -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>
+4 -4
View File
@@ -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
View File
@@ -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">
&nbsp;
{% block footer %}{% endblock %}
</div>
</div>
</body>