Commit 4e00e7c56a48d36f0ecd450524f11b016ade6fa4

Authored by Sergio Oliveira
1 parent a4d52056

Updating message thread layout

src/api/handlers.py
1 1
2 from django.core.cache import cache 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 from piston.utils import rc 4 from piston.utils import rc
8 from piston.handler import BaseHandler 5 from piston.handler import BaseHandler
9 6
10 from colab.deprecated import solrutils 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 class CountHandler(BaseHandler): 11 class CountHandler(BaseHandler):
src/api/urls.py
@@ -2,15 +2,15 @@ from django.conf.urls import patterns, include, url @@ -2,15 +2,15 @@ from django.conf.urls import patterns, include, url
2 2
3 from piston.resource import Resource 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 count_handler = Resource(CountHandler) 9 count_handler = Resource(CountHandler)
10 search_handler = Resource(SearchHandler) 10 search_handler = Resource(SearchHandler)
11 11
12 urlpatterns = patterns('', 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 url(r'hit/$', count_handler), 14 url(r'hit/$', count_handler),
15 url(r'search/$', search_handler), 15 url(r'search/$', search_handler),
16 ) 16 )
src/api/views.py 0 → 100644
@@ -0,0 +1,45 @@ @@ -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,7 +2,7 @@
2 {% load i18n browserid conversejs gravatar %} 2 {% load i18n browserid conversejs gravatar %}
3 <html> 3 <html>
4 <head> 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 <link rel="stylesheet" href="{{ STATIC_URL }}third-party/bootstrap/css/bootstrap.css" type="text/css" media="screen, projection" /> 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,6 +14,7 @@
14 14
15 <script type="text/javascript" src="{{ STATIC_URL }}third-party/jquery-2.0.3.min.js"></script> 15 <script type="text/javascript" src="{{ STATIC_URL }}third-party/jquery-2.0.3.min.js"></script>
16 <script type="text/javascript" src="{{ STATIC_URL }}third-party/jquery.debouncedresize.js"></script> 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 <script src="{{ STATIC_URL }}third-party/bootstrap/js/bootstrap.js"></script> 18 <script src="{{ STATIC_URL }}third-party/bootstrap/js/bootstrap.js"></script>
18 19
19 <script type="text/javascript" src="{{ STATIC_URL }}js/base.js"></script> 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,33 +250,26 @@ ul.unstyled-list .glyphicon-chevron-right {
250 margin-top: 1em; 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 vertical-align: middle; 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 .selected .glyphicon-remove { 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 function pagehit(path_info) { 1 function pagehit(path_info) {
62 jQuery.ajax({ 2 jQuery.ajax({
63 url: '/api/hit/', 3 url: '/api/hit/',
src/colab/static/third-party/jquery.cookie.js 0 → 100755
@@ -0,0 +1,90 @@ @@ -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,21 +26,24 @@ class PageHit(models.Model):
26 26
27 27
28 class EmailAddress(models.Model): 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 address = models.EmailField(unique=True) 30 address = models.EmailField(unique=True)
31 real_name = models.CharField(max_length=64, blank=True, db_index=True) 31 real_name = models.CharField(max_length=64, blank=True, db_index=True)
32 md5 = models.CharField(max_length=32, null=True) 32 md5 = models.CharField(max_length=32, null=True)
33 - 33 +
34 def save(self, *args, **kwargs): 34 def save(self, *args, **kwargs):
35 self.md5 = md5(self.address).hexdigest() 35 self.md5 = md5(self.address).hexdigest()
36 super(EmailAddress, self).save(*args, **kwargs) 36 super(EmailAddress, self).save(*args, **kwargs)
37 - 37 +
38 def get_full_name(self): 38 def get_full_name(self):
39 if self.user and self.user.get_full_name(): 39 if self.user and self.user.get_full_name():
40 return self.user.get_full_name() 40 return self.user.get_full_name()
41 elif self.real_name: 41 elif self.real_name:
42 return self.real_name 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 def __unicode__(self): 47 def __unicode__(self):
45 return '"%s" <%s>' % (self.get_full_name(), self.address) 48 return '"%s" <%s>' % (self.get_full_name(), self.address)
46 49
@@ -65,21 +68,21 @@ class MailingListMembership(models.Model): @@ -65,21 +68,21 @@ class MailingListMembership(models.Model):
65 68
66 69
67 class Thread(models.Model): 70 class Thread(models.Model):
68 - 71 +
69 subject_token = models.CharField(max_length=512) 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 help_text=_(u"The Mailing List where is the thread")) 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 help_text=_(u"Latest message posted")) 79 help_text=_(u"Latest message posted"))
77 score = models.IntegerField(default=0, verbose_name=_(u"Score"), help_text=_(u"Thread score")) 80 score = models.IntegerField(default=0, verbose_name=_(u"Score"), help_text=_(u"Thread score"))
78 spam = models.BooleanField(default=False) 81 spam = models.BooleanField(default=False)
79 - 82 +
80 all_objects = models.Manager() 83 all_objects = models.Manager()
81 objects = NotSpamManager() 84 objects = NotSpamManager()
82 - 85 +
83 class Meta: 86 class Meta:
84 verbose_name = _(u"Thread") 87 verbose_name = _(u"Thread")
85 verbose_name_plural = _(u"Threads") 88 verbose_name_plural = _(u"Threads")
@@ -87,17 +90,17 @@ class Thread(models.Model): @@ -87,17 +90,17 @@ class Thread(models.Model):
87 90
88 def __unicode__(self): 91 def __unicode__(self):
89 return '%s - %s (%s)' % (self.id, 92 return '%s - %s (%s)' % (self.id,
90 - self.subject_token, 93 + self.subject_token,
91 self.message_set.count()) 94 self.message_set.count())
92 95
93 def update_score(self): 96 def update_score(self):
94 """Update the relevance score for this thread. 97 """Update the relevance score for this thread.
95 - 98 +
96 The score is calculated with the following variables: 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 voted with minimum of 5. 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 replied with minimum of 5. 104 replied with minimum of 5.
102 * page_view_weight: 10. 105 * page_view_weight: 10.
103 106
@@ -111,8 +114,8 @@ class Thread(models.Model): @@ -111,8 +114,8 @@ class Thread(models.Model):
111 """ 114 """
112 115
113 if not self.subject_token: 116 if not self.subject_token:
114 - return  
115 - 117 + return
  118 +
116 # Save this pseudo now to avoid calling the 119 # Save this pseudo now to avoid calling the
117 # function N times in the loops below 120 # function N times in the loops below
118 now = timezone.now() 121 now = timezone.now()
@@ -130,8 +133,8 @@ class Thread(models.Model): @@ -130,8 +133,8 @@ class Thread(models.Model):
130 for vote in msg.vote_set.all(): 133 for vote in msg.vote_set.all():
131 vote_score += get_score(100, vote.created) 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 url = reverse('thread_view', args=[self.mailinglist.name, 138 url = reverse('thread_view', args=[self.mailinglist.name,
136 self.subject_token]) 139 self.subject_token])
137 pagehit = PageHit.objects.get(url_path=url) 140 pagehit = PageHit.objects.get(url_path=url)
@@ -157,18 +160,18 @@ class Vote(models.Model): @@ -157,18 +160,18 @@ class Vote(models.Model):
157 160
158 161
159 class Message(models.Model): 162 class Message(models.Model):
160 - 163 +
161 from_address = models.ForeignKey(EmailAddress, db_index=True) 164 from_address = models.ForeignKey(EmailAddress, db_index=True)
162 thread = models.ForeignKey(Thread, null=True, db_index=True) 165 thread = models.ForeignKey(Thread, null=True, db_index=True)
163 # RFC 2822 recommends to use 78 chars + CRLF (so 80 chars) for 166 # RFC 2822 recommends to use 78 chars + CRLF (so 80 chars) for
164 # the max_length of a subject but most of implementations 167 # the max_length of a subject but most of implementations
165 # goes for 256. We use 512 just in case. 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 help_text=_(u"Please enter a message subject")) 171 help_text=_(u"Please enter a message subject"))
169 subject_clean = models.CharField(max_length=512, db_index=True) 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 help_text=_(u"Please enter a message body")) 175 help_text=_(u"Please enter a message body"))
173 received_time = models.DateTimeField() 176 received_time = models.DateTimeField()
174 message_id = models.CharField(max_length=512) 177 message_id = models.CharField(max_length=512)
@@ -176,39 +179,37 @@ class Message(models.Model): @@ -176,39 +179,37 @@ class Message(models.Model):
176 179
177 all_objects = models.Manager() 180 all_objects = models.Manager()
178 objects = NotSpamManager() 181 objects = NotSpamManager()
179 - 182 +
180 class Meta: 183 class Meta:
181 verbose_name = _(u"Message") 184 verbose_name = _(u"Message")
182 verbose_name_plural = _(u"Messages") 185 verbose_name_plural = _(u"Messages")
183 unique_together = ('thread', 'message_id') 186 unique_together = ('thread', 'message_id')
184 - 187 +
185 def __unicode__(self): 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 self.subject_clean) 191 self.subject_clean)
189 - 192 +
190 @property 193 @property
191 def mailinglist(self): 194 def mailinglist(self):
192 if not self.thread or not self.thread.mailinglist: 195 if not self.thread or not self.thread.mailinglist:
193 return None 196 return None
194 - 197 +
195 return self.thread.mailinglist 198 return self.thread.mailinglist
196 199
197 -  
198 def vote_list(self): 200 def vote_list(self):
199 """Return a list of user that voted in this message.""" 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 def votes_count(self): 204 def votes_count(self):
204 return len(self.vote_list()) 205 return len(self.vote_list())
205 - 206 +
206 def vote(self, user): 207 def vote(self, user):
207 Vote.objects.create( 208 Vote.objects.create(
208 message=self, 209 message=self,
209 user=user 210 user=user
210 ) 211 )
211 - 212 +
212 def unvote(self, user): 213 def unvote(self, user):
213 Vote.objects.get( 214 Vote.objects.get(
214 message=self, 215 message=self,
@@ -220,7 +221,7 @@ class Message(models.Model): @@ -220,7 +221,7 @@ class Message(models.Model):
220 """Shortcut to get thread url""" 221 """Shortcut to get thread url"""
221 return reverse('thread_view', args=[self.mailinglist.name, 222 return reverse('thread_view', args=[self.mailinglist.name,
222 self.thread.subject_token]) 223 self.thread.subject_token])
223 - 224 +
224 @property 225 @property
225 def Description(self): 226 def Description(self):
226 """Alias to self.body""" 227 """Alias to self.body"""
@@ -247,4 +248,4 @@ class MessageMetadata(models.Model): @@ -247,4 +248,4 @@ class MessageMetadata(models.Model):
247 def __unicode__(self): 248 def __unicode__(self):
248 return 'Email Message Id: %s - %s: %s' % (self.Message.id, 249 return 'Email Message Id: %s - %s: %s' % (self.Message.id,
249 self.name, self.value) 250 self.name, self.value)
250 - 251 +
src/super_archives/templates/message-thread.html
1 {% extends "base.html" %} 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 {% block main-content %} 100 {% block main-content %}
6 <div class="row"> 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 <h4><strong>{% trans "Order by" %}:</strong></h4> 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 <a href="{% append_to_get order='voted' %}">{% trans "Votes" %}</a> 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 <a href="{% append_to_get order='date' %}">{% trans "Date" %}</a> 200 <a href="{% append_to_get order='date' %}">{% trans "Date" %}</a>
19 - </span></li> 201 + </li>
20 </ul> 202 </ul>
21 203
22 - <div>&nbsp;</div>  
23 -  
24 <h4><strong>{% trans "Statistics:" %}</strong></h4> 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 {% trans "started at" %} 209 {% trans "started at" %}
29 - </span></li>  
30 <h5>{{ first_msg.received_time|timesince }} {% trans "ago" %}</h5> 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 {% trans "viewed" %} 215 {% trans "viewed" %}
33 - </span></li>  
34 <h5>{{ pagehits }} {% trans "times" %}</h5> 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 {% trans "answered" %} 220 {% trans "answered" %}
37 - </span></li>  
38 <h5>{{ emails|length }} {% trans "times" %}</h5> 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 {% trans "voted" %} 225 {% trans "voted" %}
41 - </span></li>  
42 <h5>{{ total_votes }} {% trans "times" %}</h5> 226 <h5>{{ total_votes }} {% trans "times" %}</h5>
  227 + </li>
43 </ul> 228 </ul>
44 </div> 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>&nbsp;</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>&nbsp;</div>  
74 - {% endif %}  
75 - </li>  
76 - {% endfor %}  
77 - </ul>  
78 -  
79 <script type="text/javascript" charset="utf-8"> 231 <script type="text/javascript" charset="utf-8">
80 pagehit("{{ request.path_info }}"); 232 pagehit("{{ request.path_info }}");
81 </script> 233 </script>