Commit 2c6e925930cab0a22941c76fb1ee5cfef977fa19

Authored by Zambom
1 parent 753af621

Fixing new post location [Issue: #215]

core/static/css/base/amadeus.css
... ... @@ -368,4 +368,30 @@ ul, li {
368 368 /* core/reset_password.html classes*/
369 369 .send-reset-email{
370 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 398 \ No newline at end of file
... ...
core/static/js/vendor/jscookie.js 0 → 100644
... ... @@ -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 157 \ No newline at end of file
... ...
core/templates/base.html
... ... @@ -29,6 +29,7 @@
29 29 <script type="text/javascript" src="{% static 'js/vendor/ripples.min.js' %}"></script>
30 30 <script type="text/javascript" src="{% static 'js/vendor/bootstrap-datepicker.js' %}"></script>
31 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 34 <!-- Font awesome -->
34 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 12 type: frm.attr('method'),
32 13 url: frm.attr('action'),
33 14 data: frm.serialize(),
  15 + dataType: 'json',
34 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 25 frm[0].reset();
37 26 },
38 27 error: function(data) {
... ... @@ -86,8 +75,6 @@ function setForumCreateFormSubmit() {
86 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 77 $("#createForum").modal('hide');
89   -
90   - showForum(data[0], data[1]);
91 78 },
92 79 error: function(data) {
93 80 $(".forum_form").html(data.responseText);
... ... @@ -157,7 +144,7 @@ function setForumUpdateFormSubmit(success_message) {
157 144 */
158 145 function delete_forum(url, forum, message, return_url) {
159 146 alertify.confirm(message, function(){
160   - var csrftoken = getCookie('csrftoken');
  147 + var csrftoken = Cookies.get('csrftoken');
161 148  
162 149 $.ajax({
163 150 method: 'post',
... ... @@ -225,7 +212,7 @@ function cancelEditPost(post_id) {
225 212 *
226 213 */
227 214 function delete_post(url, post) {
228   - var csrftoken = getCookie('csrftoken');
  215 + var csrftoken = Cookies.get('csrftoken');
229 216  
230 217 $.ajax({
231 218 method: 'post',
... ... @@ -255,6 +242,8 @@ function load_more_posts(pageNum, numberPages, url) {
255 242  
256 243 pageNum += 1;
257 244  
  245 + var showing = new_posts.join(',');
  246 +
258 247 // Show loader
259 248 $("#loading_posts").show();
260 249  
... ... @@ -262,11 +251,22 @@ function load_more_posts(pageNum, numberPages, url) {
262 251 setTimeout(function (){
263 252 $.ajax({
264 253 url: url,
265   - data: {'page': pageNum},
  254 + data: {'page': pageNum, 'showing': showing},
  255 + dataType: 'json',
266 256 success: function(data) {
267 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 271 error: function(data) {
272 272 console.log(data);
... ... @@ -364,7 +364,7 @@ function cancelEditPostAnswer(answer_id) {
364 364 */
365 365 function delete_answer(url, answer, message) {
366 366 alertify.confirm(message, function(){
367   - var csrftoken = getCookie('csrftoken');
  367 + var csrftoken = Cookies.get('csrftoken');
368 368  
369 369 $.ajax({
370 370 method: 'post',
... ...
forum/templates/post/post_load_more_render.html 0 → 100644
... ... @@ -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 51 \ No newline at end of file
... ...
forum/views.py
... ... @@ -5,15 +5,17 @@ from django.utils.translation import ugettext_lazy as _
5 5 from django.views import generic
6 6 from django.contrib.auth.mixins import LoginRequiredMixin
7 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 12 from .models import Forum, Post, PostAnswer
11 13 from courses.models import Topic
12   -from core.mixins import NotificationMixin
13 14 from core.models import Action, Resource
14 15  
15 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 21 Forum Section
... ... @@ -130,7 +132,13 @@ def load_posts(request, forum_id):
130 132  
131 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 143 paginator = Paginator(posts, 2)
136 144  
... ... @@ -150,7 +158,9 @@ def load_posts(request, forum_id):
150 158 context['posts'] = page_obj.object_list
151 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 165 class CreatePostView(LoginRequiredMixin, generic.edit.CreateView, NotificationMixin):
156 166 login_url = reverse_lazy("core:home")
... ... @@ -181,7 +191,9 @@ def render_post(request, post):
181 191 context = {}
182 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 198 class PostUpdateView(LoginRequiredMixin, generic.UpdateView):
187 199 login_url = reverse_lazy("core:home")
... ...