diff options
author | Aurélien Bompard <aurelien@bompard.org> | 2013-02-12 13:29:32 +0100 |
---|---|---|
committer | Aurélien Bompard <aurelien@bompard.org> | 2013-02-12 13:29:32 +0100 |
commit | ccafc51ade9536046aeb096b04b069f17874fab4 (patch) | |
tree | e93564b45884debca14d561552a96a980819943d /hyperkitty | |
parent | 0b0395fe000957b336d0ece6b23af0083a0853ab (diff) | |
download | hyperkitty-ccafc51ade9536046aeb096b04b069f17874fab4.tar.gz hyperkitty-ccafc51ade9536046aeb096b04b069f17874fab4.tar.xz hyperkitty-ccafc51ade9536046aeb096b04b069f17874fab4.zip |
Add a "Create new thread" button
Diffstat (limited to 'hyperkitty')
-rw-r--r-- | hyperkitty/static/css/hyperkitty.css | 34 | ||||
-rw-r--r-- | hyperkitty/static/js/hyperkitty.js | 14 | ||||
-rw-r--r-- | hyperkitty/templates/message_new.html | 42 | ||||
-rw-r--r-- | hyperkitty/templates/thread_list.html | 9 | ||||
-rw-r--r-- | hyperkitty/urls.py | 2 | ||||
-rw-r--r-- | hyperkitty/views/forms.py | 4 | ||||
-rw-r--r-- | hyperkitty/views/list.py | 9 | ||||
-rw-r--r-- | hyperkitty/views/message.py | 75 | ||||
-rw-r--r-- | hyperkitty/views/thread.py | 10 |
9 files changed, 175 insertions, 24 deletions
diff --git a/hyperkitty/static/css/hyperkitty.css b/hyperkitty/static/css/hyperkitty.css index 5381e86..f08baaa 100644 --- a/hyperkitty/static/css/hyperkitty.css +++ b/hyperkitty/static/css/hyperkitty.css @@ -134,6 +134,16 @@ table.user-data { list-style-type: none; } +.flashmsg { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.flashmsg .alert { + display: inline-block; +} + /* Add icons to some text */ .participant, .discussion, .saved, .notsaved { @@ -394,6 +404,15 @@ form.likeform { margin-right: 4em; } +#thread-list .thread-list-header .thread-new { + float: right; +} +#thread-list .thread-list-header .thread-new strong { + font-size: 150%; + font-weight: bold; + margin-right: 0.2em; +} + /* Thread view */ @@ -902,3 +921,18 @@ a.reply.disabled { white-space: pre; text-align: left; } + + +/* + * New thread form + */ +.new-thread-form { + margin-left: 2em; +} +.new-thread-form #id_subject, +.new-thread-form textarea { + width: 90%; +} +.new-thread-form .buttons .submit { + margin-right: 2em; +} diff --git a/hyperkitty/static/js/hyperkitty.js b/hyperkitty/static/js/hyperkitty.js index 87d6987..5371657 100644 --- a/hyperkitty/static/js/hyperkitty.js +++ b/hyperkitty/static/js/hyperkitty.js @@ -61,7 +61,6 @@ function vote(elem, value) { function setup_vote() { - $("a.youlike.disabled").add("a.youdislike.disabled").tooltip(); $("a.youlike").click(function(e) { e.preventDefault(); vote(this, 1); }); $("a.youdislike").click(function(e) { e.preventDefault(); vote(this, -1); }); } @@ -107,7 +106,6 @@ function setup_favorites() { form.find("a.saved").show(); } }).trigger("change"); - $(".favorite a.disabled").tooltip(); $(".favorite a").bind("click", function(e) { e.preventDefault(); if ($(elem).hasClass("disabled")) { @@ -145,7 +143,6 @@ function setup_favorites() { */ function setup_replies() { - $("a.reply.disabled").tooltip(); $("a.reply").click(function(e) { e.preventDefault(); if (!$(this).hasClass("disabled")) { @@ -301,6 +298,15 @@ function setup_months_list() { $("#months-list").accordion({ collapsible: true, active: current }); } +function setup_disabled_tooltips() { + $("a.disabled").tooltip().click(function (e) { + e.preventDefault(); + }); +} + +function setup_flash_messages() { + $('.flashmsg').delay(3000).fadeOut('slow'); +} /* @@ -315,4 +321,6 @@ $(document).ready(function() { setup_months_list(); setup_favorites(); setup_replies(); + setup_disabled_tooltips(); + setup_flash_messages(); }); diff --git a/hyperkitty/templates/message_new.html b/hyperkitty/templates/message_new.html new file mode 100644 index 0000000..c764c1a --- /dev/null +++ b/hyperkitty/templates/message_new.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} +{% load gravatar %} +{% load hk_generic %} +{% load storm %} +{% load crispy_forms_tags %} + + +{% block title %} +Create a new thread - {{ mlist.display_name|default:mlist.name|escapeemail }} - {{ app_name|title }} +{% endblock %} + +{% block content %} + +<div class="row-fluid"> + +{% include 'threads/month_list.html' %} + + <div class="span7"> + + <div class="message-header"> + <h1>Create a new thread</h1> + </div> + + <div class="new-thread-form"> + <form method="post" + action="{% url message_new mlist_fqdn=mlist.name %}"> + {% csrf_token %} + {{ post_form|crispy }} + <p class="buttons"> + <button type="submit" class="submit btn btn-primary">Send</button> + or <a class="cancel" href="{% url archives_latest mlist_fqdn=mlist.name %}">cancel</a> + </p> + </form> + </div> + + </div> + +</div> + +{% endblock %} + +{# vim: set noet: #} diff --git a/hyperkitty/templates/thread_list.html b/hyperkitty/templates/thread_list.html index cb95daa..2b61792 100644 --- a/hyperkitty/templates/thread_list.html +++ b/hyperkitty/templates/thread_list.html @@ -15,7 +15,16 @@ <div id="thread-list" class="span8"> + {% if flash_msg %} + <div class="flashmsg"> + <div class="alert alert-success">{{ flash_msg }}</div> + </div> + {% endif %} + <div class="thread-list-header page-header"> + <a href="{% url message_new mlist_fqdn=mlist.name %}" + class="thread-new btn{% if not user.is_authenticated %} disabled" title="You must be logged-in to create a thread.{% endif %}" + ><strong>+</strong> Start a new thread</a> <h1>{{ mlist.display_name|default:mlist.name|escapeemail }} <small>{{ list_title }}</small> </h1> diff --git a/hyperkitty/urls.py b/hyperkitty/urls.py index ac1e4d7..ddf13bc 100644 --- a/hyperkitty/urls.py +++ b/hyperkitty/urls.py @@ -64,6 +64,8 @@ urlpatterns = patterns('hyperkitty.views', 'message.vote', name='message_vote'), url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/message/(?P<message_id_hash>\w+)/reply$', 'message.reply', name='message_reply'), + url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/message/new$', + 'message.new_message', name='message_new'), # Thread url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/thread/(?P<threadid>\w+)/$', diff --git a/hyperkitty/views/forms.py b/hyperkitty/views/forms.py index 90c288f..3fe6e7e 100644 --- a/hyperkitty/views/forms.py +++ b/hyperkitty/views/forms.py @@ -103,3 +103,7 @@ class SearchForm(forms.Form): class ReplyForm(forms.Form): message = forms.CharField(widget=forms.Textarea, label="") + +class PostForm(forms.Form): + subject = forms.CharField() + message = forms.CharField(widget=forms.Textarea) diff --git a/hyperkitty/views/list.py b/hyperkitty/views/list.py index 6efd0b3..c8439ef 100644 --- a/hyperkitty/views/list.py +++ b/hyperkitty/views/list.py @@ -52,6 +52,10 @@ if settings.USE_MOCKUPS: from hyperkitty.lib.mockup import generate_top_author, generate_thread_per_category +FLASH_MESSAGES = { + "sent-ok": "The message has been sent successfully.", +} + def archives(request, mlist_fqdn, year=None, month=None, day=None): if year is None and month is None: @@ -132,6 +136,10 @@ def _thread_list(request, mlist, threads, template_name='thread_list.html', extr # If page is out of range (e.g. 9999), deliver last page of results. threads = paginator.page(paginator.num_pages) + flash_msg = request.GET.get("msg") + if flash_msg: + flash_msg = FLASH_MESSAGES[flash_msg] + context = { 'mlist' : mlist, 'current_page': page_num, @@ -139,6 +147,7 @@ def _thread_list(request, mlist, threads, template_name='thread_list.html', extr 'threads': threads, 'participants': len(participants), 'months_list': get_months(store, mlist.name), + 'flash_msg': flash_msg, 'use_mockups': settings.USE_MOCKUPS, } context.update(extra_context) diff --git a/hyperkitty/views/message.py b/hyperkitty/views/message.py index e64ac5f..de2da05 100644 --- a/hyperkitty/views/message.py +++ b/hyperkitty/views/message.py @@ -23,15 +23,17 @@ import re import os import urllib +import datetime import django.utils.simplejson as simplejson from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.shortcuts import redirect, render +from django.shortcuts import redirect, render, render_to_response 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.core.exceptions import SuspiciousOperation from django.contrib.auth.decorators import (login_required, permission_required, user_passes_test) @@ -142,26 +144,69 @@ def reply(request, mlist_fqdn, message_id_hash): TODO: unit tests """ if request.method != 'POST': - return HttpResponse("Something went wrong", content_type="text/plain", status=500) + raise SuspiciousOperation form = ReplyForm(request.POST) if not form.is_valid(): - return HttpResponse(form.errors.as_text(), content_type="text/plain", status=400) + 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_hash) + mlist = store.get_list(mlist_fqdn) + message = store.get_message_by_hash_from_list(mlist.name, message_id_hash) 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={ + _send_email(request, mlist, subject, form.cleaned_data["message"], { "In-Reply-To": "<%s>" % message.message_id, "References": "<%s>" % message.message_id, }) - reply.send() - return HttpResponse("The reply has been sent successfully.", mimetype="text/plain") + return HttpResponse("The reply has been sent successfully.", + mimetype="text/plain") + + +@login_required +def new_message(request, mlist_fqdn): + """ Sends a new thread-starting message to the list. + TODO: unit tests + """ + store = get_store(request) + mlist = store.get_list(mlist_fqdn) + if request.method == 'POST': + form = PostForm(request.POST) + if form.is_valid(): + _send_email(request, mlist, form.cleaned_data['subject'], + form.cleaned_data["message"]) + today = datetime.date.today() + redirect_url = reverse( + 'archives_with_month', kwargs={ + "mlist_fqdn": mlist_fqdn, + 'year': today.year, + 'month': today.month}) + redirect_url += "?msg=sent-ok" + return HttpResponseRedirect(redirect_url) + else: + form = PostForm() + context = { + "mlist": mlist, + "post_form": form, + 'months_list': get_months(store, mlist.name), + 'use_mockups': settings.USE_MOCKUPS, + } + return render_to_response("message_new.html", context, + context_instance=RequestContext(request)) + + +def _send_email(request, mlist, subject, message, headers={}): + if not mlist: + # Make sure the list exists to avoid posting to any email addess + raise SuspiciousOperation("I don't know this mailing-list") + headers["User-Agent"] = "HyperKitty on %s" % request.build_absolute_uri("/") + msg = EmailMessage( + subject=subject, + body=message, + from_email='"%s %s" <%s>' % + (request.user.first_name, request.user.last_name, + request.user.email), + to=[mlist.name], + headers=headers, + ) + msg.send() diff --git a/hyperkitty/views/thread.py b/hyperkitty/views/thread.py index c819792..5375bea 100644 --- a/hyperkitty/views/thread.py +++ b/hyperkitty/views/thread.py @@ -29,6 +29,7 @@ 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.exceptions import SuspiciousOperation from django.utils.datastructures import SortedDict from django.contrib.auth.decorators import (login_required, permission_required, @@ -136,8 +137,7 @@ def add_tag(request, mlist_fqdn, threadid): content_type="text/plain", status=403) if request.method != 'POST': - return HttpResponse("Something went wrong here", - content_type="text/plain", status=500) + raise SuspiciousOperation form = AddTagForm(request.POST) if not form.is_valid(): @@ -171,8 +171,7 @@ def favorite(request, mlist_fqdn, threadid): content_type="text/plain", status=403) if request.method != 'POST': - return HttpResponse("Something went wrong here", - content_type="text/plain", status=500) + raise SuspiciousOperation props = dict(list_address=mlist_fqdn, threadid=threadid, user=request.user) if request.POST["action"] == "add": @@ -189,7 +188,6 @@ def favorite(request, mlist_fqdn, threadid): else: fav.delete() else: - return HttpResponse("Something went wrong here", - content_type="text/plain", status=500) + raise SuspiciousOperation return HttpResponse("success", mimetype='text/plain') |