summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askbot/media/js/group_messaging.js204
-rw-r--r--askbot/media/style/style.less4
-rw-r--r--askbot/templates/group_messaging/home.html1
-rw-r--r--askbot/templates/group_messaging/macros.html16
-rw-r--r--askbot/templates/group_messaging/senders_list.html10
-rw-r--r--askbot/templates/group_messaging/thread_details.html7
-rw-r--r--askbot/templates/group_messaging/threads_list.html8
-rw-r--r--askbot/templates/user_inbox/messages.html40
-rw-r--r--askbot/views/commands.py42
-rw-r--r--group_messaging/urls.py5
-rw-r--r--group_messaging/views.py32
11 files changed, 318 insertions, 51 deletions
diff --git a/askbot/media/js/group_messaging.js b/askbot/media/js/group_messaging.js
index d9d3f0df..1b0275e2 100644
--- a/askbot/media/js/group_messaging.js
+++ b/askbot/media/js/group_messaging.js
@@ -253,6 +253,19 @@ NewThreadComposer.prototype.createDom = function() {
label.after(error);
};
+var ThreadHeading = function() {
+ SimpleControl.call(this);
+};
+inherits(ThreadHeading, SimpleControl);
+
+ThreadHeading.prototype.getId = function() {
+ return this._id;
+};
+
+ThreadHeading.prototype.decorate = function(element) {
+ this._element = element;
+ this._id = element.data('threadId');
+};
/**
* @constructor
@@ -262,6 +275,40 @@ var ThreadsList = function() {
};
inherits(ThreadsList, HideableWidget);
+ThreadsList.prototype.setMessageCenter = function(ctr) {
+ this._messageCenter = ctr;
+};
+
+ThreadsList.prototype.getOpenThreadHandler = function(threadId) {
+ var messageCenter = this._messageCenter;
+ return function() {
+ messageCenter.openThread(threadId);
+ };
+};
+
+ThreadsList.prototype.setHTML = function(html) {
+ $.each(this._threads, function(idx, thread) {
+ thread.dispose();
+ });
+ this._element.html(html);
+ this.decorate(this._element);
+};
+
+ThreadsList.prototype.decorate = function(element) {
+ this._element = element;
+ var headingElements = element.find('tr.thread-heading');
+ var me = this;
+ var threads = [];
+ $.each(headingElements, function(idx, headingElement) {
+ var heading = new ThreadHeading();
+ heading.decorate($(headingElement));
+ var threadId = heading.getId();
+ heading.setHandler(me.getOpenThreadHandler(threadId));
+ threads.push(heading);
+ });
+ this._threads = threads;
+}
+
/**
* @constructor
@@ -275,10 +322,91 @@ inherits(Message, Widget);
/**
* @constructor
*/
-var Thread = function() {
+var ThreadContainer = function() {
HideableWidget.call(this);
};
-inherits(Thread, HideableWidget);
+inherits(ThreadContainer, HideableWidget);
+
+
+/**
+ * @constructor
+ */
+var Thread = function() {
+ WrappedElement.call(this);
+};
+inherits(Thread, WrappedElement);
+
+
+/**
+ * @constructor
+ */
+var Sender = function() {
+ SimpleControl.call(this);
+};
+inherits(Sender, SimpleControl);
+
+Sender.prototype.getId = function() {
+ return this._id;
+};
+
+Sender.prototype.select = function() {
+ this._element.addClass('selected');
+};
+
+Sender.prototype.unselect = function() {
+ this._element.removeClass('selected');
+};
+
+Sender.prototype.decorate = function(element) {
+ Sender.superClass_.decorate.call(this, element);
+ this._id = element.data('senderId');
+};
+
+
+/**
+ * @constructor
+ * list of senders in the first column of inbox
+ */
+var SendersList = function() {
+ WrappedElement.call(this);
+ this._messageCenter = undefined;
+};
+inherits(SendersList, WrappedElement);
+
+SendersList.prototype.setMessageCenter = function(ctr) {
+ this._messageCenter = ctr;
+};
+
+SendersList.prototype.getSenders = function() {
+ return this._senders;
+};
+
+SendersList.prototype.getSenderSelectHandler = function(sender) {
+ var messageCenter = this._messageCenter;
+ var me = this;
+ return function() {
+ $.map(me.getSenders(), function(s){ s.unselect() });
+ sender.select();
+ messageCenter.loadThreadsForSender(sender.getId());
+ };
+};
+
+SendersList.prototype.decorate = function(element) {
+ this._element = element;
+ var senders = [];
+ $.each(element.find('a'), function(idx, item) {
+ var sender = new Sender();
+ sender.decorate($(item));
+ senders.push(sender);
+ });
+
+ this._senders = senders;
+
+ var me = this;
+ $.each(senders, function(idx, sender) {
+ sender.setHandler(me.getSenderSelectHandler(sender));
+ });
+};
/**
@@ -292,25 +420,91 @@ inherits(MessageCenter, Widget);
MessageCenter.prototype.setState = function(state) {
this._editor.hide();
this._threadsList.hide();
- //this._thread.hide();
+ this._threadContainer.hide();
if (state === 'compose') {
this._editor.show();
} else if (state === 'show-list') {
this._threadsList.show();
} else if (state === 'show-thread') {
- this._thread.show();
+ this._threadContainer.show();
+ }
+};
+
+MessageCenter.prototype.clearThread = function() {
+ if (this._thread) {
+ this._thread.dispose();
}
+ this._threadContainer.html('');
+};
+
+MessageCenter.prototype.setThreadHTML = function(html) {
+ this._threadContainer.html(html);
+ var thread = new Thread();
+ thread.decorate($(this._threadContainer.children()[0]));
+ this._thread = thread;
+};
+
+MessageCenter.prototype.openThread = function(threadId) {
+ var url = this._urls['getThreads'] + threadId + '/';
+ var me = this;
+ $.ajax({
+ type: 'GET',
+ dataType: 'json',
+ url: url,
+ cache: false,
+ success: function(data) {
+ if (data['success']) {
+ me.clearThread();
+ me.setThreadHTML(data['html']);
+ me.setState('show-thread');
+ }
+ }
+ });
+};
+
+MessageCenter.prototype.loadThreadsForSender = function(senderId) {
+ var threadsList = this._threadsList;
+ var url = this._urls['getThreads'];
+ me = this;
+ $.ajax({
+ type: 'GET',
+ dataType: 'json',
+ url: url,
+ cache: false,
+ data: {sender_id: senderId},
+ success: function(data) {
+ if (data['success']) {
+ threadsList.setHTML(data['html']);
+ me.setState('show-list');
+ }
+ }
+ });
};
MessageCenter.prototype.decorate = function(element) {
this._element = element;
this._firstCol = element.find('.first-col');
this._secondCol = element.find('.second-col');
+
+ this._urls = {
+ getThreads: element.data('getThreadsUrl'),
+ getThreadDetails: element.data('getThreadDetailsUrl')
+ };
+
//read sender list
+ var senders = new SendersList();
+ senders.setMessageCenter(this);
+ senders.decorate($('.senders-list'));
+ this._sendersList = senders;
//read message list
var threads = new ThreadsList();
+ threads.setMessageCenter(this);
threads.decorate($('.threads-list'));
this._threadsList = threads;
+ //add empty thread container
+ var threadContainer = new ThreadContainer();
+ this._secondCol.append(threadContainer.getElement());
+ this._threadContainer = threadContainer.getElement();
var me = this;
//create editor
@@ -319,7 +513,7 @@ MessageCenter.prototype.decorate = function(element) {
editor.setSendUrl(element.data('createThreadUrl'));
editor.onAfterCancel(function() { me.setState('show-list') });
editor.onSendSuccess(function() {
- me.setState('show-list');
+ editor.cancel();
notify.show(gettext('message sent'), true);
});
this._editor = editor;
diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less
index 053d4307..607261ce 100644
--- a/askbot/media/style/style.less
+++ b/askbot/media/style/style.less
@@ -123,6 +123,10 @@ blockquote {
background-color: #F5F5F5;
}
+html {
+ overflow-y: scroll;
+}
+
/* http://pathfindersoftware.com/2007/09/developers-note-2/ */
* html .clearfix,
* html .paginator {
diff --git a/askbot/templates/group_messaging/home.html b/askbot/templates/group_messaging/home.html
index b6733624..258ee6e8 100644
--- a/askbot/templates/group_messaging/home.html
+++ b/askbot/templates/group_messaging/home.html
@@ -1,5 +1,6 @@
<div class="group-messaging"
data-create-thread-url="{% url create_thread %}"
+ data-get-threads-url="{% url get_threads %}"
>
<div class="first-col">
<button class="submit compose">{% trans %}compose{% endtrans %}</button>
diff --git a/askbot/templates/group_messaging/macros.html b/askbot/templates/group_messaging/macros.html
new file mode 100644
index 00000000..312fe1e2
--- /dev/null
+++ b/askbot/templates/group_messaging/macros.html
@@ -0,0 +1,16 @@
+{%- macro message(post, visitor) -%}
+<div class="message">
+ <p class="header">
+ {% if post.sender == visitor %}
+ {% trans date=post.sent_at %}You wrote on {{ date }}:{% endtrans %}
+ {% else %}
+ {% trans user=post.sender.username,
+ date=post.sent_at
+ %}{{ user }} wrote on {{ date }}:{% endtrans %}
+ {% endif %}
+ </p>
+ <div class="content">
+ {{ post.html|safe }}
+ </div>
+</div>
+{%- endmacro -%}
diff --git a/askbot/templates/group_messaging/senders_list.html b/askbot/templates/group_messaging/senders_list.html
index 43f8ea28..a2e4766f 100644
--- a/askbot/templates/group_messaging/senders_list.html
+++ b/askbot/templates/group_messaging/senders_list.html
@@ -1,9 +1,9 @@
{% if senders %}
<ul class="senders-list">
-{% for sender in senders %}
- <li>{% trans %}Senders:{% endtrans %}</li>
- <li><a data-sender-id="-1">{% trans %}all{% endtrans %}</a></li>
- <li><a data-sender-id="{{ sender.id }}">{{ sender.username|escape }}</a></li>
-{% endfor %}
+ <li>{% trans %}Messages by sender:{% endtrans %}</li>
+ <li><a class="selected" data-sender-id="-1">{% trans %}all users{% endtrans %}</a></li>
+ {% for sender in senders %}
+ <li><a data-sender-id="{{ sender.id }}">{{ sender.username|escape }}</a></li>
+ {% endfor %}
</ul>
{% endif %}
diff --git a/askbot/templates/group_messaging/thread_details.html b/askbot/templates/group_messaging/thread_details.html
new file mode 100644
index 00000000..969479d8
--- /dev/null
+++ b/askbot/templates/group_messaging/thread_details.html
@@ -0,0 +1,7 @@
+{% from "group_messaging/macros.html" import message %}
+<ul class="thread" data-thread-id="{{ root_message.id }}">
+ <li>{{ message(root_message, request.user) }}</li>
+ {% for response in responses %}
+ <li>{{ message(response, request.user) }}</li>
+ {% endfor %}
+</ul>
diff --git a/askbot/templates/group_messaging/threads_list.html b/askbot/templates/group_messaging/threads_list.html
index 80df18b9..8469198c 100644
--- a/askbot/templates/group_messaging/threads_list.html
+++ b/askbot/templates/group_messaging/threads_list.html
@@ -2,17 +2,17 @@
{% if threads %}
{% for thread in threads %}
{% set thread_data = threads_data[thread.id] %}
- <tr class="{{ thread_data['status'] }}"
+ <tr class="thread-heading {{ thread_data['status'] }}"
data-thread-id="{{ thread.id }}"
>
<td class="senders">{{ thread_data['senders_info']|escape }}</td>
<td class="subject">{{ thread.headline|escape }}</td>
- <td class="timestamp">{{ thread.last_active_at }}</td>
+ <td class="timestamp">{{ thread.last_active_at|timesince }}</td>
</tr>
{% endfor %}
{% else %}
- <tr class="empty">
- <td colspan="3">{% trans %}there are no messages yet...{% endtrans %}<td>
+ <tr>
+ <td class="empty" colspan="3">{% trans %}there are no messages yet...{% endtrans %}<td>
</tr>
{% endif %}
</table>
diff --git a/askbot/templates/user_inbox/messages.html b/askbot/templates/user_inbox/messages.html
index 9886e148..d42e5fb4 100644
--- a/askbot/templates/user_inbox/messages.html
+++ b/askbot/templates/user_inbox/messages.html
@@ -6,20 +6,49 @@
.group-messaging {
padding-top: 25px;
}
- .group-messaging ul {
+ ul.senders-list {
padding: 0px;
+ margin-left: 1em;
+ margin-top: 0.5em;
}
- .group-messaging li {
+ .senders-list li {
+ height: 1.5em;
+ vertical-align: center;
list-style-type: none;
list-style-position: outside;
}
- tr.empty {
+ .senders-list .selected {
+ font-weight: bold;
+ }
+ table.threads-list {
+ width: 100%;
+ }
+ .threads-list tr {
+ height: 2em;
+ }
+ .threads-list td {
+ vertical-align: center;
+ }
+ .threads-list tr.new {
+ font-weight: bold;
+ color: #777;
+ }
+ .threads-list tr:hover {
+ background-color: #eff5f6;
+ }
+ td.empty {
line-height: 30px;
vertical-align: middle;
background: #eee;
padding-left: 320px;
margin: 0px;
}
+ td.senders {
+ padding-left: 10px;
+ }
+ td.timestamp {
+ width: 180px;
+ }
button.compose {
width: 150px;
}
@@ -31,10 +60,11 @@
width: 150px;
}
.second-col {
- width: 810px;
+ width: 790px;
+ margin-left: 20px;
}
.message-composer {
- padding: 0 0 10px 25px;
+ padding-bottom: 10px;
margin-top: -25px;
}
.message-composer input.recipients,
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index 2ab15c35..f05cc9e2 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -13,7 +13,11 @@ from django.core import exceptions
#from django.core.management import call_command
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
-from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponseBadRequest
+from django.http import Http404
+from django.http import HttpResponse
+from django.http import HttpResponseBadRequest
+from django.http import HttpResponseRedirect
+from django.http import HttpResponseForbidden
from django.forms import ValidationError, IntegerField, CharField
from django.shortcuts import get_object_or_404
from django.views.decorators import csrf
@@ -1196,30 +1200,26 @@ def save_draft_answer(request):
draft.save()
@decorators.get_only
-@decorators.admins_only
def get_users_info(request):
"""retuns list of user names and email addresses
of "fake" users - so that admins can post on their
behalf"""
- #user_info_list = models.User.objects.filter(
- # is_fake=True
- # ).values_list(
- # 'username',
- # 'email'
- # )
- user_info_list = models.User.objects.values_list(
- 'username',
- 'email'
- )
-
- result_list = list()
- for user_info in user_info_list:
- username = user_info[0]
- email = user_info[1]
- result_list.append('%s|%s' % (username, email))
-
- output = '\n'.join(result_list)
- return HttpResponse(output, mimetype = 'text/plain')
+ if request.user.is_anonymous():
+ return HttpResponseForbidden()
+
+ query = request.GET['q']
+ limit = IntegerField().clean(request.GET['limit'])
+
+ users = models.User.objects
+ user_info_list = users.filter(username__istartswith=query)
+
+ if request.user.is_administrator_or_moderator():
+ user_info_list = user_info_list.values_list('username', 'email')
+ else:
+ user_info_list = user_info_list.values_list('username')
+
+ result_list = ['|'.join(info) for info in user_info_list[:limit]]
+ return HttpResponse('\n'.join(result_list), mimetype = 'text/plain')
@csrf.csrf_protect
def share_question_with_group(request):
diff --git a/group_messaging/urls.py b/group_messaging/urls.py
index eb033751..618ae1d5 100644
--- a/group_messaging/urls.py
+++ b/group_messaging/urls.py
@@ -10,6 +10,11 @@ urlpatterns = patterns('',
name='get_threads'
),
url(
+ '^threads/(?P<thread_id>\d+)/$',
+ views.ThreadDetails().as_view(),
+ name='thread_details'
+ ),
+ url(
'^threads/create/$',
views.NewThread().as_view(),
name='create_thread'
diff --git a/group_messaging/views.py b/group_messaging/views.py
index 6511fe6e..0ea710db 100644
--- a/group_messaging/views.py
+++ b/group_messaging/views.py
@@ -42,7 +42,7 @@ class InboxView(object):
template_name = self.template_name
template = get_template(self.template_name)
html = template.render(context)
- json = simplejson.dumps({'html': html})
+ json = simplejson.dumps({'html': html, 'success': True})
return HttpResponse(json, mimetype='application/json')
@@ -134,7 +134,7 @@ class NewResponse(InboxView):
class ThreadsList(InboxView):
"""shows list of threads for a given user"""
- template_name = 'threads_list.html'
+ template_name = 'group_messaging/threads_list.html'
http_method_list = ('GET',)
def get_context(self, request):
@@ -142,6 +142,10 @@ class ThreadsList(InboxView):
#get threads and the last visit time
threads = Message.objects.get_threads_for_user(request.user)
+ sender_id = IntegerField().clean(request.GET.get('sender_id', '-1'))
+ if sender_id != -1:
+ threads = threads.filter(sender__id=sender_id)
+
#for each thread we need to know if there is something
#unread for the user - to mark "new" threads as bold
threads_data = dict()
@@ -154,15 +158,15 @@ class ThreadsList(InboxView):
if request.user.username in senders_names:
senders_names.remove(request.user.username)
thread_data['senders_info'] = ', '.join(senders_names)
+ thread_data['thread'] = thread
threads_data[thread.id] = thread_data
- threads_data[thread] = thread
last_visit_times = LastVisitTime.objects.filter(
user=request.user,
message__in=threads
)
for last_visit in last_visit_times:
- thread_data = threads_data[last_visit.thread_id]
+ thread_data = threads_data[last_visit.message_id]
if thread_data['thread'].last_active_at <= last_visit.at:
thread_data['status'] = 'seen'
@@ -173,7 +177,7 @@ class ThreadsList(InboxView):
class SendersList(InboxView):
"""shows list of senders for a user"""
- template_name = 'senders_list.html'
+ template_name = 'group_messaging/senders_list.html'
http_method_names = ('GET',)
def get_context(self, request):
@@ -185,13 +189,19 @@ class SendersList(InboxView):
class ThreadDetails(InboxView):
"""shows entire thread in the unfolded form"""
- template_name = 'thread_details.html'
+ template_name = 'group_messaging/thread_details.html'
http_method_names = ('GET',)
- def get_context(self, request):
+ def get_context(self, request, thread_id=None):
"""shows individual thread"""
- thread_id = IntegerField().clean(request.GET['thread_id'])
#todo: assert that current thread is the root
- messages = Message.objects.filter(root__id=thread_id)
- messages = messages.values('html')
- return {'messages': messages}
+ root = Message.objects.get(id=thread_id)
+ responses = Message.objects.filter(root__id=thread_id)
+ last_visit, created = LastVisitTime.objects.get_or_create(
+ message=root,
+ user=request.user
+ )
+ if created is False:
+ last_visit.at = datetime.datetime.now()
+ last_visit.save()
+ return {'root_message': root, 'responses': responses, 'request': request}