Commit 2c6e925930cab0a22941c76fb1ee5cfef977fa19
1 parent
753af621
Exists in
master
and in
5 other branches
Fixing new post location [Issue: #215]
Showing
6 changed files
with
280 additions
and
35 deletions
Show diff stats
core/static/css/base/amadeus.css
@@ -368,4 +368,30 @@ ul, li { | @@ -368,4 +368,30 @@ ul, li { | ||
368 | /* core/reset_password.html classes*/ | 368 | /* core/reset_password.html classes*/ |
369 | .send-reset-email{ | 369 | .send-reset-email{ |
370 | float: right; | 370 | float: right; |
371 | +} | ||
372 | + | ||
373 | +/* forum post loaded */ | ||
374 | +@-webkit-keyframes loaded { | ||
375 | + 0% { | ||
376 | + background-color: Yellow; | ||
377 | + opacity: 0.2; | ||
378 | + } | ||
379 | + 22% { | ||
380 | + background-color: Yellow; | ||
381 | + opacity: 0.3; | ||
382 | + } | ||
383 | + 77% { | ||
384 | + background-color: Yellow; | ||
385 | + opacity: 0.6; | ||
386 | + } | ||
387 | + 100% { | ||
388 | + background-color: White; | ||
389 | + } | ||
390 | +} | ||
391 | + | ||
392 | +.loaded { | ||
393 | + -webkit-animation-name: loaded; | ||
394 | + -webkit-animation-duration: 900ms; | ||
395 | + -webkit-animation-iteration-count: 3; | ||
396 | + -webkit-animation-timing-function: ease-in-out; | ||
371 | } | 397 | } |
372 | \ No newline at end of file | 398 | \ No newline at end of file |
@@ -0,0 +1,156 @@ | @@ -0,0 +1,156 @@ | ||
1 | +/*! | ||
2 | + * JavaScript Cookie v2.1.3 | ||
3 | + * https://github.com/js-cookie/js-cookie | ||
4 | + * | ||
5 | + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack | ||
6 | + * Released under the MIT license | ||
7 | + */ | ||
8 | +;(function (factory) { | ||
9 | + var registeredInModuleLoader = false; | ||
10 | + if (typeof define === 'function' && define.amd) { | ||
11 | + define(factory); | ||
12 | + registeredInModuleLoader = true; | ||
13 | + } | ||
14 | + if (typeof exports === 'object') { | ||
15 | + module.exports = factory(); | ||
16 | + registeredInModuleLoader = true; | ||
17 | + } | ||
18 | + if (!registeredInModuleLoader) { | ||
19 | + var OldCookies = window.Cookies; | ||
20 | + var api = window.Cookies = factory(); | ||
21 | + api.noConflict = function () { | ||
22 | + window.Cookies = OldCookies; | ||
23 | + return api; | ||
24 | + }; | ||
25 | + } | ||
26 | +}(function () { | ||
27 | + function extend () { | ||
28 | + var i = 0; | ||
29 | + var result = {}; | ||
30 | + for (; i < arguments.length; i++) { | ||
31 | + var attributes = arguments[ i ]; | ||
32 | + for (var key in attributes) { | ||
33 | + result[key] = attributes[key]; | ||
34 | + } | ||
35 | + } | ||
36 | + return result; | ||
37 | + } | ||
38 | + | ||
39 | + function init (converter) { | ||
40 | + function api (key, value, attributes) { | ||
41 | + var result; | ||
42 | + if (typeof document === 'undefined') { | ||
43 | + return; | ||
44 | + } | ||
45 | + | ||
46 | + // Write | ||
47 | + | ||
48 | + if (arguments.length > 1) { | ||
49 | + attributes = extend({ | ||
50 | + path: '/' | ||
51 | + }, api.defaults, attributes); | ||
52 | + | ||
53 | + if (typeof attributes.expires === 'number') { | ||
54 | + var expires = new Date(); | ||
55 | + expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); | ||
56 | + attributes.expires = expires; | ||
57 | + } | ||
58 | + | ||
59 | + try { | ||
60 | + result = JSON.stringify(value); | ||
61 | + if (/^[\{\[]/.test(result)) { | ||
62 | + value = result; | ||
63 | + } | ||
64 | + } catch (e) {} | ||
65 | + | ||
66 | + if (!converter.write) { | ||
67 | + value = encodeURIComponent(String(value)) | ||
68 | + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); | ||
69 | + } else { | ||
70 | + value = converter.write(value, key); | ||
71 | + } | ||
72 | + | ||
73 | + key = encodeURIComponent(String(key)); | ||
74 | + key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); | ||
75 | + key = key.replace(/[\(\)]/g, escape); | ||
76 | + | ||
77 | + return (document.cookie = [ | ||
78 | + key, '=', value, | ||
79 | + attributes.expires ? '; expires=' + attributes.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE | ||
80 | + attributes.path ? '; path=' + attributes.path : '', | ||
81 | + attributes.domain ? '; domain=' + attributes.domain : '', | ||
82 | + attributes.secure ? '; secure' : '' | ||
83 | + ].join('')); | ||
84 | + } | ||
85 | + | ||
86 | + // Read | ||
87 | + | ||
88 | + if (!key) { | ||
89 | + result = {}; | ||
90 | + } | ||
91 | + | ||
92 | + // To prevent the for loop in the first place assign an empty array | ||
93 | + // in case there are no cookies at all. Also prevents odd result when | ||
94 | + // calling "get()" | ||
95 | + var cookies = document.cookie ? document.cookie.split('; ') : []; | ||
96 | + var rdecode = /(%[0-9A-Z]{2})+/g; | ||
97 | + var i = 0; | ||
98 | + | ||
99 | + for (; i < cookies.length; i++) { | ||
100 | + var parts = cookies[i].split('='); | ||
101 | + var cookie = parts.slice(1).join('='); | ||
102 | + | ||
103 | + if (cookie.charAt(0) === '"') { | ||
104 | + cookie = cookie.slice(1, -1); | ||
105 | + } | ||
106 | + | ||
107 | + try { | ||
108 | + var name = parts[0].replace(rdecode, decodeURIComponent); | ||
109 | + cookie = converter.read ? | ||
110 | + converter.read(cookie, name) : converter(cookie, name) || | ||
111 | + cookie.replace(rdecode, decodeURIComponent); | ||
112 | + | ||
113 | + if (this.json) { | ||
114 | + try { | ||
115 | + cookie = JSON.parse(cookie); | ||
116 | + } catch (e) {} | ||
117 | + } | ||
118 | + | ||
119 | + if (key === name) { | ||
120 | + result = cookie; | ||
121 | + break; | ||
122 | + } | ||
123 | + | ||
124 | + if (!key) { | ||
125 | + result[name] = cookie; | ||
126 | + } | ||
127 | + } catch (e) {} | ||
128 | + } | ||
129 | + | ||
130 | + return result; | ||
131 | + } | ||
132 | + | ||
133 | + api.set = api; | ||
134 | + api.get = function (key) { | ||
135 | + return api.call(api, key); | ||
136 | + }; | ||
137 | + api.getJSON = function () { | ||
138 | + return api.apply({ | ||
139 | + json: true | ||
140 | + }, [].slice.call(arguments)); | ||
141 | + }; | ||
142 | + api.defaults = {}; | ||
143 | + | ||
144 | + api.remove = function (key, attributes) { | ||
145 | + api(key, '', extend(attributes, { | ||
146 | + expires: -1 | ||
147 | + })); | ||
148 | + }; | ||
149 | + | ||
150 | + api.withConverter = init; | ||
151 | + | ||
152 | + return api; | ||
153 | + } | ||
154 | + | ||
155 | + return init(function () {}); | ||
156 | +})); | ||
0 | \ No newline at end of file | 157 | \ No newline at end of file |
core/templates/base.html
@@ -29,6 +29,7 @@ | @@ -29,6 +29,7 @@ | ||
29 | <script type="text/javascript" src="{% static 'js/vendor/ripples.min.js' %}"></script> | 29 | <script type="text/javascript" src="{% static 'js/vendor/ripples.min.js' %}"></script> |
30 | <script type="text/javascript" src="{% static 'js/vendor/bootstrap-datepicker.js' %}"></script> | 30 | <script type="text/javascript" src="{% static 'js/vendor/bootstrap-datepicker.js' %}"></script> |
31 | <script type="text/javascript" src="{% static 'js/vendor/alertify.min.js' %}"></script> | 31 | <script type="text/javascript" src="{% static 'js/vendor/alertify.min.js' %}"></script> |
32 | + <script type="text/javascript" src="{% static 'js/vendor/jscookie.js' %}"></script> | ||
32 | 33 | ||
33 | <!-- Font awesome --> | 34 | <!-- Font awesome --> |
34 | <link rel="stylesheet" type="text/css" href="{% static 'font-awesome-4.6.3/css/font-awesome.min.css' %}"> | 35 | <link rel="stylesheet" type="text/css" href="{% static 'font-awesome-4.6.3/css/font-awesome.min.css' %}"> |
forum/static/js/forum.js
1 | -/* | ||
2 | -* | ||
3 | -* Function to get a cookie stored on browser | ||
4 | -* | ||
5 | -*/ | ||
6 | -function getCookie(name) { | ||
7 | - var cookieValue = null; | ||
8 | - if (document.cookie && document.cookie !== '') { | ||
9 | - var cookies = document.cookie.split(';'); | ||
10 | - for (var i = 0; i < cookies.length; i++) { | ||
11 | - var cookie = jQuery.trim(cookies[i]); | ||
12 | - // Does this cookie string begin with the name we want? | ||
13 | - if (cookie.substring(0, name.length + 1) === (name + '=')) { | ||
14 | - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); | ||
15 | - break; | ||
16 | - } | ||
17 | - } | ||
18 | - } | ||
19 | - return cookieValue; | ||
20 | -} | 1 | +var new_posts = []; //Store the new posts ids |
21 | 2 | ||
22 | /* | 3 | /* |
23 | * | 4 | * |
@@ -31,8 +12,16 @@ $(document).ready(function (){ | @@ -31,8 +12,16 @@ $(document).ready(function (){ | ||
31 | type: frm.attr('method'), | 12 | type: frm.attr('method'), |
32 | url: frm.attr('action'), | 13 | url: frm.attr('action'), |
33 | data: frm.serialize(), | 14 | data: frm.serialize(), |
15 | + dataType: 'json', | ||
34 | success: function (data) { | 16 | success: function (data) { |
35 | - $("#posts_list").append(data); | 17 | + if ($("#load_more_posts").length == 0) { |
18 | + $("#posts_list").append(data.html); | ||
19 | + } else { | ||
20 | + $("#load_more_posts").before(data.html); | ||
21 | + } | ||
22 | + | ||
23 | + new_posts.push(data.new_id); | ||
24 | + | ||
36 | frm[0].reset(); | 25 | frm[0].reset(); |
37 | }, | 26 | }, |
38 | error: function(data) { | 27 | error: function(data) { |
@@ -86,8 +75,6 @@ function setForumCreateFormSubmit() { | @@ -86,8 +75,6 @@ function setForumCreateFormSubmit() { | ||
86 | $('.foruns_list').append("<li><i class='fa fa-commenting' aria-hidden='true'></i> <a id='forum_"+data[1]+"' href='"+data[0]+"'> "+data[2]+"</a></li>"); | 75 | $('.foruns_list').append("<li><i class='fa fa-commenting' aria-hidden='true'></i> <a id='forum_"+data[1]+"' href='"+data[0]+"'> "+data[2]+"</a></li>"); |
87 | 76 | ||
88 | $("#createForum").modal('hide'); | 77 | $("#createForum").modal('hide'); |
89 | - | ||
90 | - showForum(data[0], data[1]); | ||
91 | }, | 78 | }, |
92 | error: function(data) { | 79 | error: function(data) { |
93 | $(".forum_form").html(data.responseText); | 80 | $(".forum_form").html(data.responseText); |
@@ -157,7 +144,7 @@ function setForumUpdateFormSubmit(success_message) { | @@ -157,7 +144,7 @@ function setForumUpdateFormSubmit(success_message) { | ||
157 | */ | 144 | */ |
158 | function delete_forum(url, forum, message, return_url) { | 145 | function delete_forum(url, forum, message, return_url) { |
159 | alertify.confirm(message, function(){ | 146 | alertify.confirm(message, function(){ |
160 | - var csrftoken = getCookie('csrftoken'); | 147 | + var csrftoken = Cookies.get('csrftoken'); |
161 | 148 | ||
162 | $.ajax({ | 149 | $.ajax({ |
163 | method: 'post', | 150 | method: 'post', |
@@ -225,7 +212,7 @@ function cancelEditPost(post_id) { | @@ -225,7 +212,7 @@ function cancelEditPost(post_id) { | ||
225 | * | 212 | * |
226 | */ | 213 | */ |
227 | function delete_post(url, post) { | 214 | function delete_post(url, post) { |
228 | - var csrftoken = getCookie('csrftoken'); | 215 | + var csrftoken = Cookies.get('csrftoken'); |
229 | 216 | ||
230 | $.ajax({ | 217 | $.ajax({ |
231 | method: 'post', | 218 | method: 'post', |
@@ -255,6 +242,8 @@ function load_more_posts(pageNum, numberPages, url) { | @@ -255,6 +242,8 @@ function load_more_posts(pageNum, numberPages, url) { | ||
255 | 242 | ||
256 | pageNum += 1; | 243 | pageNum += 1; |
257 | 244 | ||
245 | + var showing = new_posts.join(','); | ||
246 | + | ||
258 | // Show loader | 247 | // Show loader |
259 | $("#loading_posts").show(); | 248 | $("#loading_posts").show(); |
260 | 249 | ||
@@ -262,11 +251,22 @@ function load_more_posts(pageNum, numberPages, url) { | @@ -262,11 +251,22 @@ function load_more_posts(pageNum, numberPages, url) { | ||
262 | setTimeout(function (){ | 251 | setTimeout(function (){ |
263 | $.ajax({ | 252 | $.ajax({ |
264 | url: url, | 253 | url: url, |
265 | - data: {'page': pageNum}, | 254 | + data: {'page': pageNum, 'showing': showing}, |
255 | + dataType: 'json', | ||
266 | success: function(data) { | 256 | success: function(data) { |
267 | $("#loading_posts").hide(); | 257 | $("#loading_posts").hide(); |
268 | - | ||
269 | - $("#posts_list").append(data); | 258 | + |
259 | + var child = $("#posts_list").find(".new_post:first"); | ||
260 | + | ||
261 | + if (child.length == 0) { | ||
262 | + $("#posts_list").append(data.html); | ||
263 | + } else { | ||
264 | + child.before(data.html); | ||
265 | + } | ||
266 | + | ||
267 | + if (data.page != data.num_pages) { | ||
268 | + $("#posts_list").append('<a id="load_more_posts" href="javascript:load_more_posts(' + data.page + ',' + data.num_pages + ',\'' + url + '\');" class="btn btn-raised btn-primary btn-block">' + data.btn_text + '</a>'); | ||
269 | + } | ||
270 | }, | 270 | }, |
271 | error: function(data) { | 271 | error: function(data) { |
272 | console.log(data); | 272 | console.log(data); |
@@ -364,7 +364,7 @@ function cancelEditPostAnswer(answer_id) { | @@ -364,7 +364,7 @@ function cancelEditPostAnswer(answer_id) { | ||
364 | */ | 364 | */ |
365 | function delete_answer(url, answer, message) { | 365 | function delete_answer(url, answer, message) { |
366 | alertify.confirm(message, function(){ | 366 | alertify.confirm(message, function(){ |
367 | - var csrftoken = getCookie('csrftoken'); | 367 | + var csrftoken = Cookies.get('csrftoken'); |
368 | 368 | ||
369 | $.ajax({ | 369 | $.ajax({ |
370 | method: 'post', | 370 | method: 'post', |
@@ -0,0 +1,50 @@ | @@ -0,0 +1,50 @@ | ||
1 | +{% load i18n permission_tags list_post_answer %} | ||
2 | + | ||
3 | +{% if posts|length > 0 %} | ||
4 | + {% for post in posts %} | ||
5 | + <div class="row loaded"> | ||
6 | + <div id="post_{{ post.id }}" class="col-sm-12 col-xs-12"> | ||
7 | + <h3 class="user-name"> | ||
8 | + {{ post.user }} | ||
9 | + <div class="pull-right"> | ||
10 | + <a href="javascript:answer('{{ post.id }}', '{% url 'course:forum:reply_post' %}');"> | ||
11 | + <i class="material-icons">reply</i> | ||
12 | + </a> | ||
13 | + {% if request.user|has_role:'system_admin' or request.user == post.user %} | ||
14 | + {% csrf_token %} | ||
15 | + <div class="btn-group icon-more-horiz"> | ||
16 | + <a class="btn btn-default btn-xs dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||
17 | + <i class="material-icons">more_horiz</i> | ||
18 | + </a> | ||
19 | + <ul class="dropdown-menu pull-right" aria-labelledby="dropdownMenu1"> | ||
20 | + <li><a href="javascript:edit_post('{% url 'course:forum:update_post' post.id %}', '{{ post.id }}', '{% trans 'Post edited successfully!' %}')"><i class="material-icons">create</i> {% trans 'Edit' %}</a></li> | ||
21 | + <li><a href="javascript:javascript:delete_post('{% url 'course:forum:delete_post' post.id %}', '{{ post.id }}')"><i class="material-icons">delete_sweep</i> {% trans 'Remove' %}</a></li> | ||
22 | + </ul> | ||
23 | + </div> | ||
24 | + {% endif %} | ||
25 | + </div> | ||
26 | + </h3> | ||
27 | + <div class="post_content"> | ||
28 | + <div class="card-data"> | ||
29 | + <p class="comment-date"> | ||
30 | + <i class="fa fa-clock-o"></i> {{ post.post_date|timesince }} {% trans 'ago' %} | ||
31 | + {% if post.is_modified %} | ||
32 | + <em> - {% trans 'Edited' %}</em> | ||
33 | + {% endif %} | ||
34 | + </p> | ||
35 | + </div> | ||
36 | + <p class="comment-text">{{ post.message|linebreaks }}</p> | ||
37 | + </div> | ||
38 | + <div class="answer_post"></div> | ||
39 | + <div class="answer_list"> | ||
40 | + {% list_post_answer request post %} | ||
41 | + </div> | ||
42 | + <div class="alert alert-primary loading_answers" role="alert" style="display: none"> | ||
43 | + <center> | ||
44 | + <span class="fa fa-spin fa-circle-o-notch"></span> | ||
45 | + </center> | ||
46 | + </div> | ||
47 | + </div> | ||
48 | + </div> | ||
49 | + {% endfor %} | ||
50 | +{% endif %} | ||
0 | \ No newline at end of file | 51 | \ No newline at end of file |
forum/views.py
@@ -5,15 +5,17 @@ from django.utils.translation import ugettext_lazy as _ | @@ -5,15 +5,17 @@ from django.utils.translation import ugettext_lazy as _ | ||
5 | from django.views import generic | 5 | from django.views import generic |
6 | from django.contrib.auth.mixins import LoginRequiredMixin | 6 | from django.contrib.auth.mixins import LoginRequiredMixin |
7 | from django.core.paginator import Paginator, EmptyPage | 7 | from django.core.paginator import Paginator, EmptyPage |
8 | -from django.http import Http404 | 8 | +from django.http import Http404, JsonResponse |
9 | +from django.urls import reverse | ||
10 | +from django.template.loader import render_to_string | ||
9 | 11 | ||
10 | from .models import Forum, Post, PostAnswer | 12 | from .models import Forum, Post, PostAnswer |
11 | from courses.models import Topic | 13 | from courses.models import Topic |
12 | -from core.mixins import NotificationMixin | ||
13 | from core.models import Action, Resource | 14 | from core.models import Action, Resource |
14 | 15 | ||
15 | from .forms import ForumForm, PostForm, PostAnswerForm | 16 | from .forms import ForumForm, PostForm, PostAnswerForm |
16 | -from django.urls import reverse | 17 | + |
18 | +from core.mixins import NotificationMixin | ||
17 | 19 | ||
18 | """ | 20 | """ |
19 | Forum Section | 21 | Forum Section |
@@ -130,7 +132,13 @@ def load_posts(request, forum_id): | @@ -130,7 +132,13 @@ def load_posts(request, forum_id): | ||
130 | 132 | ||
131 | forum = get_object_or_404(Forum, id = forum_id) | 133 | forum = get_object_or_404(Forum, id = forum_id) |
132 | 134 | ||
133 | - posts = Post.objects.filter(forum = forum).order_by('post_date') | 135 | + showing = request.GET.get('showing', '') |
136 | + | ||
137 | + if showing == '': | ||
138 | + posts = Post.objects.filter(forum = forum).order_by('post_date') | ||
139 | + else: | ||
140 | + showing = showing.split(',') | ||
141 | + posts = Post.objects.filter(forum = forum).exclude(id__in = showing).order_by('post_date') | ||
134 | 142 | ||
135 | paginator = Paginator(posts, 2) | 143 | paginator = Paginator(posts, 2) |
136 | 144 | ||
@@ -150,7 +158,9 @@ def load_posts(request, forum_id): | @@ -150,7 +158,9 @@ def load_posts(request, forum_id): | ||
150 | context['posts'] = page_obj.object_list | 158 | context['posts'] = page_obj.object_list |
151 | context['forum'] = forum | 159 | context['forum'] = forum |
152 | 160 | ||
153 | - return render(request, 'post/post_list.html', context) | 161 | + html = render_to_string('post/post_load_more_render.html', context, request) |
162 | + | ||
163 | + return JsonResponse({'num_pages': paginator.num_pages, 'page': page_obj.number, 'btn_text': _('Load more posts'), 'html': html}) | ||
154 | 164 | ||
155 | class CreatePostView(LoginRequiredMixin, generic.edit.CreateView, NotificationMixin): | 165 | class CreatePostView(LoginRequiredMixin, generic.edit.CreateView, NotificationMixin): |
156 | login_url = reverse_lazy("core:home") | 166 | login_url = reverse_lazy("core:home") |
@@ -181,7 +191,9 @@ def render_post(request, post): | @@ -181,7 +191,9 @@ def render_post(request, post): | ||
181 | context = {} | 191 | context = {} |
182 | context['post'] = last_post | 192 | context['post'] = last_post |
183 | 193 | ||
184 | - return render(request, "post/post_render.html", context) | 194 | + html = render_to_string("post/post_render.html", context, request) |
195 | + | ||
196 | + return JsonResponse({'new_id': last_post.id, 'html': html}) | ||
185 | 197 | ||
186 | class PostUpdateView(LoginRequiredMixin, generic.UpdateView): | 198 | class PostUpdateView(LoginRequiredMixin, generic.UpdateView): |
187 | login_url = reverse_lazy("core:home") | 199 | login_url = reverse_lazy("core:home") |