wm-folders branch formalization

This commit is contained in:
Antoine Nguyen
2011-03-28 17:07:47 +02:00
26 changed files with 1059 additions and 517 deletions
+1 -1
View File
@@ -91,7 +91,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -16,6 +16,12 @@
<script type="text/javascript">
window.addEvent("domready", function() {
var cal = new Calendar({id_untildate: "Y-m-d"}, {tweak: {x: -100, y:-100}});
register_error_callback("armform", function() {
new Calendar({id_untildate: "Y-m-d"}, {
tweak: {x: -100, y:-100}
});
});
});
</script>
{% endblock %}
+6 -10
View File
@@ -7,13 +7,13 @@ from django.contrib.auth.decorators \
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from modoboa.lib import _render, _ctx_ok, _ctx_ko, is_not_localadmin
from modoboa.lib import _render, ajax_response, is_not_localadmin
from forms import *
from models import *
@login_required
@is_not_localadmin('boxerror')
def autoreply(request):
def autoreply(request, tplname="postfix_autoreply/autoreply.html"):
mb = Mailbox.objects.get(user=request.user.id)
try:
arm = ARmessage.objects.get(mbox=mb.id)
@@ -34,17 +34,13 @@ def autoreply(request):
arm.save()
messages.info(request, _("Auto reply message updated successfully."),
fail_silently=True)
ctx = _ctx_ok(reverse(userprefs.views.index))
else:
ctx = _ctx_ko("postfix_autoreply/autoreply.html", {
"form" : form, "error" : error
})
return HttpResponse(simplejson.dumps(ctx),
mimetype="application/json")
return ajax_response(request, url=reverse(userprefs.views.index))
return ajax_response(request, status="ko", template=tplname,
form=form, error=error)
form = ARmessageForm(instance=arm)
if arm is not None:
form.fields['untildate'].initial = arm.untildate
return _render(request, "postfix_autoreply/autoreply.html", {
return _render(request, tplname, {
"form" : form
})
+2
View File
@@ -7,3 +7,5 @@ class ComposeMailForm(forms.Form):
cc = forms.CharField(label=_("Cc"), widget=forms.Textarea(), required=False)
subject = forms.CharField(label=_("Subject"), max_length=255)
class FolderForm(forms.Form):
name = forms.CharField()
+23 -1
View File
@@ -257,7 +257,8 @@ class IMAPconnector(object):
def getfolders(self, user):
md_folders = [{"name" : "INBOX", "class" : "inbox"},
{"name" : 'Drafts'},
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
"class" : "drafts"},
{"name" : 'Junk'},
{"name" : parameters.get_user(user, "SENT_FOLDER")},
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
@@ -309,6 +310,27 @@ class IMAPconnector(object):
self.m.select(self._encodefolder(folder))
self.m.expunge()
def create_folder(self, name, parent=None):
if parent is not None:
name = "%s.%s" % (parent, name)
typ, data = self.m.create(self._encodefolder(name))
if typ == "NO":
return False
return True
def rename_folder(self, oldname, newname):
typ, data = self.m.rename(self._encodefolder(oldname),
self._encodefolder(newname))
if typ == "NO":
return False
return True
def delete_folder(self, name):
typ, data = self.m.delete(self._encodefolder(name))
if typ == "NO":
return False
return True
def getquota(self, folder):
status, data = self.m.getquotaroot(self._encodefolder(folder))
if status == "OK":
+3
View File
@@ -43,6 +43,9 @@ def init():
parameters.register_user("SENT_FOLDER", type="string", deflt="Sent",
label=_("Sent folder"),
help=_("Folder where copies of sent messages go"))
parameters.register_user("DRAFTS_FOLDER", type="string", deflt="Drafts",
label=_("Drafts folder"),
help=_("Folder where drafts go"))
parameters.register_user("SIGNATURE", type="text", deflt="",
label=_("Signature text"),
help=_("User defined email signature"))
+136
View File
@@ -0,0 +1,136 @@
#folders {
float: left;
width: 150px;
}
#folders2 {
border: 1px solid #ccc;
background-color: #eee;
position: absolute;
top: 100px;
left: 20px;
right: 20px;
bottom: 40px;
overflow-x: hidden;
overflow-y: auto;
}
#fdcreate {
position: absolute;
top: 370px;
left: 20px;
right: 20px;
text-align: center;
}
.box h3 {
font-size: small;
margin: 2px 0px 2px 0px;
text-align: center;
}
.box ul {
list-style: none;
padding: 0 0 0 5px;
margin: 0;
background-color: #FFFFFF;
}
.box li ul {
padding: 0 0 0 8px;
}
.box li {
margin: 0;
background-position: 2px 2px;
position: relative;
}
.box li.selected {
background-color: #FFFF77;
}
.box a:link, .box a:visited {
color: black;
text-decoration: none;
}
a.block {
display: block;
padding: 2px 10px 2px 30px;
}
.box a:hover {
color: #6AB0C5;
text-decoration: none;
}
.box img {
border: 0;
}
.box li.dragging {
background-color: #6AB0C5;
}
img.aligned {
float: right;
}
img.footer {
float: left;
margin-top: 10px;
margin-left: 18px;
}
li.inbox {
background-image: url("/static/pics/overview.png");
background-repeat: no-repeat;
}
li.trash {
background-image: url("/static/pics/trash.png");
background-repeat: no-repeat;
}
li.folder {
background-image: url("/static/pics/folder.png");
background-repeat: no-repeat;
}
li.drafts {
background-image: url("/static/pics/draft.png");
background-repeat: no-repeat;
}
li.subfolders {
background-image: url("/static/pics/folder.png");
background-repeat: no-repeat;
}
ul.hidden {
display: none;
}
ul.visible {
display: block;
padding-bottom: 0;
}
div.clickbox {
position: absolute;
left: 2px;
top: 2px;
width: 14px;
height: 14px;
}
div.collapsed {
background-image: url("/static/pics/collapsed.png");
background-repeat: no-repeat;
}
div.expanded {
background-image: url("/static/pics/expanded.png");
background-repeat: no-repeat;
}
+23 -65
View File
@@ -12,11 +12,6 @@ html > body #listing {
bottom: 35px;
}
#folders {
float: left;
width: 150px;
}
/*thead tr {
position: relative;
top: expression(offsetParent.scrollTop);
@@ -50,60 +45,6 @@ html > body #listing {
-khtml-user-select: none; /* Webkit (Safari, Chrome) */
}
.box h3 {
font-size: small;
margin: 2px 0px 2px 0px;
text-align: center;
}
.box ul {
list-style: none;
padding: 5px 5px;
margin: 0;
}
.box li {
margin-bottom: 4px;
padding-left: 20px;
}
.box li.selected {
background-color: #FFFF77;
}
.box a:link, .box a:visited {
background: transparent;
color: black;
text-decoration: none;
margin-left: 5px;
}
.box a:hover {
color: #6AB0C5;
text-decoration: none;
}
.box img {
float: left;
border: 0;
}
li.inbox {
background: url("/static/pics/overview.png") no-repeat;
}
li.trash {
background: url("/static/pics/trash.png") no-repeat;
}
li.folder {
background: url("/static/pics/folder.png") no-repeat;
}
li.subfolders {
background: url("/static/pics/subfolders.png") no-repeat;
}
th[class*=sortable] {
cursor: pointer;
/*-moz-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;
@@ -124,16 +65,11 @@ th.selected {
position: static;
}
.box li.dragging {
background-color: #6AB0C5;
}
ul.hidden {
display: none;
}
ul.visible {
background-color: white;
display: block;
padding-bottom: 0;
}
@@ -219,4 +155,26 @@ textarea:hover {
#quotatext {
padding: 2px 0 0 2px;
float: left;
}
}
#fdmenu { border:1px solid #999; padding:0; background:#eee; width:150px; display:none; }
#fdmenu .separator { border-top:1px solid #999; }
#fdmenu ul { list-style-type:none; margin:0; padding:0; }
#fdmenu li { margin:0; padding:0; }
#fdmenu li a { display:block; padding:5px 10px 5px 5px; font-size:12px; text-decoration:none; font-family:tahoma,arial,sans-serif; color:#000; }
#fdmenu li a:hover { background-color:#ddd; }
#fdmenu img { float:left; margin: 0 5px 0 0; }
.folder_menu {
float: right;
opacity: 0.8;
}
.folder_btn {
float: right;
margin: 3px;
}
.folder_menubar {
clear: both;
}
+109
View File
@@ -0,0 +1,109 @@
/*
* Simple dropdown menu plugin used for folders manipulation.
*
*/
var FdMenu = new Class({
Implements: [Options],
options: {
modify_url: "",
delete_url: ""
},
add_entry: function(name, label, pic, url, onclick) {
var item = new Element("li");
var picpath = static_url("pics/" + pic);
this[name] = new Element("a")
.set("html", "<img src='" + picpath + "' /> " + label)
.addEvent("click", onclick.bind(this))
.inject(item);
item.inject(this.list);
},
initialize: function(options) {
this.setOptions(options);
this.container = new Element("div", {"id" : "fdmenu"})
.addClass("fdmenu");
this.list = new Element("ul");
this.add_entry("modbutton", gettext("Edit folder"),
"edit.png", this.options.modify_url, function(evt) {
evt.stop();
SqueezeBox.open(evt.target.get("href"), {
handler: "iframe",
size: {x: 350, y: 400}
});
$(document.body).fireEvent("click");
});
this.add_entry("delbutton", gettext("Delete folder"), "remove.png",
this.options.delete_url, function(evt) {
evt.stop();
$(document.body).fireEvent("click");
if (!confirm(gettext("Delete folder?"))) {
return;
}
new Request.JSON({
url: evt.target.get("href"),
method: "GET",
onSuccess: function(resp) {
if (resp.status == "ok") {
var dfolder = this.current_folder + "/";
infobox.info(gettext("Folder deleted."));
if (dfolder == current_anchor.base) {
var pfolder = ((this.current_folder.split(".")).slice(0, -1)).join(".");
if (pfolder == "") {
pfolder = "INBOX";
}
current_anchor.baseurl(pfolder, 1);
}
current_anchor.update.delay(500, current_anchor, 1);
} else {
infobox.error(gettext("Failed to delete folder"));
infobox.hide(2);
}
}.bind(this)
}).send();
}
);
this.list.inject(this.container);
this.shown = false;
$(document.body).addEvent("click", function() {
if (this.shown) {
this.hide();
}
}.bind(this));
//this.fx = new Fx.Slide(this.container);
},
show: function(origin, folder) {
var position = origin.getPosition();
this.container.setStyles({
position: "absolute",
top: position.y + 5,
left: position.x + 5,
display: "block"
});
this.container.inject(document.body);
this.shown = true;
this.current_folder = folder;
this.modbutton.set("href", this.options.modify_url + "?name=" + folder);
this.delbutton.set("href", this.options.delete_url + "?name=" + folder);
},
hide: function() {
this.container.setStyles({
display: "none"
});
this.container = this.container.dispose();
this.shown = false;
},
toggle: function(origin, folder) {
return (!this.shown) ? this.show(origin, folder) : this.hide();
}
});
+227
View File
@@ -0,0 +1,227 @@
/*
* Globals
*/
var rtimer = null;
/*
* Enables folders opening/closing
*
* Available for folders with subfolders.
*/
function init_folders_browsing() {
$$("div[class*=clickbox]").addEvent("click", function(evt) {
var parent = this.getParent();
var ul = parent.getFirst("ul[name=" + parent.get("name") + "]");
if (!$defined(ul)) {
return;
}
if (ul.hasClass("hidden")) {
this.removeClass("collapsed").addClass("expanded");
ul.removeClass("hidden").addClass("visible");
} else {
this.removeClass("expanded").addClass("collapsed");
ul.removeClass("visible").addClass("hidden");
}
evt.stop();
});
}
/*
* Select a particular folder
*/
function select_folder(obj) {
$$("a[name=loadfolder]").each(function(item) {
item.getParent().removeClass("selected");
item.getParent().addClass("droppable");
});
obj.getParent().removeClass("droppable");
obj.getParent().addClass("selected");
$$(obj.getParents("ul[class*=hidden]")).each(function(item) {
item.removeClass("hidden");
item.addClass("visible");
$$("li[name=" + item.get("name") + "]").each(function(item) {
item.setStyle("background",
"url('" + static_url("pics/folder-open.png") + "') no-repeat");
});
});
}
/*
* Callback of the 'viewmail' action
*/
function viewmail_cb(resp) {
wm_updatelisting(resp, "hidden");
$$("a[name=back]").addEvent("click", loadFolder);
$$("a[name=reply]").addEvent("click", reply_loader);
$$("a[name=replyall]").addEvent("click", replyall_loader);
$$("a[name=forward]").addEvent("click", forward_loader);
$$("a[name=delete]").addEvent("click", function(event) {
var lnk = event.target;
event.stop();
new Request.JSON({
url: lnk.get("href"), onSuccess: function(resp) {
current_anchor.parse_string(resp.next).update();
select_folder($$("a[href=" + current_anchor.getbaseurl() + "]"));
}
}).get();
});
$$("a[name=activate_links]").addEvent("click", function(evt) {
evt.stop();
current_anchor.setparam("links", "1").update();
});
$$("a[name=disable_links]").addEvent("click", function(evt) {
evt.stop();
current_anchor.setparam("links", "0").update();
});
setDivHeight("mailcontent", 5, 0);
}
/*
* Loader of the 'compose' action (called when the associated button
* is clicked)
*/
function compose_loader(event) {
event.stop();
current_anchor.baseurl("compose").update();
}
/*
* Callback of the 'compose' action
*/
function compose_callback(resp) {
wm_updatelisting(resp);
window.addEvent("resize", function(evt) {
setDivHeight("id_body", $("mailheader").getSize().y, 0);
});
window.fireEvent("resize");
var editormode = resp.editor;
var editorid = "id_body";
if (editormode == "html") {
var instance = CKEDITOR.instances[editorid];
if (instance) {
CKEDITOR.remove(instance);
}
CKEDITOR.replace(editorid, {
customConfig: static_url("js/editor_config.js"),
height: $(editorid).getSize().y
});
}
$$("a[name=back]").addEvent("click", loadFolder);
$$("a[name=sendmail]").addEvent("click", function(evt) {
evt.stop();
$("composemail").set("send", {
onSuccess: function(resp) {
resp = JSON.decode(resp);
if (resp.status == "ko") {
wm_updatelisting(resp);
window.fireEvent("resize");
if ($defined(resp.error)) {
infobox.error(resp.error);
}
return;
}
current_anchor.parse_string(resp.url, true).setparams(navparams);
current_anchor.update();
}
});
if (editormode == "html") {
CKEDITOR.instances[editorid].updateElement();
}
$("composemail").send();
});
}
/*
* Loader of the 'reply' action (called when the associated button
* is clicked).
*/
function reply_loader(event) {
event.stop();
location.hash += event.target.get("href");
}
/*
* Loader of the 'replyall' action (called when the associated button
* is clicked).
*/
function replyall_loader(event) {
event.stop();
location.hash += event.target.get("href") + "?all=1";
}
/*
* Loader of the 'forward' action (called when the associated button
* is clicked).
*/
function forward_loader(event) {
event.stop();
location.hash += event.target.get("href");
}
/*
* Onclick callback used to load the content of a particular
* folder. (activated when clicking on a folder's name)
*/
function loadFolder(event, obj) {
event.stop();
if (!$defined(obj)) {
obj = $(event.target);
}
current_anchor.parse_string(gethref(obj), true).setparams(navparams);
current_anchor.update();
}
function foldercallback(event) {
select_folder(event.target);
loadFolder(event, event.target);
}
/*
* Automatic folder listing refresh
*
* Called at a regular frequency to make an asynchronous request that
* retrieves a folder's content (ie. mails)
*/
function refreshFolder() {
var query = current_anchor.serialized.substring(1);
clearInterval(rtimer);
infobox.show(gettext("Reloading..."), {
profile : "gray",
spinner : true
});
new Request.JSON({url: query, noCache : true, onSuccess: function(resp) {
var callback = ($defined(resp.callback)) ? resp.callback : "default";
callbacks[callback](resp);
infobox.info(gettext("Done"));
infobox.hide(1);
}}).get();
}
/*
* Simple AJAX request
*
* Initialize the infobox with a loading message and wait for a JSON
* encoded answer. The infobox is cleared by the request callback
* function.
*/
function simple_request(url, params) {
infobox.show(gettext("Waiting..."), {
profile: "gray",
spinner: true
});
new Request.JSON({
url : url,
onSuccess : function(response) {
get_callback("folder")(response);
infobox.info(gettext("Done"));
infobox.hide(1);
}
}).get(params);
}
@@ -0,0 +1,45 @@
{% extends "layout_simple.html" %}
{% load i18n %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/webmail/folders.css" />
<script src="{{ MEDIA_URL }}js/form.js" type="text/javascript"></script>
<script src="{{ MEDIA_URL }}js/webmail/webmail.js" type="text/javascript"></script>
<script type="text/javascript">
window.addEvent("domready", function() {
init_folders_browsing();
$$("a[name=loadfolder]").addEvent("click", function(evt) {
evt.stop();
$$("a[name=loadfolder]").each(function(item) {
item.getParent().removeClass("selected");
});
this.getParent().addClass("selected");
});
register_extra_data_callback("{{ fname }}", function() {
var result = "";
$$("li[class*=selected]").each(function(item) {
var atag = item.getFirst("a");
result = atag.get("href");
});
return "parent_folder=" + result;
});
});
</script>
{% endblock %}
{% block content %}
<div class="title"><h1>{{ title }}</h1></div>
<form action="" method="POST" id="{{ fname }}" name="{{ fname }}">
<input type="hidden" name="oldname" value="{{ oldname }}" />
{% include "common/genericform.html" %}
<div class="row">
<label>{% trans "Parent folder" %}</label>
</div>
<div id="folders2" class="box">
{% include "webmail/folders.html" %}
</div>
<div id="fdcreate">
<input type="submit" name="submit" value='{{ submit_label }}'/>
</div>
</form>
{% endblock %}
@@ -1,6 +1,13 @@
{% load i18n %}
{% load webextras %}
{% if titlebar %}
<a href="{% url extensions.webmail.views.newfolder %}" class="boxed"
rel="{handler: 'iframe', size: {x: 350, y: 400}}">
<img class="aligned" src="{{MEDIA_URL}}pics/add.png"
alt="{% trans 'Add a new folder' %}"/>
</a>
<h3>{% trans "Folders" %}</h3>
{% endif %}
<ul>
{% print_folders folders selected %}
{% print_folders folders selected withunseen withmenu %}
</ul>
+47 -184
View File
@@ -1,8 +1,51 @@
{% extends "common/email_listing.html" %}
{% load i18n %}
{% block css %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/webmail/webmail.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/webmail/folders.css" />
<script type="text/javascript" src="{{ MEDIA_URL }}js/webmail/fdmenu.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/webmail/webmail.js"></script>
{{ block.super }}
<script type="text/javascript">
function wm_updatelisting(resp) {
window.removeEvents("resize");
updatelisting(resp);
if ($defined(resp.folders)) {
$("folders").set("html", resp.folders);
$$("li[class*=clickable]").removeEvents();
SqueezeBox.assign($$('a[class=boxed]'), {
parse: 'rel'
});
var fdbuttons = $$("img[class*=footer]");
var fdmenu = new FdMenu({
modify_url: "{% url extensions.webmail.views.editfolder %}",
delete_url: "{% url extensions.webmail.views.delfolder %}"
});
fdbuttons.addEvents({
click: function(evt) {
var parent = this.getParent();
var fdname = parent.getFirst("a[name=loadfolder]").get("href");
fdmenu.toggle(this, fdname);
evt.stop();
}
});
}
select_folder($$("a[href=" + current_anchor.getbaseurl() + "]"));
init_folders_browsing();
if ($defined(resp.quota) && resp.quota != "-1") {
quota_pb.set(resp.quota);
$("quotabox").setStyle("display", "block");
$("quotalabel").setStyle("display", "block");
}
$$("a[name=loadfolder]").addEvent("click", foldercallback);
$$("a[name=compose]").addEvent("click", compose_loader);
}
</script>
{% endblock %}
{% block js %}
quota_pb = new ProgressBar({
@@ -14,148 +57,12 @@
displayID: "quotatext"
});
wm_updatelisting = function(resp) {
window.removeEvents("resize");
updatelisting(resp);
if ($defined(resp.folders)) {
$("folders").set("html", resp.folders);
}
select($$("a[href=" + current_anchor.getbaseurl() + "]"));
$$("li[class*=clickable]").removeEvents();
$$("li[class*=clickable]").addEvent("click", function(evt) {
$$("ul[name=" + evt.target.get("name") + "]").each(function(item) {
if (item.hasClass("hidden")) {
evt.target.setStyle("background",
"url('{{ MEDIA_URL }}pics/folder-open.png') no-repeat");
item.removeClass("hidden").addClass("visible");
} else {
evt.target.setStyle("background",
"url('{{ MEDIA_URL }}pics/subfolders.png') no-repeat");
item.removeClass("visible").addClass("hidden");
}
});
evt.stop();
});
if ($defined(resp.quota) && resp.quota != "-1") {
quota_pb.set(resp.quota);
$("quotabox").setStyle("display", "block");
$("quotalabel").setStyle("display", "block");
}
$$("a[name=loadfolder]").addEvent("click", foldercallback);
$$("a[name=compose]").addEvent("click", loadCompose);
};
select = function(obj) {
$$("a[name=loadfolder]").each(function(item) {
item.getParent().removeClass("selected");
item.getParent().addClass("droppable");
});
obj.getParent().removeClass("droppable");
obj.getParent().addClass("selected");
$$(obj.getParents("ul[class*=hidden]")).each(function(item) {
item.removeClass("hidden");
item.addClass("visible");
$$("li[name=" + item.get("name") + "]").each(function(item) {
item.setStyle("background", "url('{{ MEDIA_URL }}pics/folder-open.png') no-repeat");
});
});
};
register_callback("viewmail", function(resp) {
wm_updatelisting(resp, "hidden");
$$("a[name=back]").addEvent("click", loadFolder);
$$("a[name=reply]").addEvent("click", reply);
$$("a[name=replyall]").addEvent("click", replyall);
$$("a[name=forward]").addEvent("click", forward);
$$("a[name=delete]").addEvent("click", function(event) {
var lnk = event.target;
event.stop();
new Request.JSON({url: lnk.get("href"), onSuccess: function(resp) {
current_anchor.parse_string(resp.next).update();
select($$("a[href=" + current_anchor.getbaseurl() + "]"));
}}).get();
});
$$("a[name=activate_links]").addEvent("click", function(evt) {
evt.stop();
current_anchor.setparam("links", "1").update();
});
$$("a[name=disable_links]").addEvent("click", function(evt) {
evt.stop();
current_anchor.setparam("links", "0").update();
});
setDivHeight("mailcontent", 5, 0);
});
compose_callback = function(resp) {
wm_updatelisting(resp);
window.addEvent("resize", function(evt) {
setDivHeight("id_body", $("mailheader").getSize().y, 0);
});
window.fireEvent("resize");
var editormode = resp.editor;
var editorid = "id_body";
if (editormode == "html") {
var instance = CKEDITOR.instances[editorid];
if (instance) {
CKEDITOR.remove(instance);
}
CKEDITOR.replace(editorid, {
customConfig: "{{ MEDIA_URL }}/js/editor_config.js",
height: $(editorid).getSize().y
});
}
$$("a[name=back]").addEvent("click", loadFolder);
$$("a[name=sendmail]").addEvent("click", function(evt) {
evt.stop();
$("composemail").set("send", {
onSuccess: function(resp) {
resp = JSON.decode(resp);
if (resp.status == "ko") {
wm_updatelisting(resp);
window.fireEvent("resize");
if ($defined(resp.error)) {
infobox.error(resp.error);
}
return;
}
current_anchor.parse_string(resp.url, true).setparams(navparams);
current_anchor.update();
}
});
if (editormode == "html") {
CKEDITOR.instances[editorid].updateElement();
}
$("composemail").send();
});
};
loadCompose = function(event) {
event.stop();
current_anchor.baseurl("compose").update();
};
register_callback("viewmail", viewmail_cb);
register_callback("compose", compose_callback);
reply = function(event) {
event.stop();
location.hash += event.target.get("href");
};
register_callback("reply", compose_callback);
replyall = function(event) {
event.stop();
location.hash += event.target.get("href") + "?all=1";
};
register_callback("replyall", compose_callback);
forward = function(event) {
event.stop();
location.hash += event.target.get("href");
};
register_callback("forward", compose_callback);
var rtimer = null;
window.addEvent("pageRefresh", function() {
if (rtimer) {
clearInterval(rtimer);
@@ -163,30 +70,6 @@
}
});
refreshFolder = function() {
var query = current_anchor.serialized.substring(1);
clearInterval(rtimer);
infobox.show('{% trans "Reloading..." %}', {
profile : "gray",
spinner : true
});
new Request.JSON({url: query, noCache : true, onSuccess: function(resp) {
callback = ($defined(resp.callback)) ? resp.callback : "default";
callbacks[callback](resp);
infobox.info('{% trans "Done" %}');
infobox.hide(1);
}}).get();
};
loadFolder = function(event, obj) {
event.stop();
if (!$defined(obj)) {
obj = $(event.target);
}
current_anchor.parse_string(gethref(obj), true).setparams(navparams);
current_anchor.update();
};
register_callback("folder", function(resp) {
wm_updatelisting(resp);
rtimer = refreshFolder.periodical({{ refreshrate }});
@@ -194,7 +77,7 @@
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", "{{ MEDIA_URL }}pics/" + src);
th.getElement("img").set("src", static_url("pics/" + src));
th.getElement("img").setStyle("display", "block");
th.addClass("selected");
}
@@ -299,20 +182,6 @@
});
});
simple_request = function(url, params) {
infobox.show(gettext("Waiting..."), {
profile: "gray",
spinner: true
});
new Request.JSON({url : url,
onSuccess : function(response) {
get_callback("folder")(response);
infobox.info(gettext("Done"));
infobox.hide(1);
}
}).get(params);
};
buildmarkrequest = function(evt) {
evt.stop();
if (!emails_table._selectedRows.length) {
@@ -360,12 +229,6 @@
current_anchor.setparams(navparams).update();
});
});
foldercallback = function(event) {
select(event.target);
loadFolder(event, event.target);
};
{% endblock %}
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
{% block leftcol %}
+22 -15
View File
@@ -116,29 +116,36 @@ def listing_menu(selection, folder, user):
return menu + searchbar
@register.simple_tag
def print_folders(folders, selected=None):
def print_folders(folders, selected=None, withunseen=False, withmenu=False):
result = ""
for fd in folders:
if fd.has_key("class"):
cssclass = fd["class"]
else:
cssclass = "folder"
name = ""
if fd.has_key("sub"):
cssclass += " clickable"
name = fd["path"]
cssclass = fd["class"] if fd.has_key("class") else "folder"
name = fd["path"] if fd.has_key("sub") else fd["name"]
label = fd["name"]
if selected == label:
if selected == name:
cssclass += " selected"
result += "<li name='%s' class='droppable %s'>\n" % (name, cssclass)
cssclass = ""
if fd.has_key("unseen"):
if fd.has_key("sub") and len(fd["sub"]):
if selected is not None and selected != name and selected.count(name):
ul_state = "visible"
div_state = "expanded"
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
cssclass = "block"
if withunseen and fd.has_key("unseen"):
label += " (%d)" % fd["unseen"]
cssclass = "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)
if fd.has_key("sub") and len(fd["sub"]):
result += "<ul name='%s' class='hidden'>" % (fd["path"]) + print_folders(fd["sub"], selected) + "</ul>\n"
result += "<ul name='%s' class='%s'>" % (fd["path"], ul_state) \
+ print_folders(fd["sub"], selected, withunseen, withmenu) + "</ul>\n"
result += "</li>\n"
return result
+3
View File
@@ -7,6 +7,9 @@ urlpatterns = patterns('modoboa.extensions.webmail.views',
(r'^mark/(?P<name>.+)/$', "mark"),
(r'^empty/(?P<name>.+)/$', "empty"),
(r'^compact/(?P<name>.+)/$', "compact"),
(r'^newfolder/$', "newfolder"),
(r'^editfolder/$', 'editfolder'),
(r'^delfolder/$', 'delfolder'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/part/$', 'getattachment'),
(r'^(?P<fdname>.+)/(?P<mail_id>[\w\-\+]+)/delete/$', 'delete'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/content/$', 'getmailcontent'),
+101 -3
View File
@@ -10,7 +10,8 @@ from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from modoboa.admin.models import Mailbox
from modoboa.lib import parameters, _render, _render_error, \
getctx, is_not_localadmin, _render_to_string, split_mailbox
getctx, is_not_localadmin, _render_to_string, split_mailbox, \
ajax_response
from modoboa.lib.email_listing import parse_search_parameters, Paginator
from lib import *
from forms import *
@@ -63,9 +64,12 @@ def __render_common_components(request, folder_name, lst=None, content=None, men
% _("This folder contains no messages")
ret = {
"folders" : render_to_string("webmail/folders.html", {
"folders" : _render_to_string(request, "webmail/folders.html", {
"titlebar" : True,
"selected" : request.session["folder"],
"folders" : mbc.getfolders(request.user)
"folders" : mbc.getfolders(request.user),
"withmenu" : True,
"withunseen" : True
}),
"menu" : menu,
"listing" : content,
@@ -423,3 +427,97 @@ def forward(request, folder, mail_id):
email = ForwardModifier(msg, request.user, form, addrfull=True, links="1")
return render_compose(request, form, reverse(forward, args=[folder, mail_id]),
email)
def separate_folder(fullname, sep="."):
"""Split a folder name
If a separator is found in fullname, this function returns the
corresponding name and parent folder name.
"""
if fullname.count("."):
parts = fullname.split(sep)
name = parts[-1]
parent = sep.join(parts[0:len(parts) - 1])
return name, parent
return fullname, None
@login_required
@is_not_localadmin()
def newfolder(request, tplname="webmail/folder.html"):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
ctx = {"title" : _("Create a new folder"),
"fname" : "newfolder",
"submit_label" : _("Create"),
"withmenu" : False,
"withunseen" : False}
ctx["folders"] = mbc.getfolders(request.user)
if request.method == "POST":
form = FolderForm(request.POST)
if form.is_valid():
pf = request.POST.has_key("parent_folder") \
and request.POST["parent_folder"] or None
if mbc.create_folder(form.cleaned_data["name"], pf):
return ajax_response(request, ajaxnav=True)
ctx["form"] = form
ctx["selected"] = None
return ajax_response(request, status="ko", template=tplname, **ctx)
ctx["form"] = FolderForm()
ctx["selected"] = None
return _render(request, tplname, ctx)
@login_required
@is_not_localadmin()
def editfolder(request, tplname="webmail/folder.html"):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
ctx = {"title" : _("Edit folder"),
"fname" : "editfolder",
"submit_label" : _("Update"),
"withmenu" : False,
"withunseen" : False}
ctx["folders"] = mbc.getfolders(request.user)
if request.method == "POST":
form = FolderForm(request.POST)
if form.is_valid():
pf = request.POST.has_key("parent_folder") \
and request.POST["parent_folder"] or None
ctx["selected"] = pf
oldname, oldparent = separate_folder(request.POST["oldname"])
try:
extra = {}
if form.cleaned_data["name"] != oldname \
or (pf is not None and pf != oldparent):
newname = form.cleaned_data["name"] if pf is None \
else "%s.%s" % (pf, form.cleaned_data["name"])
if not mbc.rename_folder(request.POST["oldname"], newname):
raise Exception
extra["url"] = newname
return ajax_response(request, ajaxnav=True, **extra)
except Exception:
pass
ctx["form"] = form
return ajax_response(request, status="ko", template=tplname, **ctx)
if not request.GET.has_key("name") or request.GET["name"] == "":
return
name = request.GET["name"]
ctx["oldname"] = name
name, parent = separate_folder(name)
ctx["form"] = FolderForm()
ctx["form"].fields["name"].initial = name
ctx["selected"] = parent
return _render(request, tplname, ctx)
@login_required
@is_not_localadmin()
def delfolder(request):
if not request.GET.has_key("name") or request.GET["name"] == "":
return
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
if mbc.delete_folder(request.GET["name"]):
return ajax_response(request)
return ajax_response(request, status="ko")
+7 -5
View File
@@ -74,7 +74,7 @@ def getctx(status, level=1, callback=None, **kwargs):
ctx[kw] = v
return ctx
def ajax_response(request, status="ok", url=None, template=None, **kwargs):
def ajax_response(request, status="ok", url="", ajaxnav=False, template=None, **kwargs):
"""Ajax response shortcut
Simple shortcut that sends an JSON response. If a template is
@@ -94,10 +94,12 @@ def ajax_response(request, status="ok", url=None, template=None, **kwargs):
content = _render_to_string(request, template, ctx)
else:
content = ""
return HttpResponse(simplejson.dumps({"status" : status,
"content" : content,
"url" : url}),
mimetype="application/json")
jsonctx = {"status" : status, "content" : content}
if ajaxnav:
jsonctx["ajaxnav"] = True
if url != "":
jsonctx["url"] = url
return HttpResponse(simplejson.dumps(jsonctx), mimetype="application/json")
def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii'), charset=None):
if charset is not None:
+166 -142
View File
@@ -8,9 +8,26 @@ window.addEvent('domready', function(){
});
});
var media_url = "";
callbacks = {};
register_callback = function(name, callback) {
/*
* A simple function to initialize the value of the global variable
* 'media_url' (corresponding to django's MEDIA_URL variable).
*/
function set_media_url(url) {
media_url = url;
}
/*
* Shortcut function that construct an url from the media_url and the
* given value.
*/
function static_url(value) {
return media_url + value;
}
function register_callback(name, callback) {
callbacks[name] = callback;
};
@@ -18,170 +35,177 @@ get_callback = function(name) {
return callbacks[name];
};
current_anchor = null;
/*
* AJAX anchor based navigation
*/
var current_anchor = null;
check_anchor = function() {
if (current_anchor.serialized == location.hash
&& !$defined(current_anchor.force)) {
return;
}
delete(current_anchor.force);
current_anchor.from_string(location.hash);
if (!current_anchor.serialized) {
location.hash = current_anchor.deflocation;
return;
} else {
query = current_anchor.serialized.substring(1);
}
infobox.show(gettext("Loading..."), {
profile: "gray",
spinner: true
});
new Request.JSON({url: query, noCache : true, onSuccess: function(resp) {
var callback = ($defined(resp.callback)) ? resp.callback : "default";
callbacks[callback](resp);
infobox.info(gettext("Done"));
infobox.hide(1);
}}).get();
};
function check_anchor() {
if (current_anchor.serialized == location.hash
&& !$defined(current_anchor.force)) {
return;
}
delete(current_anchor.force);
current_anchor.from_string(location.hash);
if (!current_anchor.serialized) {
location.hash = current_anchor.deflocation;
return;
}
ajaxListener = function(deflocation, defcallback) {
current_anchor = new HashWrapper(deflocation);
register_callback("default", defcallback);
check_anchor.periodical(300);
var query = current_anchor.serialized.substring(1);
infobox.show(gettext("Loading..."), {
profile: "gray",
spinner: true
});
new Request.JSON({url: query, noCache : true, onSuccess: function(resp) {
var callback = ($defined(resp.callback)) ? resp.callback : "default";
callbacks[callback](resp);
infobox.info(gettext("Done"));
infobox.hide(1);
}}).get();
};
/*
*
*/
* Initialize the AJAX anchor based navigation
*/
function ajaxListener(deflocation, defcallback) {
current_anchor = new HashWrapper(deflocation);
register_callback("default", defcallback);
check_anchor.periodical(300);
};
/*
*
*/
function HashWrapper(deflocation) {
this.deflocation = deflocation;
this.params = $H();
this.base = '';
this.serialized = null;
this.deflocation = deflocation;
this.params = $H();
this.base = '';
this.serialized = null;
this.reset = function() {
this.base = null;
this.params.empty();
return this;
};
this.reset = function() {
this.base = null;
this.params.empty();
return this;
};
this.parse_string = function(value, reset) {
var splits = (value.charAt(0) == '#')
? value.substring(1).split('?') : value.split('?');
this.parse_string = function(value, reset) {
var splits = (value.charAt(0) == '#')
? value.substring(1).split('?') : value.split('?');
if (splits.length == 0) {
return this;
}
if (!$defined(reset)) {
reset = false;
}
if (reset) {
this.reset();
}
this.base = (splits[0] == "") ? this.deflocation : splits[0];
/*if (this.base[this.base.length - 1] != '/') {
this.base += '/';
}*/
var re = new RegExp("/$");
if (!this.base.match(re)) {
this.base += '/';
}
if (splits.length > 1) {
params = splits[1].split('&');
for (var i = 0; i < params.length; i++) {
tmp = params[i].split('=');
this.setparam(tmp[0], tmp[1]);
}
}
return this;
};
if (splits.length == 0) {
return this;
}
if (!$defined(reset)) {
reset = false;
}
if (reset) {
this.reset();
}
this.base = (splits[0] == "") ? this.deflocation : splits[0];
this.from_string = function(value, reset) {
this.parse_string(value);
this.serialized = value;
return this;
};
var re = new RegExp("/$");
if (!this.base.match(re)) {
this.base += '/';
}
if (splits.length > 1) {
var params = splits[1].split('&');
for (var i = 0; i < params.length; i++) {
var tmp = params[i].split('=');
this.setparam(tmp[0], tmp[1]);
}
}
return this;
};
this.serialize = function() {
var res = this.base;
this.from_string = function(value, reset) {
this.parse_string(value);
this.serialized = value;
return this;
};
if (this.params.getLength() != 0) {
res += "?" + this.params.toQueryString();
}
return res;
};
this.serialize = function() {
var res = this.base;
this.update = function(force) {
window.fireEvent("pageRefresh");
location.hash = this.serialize();
if ($defined(force)) {
this.force = force;
}
};
if (this.params.getLength() != 0) {
res += "?" + this.params.toQueryString();
}
return res;
};
this.deleteParam = function(str) {
this.params.erase(str);
};
this.update = function(force) {
window.fireEvent("pageRefresh");
location.hash = this.serialize();
if ($defined(force)) {
this.force = force;
}
};
this.updateparams = function(str) {
if (str.charAt(0) == '?') {
str = str.substring(1);
}
var elems = str.split('&');
for (var i = 0; i < elems.length; i++) {
this.setparamfromstring(elems[i]);
}
};
this.deleteParam = function(str) {
this.params.erase(str);
};
this.setparamfromstring = function(str) {
var def = str.split('=');
this.params.set(def[0], def[1]);
};
this.updateparams = function(str) {
if (str.charAt(0) == '?') {
str = str.substring(1);
}
var elems = str.split('&');
for (var i = 0; i < elems.length; i++) {
this.setparamfromstring(elems[i]);
}
};
this.setparam = function(name, value) {
this.params.set(name, value);
return this;
};
this.setparamfromstring = function(str) {
var def = str.split('=');
this.params.set(def[0], def[1]);
};
this.setparams = function(params) {
this.params.extend(params);
return this;
};
this.setparam = function(name, value) {
this.params.set(name, value);
return this;
};
this.getparam = function(name) {
if (!this.params.has(name)) {
return "";
}
return this.params.get(name);
};
this.setparams = function(params) {
this.params.extend(params);
return this;
};
this.baseurl = function(value) {
this.reset();
this.base = value;
var re = new RegExp("/$");
if (!this.base.match(re)) {
this.base += '/';
}
return this;
};
this.getparam = function(name) {
if (!this.params.has(name)) {
return "";
}
return this.params.get(name);
};
this.addbaseurl = function(value) {
newbase = this.base + value;
return this.baseurl(newbase);
};
this.baseurl = function(value, noreset) {
if (!$defined(noreset) || noreset == 0) {
this.reset();
}
this.base = value;
var re = new RegExp("/$");
if (!this.base.match(re)) {
this.base += '/';
}
return this;
};
this.getbaseurl = function() {
return this.base.substr(0, this.base.length - 1);
};
this.addbaseurl = function(value) {
var newbase = this.base + value;
return this.baseurl(newbase);
};
this.delparam = function(name) {
if (!this.params.has(name)) {
return false;
}
this.params.erase(name);
return this;
};
this.getbaseurl = function() {
return this.base.substr(0, this.base.length - 1);
};
this.delparam = function(name) {
if (!this.params.has(name)) {
return false;
}
this.params.erase(name);
return this;
};
};
parse_menubar = function(id) {
+25
View File
@@ -0,0 +1,25 @@
/*
* Shared functions between the webmail and the quarantine plugin.
*
*/
function navbarcallback(event) {
event.stop();
current_anchor.setparams(navparams);
current_anchor.parse_string($(event.target).getParent().get("href")).update();
}
function updatelisting(resp) {
if ($defined(resp.menu)) {
$("menubar").set("html", resp.menu);
parse_menubar("menubar");
}
if ($defined(resp.listing)) {
$("listing").set("html", resp.listing);
}
if ($defined(resp.navbar)) {
$("navbar").set("html", resp.navbar);
$("navbar").getChildren("a").addEvent("click", navbarcallback);
}
}
+80 -55
View File
@@ -1,59 +1,84 @@
var error_callbacks = {
mbaliasform: function() {
if ($("id_domain").value == 0) {
$("id_mboxes").set("html", "");
}
mbaliasform_init();
},
permform: function() {
if ($defined($("id_domain"))) {
$("id_domain").options[0].setProperty("selected", "selected");
domadminform_init();
}
}
};
var extra_data_callbacks = {
};
function register_callback(hash, name, callback) {
if ($defined(hash[name])) {
return false;
}
hash[name] = callback;
return true;
}
function register_error_callback(name, callback) {
return register_callback(error_callbacks, name, callback);
}
function register_extra_data_callback(name, callback) {
return register_callback(extra_data_callbacks, name, callback);
}
function ajax_submit(event) {
var form = event.target;
var myid = form.get("id");
var data = form.toQueryString();
event.stop();
if ($defined(extra_data_callbacks[myid])) {
if (data != "") {
data += "&";
}
data += extra_data_callbacks[myid]();
}
new Request({
method: form.get("method"),
data: data,
onSuccess: function(responseText) {
var response = JSON.decode(responseText);
if (response.status == "ko") {
$(document.body).set("html", response.content);
$(document.body).addEvent("submit", ajax_submit);
if ($defined(error_callbacks[myid])) {
error_callbacks[myid]();
}
} else {
if ($defined(response.ajaxnav)) {
if ($defined(response.url)) {
parent.current_anchor.baseurl(response.url, 1);
}
parent.current_anchor.update(1);
} else if ($defined(response.url)) {
parent.location.href = response.url;
}
parent.SqueezeBox.close();
}
},
onFailure: function(xhr) {
parent.document.getElement('iframe').setStyles({width: 800, height: 600});
$(document.body).set("html", xhr.responseText);
}
}).send();
}
window.addEvent("domready", function() {
ajax_submit = function(event, error_callbacks) {
var form = event.target;
var myid = form.get("id");
event.stop();
form.set('send', {
onSuccess: function(responseText) {
var response = JSON.decode(responseText);
if (response.status == "ko") {
$(document.body).set("html", response.content);
$(document.body).addEvent("submit", default_submit);
if ($defined(error_callbacks[myid])) {
error_callbacks[myid]();
}
} else {
if (response.url != "") {
parent.location.href = response.url;
}
parent.SqueezeBox.close();
}
},
onFailure: function(xhr) {
parent.document.getElement('iframe').setStyles({width: 800, height: 600});
$(document.body).set("html", xhr.responseText);
}
});
form.send();
};
default_submit = function(event) {
var error_callbacks = {
mbaliasform: function() {
if ($("id_domain").value == 0) {
$("id_mboxes").set("html", "");
}
mbaliasform_init();
},
armform: function() {
new Calendar({id_untildate: "Y-m-d"}, {
tweak: {x: -100, y:-100}
});
},
permform: function() {
if ($defined($("id_domain"))) {
$("id_domain").options[0].setProperty("selected", "selected");
domadminform_init();
}
}
};
ajax_submit(event, error_callbacks);
};
$(document.body).addEvent("submit", default_submit);
$(document.body).addEvent("submit", ajax_submit);
$$("input[type!=submit]", "select").addEvents({
focus: function(evt) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

+15 -34
View File
@@ -5,6 +5,7 @@
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/searchbar.css" />
<script type="text/javascript" src="{{ MEDIA_URL }}js/ProgressBar.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/email_listing.js"></script>
{% block css %}{% endblock %}
<style type="text/css">
#navbar {
@@ -27,43 +28,23 @@
</style>
<script type="text/javascript">
function viewmail(event) {
event.stop();
if (event.target.parentNode.id) {
var url = event.target.parentNode.id;
event.target.parentNode.setStyle("background", "#ffffcc");
{% if reset_wm_url %}
current_anchor.baseurl(url).update();
{% else %}
current_anchor.addbaseurl(url).update();
{% endif %}
}
}
window.addEvent("domready", function() {
navparams = new Hash({
{% join navparams %}
});
navbarcallback = function(event) {
event.stop();
current_anchor.setparams(navparams);
current_anchor.parse_string($(event.target).getParent().get("href")).update();
}
viewmail = function(event) {
event.stop();
if (event.target.parentNode.id) {
var url = event.target.parentNode.id;
event.target.parentNode.setStyle("background", "#ffffcc");
{% if reset_wm_url %}
current_anchor.baseurl(url).update();
{% else %}
current_anchor.addbaseurl(url).update();
{% endif %}
}
}
updatelisting = function(resp) {
if ($defined(resp.menu)) {
$("menubar").set("html", resp.menu);
parse_menubar("menubar");
}
if ($defined(resp.listing)) {
$("listing").set("html", resp.listing);
}
if ($defined(resp.navbar)) {
$("navbar").set("html", resp.navbar);
$("navbar").getChildren("a").addEvent("click", navbarcallback);
}
}
});
{% block js %}{% endblock %}
ajaxListener("{{ deflocation }}", {{ defcallback }});
});
+4 -1
View File
@@ -17,7 +17,10 @@
<script src="{{ MEDIA_URL }}js/common.js" type="text/javascript"></script>
{% url django.views.i18n.javascript_catalog as catalog_url %}
<script type="text/javascript" src="{{ catalog_url }}"></script>
{%block html_head %}{% endblock %}
<script type="text/javascript">
set_media_url("{{ MEDIA_URL }}");
</script>
{% block html_head %}{% endblock %}
</head>
<body>