summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--hyperkitty/static/css/hyperkitty.css36
-rw-r--r--hyperkitty/static/img/reply.pngbin0 -> 641 bytes
-rw-r--r--hyperkitty/static/js/hyperkitty.js70
-rw-r--r--hyperkitty/static/libs/bootstrap/bootstrap.css2
-rw-r--r--hyperkitty/templates/messages/message.html1
-rw-r--r--hyperkitty/templates/messages/reply_form.html14
-rw-r--r--hyperkitty/urls.py2
-rw-r--r--hyperkitty/views/accounts.py1
-rw-r--r--hyperkitty/views/forms.py2
-rw-r--r--hyperkitty/views/message.py35
-rw-r--r--hyperkitty/views/thread.py1
11 files changed, 151 insertions, 13 deletions
diff --git a/hyperkitty/static/css/hyperkitty.css b/hyperkitty/static/css/hyperkitty.css
index 6017c74..c6aefa1 100644
--- a/hyperkitty/static/css/hyperkitty.css
+++ b/hyperkitty/static/css/hyperkitty.css
@@ -855,3 +855,39 @@ ul.attachments-list li {
border-left: 2px solid rgb(55, 113, 200);
padding-left: 0.2em;
}
+
+
+/*
+ * Replies
+ */
+a.reply {
+ background: url("../img/reply.png") no-repeat left center;
+ padding-left: 20px;
+ margin-left: 2em;
+}
+.reply-form {
+ display: none;
+ padding-top: 1em;
+}
+.reply-form p {
+ margin: 0;
+}
+.reply-form textarea {
+ width: 95%;
+}
+.reply-form .buttons .submit {
+ margin-right: 2em;
+}
+.reply-result {
+ text-align: center;
+}
+.reply-result .alert {
+ display: inline-block;
+}
+.reply-result .alert-success {
+ margin-bottom: 0;
+}
+.reply-result .alert-error {
+ white-space: pre;
+ text-align: left;
+}
diff --git a/hyperkitty/static/img/reply.png b/hyperkitty/static/img/reply.png
new file mode 100644
index 0000000..7348aed
--- /dev/null
+++ b/hyperkitty/static/img/reply.png
Binary files differ
diff --git a/hyperkitty/static/js/hyperkitty.js b/hyperkitty/static/js/hyperkitty.js
index 1bb6719..7464314 100644
--- a/hyperkitty/static/js/hyperkitty.js
+++ b/hyperkitty/static/js/hyperkitty.js
@@ -20,17 +20,25 @@
*/
-
/*
- * Voting
+ * Generic
*/
-
-function vote(elem, value) {
- var form_data = $(elem).parent("form").serializeArray();
+function form_to_json(form) {
+ var form_data = form.serializeArray();
var data = {};
for (input in form_data) {
data[form_data[input].name] = form_data[input].value;
}
+ return data;
+}
+
+
+/*
+ * Voting
+ */
+
+function vote(elem, value) {
+ var data = form_to_json($(elem).parent("form"));
data['vote'] = value;
$.ajax({
type: "POST",
@@ -102,11 +110,7 @@ function setup_favorites() {
e.preventDefault();
var form = $(this).parents("form").first();
var action_field = form.find("input[name='action']");
- var form_data = form.serializeArray();
- var data = {};
- for (input in form_data) {
- data[form_data[input].name] = form_data[input].value;
- }
+ var data = form_to_json(form);
$.ajax({
type: "POST",
url: form.attr("action"),
@@ -131,6 +135,51 @@ function setup_favorites() {
}
+/*
+ * Replies
+ */
+
+function setup_replies() {
+ $("a.reply").click(function(e) {
+ e.preventDefault();
+ $(this).next().slideToggle("fast", function() {
+ if ($(this).css("display") === "block") {
+ $(this).find("textarea").focus();
+ }
+ });
+ });
+ $(".reply-form button[type='submit']").click(function(e) {
+ e.preventDefault();
+ var form = $(this).parents("form").first();
+ var data = form_to_json(form);
+ $.ajax({
+ type: "POST",
+ url: form.attr("action"),
+ //dataType: "json",
+ data: data,
+ success: function(response) {
+ form.parents(".reply-form").first().slideUp(function() {
+ form.find("textarea").val("");
+ });
+ $('<div class="reply-result"><div class="alert alert-success">'
+ + response + '</div></div>')
+ .appendTo(form.parents('.email_info').first())
+ .delay(2000).fadeOut('slow', function() { $(this).remove(); });
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ $('<div class="reply-result"><div class="alert alert-error">'
+ + '<button type="button" class="close" data-dismiss="alert">&times;</button> '
+ + jqXHR.responseText + '</div></div>')
+ .css("display", "none").insertBefore(form).slideDown();
+ }
+ });
+ });
+ $(".reply-form a.cancel").click(function(e) {
+ e.preventDefault();
+ $(this).parents(".reply-form").first().slideUp();
+ });
+}
+
/*
* Recent activity graph
@@ -230,4 +279,5 @@ $(document).ready(function() {
setup_quotes();
setup_months_list();
setup_favorites();
+ setup_replies();
});
diff --git a/hyperkitty/static/libs/bootstrap/bootstrap.css b/hyperkitty/static/libs/bootstrap/bootstrap.css
index 8ab3cef..4da7f6d 100644
--- a/hyperkitty/static/libs/bootstrap/bootstrap.css
+++ b/hyperkitty/static/libs/bootstrap/bootstrap.css
@@ -2249,7 +2249,7 @@ table th[class*="span"],
width: 14px;
height: 14px;
margin-top: 1px;
- *margin-right: .3em;
+ margin-right: .3em;
line-height: 14px;
vertical-align: text-top;
background-image: url("../img/glyphicons-halflings.png");
diff --git a/hyperkitty/templates/messages/message.html b/hyperkitty/templates/messages/message.html
index 5d7261d..a47d831 100644
--- a/hyperkitty/templates/messages/message.html
+++ b/hyperkitty/templates/messages/message.html
@@ -57,6 +57,7 @@
</ul>
</li>
{% endif %}
+ {% include "messages/reply_form.html" with mlist_fqdn=list_address message_id=email.message_id_hash %}
</ul>
{# vim: set noet: #}
diff --git a/hyperkitty/templates/messages/reply_form.html b/hyperkitty/templates/messages/reply_form.html
new file mode 100644
index 0000000..29bd15b
--- /dev/null
+++ b/hyperkitty/templates/messages/reply_form.html
@@ -0,0 +1,14 @@
+ <a class="reply icon-envelope" href="#">Reply</a>
+ <div class="reply-form dropdown">
+ <form method="post"
+ action="{% url message_reply mlist_fqdn=mlist_fqdn, message_id=message_id %}">
+ {% csrf_token %}
+ {{ reply_form.as_p }}
+ <p class="buttons">
+ <button type="submit" class="submit btn btn-primary">Send</button>
+ or <a class="cancel" href="#">cancel</a>
+ </p>
+ </form>
+ </div>
+
+{# vim: set noet: #}
diff --git a/hyperkitty/urls.py b/hyperkitty/urls.py
index ae38fe7..591ad41 100644
--- a/hyperkitty/urls.py
+++ b/hyperkitty/urls.py
@@ -61,6 +61,8 @@ urlpatterns = patterns('hyperkitty.views',
'message.attachment', name='message_attachment'),
url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/message/(?P<hashid>\w+)/vote$',
'message.vote', name='message_vote'),
+ url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/message/(?P<message_id>\w+)/reply$',
+ 'message.reply', name='message_reply'),
# Thread
url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/thread/(?P<threadid>\w+)/$',
diff --git a/hyperkitty/views/accounts.py b/hyperkitty/views/accounts.py
index 85b6a9a..2ee36da 100644
--- a/hyperkitty/views/accounts.py
+++ b/hyperkitty/views/accounts.py
@@ -33,7 +33,6 @@ from django.contrib.auth.decorators import (login_required,
user_passes_test)
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
-from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, redirect
from django.template import Context, loader, RequestContext
diff --git a/hyperkitty/views/forms.py b/hyperkitty/views/forms.py
index d52eaea..90c288f 100644
--- a/hyperkitty/views/forms.py
+++ b/hyperkitty/views/forms.py
@@ -101,3 +101,5 @@ class SearchForm(forms.Form):
)
+class ReplyForm(forms.Form):
+ message = forms.CharField(widget=forms.Textarea, label="")
diff --git a/hyperkitty/views/message.py b/hyperkitty/views/message.py
index 15aee66..509205c 100644
--- a/hyperkitty/views/message.py
+++ b/hyperkitty/views/message.py
@@ -26,10 +26,12 @@ import urllib
import django.utils.simplejson as simplejson
from django.http import HttpResponse, HttpResponseRedirect, Http404
-from django.shortcuts import redirect
+from django.shortcuts import redirect, render
from django.template import RequestContext, loader
from django.conf import settings
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger, InvalidPage
+from django.core.urlresolvers import reverse
+from django.core.mail import EmailMessage
from django.contrib.auth.decorators import (login_required,
permission_required,
user_passes_test)
@@ -92,6 +94,7 @@ def index(request, mlist_fqdn, hashid):
'hashid' : hashid,
'archives_length': get_months(store, mlist_fqdn),
'use_mockups': settings.USE_MOCKUPS,
+ 'reply_form': ReplyForm(),
})
return HttpResponse(t.render(c))
@@ -153,3 +156,33 @@ def vote(request, mlist_fqdn, hashid):
return HttpResponse(simplejson.dumps(status),
mimetype='application/javascript')
+
+
+@login_required
+def reply(request, mlist_fqdn, message_id):
+ """ Sends a reply to the list.
+ TODO: unit tests
+ """
+ if request.method != 'POST':
+ return HttpResponse("Something went wrong", content_type="text/plain", status=500)
+ form = ReplyForm(request.POST)
+ if not form.is_valid():
+ return HttpResponse(form.errors.as_text(), content_type="text/plain", status=400)
+ store = get_store(request)
+ message = store.get_message_by_hash_from_list(mlist_fqdn, message_id)
+ subject = message.subject
+ if not message.subject.lower().startswith("re:"):
+ subject = "Re: %s" % subject
+ reply = EmailMessage(
+ subject=subject,
+ body=form.cleaned_data["message"],
+ from_email='"%s %s" <%s>' %
+ (request.user.first_name, request.user.last_name,
+ request.user.email),
+ to=[mlist_fqdn],
+ cc=['aurelien@bompard.org'],
+ headers={
+ "In-Reply-To": message.message_id,
+ })
+ reply.send()
+ return HttpResponse("The reply has been sent successfully.", mimetype="text/plain")
diff --git a/hyperkitty/views/thread.py b/hyperkitty/views/thread.py
index 6769034..549dfe0 100644
--- a/hyperkitty/views/thread.py
+++ b/hyperkitty/views/thread.py
@@ -144,6 +144,7 @@ def thread_index(request, mlist_fqdn, threadid, month=None, year=None):
'use_mockups': settings.USE_MOCKUPS,
'sort_mode': sort_mode,
'fav_action': fav_action,
+ 'reply_form': ReplyForm(),
})
return HttpResponse(t.render(c))