- {{ email.from|parse_imap_header:"from" }}
+ {{ email.subject|parse_imap_header:"subject" }}
- {{ email.subject|parse_imap_header:"subject" }}
+ {{ email.from|parse_imap_header:"from" }}
diff --git a/modoboa/extensions/webmail/templatetags/webmail_tags.py b/modoboa/extensions/webmail/templatetags/webmail_tags.py
index 396e420cd..de0bdbfcd 100644
--- a/modoboa/extensions/webmail/templatetags/webmail_tags.py
+++ b/modoboa/extensions/webmail/templatetags/webmail_tags.py
@@ -18,7 +18,9 @@ register = template.Library()
def viewmail_menu(selection, folder, user, mail_id=None):
entries = [
{"name": "back",
- "url": "javascript:history.go(-1);",
+ # "url": reverse("modoboa.extensions.webmail.views.index")
+ # + "#?action=listmailbox&mbox=%s" % folder,
+ "url": "javascript:history.go(-1)",
"img": "fa fa-arrow-left",
"class": "btn-default sm-margin-left-back",
"label": _("Back")},
diff --git a/modoboa/extensions/webmail/views.py b/modoboa/extensions/webmail/views.py
index c7e366432..9d6f59386 100644
--- a/modoboa/extensions/webmail/views.py
+++ b/modoboa/extensions/webmail/views.py
@@ -335,6 +335,7 @@ def listmailbox(request, defmailbox="INBOX", update_session=True):
:return: a dictionnary
"""
navparams = WebmailNavigationParameters(request, defmailbox)
+ previous_page_id = int(navparams["page"])
if update_session:
navparams.store()
mbox = navparams.get('mbox')
@@ -345,7 +346,7 @@ def listmailbox(request, defmailbox="INBOX", update_session=True):
elems_per_page=int(parameters.get_user(request.user, "MESSAGES_PER_PAGE")),
**request.session["webmail_navparams"]
)
- page_id = int(navparams.get('page'))
+ page_id = int(navparams["page"])
page = lst.paginator.getpage(page_id)
if page is not None:
email_list = lst.mbc.fetch(
@@ -353,11 +354,14 @@ def listmailbox(request, defmailbox="INBOX", update_session=True):
nbelems=int(parameters.get_user(request.user, "MESSAGES_PER_PAGE"))
)
content = _render_to_string(request, "webmail/email_list.html", {
- "email_list": email_list, "with_top_div": page_id == 1
+ "email_list": email_list,
+ "page": page_id,
+ "with_top_div": request.GET.get("scroll", "false") == "false"
})
else:
content = ""
- return {"listing": content, "length": len(content)}
+ navparams["page"] = previous_page_id
+ return {"listing": content, "length": len(content), "page": page_id}
#return lst.render(request, navparams.get('page'))
diff --git a/modoboa/lib/webutils.py b/modoboa/lib/webutils.py
index ac882535c..20bdcebb6 100644
--- a/modoboa/lib/webutils.py
+++ b/modoboa/lib/webutils.py
@@ -181,18 +181,25 @@ class NavigationParameters(object):
return self.request.session[self.sessionkey][key]
def __contains__(self, key):
- """
- """
+ """Check if key is present."""
return key in self.request.session[self.sessionkey]
+ def __setitem__(self, key, value):
+ """Set a new item."""
+ self.request.session[self.sessionkey][key] = value
+
+ def _store_page(self):
+ """Specific method to store the current page."""
+ self["page"] = int(self.request.GET.get("page", 1))
+
def store(self):
"""Store navigation parameters into session.
"""
if not self.sessionkey in self.request.session:
self.request.session[self.sessionkey] = {}
+ self._store_page()
navparams = self.request.session[self.sessionkey]
navparams["order"] = self.request.GET.get("sort_order", "-date")
- navparams["page"] = int(self.request.GET.get("page", 1))
for param, defvalue, escape in self.parameters:
value = self.request.GET.get(param, defvalue)
if value is None:
diff --git a/modoboa/static/js/history.js b/modoboa/static/js/history.js
index 2d21dca4f..e659c09b9 100644
--- a/modoboa/static/js/history.js
+++ b/modoboa/static/js/history.js
@@ -1,5 +1,22 @@
+/**
+ * Creates an instance of History.
+ *
+ * @constructor
+ * @this {History}
+ * @param {dictionary} options - instance options
+ */
var History = function(options) {
- this.initialize(options);
+ this.options = $.extend({}, this.defaults, options);
+ this.callbacks = {};
+ this.base = '';
+ this.serialized = null;
+ this.updatenext = true;
+ this.load_params();
+ if (this.options.defcallback) {
+ this.register_callback("default", this.options.defcallback);
+ this.check_id =
+ setInterval($.proxy(this.check, this), this.options.checkinterval);
+ }
};
History.prototype = {
@@ -11,21 +28,7 @@ History.prototype = {
defcallback: null
},
- initialize: function(options) {
- this.options = $.extend({}, this.defaults, options);
- this.callbacks = {};
- this.base = '';
- this.serialized = null;
- this.updatenext = true;
- this.load_params();
- if (this.options.defcallback) {
- this.register_callback("default", this.options.defcallback);
- this.check_id =
- setInterval($.proxy(this.check, this), this.options.checkinterval);
- }
- },
-
- /*
+ /**
* Return the encoded hash part of the URL (some browsers like FF
* automatically decode the location.hash variable).
*/
@@ -61,10 +64,10 @@ History.prototype = {
},
parse_string: function(value, reset) {
- var splits = (value.charAt(0) == '#')
- ? value.substring(1).split('?') : value.split('?');
+ var splits = (value.charAt(0) == '#') ?
+ value.substring(1).split('?') : value.split('?');
- if (splits.length == 0) {
+ if (splits.length === 0) {
this.base = this.options.deflocation;
return this;
}
@@ -75,7 +78,7 @@ History.prototype = {
this.reset();
}
- if ((this.base = splits[0]) != "") {
+ if ((this.base = splits[0]) !== "") {
var re = new RegExp("/$");
if (!this.base.match(re)) {
this.base += '/';
@@ -101,7 +104,7 @@ History.prototype = {
var res = this.base;
var args = "";
- if (this.paramslength() != 0) {
+ if (this.paramslength() !== 0) {
args += "?";
$.each(this.params, function(key, value) {
if (args != "?") {
@@ -113,6 +116,12 @@ History.prototype = {
return res + args;
},
+ /**
+ * Update the current location.
+ *
+ * @param {boolean} force - force the update
+ * @param {boolean} noupdate - disable the next update
+ */
update: function(force, noupdate) {
location.hash = this.serialize();
@@ -128,20 +137,39 @@ History.prototype = {
}
},
- updateparams: function(str) {
- if (str.charAt(0) == '?') {
- str = str.substring(1);
- }
- var elems = str.split('&');
- for (var i = 0; i < elems.length; i++) {
- var pdef = elems[i].split('=');
- this.setparam(pdef[0], pdef[1]);
- }
- return this;
+ /**
+ * Check if a parameter is present.
+ *
+ * @param {string} name - name of the parameter
+ * @return {boolean} true if parameter is present, false otherwise
+ */
+ hasparam: function(name) {
+ return this.params[name] !== undefined;
},
+ /**
+ * Retrieve a parameter.
+ *
+ * @param {string} name - name of the parameter
+ * @return {string} the parameter's value (URI decoded)
+ */
+ getparam: function(name, defvalue) {
+ if (this.params[name] === undefined) {
+ return (defvalue !== undefined) ? defvalue : undefined;
+ }
+ return decodeURIComponent(this.params[name]);
+ },
+
+ /**
+ * Set a parameter.
+ *
+ * @param {string} name - name of the parameter
+ * @param {string} value - parameter's value
+ * @param {boolean} encode - URI encode the parameter if true
+ * @return {History}
+ */
setparam: function(name, value, encode) {
- if (encode == undefined || encode) {
+ if (encode === undefined || encode) {
this.params[name] = encodeURIComponent(value);
} else {
this.params[name] = value;
@@ -154,15 +182,34 @@ History.prototype = {
return this;
},
- getparam: function(name, defvalue) {
- if (this.params[name] === undefined) {
- return (defvalue != undefined) ? defvalue : undefined;
+ updateparams: function(str) {
+ if (str.charAt(0) == '?') {
+ str = str.substring(1);
}
- return decodeURIComponent(this.params[name]);
+ var elems = str.split('&');
+ for (var i = 0; i < elems.length; i++) {
+ var pdef = elems[i].split('=');
+ this.setparam(pdef[0], pdef[1]);
+ }
+ return this;
+ },
+
+ /**
+ * Delete a parameter.
+ *
+ * @param {string} name - name of the parameter
+ * @return {History}
+ */
+ delparam: function(name) {
+ if (this.params[name] === undefined) {
+ return this;
+ }
+ delete(this.params[name]);
+ return this;
},
baseurl: function(value, noreset) {
- if (noreset === undefined || noreset == 0) {
+ if (noreset === undefined || noreset === 0) {
this.reset();
}
this.base = value;
@@ -182,14 +229,6 @@ History.prototype = {
return decodeURIComponent(this.base.substr(0, this.base.length - 1));
},
- delparam: function(name) {
- if (this.params[name] == undefined) {
- return this;
- }
- delete(this.params[name]);
- return this;
- },
-
register_callback: function(name, callback) {
this.callbacks[name] = callback;
},
@@ -229,7 +268,7 @@ History.prototype = {
cache: false,
dataType: 'json'
}).done($.proxy(function(resp) {
- var callback = (resp.callback != undefined) ? resp.callback : "default";
+ var callback = (resp.callback !== undefined) ? resp.callback : "default";
this.callbacks[callback](resp);
if (resp.respmsg) {
@@ -237,4 +276,4 @@ History.prototype = {
}
}, this));
}
-};
\ No newline at end of file
+};
diff --git a/modoboa/static/js/jquery.infinitescroll.js b/modoboa/static/js/jquery.infinitescroll.js
index 841e9ae48..e577b09b2 100644
--- a/modoboa/static/js/jquery.infinitescroll.js
+++ b/modoboa/static/js/jquery.infinitescroll.js
@@ -1,77 +1,185 @@
/**
* Infinite scroll plugin for jQuery.
*
+ * @author {Antoine Nguyen}
*/
!function($) {
'use strict';
+ /**
+ * Creates an instance of InfiniteScroll.
+ *
+ * @constructor
+ * @this {InfiniteScroll}
+ * @param {object} element
+ * @param {dictionary} options - instance options
+ */
var InfiniteScroll = function(element, options) {
this.$element = $(element);
this.$data = $(element).data();
this.$options = options;
this.executing = false;
this.end_of_results = false;
- this.current_page = 1;
- var that = this;
+ this.current_page = (isNaN(this.$options.initial_page)) ?
+ 1 : this.$options.initial_page;
+ this.loaded_pages = [this.current_page];
- this.$element.scroll(function() {
- if (that.$element.scrollTop() >= that.$options.calculate_bottom(that.$element)) {
- that.load_more();
- }
- });
+ this.resume();
};
InfiniteScroll.prototype = {
constructor: InfiniteScroll,
- load_more: function() {
+ /**
+ * Scroll event handler.
+ *
+ * @this {InfiniteScroll}
+ */
+ on_scroll: function() {
+ var bottom = this.$options.calculate_bottom(this.$element);
+
+ if (this.$element.scrollTop() >= bottom) {
+ this.load_next_page();
+ } else if (this.$element.scrollTop() === 0) {
+ this.load_previous_page();
+ }
+ },
+
+ /**
+ * Load the previous page from the server.
+ *
+ * @private
+ * @this {InfiniteScroll}
+ */
+ load_previous_page: function() {
+ var $this = this;
+
+ if ($this.executing || $.inArray(1, $this.loaded_pages) != -1) {
+ return;
+ }
+
+ while ($this.current_page > 0) {
+ $this.current_page -= 1;
+ if ($.inArray($this.current_page, $this.loaded_pages) == -1) {
+ break;
+ }
+ }
+ if ($this.current_page === 0) {
+ $this.current_page = 1;
+ return;
+ }
+ this.load_page("up");
+ },
+
+ /**
+ * Load the next page from the server.
+ *
+ * @this {InfiniteScroll}
+ */
+ load_next_page: function() {
var $this = this;
if ($this.executing || $this.end_of_results) {
return;
}
- /*$this.$element.find('.spinner').removeClass('hide');*/
- $this.executing = true;
- $this.current_page += 1;
- var args = $this.$options.get_args();
- args.page = $this.current_page;
+ while (true) {
+ $this.current_page += 1;
+ if ($.inArray($this.current_page, $this.loaded_pages) == -1) {
+ break;
+ }
+ }
+ this.load_page("down");
+ },
+
+ /**
+ * Load a page's content.
+ *
+ * @private
+ * @this {InfiniteScroll}
+ * @param {string} direction - direction of the scroll (up or down)
+ */
+ load_page: function(direction) {
+ var args = this.$options.get_args();
+ var $this = this;
+
+ this.executing = true;
+ args.page = this.current_page;
$.ajax({
contentType: 'application/json; charset=UTF-8',
data: args,
url: $this.$options.url,
type: 'GET',
success: function(data) {
- $this.$options.process_results(data);
+ $this.$options.process_results(data, direction);
if (data.length === 0) {
$this.end_of_results = true;
$this.$options.end_of_list_reached($this.$element);
}
- //$this.$element.find('.spinner').addClass('hide');
$this.executing = false;
+ $this.loaded_pages.push($this.current_page);
+ console.log("page", $this.current_page, "loaded successfuly");
}
});
+ },
+
+ /**
+ * Reset the loaded pages cache.
+ *
+ * @this {InfiniteScroll}
+ * @param {Array} current_page - the new current_page
+ */
+ reset_loaded_pages: function(current_page) {
+ this.current_page = current_page;
+ this.loaded_pages = [this.current_page];
+ },
+
+ /**
+ * Suspend the "infinite scroll" mode.
+ *
+ * @this {InfiniteScroll}
+ */
+ pause: function() {
+ this.$element.unbind("scroll");
+ },
+
+ /**
+ * Resume the "infinite scroll" mode.
+ *
+ * @this {InfiniteScroll}
+ */
+ resume: function() {
+ this.$element.scroll($.proxy(this.on_scroll, this));
}
};
$.fn.infinite_scroll = function (option) {
+ var args = arguments;
+
return this.each(function () {
var $this = $(this),
data = $this.data('infinite-scroll'),
- options = $.extend({}, $.fn.infinite_scroll.defaults, typeof option == 'object' && option);
+ options = $.extend(
+ {}, $.fn.infinite_scroll.defaults,
+ typeof option == 'object' && option
+ );
if (!data) {
- $this.data('infinite-scroll', (data = new InfiniteScroll(this, options)));
+ $this.data('infinite-scroll', (
+ data = new InfiniteScroll(this, options))
+ );
}
- if (typeof options == 'string') {
- data[options]();
+ if (typeof option == 'string') {
+ data[option].apply(data, Array.prototype.slice.call(args, 1));
}
});
};
$.fn.infinite_scroll.defaults = {
+ initial_page: 1,
calculate_bottom: null,
get_data: null,
process_results: null,
+ end_of_list_reached: null,
url: ''
};