summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAurélien Bompard <aurelien@bompard.org>2013-11-13 12:47:42 +0100
committerAurélien Bompard <aurelien@bompard.org>2013-11-20 19:15:40 +0100
commitcdf8249b4cb0a05e88c181674aa0a70f25019e00 (patch)
treedcb444f60b8dfb0be7a8f9840e18c939f608ed68
parent85037f95b2b22a812938dc19aa2a9da35a6ea46f (diff)
downloadhyperkitty-cdf8249b4cb0a05e88c181674aa0a70f25019e00.tar.gz
hyperkitty-cdf8249b4cb0a05e88c181674aa0a70f25019e00.tar.xz
hyperkitty-cdf8249b4cb0a05e88c181674aa0a70f25019e00.zip
Front page redesign
-rw-r--r--hyperkitty/lib/view_helpers.py25
-rw-r--r--hyperkitty/static/hyperkitty/css/hyperkitty-common.css7
-rw-r--r--hyperkitty/static/hyperkitty/css/hyperkitty-index.css81
-rw-r--r--hyperkitty/static/hyperkitty/js/hyperkitty-common.js133
-rw-r--r--hyperkitty/static/hyperkitty/js/hyperkitty-frontpage.js67
-rw-r--r--hyperkitty/static/hyperkitty/js/hyperkitty-overview.js285
-rw-r--r--hyperkitty/templates/base.html2
-rw-r--r--hyperkitty/templates/index.html110
-rw-r--r--hyperkitty/templates/overview.html27
-rw-r--r--hyperkitty/templates/threads/summary_thread.html10
-rw-r--r--hyperkitty/templates/threads/summary_thread_large.html2
-rw-r--r--hyperkitty/templatetags/hk_generic.py11
-rw-r--r--hyperkitty/urls.py4
-rw-r--r--hyperkitty/views/index.py52
-rw-r--r--hyperkitty/views/list.py64
15 files changed, 376 insertions, 504 deletions
diff --git a/hyperkitty/lib/view_helpers.py b/hyperkitty/lib/view_helpers.py
index 1187335..f7f9a44 100644
--- a/hyperkitty/lib/view_helpers.py
+++ b/hyperkitty/lib/view_helpers.py
@@ -170,3 +170,28 @@ def is_thread_unread(request, mlist_name, thread):
> last_view_obj.view_date:
unread = True
return unread
+
+
+def get_recent_list_activity(store, mlist):
+ """Return the number of emails posted in the last 30 days"""
+ begin_date, end_date = mlist.get_recent_dates()
+ days = daterange(begin_date, end_date)
+
+ # Use get_messages and not get_threads to count the emails, because
+ # recently active threads include messages from before the start date
+ emails_in_month = store.get_messages(list_name=mlist.name,
+ start=begin_date, end=end_date)
+ # graph
+ emails_per_date = {}
+ # populate with all days before adding data.
+ for day in days:
+ emails_per_date[day.strftime("%Y-%m-%d")] = 0
+ # now count the emails
+ for email in emails_in_month:
+ date_str = email.date.strftime("%Y-%m-%d")
+ if date_str not in emails_per_date:
+ continue # outside the range
+ emails_per_date[date_str] += 1
+ # return the proper format for the javascript chart function
+ return [ {"date": d, "count": emails_per_date[d]}
+ for d in sorted(emails_per_date) ]
diff --git a/hyperkitty/static/hyperkitty/css/hyperkitty-common.css b/hyperkitty/static/hyperkitty/css/hyperkitty-common.css
index c262308..310bfb4 100644
--- a/hyperkitty/static/hyperkitty/css/hyperkitty-common.css
+++ b/hyperkitty/static/hyperkitty/css/hyperkitty-common.css
@@ -78,7 +78,12 @@ form .buttons .submit {
}
-/* On the thread list and the overview page */
+/* On the thread list, overview page and list directory */
+ul.list-stats {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
.participant, .discussion {
padding-left: 20px;
background: no-repeat scroll left center;
diff --git a/hyperkitty/static/hyperkitty/css/hyperkitty-index.css b/hyperkitty/static/hyperkitty/css/hyperkitty-index.css
index 0bd8889..9425f49 100644
--- a/hyperkitty/static/hyperkitty/css/hyperkitty-index.css
+++ b/hyperkitty/static/hyperkitty/css/hyperkitty-index.css
@@ -6,32 +6,79 @@
clear: both;
}
-h1.lists {
+
+.all-lists .lists-menu h2 {
+ font-size: 100%;
+ line-height: 100%;
+ font-weight: bold;
+ color: #666;
+ text-transform: uppercase;
+ margin: 0;
+ margin-bottom: 1em;
+ padding: 0;
+}
+.all-lists .lists-menu ul {
+ list-style-type: none;
+ margin-left: 1em;
+}
+.all-lists .lists-menu li {
+ margin: 0.5em 0;
+}
+
+.all-lists h1.lists {
margin-bottom: 0.5em;
}
-.all-lists .list-name {
+.all-lists table.lists th {
+ color: #999;
+ text-transform: uppercase;
+ font-size: small;
+ font-weight: normal;
+}
+.all-lists table.lists tbody tr {
+ border: 1px solid #ccc;
+ margin: 0.2em 0;
+}
+.all-lists table.lists td {
+ vertical-align: middle;
+ padding: 5px 8px;
+ margin-bottom: 5px;
+}
+.all-lists table.lists .list-name {
font-size: 120%;
color: black;
font-weight: bold;
}
-.all-lists .list-address {
- font-size: 90%;
+.all-lists table.lists .list-tags {
+ font-size: 80%;
+ color: #999;
font-style: italic;
}
-.all-lists a {
- display: block;
- padding: 2em;
- margin: 1em auto;
- overflow: hidden;
- border: 1px solid #ccc;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
- color: black;
+.all-lists table.lists .list-address {
+ font-size: 90%;
+}
+.all-lists table.lists td.activity {
+ width: 260px;
+}
+.all-lists table.lists ul.list-stats {
+ margin: 0;
+ text-align: center;
+}
+.all-lists table.lists ul.list-stats li {
+ display: inline;
+ font-size: 85%;
}
-.all-lists a:hover {
- text-decoration: none;
+
+.all-lists table.lists tr.private {
+ background-color: #e6f2f5;
+}
+.all-lists table.lists tr.private .list-name {
+ color: #2e6c83;
+}
+
+.all-lists table.lists tr.inactive {
background-color: #eee;
- color: black;
+}
+.all-lists table.lists tr.inactive .list-name {
+ color: #999;
}
diff --git a/hyperkitty/static/hyperkitty/js/hyperkitty-common.js b/hyperkitty/static/hyperkitty/js/hyperkitty-common.js
index 1fb1eb3..41f2c3e 100644
--- a/hyperkitty/static/hyperkitty/js/hyperkitty-common.js
+++ b/hyperkitty/static/hyperkitty/js/hyperkitty-common.js
@@ -33,6 +33,7 @@ function form_to_json(form) {
}
+
/*
* Voting
*/
@@ -73,6 +74,7 @@ function setup_vote(baseElem) {
}
+
/*
* New messages (or replies)
*/
@@ -103,6 +105,136 @@ function setup_attachments(baseElem) {
}
+
+/*
+ * Recent activity bar chart
+ */
+function chart(elem_id, data, default_props) {
+ /* Function for grid lines, for x-axis */
+ function make_x_axis() {
+ return d3.svg.axis()
+ .scale(x)
+ .orient("bottom")
+ .ticks(d3.time.days, 1)
+ }
+
+ /* Function for grid lines, for y-axis */
+ function make_y_axis() {
+ return d3.svg.axis()
+ .scale(y)
+ .orient("left")
+ .ticks(5)
+ }
+ if (typeof default_props === "undefined") {
+ default_props = {};
+ }
+
+ var props = {width: 250, height: 50};
+ $.extend(props, default_props);
+ var margin = {top: 0, right: 0, bottom: 0, left: 0},
+ width = props.width - margin.left - margin.right,
+ height = props.height - margin.top - margin.bottom;
+
+ var w = Math.floor(width / data.length);
+
+ var format_in = d3.time.format("%Y-%m-%d");
+ var format_out = d3.time.format("");
+
+ var x = d3.time.scale()
+ .range([0, width]);
+
+ var y = d3.scale.linear()
+ .range([height, 0]);
+
+ var xAxis = d3.svg.axis()
+ .scale(x)
+ .orient("bottom")
+ .tickSize(0,0) // change to 2,2 for ticks
+ .tickFormat(format_out)
+ .ticks(d3.time.days, 1);
+
+ var yAxis = d3.svg.axis()
+ .scale(y)
+ .orient("left")
+ .tickSize(0,0) // change to 4,3 for ticks
+ .ticks("") // change to 2 for y-axis tick labels
+ .tickSubdivide(1);
+
+ var area = d3.svg.area()
+ .x(function(d) { return x(d.date); })
+ // .y0(height)
+ .y(function(d) { return y(d.count); });
+
+ var svg = d3.select(elem_id).append("svg")
+ .attr("id", "chart-data")
+ .attr("width", width + margin.left + margin.right)
+ .attr("height", height + margin.top + margin.bottom)
+ .append("g")
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+ data.forEach(function(d) {
+ d.date = format_in.parse(d.date);
+ d.count = parseInt(d.count);
+ });
+
+ x.domain(d3.extent(data, function(d) { return d.date; }));
+ y.domain([0, d3.max(data, function(d) { return d.count; })]);
+
+
+ /* Draw the grid lines, for x-axis */
+ svg.append("g")
+ .attr("class", "grid")
+ .attr("Transform", "translate(0, " + height + ")")
+ .call(make_x_axis()
+ .tickSize(height, 0, 0)
+ .tickFormat("")
+ )
+
+ /* Draw the grid lines, for y-axis */
+ svg.append("g")
+ .attr("class", "grid")
+ .call(make_y_axis()
+ .tickSize(-width, 0, 0)
+ .tickFormat("")
+ )
+
+ svg.append("g").attr("class","bars").selectAll("rect")
+ .data(data)
+ .enter().append("rect")
+ .attr("x", function(d) { return x(d.date); })
+ //.attr("y0", height)
+ .attr("y", function(d) { return y(d.count); })
+ .attr("width", w)
+ .attr("height", function(d) { return height - y(d.count); });
+
+ /* draw x-axis */
+ svg.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + height + ")")
+ .call(xAxis)
+ /*.selectAll("text")
+ .attr("y", -5)
+ .attr("x", -30)
+ .attr("transform", function(d) {
+ return "rotate(-90)"
+ });*/
+
+ /* Y-axis label */
+ svg.append("g")
+ .attr("class", "y axis")
+ .call(yAxis)
+ /*.append("text")
+ .attr("transform", "rotate(-90)")
+ .attr("y", 0)
+ .attr("x", 0 - height/2)
+ .attr("dy", "-3em")
+ .style("text-anchor", "middle")
+ .style("fill", "#777")
+ .text("Messages"); */
+}
+
+
+
/*
* Misc.
*/
@@ -131,6 +263,7 @@ function setup_flash_messages() {
}
+
/*
* Activate
*/
diff --git a/hyperkitty/static/hyperkitty/js/hyperkitty-frontpage.js b/hyperkitty/static/hyperkitty/js/hyperkitty-frontpage.js
deleted file mode 100644
index 2ba7d43..0000000
--- a/hyperkitty/static/hyperkitty/js/hyperkitty-frontpage.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 1998-2012 by the Free Software Foundation, Inc.
- *
- * This file is part of HyperKitty.
- *
- * HyperKitty is free software: you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free
- * Software Foundation, either version 3 of the License, or (at your option)
- * any later version.
- *
- * HyperKitty is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * HyperKitty. If not, see <http://www.gnu.org/licenses/>.
- *
- * Author: Aurelien Bompard <abompard@fedoraproject.org>
- */
-
-
-/*
- * List descriptions on the front page
- */
-function update_list_properties(url) {
- // Don't try to update them all at once, there may be hundreds of lists
- var bulksize = 5;
- // If there is still an ajaxloader, then request the properties
- var lists = $(".all-lists .mailinglist img.ajaxloader")
- .slice(0, bulksize).parents(".mailinglist");
- if (lists.length === 0) {
- return;
- }
- var listnames = $.makeArray(lists.find(".list-address").map(
- function() { return $(this).text(); }));
- $.ajax({
- dataType: "json",
- url: url + "?name=" + listnames.join("&name="),
- success: function(data) {
- lists.each(function() {
- var name = $(this).find(".list-address").text();
- if (!data[name]) {
- return;
- }
- if (data[name]["display_name"]) {
- $(this).find(".list-name").html(data[name]["display_name"]);
- }
- if (data[name]["description"]) {
- $(this).find(".list-description")
- .html(data[name]["description"]);
- }
- });
- },
- error: function(jqXHR, textStatus, errorThrown) {
- //alert(jqXHR.responseText);
- },
- complete: function(jqXHR, textStatus) {
- // Request may have failed if mailman's REST server is unavailable,
- // keep going anyway.
- lists.find("img.ajaxloader").remove();
- // do it again, until all lists have been populated (or at least we
- // tried to)
- update_list_properties(url);
- }
- });
-}
diff --git a/hyperkitty/static/hyperkitty/js/hyperkitty-overview.js b/hyperkitty/static/hyperkitty/js/hyperkitty-overview.js
deleted file mode 100644
index e020554..0000000
--- a/hyperkitty/static/hyperkitty/js/hyperkitty-overview.js
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 1998-2012 by the Free Software Foundation, Inc.
- *
- * This file is part of HyperKitty.
- *
- * HyperKitty is free software: you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free
- * Software Foundation, either version 3 of the License, or (at your option)
- * any later version.
- *
- * HyperKitty is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * HyperKitty. If not, see <http://www.gnu.org/licenses/>.
- *
- * Author: Aurelien Bompard <abompard@fedoraproject.org>
- */
-
-
-/*
- * Recent activity graph (area graph)
- */
-
-function activity_graph(elem_id, dates, counts, baseurl) {
- function merge(dates, counts) {
- result = []
- for(i = 0; i < dates.length; i++) {
- result.push({
- "date": dates[i],
- "count": counts[i]
- })
- }
- return result;
- }
-
- /* Function for grid lines, for x-axis */
- function make_x_axis() {
- return d3.svg.axis()
- .scale(x)
- .orient("bottom")
- .ticks(d3.time.days, 1)
- }
-
- /* Function for grid lines, for y-axis */
- function make_y_axis() {
- return d3.svg.axis()
- .scale(y)
- .orient("left")
- .ticks(5)
- }
-
- var data = merge(dates, counts);
- var margin = {top: 20, right: 20, bottom: 50, left: 40},
- width = 350 - margin.left - margin.right,
- height = 150 - margin.top - margin.bottom;
-
- var format_in = d3.time.format("%Y-%m-%d");
- var format_out = d3.time.format("");
-
- var x = d3.time.scale()
- .range([0, width]);
-
- var y = d3.scale.linear()
- .range([height, 0]);
-
- var xAxis = d3.svg.axis()
- .scale(x)
- .orient("bottom")
- .tickSize(2,2)
- .tickFormat(format_out)
- .ticks(d3.time.days, 1);
-
- var yAxis = d3.svg.axis()
- .scale(y)
- .orient("left")
- .tickSize(4,3)
- .ticks(2)
- .tickSubdivide(1);
-
- var area = d3.svg.area()
- .interpolate("monotone")
- .x(function(d) { return x(d.date); })
- .y0(height)
- .y1(function(d) { return y(d.count); });
-
- var svg = d3.select(elem_id).append("svg")
- .attr("width", width + margin.left + margin.right)
- .attr("height", height + margin.top + margin.bottom)
- .append("g")
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
-
- data.forEach(function(d) {
- d.date = format_in.parse(d.date);
- d.count = parseInt(d.count);
- });
-
- x.domain(d3.extent(data, function(d) { return d.date; }));
- y.domain([0, d3.max(data, function(d) { return d.count; })]);
-
- /* Draw the grid lines, for x-axis */
- svg.append("g")
- .attr("class", "grid")
- .attr("Transform", "translate(0, " + height + ")")
- .call(make_x_axis()
- .tickSize(height, 0, 0)
- .tickFormat("")
- )
-
- /* Draw the grid lines, for y-axis */
- svg.append("g")
- .attr("class", "grid")
- .call(make_y_axis()
- .tickSize(-width, 0, 0)
- .tickFormat("")
- )
-
- svg.append("path")
- .datum(data)
- .attr("class", "area")
- .attr("d", area);
-
- svg.append("g")
- .attr("class", "x axis")
- .attr("transform", "translate(0," + height + ")")
- .call(xAxis)
- .selectAll("text")
- .attr("y", -5)
- .attr("x", -30)
- .attr("transform", function(d) {
- return "rotate(-90)"
- });
-
- /* Y-axis label */
- svg.append("g")
- .attr("class", "y axis")
- .call(yAxis)
- .append("text")
- .attr("transform", "rotate(-90)")
- .attr("y", 0)
- .attr("x", 0 - height/2)
- .attr("dy", "-3em")
- .style("text-anchor", "middle")
- .style("fill", "#777")
- .text("Messages");
-
-}
-
-
-/*
- * Recent activity bar chart
- */
-function chart(elem_id, dates, counts, baseurl) {
- function merge(dates, counts) {
- result = []
- for(i = 0; i < dates.length; i++) {
- result.push({
- "date": dates[i],
- "count": counts[i]
- })
- }
- return result;
- }
-
- /* Function for grid lines, for x-axis */
- function make_x_axis() {
- return d3.svg.axis()
- .scale(x)
- .orient("bottom")
- .ticks(d3.time.days, 1)
- }
-
- /* Function for grid lines, for y-axis */
- function make_y_axis() {
- return d3.svg.axis()
- .scale(y)
- .orient("left")
- .ticks(5)
- }
-
- var data = merge(dates, counts);
- var margin = {top: 0, right: 0, bottom: 0, left: 0},
- width = 200 - margin.left - margin.right,
- height = 50 - margin.top - margin.bottom;
-
- var w = 5;
-
- var format_in = d3.time.format("%Y-%m-%d");
- var format_out = d3.time.format("");
-
- var x = d3.time.scale()
- .range([0, width]);
-
- var y = d3.scale.linear()
- .range([height, 0]);
-
- var xAxis = d3.svg.axis()
- .scale(x)
- .orient("bottom")
- .tickSize(0,0) // change to 2,2 for ticks
- .tickFormat(format_out)
- .ticks(d3.time.days, 1);
-
- var yAxis = d3.svg.axis()
- .scale(y)
- .orient("left")
- .tickSize(0,0) // change to 4,3 for ticks
- .ticks("") // change to 2 for y-axis tick labels
- .tickSubdivide(1);
-
- var area = d3.svg.area()
- .x(function(d) { return x(d.date); })
- // .y0(height)
- .y(function(d) { return y(d.count); });
-
- var svg = d3.select(elem_id).append("svg")
- .attr("id", "chart-data")
- .attr("width", width + margin.left + margin.right)
- .attr("height", height + margin.top + margin.bottom)
- .append("g")
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
-
- data.forEach(function(d) {
- d.date = format_in.parse(d.date);
- d.count = parseInt(d.count);
- });
-
- x.domain(d3.extent(data, function(d) { return d.date; }));
- y.domain([0, d3.max(data, function(d) { return d.count; })]);
-
-
- /* Draw the grid lines, for x-axis */
- svg.append("g")
- .attr("class", "grid")
- .attr("Transform", "translate(0, " + height + ")")
- .call(make_x_axis()
- .tickSize(height, 0, 0)
- .tickFormat("")
- )
-
- /* Draw the grid lines, for y-axis */
- svg.append("g")
- .attr("class", "grid")
- .call(make_y_axis()
- .tickSize(-width, 0, 0)
- .tickFormat("")
- )
-
- svg.append("g").attr("class","bars").selectAll("rect")
- .data(data)
- .enter().append("rect")
- .attr("x", function(d) { return x(d.date); })
- //.attr("y0", height)
- .attr("y", function(d) { return y(d.count); })
- .attr("width", w)
- .attr("height", function(d) { return height - y(d.count); });
-
- /* draw x-axis */
- svg.append("g")
- .attr("class", "x axis")
- .attr("transform", "translate(0," + height + ")")
- .call(xAxis)
- /*.selectAll("text")
- .attr("y", -5)
- .attr("x", -30)
- .attr("transform", function(d) {
- return "rotate(-90)"
- });*/
-
- /* Y-axis label */
- svg.append("g")
- .attr("class", "y axis")
- .call(yAxis)
- /*.append("text")
- .attr("transform", "rotate(-90)")
- .attr("y", 0)
- .attr("x", 0 - height/2)
- .attr("dy", "-3em")
- .style("text-anchor", "middle")
- .style("fill", "#777")
- .text("Messages"); */
-
-}
diff --git a/hyperkitty/templates/base.html b/hyperkitty/templates/base.html
index e7d5303..a6feb9a 100644
--- a/hyperkitty/templates/base.html
+++ b/hyperkitty/templates/base.html
@@ -130,7 +130,7 @@
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="{{ STATIC_URL }}hyperkitty/libs/jquery/jquery-1.10.1.min.js"><\/script>')</script>
<script src="{{ STATIC_URL }}hyperkitty/libs/jquery/jquery-ui-1.10.3.custom.min.js"></script>
- {% assets filters="rjsmin", output="gen/hyperkitty.js", "hyperkitty/libs/bootstrap/js/bootstrap.min.js", "hyperkitty/libs/jquery.expander.js", "hyperkitty/libs/d3.v2.min.js", "hyperkitty/libs/jquery.hotkeys.js", "hyperkitty/js/hyperkitty-common.js", "hyperkitty/js/hyperkitty-frontpage.js", "hyperkitty/js/hyperkitty-overview.js", "hyperkitty/js/hyperkitty-thread.js", "hyperkitty/js/hyperkitty-userprofile.js" %}
+ {% assets filters="rjsmin", output="gen/hyperkitty.js", "hyperkitty/libs/bootstrap/js/bootstrap.min.js", "hyperkitty/libs/jquery.expander.js", "hyperkitty/libs/d3.v2.min.js", "hyperkitty/libs/jquery.hotkeys.js", "hyperkitty/js/hyperkitty-common.js", "hyperkitty/js/hyperkitty-thread.js", "hyperkitty/js/hyperkitty-userprofile.js" %}
<script src="{{ ASSET_URL }}"></script>
{% endassets %}
{% block additionaljs %} {% endblock %}
diff --git a/hyperkitty/templates/index.html b/hyperkitty/templates/index.html
index fc711b2..78804e1 100644
--- a/hyperkitty/templates/index.html
+++ b/hyperkitty/templates/index.html
@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% load url from future %}
{% load i18n %}
+{% load hk_generic %}
{% block title %}
@@ -9,34 +10,84 @@
{% block content %}
-<div class="all-lists">
+<div class="row-fluid all-lists">
-<h1 class="lists">{% trans 'Available lists' %}</h1>
+<div class="span2 lists-menu">
+ <h2>{% trans 'Lists' %}</h2>
+ <ul>
+ <li><a href="{% url 'root' %}">All</a></li>
+ <li><a href="{% url 'root' %}?sort=active">Most active</a></li>
+ <li><a href="{% url 'root' %}?sort=popular">Most popular</a></li>
+ </ul>
+</div>
+
+<div class="span10">
+
+<h1>{% trans 'Available lists' %}</h1>
-<div class="row-fluid">
+{% if all_lists %}
+<table class="lists table">
+ <thead>
+ <tr><th>List</th><th>Description</th><th>Activity in the past 30 days</th></tr>
+ </thead>
+ <tbody>
{% for mlist in all_lists %}
-<div class="span3">
- <a href="{% url 'list_overview' mlist_fqdn=mlist.name %}" class="mailinglist">
- <p class="list-name">
- {% if mlist.display_name %}
- {{ mlist.display_name }}
- {% else %}
- {{ mlist.name }}
- {% endif %}
- </p>
- <p class="list-address">{{ mlist.name }}</p>
- <p class="list-description"></p>
- <img alt="Loading..." class="ajaxloader" src="{{ STATIC_URL }}hyperkitty/img/ajax-loader.gif" />
- </a>
-</div>
-{% if forloop.counter|divisibleby:"4" %}
-</div>
-<div class="row-fluid">
-{% endif %}
-{% empty %}
-<p>No archived list yet.</p>
+ <tr
+ {% if mlist.is_private %}
+ class="private"
+ {% elif mlist.recent_threads_count == 0 %}
+ class="inactive"
+ {% endif %}
+ >
+ <td>
+ <a href="{% url 'list_overview' mlist_fqdn=mlist.name %}"
+ class="list-name">
+ {% if mlist.display_name %}
+ {{ mlist.display_name }}
+ {% else %}
+ {{ mlist.name|until:"@" }}
+ {% endif %}
+ </a>
+ {% if mlist.is_private %}
+ <span class="list-tags">private</span>
+ {% elif mlist.recent_threads_count == 0 %}
+ <span class="list-tags">inactive</span>
+ {% endif %}
+ <br />
+ <a href="{% url 'list_overview' mlist_fqdn=mlist.name %}"
+ class="list-address">
+ {{ mlist.name }}
+ </a>
+ </td>
+ <td class="list-description"></td>
+ <td class="activity">
+ <div class="chart" data-chart-values="{{ mlist.evolution|to_json }}"></div>
+ <ul class="list-stats">
+ <li><span class="participant">
+ {% if mlist.can_view %}
+ {{ mlist.recent_participants_count }}
+ {% else %}
+ ...
+ {% endif %}
+ participants</span></li>
+ <li><span class="discussion">
+ {% if mlist.can_view %}
+ {{ mlist.recent_threads_count }}
+ {% else %}
+ ...
+ {% endif %}
+ discussions</span></li>
+ </ul>
+ </td>
+ </tr>
{% endfor %}
-</div>
+ </tbody>
+</table>
+{% else %}
+<p>No archived list yet.</p>
+{% endif %}
+
+</div> <!-- right column -->
</div>
@@ -46,10 +97,13 @@
{% block additionaljs %}
-<script type="text/javascript">
- $(document).ready(function() {
- // Load the properties
- update_list_properties("{% url 'list_properties' %}");
+<script>
+ $(function() {
+ $("div.chart").each(function() {
+ chart($(this).get(0),
+ $.parseJSON($(this).attr("data-chart-values")),
+ {height: 30});
+ });
});
</script>
diff --git a/hyperkitty/templates/overview.html b/hyperkitty/templates/overview.html
index 098e609..a5dc32f 100644
--- a/hyperkitty/templates/overview.html
+++ b/hyperkitty/templates/overview.html
@@ -25,7 +25,7 @@
<section id="statistics">
- <div id="chart"></div>
+ <div id="chart" data-chart-values="{{ evolution|to_json }}"></div>
<p class="caption">Post volume over the past <strong>30</strong> days.</p>
<p class="thread-new">
@@ -37,8 +37,8 @@
<h3>Activity Summary</h3>
<p>The following statistics are from the past <strong>30</strong> days:</p>
<ul class="list-stats">
- <li><span class="participant">{{ num_participants }} participants</span></li>
- <li><span class="discussion">{{ num_threads }} discussions</span></li>
+ <li><span class="participant">{{ mlist.recent_participants_count }} participants</span></li>
+ <li><span class="discussion">{{ mlist.recent_threads_count }} discussions</span></li>
</ul>
<section id="discussion-maker" class="widget">
@@ -145,24 +145,11 @@
{% block additionaljs %}
<script type="text/javascript" >
-/*
$(function() {
- activity_graph(
- "#fig",
- ["{{days|join:'","'}}"],
- {{evolution}},
- "{{ archives_baseurl }}"
- )
- });
-*/
-
- $(function() {
- chart(
- "#chart",
- ["{{days|join:'","'}}"],
- {{evolution}},
- "{{ archives_baseurl }}"
- )
+ $("#chart").each(function() {
+ chart($(this).get(0),
+ $.parseJSON($(this).attr("data-chart-values")));
+ });
});
</script>
diff --git a/hyperkitty/templates/threads/summary_thread.html b/hyperkitty/templates/threads/summary_thread.html
index e9b2b1f..5d0972c 100644
--- a/hyperkitty/templates/threads/summary_thread.html
+++ b/hyperkitty/templates/threads/summary_thread.html
@@ -8,9 +8,9 @@
<a name="{{thread.thread_id}}"
href="{% url 'thread' threadid=thread.thread_id mlist_fqdn=mlist.name %}"
>
- {% if thread.category %}
- <span class="label category" style="background-color:{{thread.category.color}}">
- {{ thread.category.name|upper }}
+ {% if thread.category_widget %}
+ <span class="label category" style="background-color:{{thread.category_widget.color}}">
+ {{ thread.category_widget.name|upper }}
</span>
{% endif %}
{% if thread.unread %}
@@ -21,10 +21,10 @@
<div class="thread-stats">
<ul class="inline-block">
<li class="participant">
- {{ thread.participants|length }}
+ {{ thread.participants_count }}
</li>
<li class="discussion">
- {{ thread.length }}
+ {{ thread|length }}
</li>
<span class="likestatus {{ thread.likestatus }}">+{{ thread.likes }}/-{{ thread.dislikes }}</span>
</ul>
diff --git a/hyperkitty/templates/threads/summary_thread_large.html b/hyperkitty/templates/threads/summary_thread_large.html
index 450278b..51e98a8 100644
--- a/hyperkitty/templates/threads/summary_thread_large.html
+++ b/hyperkitty/templates/threads/summary_thread_large.html
@@ -44,7 +44,7 @@
</ul>
{% endif %}
</div>
- <span class="participant">{{ thread.participants|length }} participants</span>
+ <span class="participant">{{ thread.participants_count }} participants</span>
<span class="discussion">{{ thread|length }} comments</span>
{% include "messages/like_form.html" with message_id_hash=thread.starting_email.message_id_hash object=thread %}
<a href="{% url 'thread' threadid=thread.thread_id mlist_fqdn=mlist.name %}"
diff --git a/hyperkitty/templatetags/hk_generic.py b/hyperkitty/templatetags/hk_generic.py
index de50f9d..9e1cb0e 100644
--- a/hyperkitty/templatetags/hk_generic.py
+++ b/hyperkitty/templatetags/hk_generic.py
@@ -22,6 +22,7 @@
import datetime
import re
+import django.utils.simplejson as json
from dateutil.tz import tzoffset
from django import template
from django.utils.datastructures import SortedDict
@@ -204,3 +205,13 @@ def add_to_query_string(context, *args, **kwargs):
for key, value in new_qs_elements.iteritems():
qs[key] = value
return qs.urlencode()
+
+
+@register.filter
+def until(value, limit):
+ return value.partition(limit)[0]
+
+
+@register.filter
+def to_json(value):
+ return json.dumps(value)
diff --git a/hyperkitty/urls.py b/hyperkitty/urls.py
index 9c92aa5..b1eda96 100644
--- a/hyperkitty/urls.py
+++ b/hyperkitty/urls.py
@@ -38,9 +38,7 @@ from hyperkitty.views import TextTemplateView
urlpatterns = patterns('hyperkitty.views',
# Index
- url(r'^/$', 'index.index', name='index'),
- url(r'^$', 'index.index', name='root'),
- url(r'^lists-properties$', 'index.list_properties', name='list_properties'),
+ url(r'^/?$', 'index.index', name='root'),
# Account
url(r'^accounts/login/$', 'accounts.login_view', {'template_name': 'login.html', 'SSL': True}, name='user_login'),
diff --git a/hyperkitty/views/index.py b/hyperkitty/views/index.py
index af9abd8..0954ee8 100644
--- a/hyperkitty/views/index.py
+++ b/hyperkitty/views/index.py
@@ -22,49 +22,47 @@
import urllib2
+import datetime
+from collections import defaultdict
import django.utils.simplejson as json
from django.shortcuts import render
from django.conf import settings
from django.http import HttpResponse
-from mailmanclient import Client
+from mailmanclient import Client, MailmanConnectionError
+from mailman.interfaces.archiver import ArchivePolicy
from hyperkitty.lib import get_store
+from hyperkitty.lib.view_helpers import get_recent_list_activity
+from hyperkitty.lib.mailman import is_mlist_authorized
def index(request):
store = get_store(request)
lists = store.get_lists()
+ for mlist in lists:
+ if mlist.archive_policy != ArchivePolicy.private:
+ mlist.is_private = False
+ mlist.can_view = True
+ else:
+ mlist.is_private = True
+ if is_mlist_authorized(request, mlist):
+ mlist.can_view = True
+ else:
+ mlist.can_view = False
+ if mlist.can_view:
+ mlist.evolution = get_recent_list_activity(store, mlist)
+
+ # sorting
+ sort_mode = request.GET.get('sort')
+ if sort_mode == "active":
+ lists.sort(key=lambda l: l.recent_threads_count)
+ elif sort_mode == "popular":
+ lists.sort(key=lambda l: l.recent_participants_count)
context = {
'view_name': 'all_lists',
'all_lists': lists,
}
return render(request, "index.html", context)
-
-
-def list_properties(request):
- """Get JSON encoded list properties"""
- store = get_store(request)
- lists = store.get_lists()
- onlynames = request.GET.getlist("name")
- if onlynames:
- lists = [ l for l in lists if l.name in onlynames ]
- client = Client('%s/3.0' % settings.MAILMAN_REST_SERVER,
- settings.MAILMAN_API_USER, settings.MAILMAN_API_PASS)
- props = {}
- for ml in lists:
- try:
- mm_list = client.get_list(ml.name)
- except urllib2.HTTPError:
- continue
- props[ml.name] = {
- "display_name": mm_list.display_name,
- "description": mm_list.settings["description"],
- }
- # Update KittyStore if necessary
- if ml.display_name != mm_list.display_name:
- ml.display_name = mm_list.display_name
- return HttpResponse(json.dumps(props),
- mimetype='application/javascript')
diff --git a/hyperkitty/views/list.py b/hyperkitty/views/list.py
index cbcc678..8feff11 100644
--- a/hyperkitty/views/list.py
+++ b/hyperkitty/views/list.py
@@ -35,7 +35,7 @@ from hyperkitty.models import Tag, Favorite
from hyperkitty.lib import get_store
from hyperkitty.lib.view_helpers import FLASH_MESSAGES, paginate, \
get_category_widget, get_months, get_display_dates, daterange, \
- is_thread_unread
+ is_thread_unread, get_recent_list_activity
from hyperkitty.lib.voting import set_message_votes, set_thread_votes
from hyperkitty.lib.mailman import check_mlist_private
@@ -44,11 +44,6 @@ if settings.USE_MOCKUPS:
from hyperkitty.lib.mockup import generate_top_author, generate_thread_per_category
-Thread = namedtuple('Thread', [
- "thread_id", "subject", "participants", "length", "date_active",
- "likes", "dislikes", "likestatus", "category", "unread",
- ])
-
@check_mlist_private
def archives(request, mlist_fqdn, year=None, month=None, day=None):
@@ -77,6 +72,9 @@ def archives(request, mlist_fqdn, year=None, month=None, day=None):
"list_title": list_title.capitalize(),
"no_results_text": no_results_text,
}
+ if day is None:
+ month_activity = mlist.get_month_activity(int(year), int(month))
+ extra_context["participants"] = month_activity.participants_count
return _thread_list(request, mlist, threads, extra_context=extra_context)
@@ -87,7 +85,8 @@ def _thread_list(request, mlist, threads, template_name='thread_list.html', extr
participants = set()
for thread in threads:
- participants.update(thread.participants)
+ if "participants" not in extra_context:
+ participants.update(thread.participants)
# Votes
set_thread_votes(thread, request.user)
@@ -143,39 +142,25 @@ def overview(request, mlist_fqdn=None):
if not mlist_fqdn:
return redirect('/')
- # Get stats for last 30 days
- today = datetime.datetime.utcnow()
- #today -= datetime.timedelta(days=365) #debug
- # the upper boundary is excluded in the search, add one day
- end_date = today + datetime.timedelta(days=1)
- begin_date = end_date - datetime.timedelta(days=32)
-
store = get_store(request)
mlist = store.get_list(mlist_fqdn)
if mlist is None:
raise Http404("No archived mailing-list by that name.")
+ begin_date, end_date = mlist.get_recent_dates()
threads_result = store.get_threads(
list_name=mlist.name, start=begin_date, end=end_date)
threads = []
- participants = set()
for thread_obj in threads_result:
# Votes
set_thread_votes(thread_obj, request.user)
- thread = Thread(thread_obj.thread_id, thread_obj.subject,
- thread_obj.participants, len(thread_obj),
- thread_obj.date_active.replace(tzinfo=utc),
- thread_obj.likes, thread_obj.dislikes,
- thread_obj.likestatus,
- get_category_widget(None, thread_obj.category)[0],
- is_thread_unread(request, mlist.name, thread_obj),
- )
- # Statistics on how many participants and threads this month
- participants.update(thread.participants)
- threads.append(thread)
+ thread_obj.category_widget = get_category_widget(
+ None, thread_obj.category)[0]
+ thread_obj.unread = is_thread_unread(request, mlist.name, thread_obj)
+ threads.append(thread_obj)
# top threads are the one with the most answers
- top_threads = sorted(threads, key=lambda t: t.length, reverse=True)
+ top_threads = sorted(threads, key=lambda t: len(t), reverse=True)
# active threads are the ones that have the most recent posting
active_threads = sorted(threads, key=lambda t: t.date_active, reverse=True)
@@ -212,25 +197,9 @@ def overview(request, mlist_fqdn=None):
continue
threads_by_category[thread.category].append(thread)
- # List activity
- # Use get_messages and not get_threads to count the emails, because
- # recently active threads include messages from before the start date
- emails_in_month = store.get_messages(list_name=mlist.name,
- start=begin_date, end=end_date)
- # graph
- dates = defaultdict(lambda: 0) # no activity by default
- # populate with all days before adding data.
- for single_date in daterange(begin_date, end_date):
- dates[single_date.strftime("%Y-%m-%d")] = 0
-
- for email in emails_in_month:
- date_str = email.date.strftime("%Y-%m-%d")
- dates[date_str] = dates[date_str] + 1
- days = dates.keys()
- days.sort()
- evolution = [dates[d] for d in days]
- if not evolution:
- evolution.append(0)
+ # List activity graph
+ evolution = get_recent_list_activity(store, mlist)
+
archives_baseurl = reverse("archives_latest",
kwargs={'mlist_fqdn': mlist.name})
archives_baseurl = archives_baseurl.rpartition("/")[0]
@@ -246,9 +215,6 @@ def overview(request, mlist_fqdn=None):
'threads_by_category': threads_by_category,
'months_list': get_months(store, mlist.name),
'evolution': evolution,
- 'days': days,
'archives_baseurl': archives_baseurl,
- 'num_threads': len(threads),
- 'num_participants': len(participants),
}
return render(request, "overview.html", context)