Commit 4e00e7c56a48d36f0ecd450524f11b016ade6fa4
1 parent
a4d52056
Exists in
master
and in
39 other branches
Updating message thread layout
Showing
9 changed files
with
398 additions
and
208 deletions
Show diff stats
src/api/handlers.py
1 | 1 | |
2 | 2 | from django.core.cache import cache |
3 | -from django.db import IntegrityError | |
4 | -from django.core.exceptions import ObjectDoesNotExist | |
5 | -from django.contrib.auth.decorators import login_required | |
6 | 3 | |
7 | 4 | from piston.utils import rc |
8 | 5 | from piston.handler import BaseHandler |
9 | 6 | |
10 | 7 | from colab.deprecated import solrutils |
11 | -from super_archives.models import Message, PageHit | |
12 | - | |
13 | - | |
14 | -class VoteHandler(BaseHandler): | |
15 | - allowed_methods = ('GET', 'POST', 'DELETE') | |
16 | - | |
17 | - def create(self, request, message_id): | |
18 | - if not request.user.is_authenticated(): | |
19 | - return rc.FORBIDDEN | |
20 | - | |
21 | - try: | |
22 | - Message.objects.get(id=message_id).vote(request.user) | |
23 | - except IntegrityError: | |
24 | - return rc.DUPLICATE_ENTRY | |
25 | - | |
26 | - return rc.CREATED | |
27 | - | |
28 | - def read(self, request, message_id): | |
29 | - return Message.objects.get(id=message_id).votes_count() | |
30 | - | |
31 | - def delete(self, request, message_id): | |
32 | - if not request.user.is_authenticated(): | |
33 | - return rc.FORBIDDEN | |
34 | - | |
35 | - try: | |
36 | - Message.objects.get(id=message_id).unvote(request.user) | |
37 | - except ObjectDoesNotExist: | |
38 | - return rc.NOT_HERE | |
39 | - | |
40 | - return rc.DELETED | |
8 | +from super_archives.models import PageHit | |
41 | 9 | |
42 | 10 | |
43 | 11 | class CountHandler(BaseHandler): | ... | ... |
src/api/urls.py
... | ... | @@ -2,15 +2,15 @@ from django.conf.urls import patterns, include, url |
2 | 2 | |
3 | 3 | from piston.resource import Resource |
4 | 4 | |
5 | -from .handlers import VoteHandler, CountHandler, SearchHandler | |
5 | +from .handlers import CountHandler, SearchHandler | |
6 | +from .views import VoteView | |
6 | 7 | |
7 | 8 | |
8 | -vote_handler = Resource(VoteHandler) | |
9 | 9 | count_handler = Resource(CountHandler) |
10 | 10 | search_handler = Resource(SearchHandler) |
11 | 11 | |
12 | 12 | urlpatterns = patterns('', |
13 | - url(r'message/(?P<message_id>\d+)/vote$', vote_handler), | |
13 | + url(r'message/(?P<msg_id>\d+)/vote$', VoteView.as_view()), | |
14 | 14 | url(r'hit/$', count_handler), |
15 | 15 | url(r'search/$', search_handler), |
16 | 16 | ) | ... | ... |
... | ... | @@ -0,0 +1,45 @@ |
1 | + | |
2 | +from django import http | |
3 | +from django.db import IntegrityError | |
4 | +from django.views.generic import View | |
5 | +from django.core.exceptions import ObjectDoesNotExist | |
6 | + | |
7 | + | |
8 | +from super_archives.models import Message | |
9 | + | |
10 | + | |
11 | +class VoteView(View): | |
12 | + | |
13 | + http_method_names = [u'get', u'put', u'delete', u'head'] | |
14 | + | |
15 | + def put(self, request, msg_id): | |
16 | + if not request.user.is_authenticated(): | |
17 | + return http.HttpResponseForbidden() | |
18 | + | |
19 | + try: | |
20 | + Message.objects.get(id=msg_id).vote(request.user) | |
21 | + except IntegrityError: | |
22 | + # 409 Conflict | |
23 | + # used for duplicated entries | |
24 | + return http.HttpResponse(status=409) | |
25 | + | |
26 | + # 201 Created | |
27 | + return http.HttpResponse(status=201) | |
28 | + | |
29 | + def get(self, request, msg_id): | |
30 | + votes = Message.objects.get(id=msg_id).votes_count() | |
31 | + return http.HttpResponse(votes, content_type='application/json') | |
32 | + | |
33 | + def delete(self, request, msg_id): | |
34 | + if not request.user.is_authenticated(): | |
35 | + return http.HttpResponseForbidden() | |
36 | + | |
37 | + try: | |
38 | + Message.objects.get(id=msg_id).unvote(request.user) | |
39 | + except ObjectDoesNotExist: | |
40 | + return http.HttpResponseGone() | |
41 | + | |
42 | + # 204 No Content | |
43 | + # empty body, as per RFC2616. | |
44 | + # object deleted | |
45 | + return http.HttpResponse(status=204) | ... | ... |
src/colab/deprecated/templates/base.html
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | {% load i18n browserid conversejs gravatar %} |
3 | 3 | <html> |
4 | 4 | <head> |
5 | - <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
6 | 6 | |
7 | 7 | <link rel="stylesheet" href="{{ STATIC_URL }}third-party/bootstrap/css/bootstrap.css" type="text/css" media="screen, projection" /> |
8 | 8 | |
... | ... | @@ -14,6 +14,7 @@ |
14 | 14 | |
15 | 15 | <script type="text/javascript" src="{{ STATIC_URL }}third-party/jquery-2.0.3.min.js"></script> |
16 | 16 | <script type="text/javascript" src="{{ STATIC_URL }}third-party/jquery.debouncedresize.js"></script> |
17 | + <script type="text/javascript" src="{{ STATIC_URL }}third-party/jquery.cookie.js"></script> | |
17 | 18 | <script src="{{ STATIC_URL }}third-party/bootstrap/js/bootstrap.js"></script> |
18 | 19 | |
19 | 20 | <script type="text/javascript" src="{{ STATIC_URL }}js/base.js"></script> | ... | ... |
src/colab/static/css/screen.css
... | ... | @@ -250,33 +250,26 @@ ul.unstyled-list .glyphicon-chevron-right { |
250 | 250 | margin-top: 1em; |
251 | 251 | } |
252 | 252 | |
253 | -.plus { | |
254 | - border: 1px dotted #ddd; | |
255 | - margin: 0; | |
253 | +.email-message pre { | |
254 | + border: 0; | |
255 | + background-color: #fff; | |
256 | + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; | |
256 | 257 | } |
257 | 258 | |
258 | -.plus span { | |
259 | +.email-message .user-fullname { | |
259 | 260 | vertical-align: middle; |
260 | - height: 40px; | |
261 | - line-height: 40px; | |
262 | - font-size: 150%; | |
263 | - font-weight: bold; | |
264 | - color: #0a0; | |
265 | 261 | } |
266 | 262 | |
267 | -.plus img { | |
268 | - vertical-align: middle; | |
269 | - cursor: pointer; | |
270 | - margin-top: 8px; | |
271 | - margin-right: 20px; | |
263 | +.email-message .panel-heading img { | |
264 | + margin-right: 10px; | |
272 | 265 | } |
273 | 266 | |
274 | -.plus img:hover { | |
275 | - opacity: 0.8; | |
267 | +.email-message .panel-heading .date { | |
268 | + margin-right: 10px; | |
276 | 269 | } |
277 | 270 | |
278 | -.no-margin { | |
279 | - margin: 0; | |
271 | +.email-message .panel-heading { | |
272 | + padding: 10px 0; | |
280 | 273 | } |
281 | 274 | |
282 | 275 | .selected .glyphicon-remove { | ... | ... |
src/colab/static/js/base.js
1 | - | |
2 | -function vote_callback(msg_id, step) { | |
3 | - return function() { | |
4 | - jQuery('#msg-' + msg_id + ' .plus span').text(function(self, count) { | |
5 | - return parseInt(count) + step; | |
6 | - }); | |
7 | - jQuery('#msg-' + msg_id + ' .minus').toggleClass('hide'); | |
8 | - jQuery('#vote-notification').addClass('hide'); | |
9 | - } | |
10 | -} | |
11 | - | |
12 | -function get_vote_ajax_dict(msg_id, type_) { | |
13 | - if (type_ === 'DELETE') { | |
14 | - step = -1; | |
15 | - } else if (type_ === 'POST') { | |
16 | - step = 1; | |
17 | - } else { | |
18 | - return {}; | |
19 | - } | |
20 | - | |
21 | - return { | |
22 | - url: "/api/message/" + msg_id + "/vote", | |
23 | - type: type_, | |
24 | - success: vote_callback(msg_id, step), | |
25 | - error: function (jqXHR, textStatus, errorThrown) { | |
26 | - | |
27 | - error_msg = '<b>Seu voto não foi computado.</b>' | |
28 | - if (jqXHR.status === 401) { | |
29 | - error_msg += ' Você deve estar autenticado para votar.'; | |
30 | - } else { | |
31 | - error_msg += ' Erro desconhecido ao tentando votar.'; | |
32 | - } | |
33 | - | |
34 | - jQuery('#vote-notification').html(error_msg).removeClass('hide'); | |
35 | - scroll(0, 0); | |
36 | - } | |
37 | - } | |
38 | -} | |
39 | - | |
40 | -function vote(msg_id) { | |
41 | - jQuery.ajax(get_vote_ajax_dict(msg_id, 'POST')); | |
42 | -} | |
43 | - | |
44 | -function unvote(msg_id) { | |
45 | - jQuery.ajax(get_vote_ajax_dict(msg_id, 'DELETE')); | |
46 | -} | |
47 | - | |
48 | -jQuery(document).ready(function() { | |
49 | - jQuery('.email_message').each(function() { | |
50 | - var msg_id = this.getAttribute('id').split('-')[1]; | |
51 | - jQuery('.plus img', this).bind('click', function() { | |
52 | - vote(msg_id); | |
53 | - }); | |
54 | - jQuery('.minus a', this).bind('click', function() { | |
55 | - unvote(msg_id); | |
56 | - return false; | |
57 | - }); | |
58 | - }); | |
59 | -}); | |
60 | - | |
61 | 1 | function pagehit(path_info) { |
62 | 2 | jQuery.ajax({ |
63 | 3 | url: '/api/hit/', | ... | ... |
... | ... | @@ -0,0 +1,90 @@ |
1 | +/*! | |
2 | + * jQuery Cookie Plugin v1.3.1 | |
3 | + * https://github.com/carhartl/jquery-cookie | |
4 | + * | |
5 | + * Copyright 2013 Klaus Hartl | |
6 | + * Released under the MIT license | |
7 | + */ | |
8 | +(function ($, document, undefined) { | |
9 | + | |
10 | + var pluses = /\+/g; | |
11 | + | |
12 | + function raw(s) { | |
13 | + return s; | |
14 | + } | |
15 | + | |
16 | + function decoded(s) { | |
17 | + return unRfc2068(decodeURIComponent(s.replace(pluses, ' '))); | |
18 | + } | |
19 | + | |
20 | + function unRfc2068(value) { | |
21 | + if (value.indexOf('"') === 0) { | |
22 | + // This is a quoted cookie as according to RFC2068, unescape | |
23 | + value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); | |
24 | + } | |
25 | + return value; | |
26 | + } | |
27 | + | |
28 | + function fromJSON(value) { | |
29 | + return config.json ? JSON.parse(value) : value; | |
30 | + } | |
31 | + | |
32 | + var config = $.cookie = function (key, value, options) { | |
33 | + | |
34 | + // write | |
35 | + if (value !== undefined) { | |
36 | + options = $.extend({}, config.defaults, options); | |
37 | + | |
38 | + if (value === null) { | |
39 | + options.expires = -1; | |
40 | + } | |
41 | + | |
42 | + if (typeof options.expires === 'number') { | |
43 | + var days = options.expires, t = options.expires = new Date(); | |
44 | + t.setDate(t.getDate() + days); | |
45 | + } | |
46 | + | |
47 | + value = config.json ? JSON.stringify(value) : String(value); | |
48 | + | |
49 | + return (document.cookie = [ | |
50 | + encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value), | |
51 | + options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE | |
52 | + options.path ? '; path=' + options.path : '', | |
53 | + options.domain ? '; domain=' + options.domain : '', | |
54 | + options.secure ? '; secure' : '' | |
55 | + ].join('')); | |
56 | + } | |
57 | + | |
58 | + // read | |
59 | + var decode = config.raw ? raw : decoded; | |
60 | + var cookies = document.cookie.split('; '); | |
61 | + var result = key ? null : {}; | |
62 | + for (var i = 0, l = cookies.length; i < l; i++) { | |
63 | + var parts = cookies[i].split('='); | |
64 | + var name = decode(parts.shift()); | |
65 | + var cookie = decode(parts.join('=')); | |
66 | + | |
67 | + if (key && key === name) { | |
68 | + result = fromJSON(cookie); | |
69 | + break; | |
70 | + } | |
71 | + | |
72 | + if (!key) { | |
73 | + result[name] = fromJSON(cookie); | |
74 | + } | |
75 | + } | |
76 | + | |
77 | + return result; | |
78 | + }; | |
79 | + | |
80 | + config.defaults = {}; | |
81 | + | |
82 | + $.removeCookie = function (key, options) { | |
83 | + if ($.cookie(key) !== null) { | |
84 | + $.cookie(key, null, options); | |
85 | + return true; | |
86 | + } | |
87 | + return false; | |
88 | + }; | |
89 | + | |
90 | +})(jQuery, document); | ... | ... |
src/super_archives/models.py
... | ... | @@ -26,21 +26,24 @@ class PageHit(models.Model): |
26 | 26 | |
27 | 27 | |
28 | 28 | class EmailAddress(models.Model): |
29 | - user = models.ForeignKey(User, null=True, related_name='emails') | |
29 | + user = models.ForeignKey(User, null=True, related_name='emails') | |
30 | 30 | address = models.EmailField(unique=True) |
31 | 31 | real_name = models.CharField(max_length=64, blank=True, db_index=True) |
32 | 32 | md5 = models.CharField(max_length=32, null=True) |
33 | - | |
33 | + | |
34 | 34 | def save(self, *args, **kwargs): |
35 | 35 | self.md5 = md5(self.address).hexdigest() |
36 | 36 | super(EmailAddress, self).save(*args, **kwargs) |
37 | - | |
37 | + | |
38 | 38 | def get_full_name(self): |
39 | 39 | if self.user and self.user.get_full_name(): |
40 | 40 | return self.user.get_full_name() |
41 | 41 | elif self.real_name: |
42 | 42 | return self.real_name |
43 | 43 | |
44 | + def get_full_name_or_anonymous(self): | |
45 | + return self.get_full_name() or _('Anonymous') | |
46 | + | |
44 | 47 | def __unicode__(self): |
45 | 48 | return '"%s" <%s>' % (self.get_full_name(), self.address) |
46 | 49 | |
... | ... | @@ -65,21 +68,21 @@ class MailingListMembership(models.Model): |
65 | 68 | |
66 | 69 | |
67 | 70 | class Thread(models.Model): |
68 | - | |
71 | + | |
69 | 72 | subject_token = models.CharField(max_length=512) |
70 | - mailinglist = models.ForeignKey(MailingList, | |
71 | - verbose_name=_(u"Mailing List"), | |
73 | + mailinglist = models.ForeignKey(MailingList, | |
74 | + verbose_name=_(u"Mailing List"), | |
72 | 75 | help_text=_(u"The Mailing List where is the thread")) |
73 | - latest_message = models.OneToOneField('Message', null=True, | |
74 | - related_name='+', | |
75 | - verbose_name=_(u"Latest message"), | |
76 | + latest_message = models.OneToOneField('Message', null=True, | |
77 | + related_name='+', | |
78 | + verbose_name=_(u"Latest message"), | |
76 | 79 | help_text=_(u"Latest message posted")) |
77 | 80 | score = models.IntegerField(default=0, verbose_name=_(u"Score"), help_text=_(u"Thread score")) |
78 | 81 | spam = models.BooleanField(default=False) |
79 | - | |
82 | + | |
80 | 83 | all_objects = models.Manager() |
81 | 84 | objects = NotSpamManager() |
82 | - | |
85 | + | |
83 | 86 | class Meta: |
84 | 87 | verbose_name = _(u"Thread") |
85 | 88 | verbose_name_plural = _(u"Threads") |
... | ... | @@ -87,17 +90,17 @@ class Thread(models.Model): |
87 | 90 | |
88 | 91 | def __unicode__(self): |
89 | 92 | return '%s - %s (%s)' % (self.id, |
90 | - self.subject_token, | |
93 | + self.subject_token, | |
91 | 94 | self.message_set.count()) |
92 | 95 | |
93 | 96 | def update_score(self): |
94 | 97 | """Update the relevance score for this thread. |
95 | - | |
98 | + | |
96 | 99 | The score is calculated with the following variables: |
97 | 100 | |
98 | - * vote_weight: 100 - (minus) 1 for each 3 days since | |
101 | + * vote_weight: 100 - (minus) 1 for each 3 days since | |
99 | 102 | voted with minimum of 5. |
100 | - * replies_weight: 300 - (minus) 1 for each 3 days since | |
103 | + * replies_weight: 300 - (minus) 1 for each 3 days since | |
101 | 104 | replied with minimum of 5. |
102 | 105 | * page_view_weight: 10. |
103 | 106 | |
... | ... | @@ -111,8 +114,8 @@ class Thread(models.Model): |
111 | 114 | """ |
112 | 115 | |
113 | 116 | if not self.subject_token: |
114 | - return | |
115 | - | |
117 | + return | |
118 | + | |
116 | 119 | # Save this pseudo now to avoid calling the |
117 | 120 | # function N times in the loops below |
118 | 121 | now = timezone.now() |
... | ... | @@ -130,8 +133,8 @@ class Thread(models.Model): |
130 | 133 | for vote in msg.vote_set.all(): |
131 | 134 | vote_score += get_score(100, vote.created) |
132 | 135 | |
133 | - # Calculate page_view_score | |
134 | - try: | |
136 | + # Calculate page_view_score | |
137 | + try: | |
135 | 138 | url = reverse('thread_view', args=[self.mailinglist.name, |
136 | 139 | self.subject_token]) |
137 | 140 | pagehit = PageHit.objects.get(url_path=url) |
... | ... | @@ -157,18 +160,18 @@ class Vote(models.Model): |
157 | 160 | |
158 | 161 | |
159 | 162 | class Message(models.Model): |
160 | - | |
163 | + | |
161 | 164 | from_address = models.ForeignKey(EmailAddress, db_index=True) |
162 | 165 | thread = models.ForeignKey(Thread, null=True, db_index=True) |
163 | 166 | # RFC 2822 recommends to use 78 chars + CRLF (so 80 chars) for |
164 | 167 | # the max_length of a subject but most of implementations |
165 | 168 | # goes for 256. We use 512 just in case. |
166 | - subject = models.CharField(max_length=512, db_index=True, | |
167 | - verbose_name=_(u"Subject"), | |
169 | + subject = models.CharField(max_length=512, db_index=True, | |
170 | + verbose_name=_(u"Subject"), | |
168 | 171 | help_text=_(u"Please enter a message subject")) |
169 | 172 | subject_clean = models.CharField(max_length=512, db_index=True) |
170 | - body = models.TextField(default='', | |
171 | - verbose_name=_(u"Message body"), | |
173 | + body = models.TextField(default='', | |
174 | + verbose_name=_(u"Message body"), | |
172 | 175 | help_text=_(u"Please enter a message body")) |
173 | 176 | received_time = models.DateTimeField() |
174 | 177 | message_id = models.CharField(max_length=512) |
... | ... | @@ -176,39 +179,37 @@ class Message(models.Model): |
176 | 179 | |
177 | 180 | all_objects = models.Manager() |
178 | 181 | objects = NotSpamManager() |
179 | - | |
182 | + | |
180 | 183 | class Meta: |
181 | 184 | verbose_name = _(u"Message") |
182 | 185 | verbose_name_plural = _(u"Messages") |
183 | 186 | unique_together = ('thread', 'message_id') |
184 | - | |
187 | + | |
185 | 188 | def __unicode__(self): |
186 | - return '(%s) %s: %s' % (self.id, | |
187 | - self.from_address.get_full_name(), | |
189 | + return '(%s) %s: %s' % (self.id, | |
190 | + self.from_address.get_full_name(), | |
188 | 191 | self.subject_clean) |
189 | - | |
192 | + | |
190 | 193 | @property |
191 | 194 | def mailinglist(self): |
192 | 195 | if not self.thread or not self.thread.mailinglist: |
193 | 196 | return None |
194 | - | |
197 | + | |
195 | 198 | return self.thread.mailinglist |
196 | 199 | |
197 | - | |
198 | 200 | def vote_list(self): |
199 | 201 | """Return a list of user that voted in this message.""" |
200 | - | |
201 | - return [vote.user for vote in self.vote_set.all()] | |
202 | - | |
202 | + return [vote.user for vote in self.vote_set.iterator()] | |
203 | + | |
203 | 204 | def votes_count(self): |
204 | 205 | return len(self.vote_list()) |
205 | - | |
206 | + | |
206 | 207 | def vote(self, user): |
207 | 208 | Vote.objects.create( |
208 | 209 | message=self, |
209 | 210 | user=user |
210 | 211 | ) |
211 | - | |
212 | + | |
212 | 213 | def unvote(self, user): |
213 | 214 | Vote.objects.get( |
214 | 215 | message=self, |
... | ... | @@ -220,7 +221,7 @@ class Message(models.Model): |
220 | 221 | """Shortcut to get thread url""" |
221 | 222 | return reverse('thread_view', args=[self.mailinglist.name, |
222 | 223 | self.thread.subject_token]) |
223 | - | |
224 | + | |
224 | 225 | @property |
225 | 226 | def Description(self): |
226 | 227 | """Alias to self.body""" |
... | ... | @@ -247,4 +248,4 @@ class MessageMetadata(models.Model): |
247 | 248 | def __unicode__(self): |
248 | 249 | return 'Email Message Id: %s - %s: %s' % (self.Message.id, |
249 | 250 | self.name, self.value) |
250 | - | |
251 | + | ... | ... |
src/super_archives/templates/message-thread.html
1 | 1 | {% extends "base.html" %} |
2 | -{% load i18n %} | |
3 | -{% load append_to_get %} | |
4 | -{% load gravatar %} | |
2 | +{% load i18n append_to_get gravatar %} | |
3 | + | |
4 | +{% trans "Anonymous" as anonymous %} | |
5 | + | |
6 | +{% block head_js %} | |
7 | + | |
8 | +<script> | |
9 | + function vote_done_callback(msg_id, step) { | |
10 | + console.debug('(un)vote successfuly (step ' + step + ')'); | |
11 | + var $msg = $('#msg-' + msg_id) | |
12 | + | |
13 | + $('.vote-count', $msg).text(function(self, count) { | |
14 | + return parseInt(count) + step; | |
15 | + }); | |
16 | + | |
17 | + if (step == -1) { | |
18 | + var $btn = $('.vote.btn-success', $msg); | |
19 | + $btn.unbind('click'); | |
20 | + $btn.bind('click', function() { | |
21 | + vote(msg_id); | |
22 | + }); | |
23 | + $('.text', $btn).text("{% trans 'Vote' %}"); | |
24 | + } else { | |
25 | + var $btn = $('.vote.btn-default', $msg); | |
26 | + $btn.unbind('click'); | |
27 | + $btn.bind('click', function() { | |
28 | + unvote(msg_id); | |
29 | + }); | |
30 | + $('.text', $btn).text("{% trans 'Voted' %}"); | |
31 | + } | |
32 | + $btn.toggleClass('btn-success'); | |
33 | + $btn.toggleClass('btn-default'); | |
34 | + } | |
35 | + | |
36 | + function vote_fail_callback(jqXHR, textStatus, errorThrown) { | |
37 | + alert('error'); | |
38 | + //error_msg = '<b>Seu voto não foi computado.</b>' | |
39 | + //if (jqXHR.status === 401) { | |
40 | + // error_msg += ' Você deve estar autenticado para votar.'; | |
41 | + //} else { | |
42 | + // error_msg += ' Erro desconhecido ao tentando votar.'; | |
43 | + //} | |
44 | + | |
45 | + //jQuery('#vote-notification').html(error_msg).removeClass('hide'); | |
46 | + //scroll(0, 0); | |
47 | + } | |
48 | + | |
49 | + function get_vote_ajax_dict(msg_id, method) { | |
50 | + var csrftoken = $.cookie('csrftoken'); | |
51 | + | |
52 | + return { | |
53 | + url: "/api/message/" + msg_id + "/vote", | |
54 | + type: method, | |
55 | + beforeSend: function(xhr, settings) { | |
56 | + xhr.setRequestHeader("X-CSRFToken", csrftoken); | |
57 | + } | |
58 | + } | |
59 | + } | |
60 | + | |
61 | + function vote(msg_id) { | |
62 | + console.debug('trying to vote'); | |
63 | + $.ajax(get_vote_ajax_dict(msg_id, 'PUT')) | |
64 | + .done(function(){ | |
65 | + vote_done_callback(msg_id, 1); | |
66 | + }) | |
67 | + .fail(vote_fail_callback); | |
68 | + } | |
69 | + | |
70 | + function unvote(msg_id) { | |
71 | + console.debug('trying to remove vote'); | |
72 | + $.ajax(get_vote_ajax_dict(msg_id, 'DELETE')) | |
73 | + .done(function(){ | |
74 | + vote_done_callback(msg_id, -1); | |
75 | + }) | |
76 | + .fail(vote_fail_callback); | |
77 | + } | |
78 | + | |
79 | + // Binding functions | |
80 | + $(function() { | |
81 | + $('.email-message').each(function() { | |
82 | + var msg_id = this.getAttribute('id').split('-')[1]; | |
83 | + console.debug('binding vote calls to ' + msg_id); | |
84 | + | |
85 | + $('.vote.btn-default', this).bind('click', function() { | |
86 | + vote(msg_id); | |
87 | + }); | |
88 | + | |
89 | + $('.vote.btn-success', this).bind('click', function() { | |
90 | + unvote(msg_id); | |
91 | + }); | |
92 | + | |
93 | + }); | |
94 | + }); | |
95 | + | |
96 | +</script> | |
97 | + | |
98 | +{% endblock %} | |
99 | + | |
5 | 100 | {% block main-content %} |
6 | 101 | <div class="row"> |
7 | 102 | |
8 | - <h2>{{ first_msg.subject_clean }}</h2> | |
9 | - <hr /> | |
103 | + <div class="col-lg-12"> | |
104 | + <h2>{{ first_msg.subject_clean }}</h2> | |
105 | + <hr /> | |
106 | + </div> | |
10 | 107 | |
11 | - <div class="col-lg-3 pull-right"> | |
108 | + <div class="col-lg-10 col-md-10 col-sm-12"> | |
109 | + <ul class="unstyled-list"> | |
110 | + {% for email in emails %} | |
111 | + {% with email.from_address.get_absolute_url as profile_link %} | |
112 | + <li> | |
113 | + <!-- | |
114 | + <div class="col-lg-2 col-md-3 hidden-sm hidden-xm text-center"> | |
115 | + {% if profile_link %} | |
116 | + <a href="{{ profile_link }}"> | |
117 | + {% endif %} | |
118 | + <div>{% gravatar email.from_address 80 %}</div> | |
119 | + <span>{{ email.from_address.get_full_name_or_anonymous }}</span> | |
120 | + {% if profile_link %} | |
121 | + </a> | |
122 | + {% endif %} | |
123 | + | |
124 | + <p>{{ email.received_time|date:"SHORT_DATETIME_FORMAT" }}</p> | |
125 | + | |
126 | + <div class="plus"> | |
127 | + <span>{{ email.votes_count }}</span> | |
128 | + <img title="{% trans 'Vote' %}" class="pull-right" src="{{ STATIC_URL }}img/plus.png"> | |
129 | + </div> | |
130 | + | |
131 | + <p class="{% if not user in email.vote_list %}hide{% endif %}"> | |
132 | + <a href="#">{% trans "Remove votes" %}</a> | |
133 | + </p> | |
134 | + </div> | |
135 | + --> | |
136 | + | |
137 | + <div class="email-message" id="msg-{{ email.id }}"> | |
138 | + <div class="panel panel-default"> | |
139 | + <div class="panel-heading clearfix"> | |
140 | + <div class="col-lg-6 col-md-6 col-sm-6"> | |
141 | + {% if profile_link %} | |
142 | + <a href="{{ profile_link }}"> | |
143 | + {% endif %} | |
144 | + {% gravatar email.from_address 34 %} | |
145 | + <strong class="user-fullname">{{ email.from_address.get_full_name_or_anonymous }}</strong> | |
146 | + {% if profile_link %} | |
147 | + </a> | |
148 | + {% endif %} | |
149 | + </div> | |
150 | + | |
151 | + <div class="col-lg-6 col-md-6 col-sm-6"> | |
152 | + <div class="pull-right text-right"> | |
153 | + <span class="date"> | |
154 | + {{ email.received_time|date:'DATETIME_FORMAT' }} | |
155 | + </span> | |
156 | + | |
157 | + <div class="btn-group"> | |
158 | + <button class="btn btn-default vote-count disabled"> | |
159 | + {{ email.votes_count }} | |
160 | + </button> | |
161 | + {% if user in email.vote_list %} | |
162 | + <button class="btn btn-success vote"> | |
163 | + {% else %} | |
164 | + <button class="btn btn-default vote"> | |
165 | + {% endif %} | |
166 | + <span class="glyphicon glyphicon-thumbs-up"></span> | |
167 | + <span class="text"> | |
168 | + {% if user in email.vote_list %} | |
169 | + {% trans "Voted" %} | |
170 | + {% else %} | |
171 | + {% trans "Vote" %} | |
172 | + {% endif %} | |
173 | + </span> | |
174 | + </button> | |
175 | + </div> | |
176 | + </div> | |
177 | + </div> | |
178 | + | |
179 | + </div> | |
180 | + <div class="panel-body"> | |
181 | + <pre>{{ email.body }}</pre> | |
182 | + </div> | |
183 | + </div> | |
184 | + </div> | |
185 | + </li> | |
186 | + {% endwith %} | |
187 | + {% endfor %} | |
188 | + </ul> | |
189 | + </div> | |
190 | + | |
191 | + <div class="col-lg-2 col-md-2 hidden-sm hidden-xs"> | |
12 | 192 | <h4><strong>{% trans "Order by" %}:</strong></h4> |
13 | - <ul class="none"> | |
14 | - <li><span class="glyphicon glyphicon-chevron-right"> | |
193 | + <ul class="unstyled-list"> | |
194 | + <li> | |
195 | + <span class="glyphicon glyphicon-chevron-right"></span> | |
15 | 196 | <a href="{% append_to_get order='voted' %}">{% trans "Votes" %}</a> |
16 | - </span></li> | |
17 | - <li><span class="glyphicon glyphicon-chevron-right"> | |
197 | + </li> | |
198 | + <li> | |
199 | + <span class="glyphicon glyphicon-chevron-right"></span> | |
18 | 200 | <a href="{% append_to_get order='date' %}">{% trans "Date" %}</a> |
19 | - </span></li> | |
201 | + </li> | |
20 | 202 | </ul> |
21 | 203 | |
22 | - <div> </div> | |
23 | - | |
24 | 204 | <h4><strong>{% trans "Statistics:" %}</strong></h4> |
25 | 205 | |
26 | - <ul class="none"> | |
27 | - <li class="quiet"><span class="glyphicon glyphicon-chevron-right"> | |
206 | + <ul class="unstyled-list"> | |
207 | + <li> | |
208 | + <span class="glyphicon glyphicon-chevron-right"></span> | |
28 | 209 | {% trans "started at" %} |
29 | - </span></li> | |
30 | 210 | <h5>{{ first_msg.received_time|timesince }} {% trans "ago" %}</h5> |
31 | - <li class="quiet"><span class="glyphicon glyphicon-chevron-right"> | |
211 | + </li> | |
212 | + | |
213 | + <li> | |
214 | + <span class="glyphicon glyphicon-chevron-right"></span> | |
32 | 215 | {% trans "viewed" %} |
33 | - </span></li> | |
34 | 216 | <h5>{{ pagehits }} {% trans "times" %}</h5> |
35 | - <li class="quiet"><span class="glyphicon glyphicon-chevron-right"> | |
217 | + </li> | |
218 | + <li> | |
219 | + <span class="glyphicon glyphicon-chevron-right"></span> | |
36 | 220 | {% trans "answered" %} |
37 | - </span></li> | |
38 | 221 | <h5>{{ emails|length }} {% trans "times" %}</h5> |
39 | - <li class="quiet"><span class="glyphicon glyphicon-chevron-right"> | |
222 | + </li> | |
223 | + <li> | |
224 | + <span class="glyphicon glyphicon-chevron-right"></span> | |
40 | 225 | {% trans "voted" %} |
41 | - </span></li> | |
42 | 226 | <h5>{{ total_votes }} {% trans "times" %}</h5> |
227 | + </li> | |
43 | 228 | </ul> |
44 | 229 | </div> |
45 | 230 | |
46 | - <ul class="none"> | |
47 | - {% for email in emails %} | |
48 | - <li> | |
49 | - <div class="col-lg-2 text-center"> | |
50 | - <a href="{{ email.from_address.get_profile_link }}"> | |
51 | - {% gravatar email.from_address 80 %} | |
52 | - <div> </div> | |
53 | - {% trans "Anonymous" as anonymous %} | |
54 | - <span>{% firstof email.from_address.get_full_name anonymous %}</span> | |
55 | - </a> | |
56 | - | |
57 | - <p>{{ email.received_time|date:"SHORT_DATETIME_FORMAT" }}</p> | |
58 | - | |
59 | - <div class="plus"> | |
60 | - <span>{{ email.votes_count }}</span> | |
61 | - <img title="{% trans 'Vote' %}" class="pull-right" src="{{ STATIC_URL }}img/plus.png"> | |
62 | - </div> | |
63 | - | |
64 | - <p class="{% if not user in email.vote_list %}hide{% endif %}"> | |
65 | - <a href="#">{% trans "Remove votes" %}</a> | |
66 | - </p> | |
67 | - </div> | |
68 | - | |
69 | - <div class="col-lg-7"> | |
70 | - <pre>{{ email.body }}</pre> | |
71 | - </div> | |
72 | - {% if not forloop.last %} | |
73 | - <div> </div> | |
74 | - {% endif %} | |
75 | - </li> | |
76 | - {% endfor %} | |
77 | - </ul> | |
78 | - | |
79 | 231 | <script type="text/javascript" charset="utf-8"> |
80 | 232 | pagehit("{{ request.path_info }}"); |
81 | 233 | </script> | ... | ... |