##// END OF EJS Templates
Merged with thread autoupdate branch
neko259 -
r374:bd40633e merge default
parent child Browse files
Show More
@@ -0,0 +1,116 b''
1 /*
2 @licstart The following is the entire license notice for the
3 JavaScript code in this page.
4
5
6 Copyright (C) 2013 neko259
7
8 The JavaScript code in this page is free software: you can
9 redistribute it and/or modify it under the terms of the GNU
10 General Public License (GNU GPL) as published by the Free Software
11 Foundation, either version 3 of the License, or (at your option)
12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15
16 As additional permission under GNU GPL version 3 section 7, you
17 may distribute non-source (e.g., minimized or compacted) forms of
18 that code without the copy of the GNU GPL normally required by
19 section 4, provided you include this license notice and a URL
20 through which recipients can access the Corresponding Source.
21
22 @licend The above is the entire license notice
23 for the JavaScript code in this page.
24 */
25
26 var THREAD_UPDATE_DELAY = 10000;
27
28 var loading = false;
29 var lastUpdateTime = null;
30
31 function blink(node) {
32 var blinkCount = 2;
33 var blinkDelay = 250;
34
35 var nodeToAnimate = node;
36 for (var i = 0; i < blinkCount; i++) {
37 nodeToAnimate = nodeToAnimate.fadeOut(blinkDelay).fadeIn(blinkDelay);
38 }
39 }
40
41 function updateThread() {
42 if (loading) {
43 return;
44 }
45
46 loading = true;
47
48 var threadPosts = $('div.thread').children('.post');
49
50 var lastPost = threadPosts.last();
51 var threadId = threadPosts.first().attr('id');
52
53 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
54 $.getJSON(diffUrl)
55 .success(function(data) {
56 var bottom = isPageBottom();
57
58 var addedPosts = data.added;
59
60 for (var i = 0; i < addedPosts.length; i++) {
61 var postText = addedPosts[i];
62
63 var post = $(postText);
64 post.appendTo(lastPost.parent());
65 addRefLinkPreview(post[0]);
66
67 lastPost = post;
68 blink(post);
69 }
70
71 var updatedPosts = data.updated;
72 for (var i = 0; i < updatedPosts.length; i++) {
73 var postText = updatedPosts[i];
74
75 var post = $(postText);
76 var postId = post.attr('id');
77
78 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
79
80 oldPost.replaceWith(post);
81 addRefLinkPreview(post[0]);
82
83 blink(post);
84 }
85
86 // TODO Process deleted posts
87
88 lastUpdateTime = data.last_update;
89 loading = false;
90
91 if (bottom) {
92 var $target = $('html,body');
93 $target.animate({scrollTop: $target.height()}, 1000);
94 }
95 })
96 .error(function(data) {
97 // TODO Show error message that server is unavailable?
98
99 loading = false;
100 });
101 }
102
103 function isPageBottom() {
104 var scroll = $(window).scrollTop() / ($(document).height()
105 - $(window).height())
106
107 return scroll == 1
108 }
109
110 function initAutoupdate() {
111 loading = false;
112
113 lastUpdateTime = $('.metapanel').attr('data-last-update');
114
115 setInterval(updateThread, THREAD_UPDATE_DELAY);
116 }
@@ -47,15 +47,17 b' class PostManager(models.Manager):'
47
47
48 def create_post(self, title, text, image=None, thread=None,
48 def create_post(self, title, text, image=None, thread=None,
49 ip=NO_IP, tags=None, user=None):
49 ip=NO_IP, tags=None, user=None):
50 posting_time = timezone.now()
51
50 post = self.create(title=title,
52 post = self.create(title=title,
51 text=text,
53 text=text,
52 pub_time=timezone.now(),
54 pub_time=posting_time,
53 thread=thread,
55 thread=thread,
54 image=image,
56 image=image,
55 poster_ip=ip,
57 poster_ip=ip,
56 poster_user_agent=UNKNOWN_UA,
58 poster_user_agent=UNKNOWN_UA,
57 last_edit_time=timezone.now(),
59 last_edit_time=posting_time,
58 bump_time=timezone.now(),
60 bump_time=posting_time,
59 user=user)
61 user=user)
60
62
61 if tags:
63 if tags:
@@ -66,7 +68,7 b' class PostManager(models.Manager):'
66 if thread:
68 if thread:
67 thread.replies.add(post)
69 thread.replies.add(post)
68 thread.bump()
70 thread.bump()
69 thread.last_edit_time = timezone.now()
71 thread.last_edit_time = posting_time
70 thread.save()
72 thread.save()
71
73
72 #cache_key = thread.get_cache_key()
74 #cache_key = thread.get_cache_key()
@@ -179,7 +181,10 b' class PostManager(models.Manager):'
179 id = reply_number.group(1)
181 id = reply_number.group(1)
180 ref_post = self.filter(id=id)
182 ref_post = self.filter(id=id)
181 if ref_post.count() > 0:
183 if ref_post.count() > 0:
182 ref_post[0].referenced_posts.add(post)
184 referenced_post = ref_post[0]
185 referenced_post.referenced_posts.add(post)
186 referenced_post.last_edit_time = post.pub_time
187 referenced_post.save()
183
188
184 def _get_page_count(self, thread_count):
189 def _get_page_count(self, thread_count):
185 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
190 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
@@ -312,7 +317,7 b' class Post(models.Model):'
312 def can_bump(self):
317 def can_bump(self):
313 """Check if the thread can be bumped by replying"""
318 """Check if the thread can be bumped by replying"""
314
319
315 post_count = self.get_reply_count() + 1
320 post_count = self.get_reply_count()
316
321
317 return post_count <= settings.MAX_POSTS_PER_THREAD
322 return post_count <= settings.MAX_POSTS_PER_THREAD
318
323
@@ -28,7 +28,7 b' function showPostPreview(e) {'
28 var pNum = $(this).text().match(/\d+/);
28 var pNum = $(this).text().match(/\d+/);
29
29
30 if (pNum.length == 0) {
30 if (pNum.length == 0) {
31 return;
31 return;
32 }
32 }
33
33
34 //position
34 //position
@@ -57,7 +57,7 b' function showPostPreview(e) {'
57 };
57 };
58
58
59
59
60 cln.innerHTML = gettext('Loading...');
60 cln.innerHTML = "<div class=\"post\">" + gettext('Loading...') + "</div>";
61
61
62 //если пост найден в дереве.
62 //если пост найден в дереве.
63 if($('div[id='+pNum+']').length > 0) {
63 if($('div[id='+pNum+']').length > 0) {
@@ -72,19 +72,20 b' function showPostPreview(e) {'
72 //ajax api
72 //ajax api
73 else {
73 else {
74 $.ajax({
74 $.ajax({
75 url: '/api/post/' + pNum
75 url: '/api/post/' + pNum + '/?truncated'
76 })
76 })
77 .success(function(data) {
77 .success(function(data) {
78 // TODO get a json, not post itself
78 // TODO get a json, not post itself
79 var postdata = $(data).wrap("<div/>").parent().html();
79 var postdata = $(data).wrap("<div/>").parent().html();
80
80
81 //make preview
81 //make preview
82 mkPreview(cln, postdata);
82 mkPreview(cln, postdata);
83
83
84 })
84 })
85 .error(function() {
85 .error(function() {
86 cln.innerHTML = gettext('Post not found');
86 cln.innerHTML = "<div class=\"post\">"
87 });
87 + gettext('Post not found') + "</div>";
88 });
88 }
89 }
89
90
90 $del(doc.getElementById(cln.id));
91 $del(doc.getElementById(cln.id));
@@ -77,4 +77,5 b' function addQuickReply(postId) {'
77
77
78 $(document).ready(function(){
78 $(document).ready(function(){
79 addGalleryPanel();
79 addGalleryPanel();
80 initAutoupdate();
80 });
81 });
@@ -1,14 +1,19 b''
1 {% load i18n %}
1 {% load i18n %}
2 {% load board %}
2 {% load board %}
3
3
4 <div class="post" id="{{ post.id }}">
4 {% if can_bump %}
5 <div class="post" id="{{ post.id }}">
6 {% else %}
7 <div class="post dead_post" id="{{ post.id }}">
8 {% endif %}
9
5 {% if post.image %}
10 {% if post.image %}
6 <div class="image">
11 <div class="image">
7 <a
12 <a
8 class="thumb"
13 class="thumb"
9 href="{{ post.image.url }}"><img
14 href="{{ post.image.url }}"><img
10 src="{{ post.image.url_200x150 }}"
15 src="{{ post.image.url_200x150 }}"
11 alt="{% trans 'Post image' %}"v
16 alt="{{ post.id }}"
12 data-width="{{ post.image_width }}"
17 data-width="{{ post.image_width }}"
13 data-height="{{ post.image_height }}"/>
18 data-height="{{ post.image_height }}"/>
14 </a>
19 </a>
@@ -18,8 +23,10 b''
18 <div class="post-info">
23 <div class="post-info">
19 <span class="title">{{ post.title }}</span>
24 <span class="title">{{ post.title }}</span>
20 <a class="post_id" href="#{{ post.id }}">
25 <a class="post_id" href="#{{ post.id }}">
21 (#{{ post.id }})</a>
26 ({{ post.id }})</a>
22 [{{ post.pub_time }}]
27 [{{ post.pub_time }}]
28 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
29 ; return false;">&gt;&gt;</a>]
23
30
24 {% if moderator %}
31 {% if moderator %}
25 <span class="moderator_info">
32 <span class="moderator_info">
@@ -32,24 +39,30 b''
32 {% endif %}
39 {% endif %}
33 </div>
40 </div>
34 {% autoescape off %}
41 {% autoescape off %}
35 {{ post.text.rendered }}
42 {% if truncated %}
43 {{ post.text.rendered|truncatewords_html:50 }}
44 {% else %}
45 {{ post.text.rendered }}
46 {% endif %}
36 {% endautoescape %}
47 {% endautoescape %}
37 <div class="refmap">
48 {% if post.is_referenced %}
38 {% trans "Replies" %}:
49 <div class="refmap">
39 {% for ref_post in post.get_sorted_referenced_posts %}
50 {% trans "Replies" %}:
40 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
51 {% for ref_post in post.get_sorted_referenced_posts %}
41 >{% if not forloop.last %},{% endif %}
52 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
42 {% endfor %}
53 >{% if not forloop.last %},{% endif %}
43 </div>
54 {% endfor %}
55 </div>
56 {% endif %}
44 </div>
57 </div>
45 {% if post.tags.exists %}
58 {% if post.tags.exists %}
46 <div class="metadata">
59 <div class="metadata">
47 <span class="tags">{% trans 'Tags' %}:
60 <span class="tags">{% trans 'Tags' %}:
48 {% for tag in post.tags.all %}
61 {% for tag in post.tags.all %}
49 <a class="tag" href="{% url 'tag' tag.name %}">
62 <a class="tag" href="{% url 'tag' tag.name %}">
50 {{ tag.name }}</a>
63 {{ tag.name }}</a>
51 {% endfor %}
64 {% endfor %}
52 </span>
65 </span>
53 </div>
66 </div>
54 {% endif %}
67 {% endif %}
55 </div> No newline at end of file
68 </div>
@@ -13,9 +13,10 b''
13 {% block content %}
13 {% block content %}
14 {% get_current_language as LANGUAGE_CODE %}
14 {% get_current_language as LANGUAGE_CODE %}
15
15
16 <script src="{% static 'js/thread_update.js' %}"></script>
16 <script src="{% static 'js/thread.js' %}"></script>
17 <script src="{% static 'js/thread.js' %}"></script>
17
18
18 {% if posts %}
19 {% if posts %}
19 {% cache 600 thread_view posts.0.last_edit_time moderator LANGUAGE_CODE %}
20 {% cache 600 thread_view posts.0.last_edit_time moderator LANGUAGE_CODE %}
20 {% if bumpable %}
21 {% if bumpable %}
21 <div class="bar-bg">
22 <div class="bar-bg">
@@ -27,7 +28,7 b''
27 </div>
28 </div>
28 {% endif %}
29 {% endif %}
29 <div class="thread">
30 <div class="thread">
30 {% for post in posts %}
31 {% for post in posts %}
31 {% if bumpable %}
32 {% if bumpable %}
32 <div class="post" id="{{ post.id }}">
33 <div class="post" id="{{ post.id }}">
33 {% else %}
34 {% else %}
@@ -67,7 +68,7 b''
67 {% autoescape off %}
68 {% autoescape off %}
68 {{ post.text.rendered }}
69 {{ post.text.rendered }}
69 {% endautoescape %}
70 {% endautoescape %}
70 {% if post.is_referenced %}
71 {% if post.is_referenced %}
71 <div class="refmap">
72 <div class="refmap">
72 {% trans "Replies" %}:
73 {% trans "Replies" %}:
73 {% for ref_post in post.get_sorted_referenced_posts %}
74 {% for ref_post in post.get_sorted_referenced_posts %}
@@ -89,15 +90,15 b''
89 </div>
90 </div>
90 {% endif %}
91 {% endif %}
91 </div>
92 </div>
92 {% endfor %}
93 {% endfor %}
93 </div>
94 </div>
94 {% endcache %}
95 {% endcache %}
95 {% endif %}
96 {% endif %}
96
97
97 <form id="form" enctype="multipart/form-data" method="post"
98 <form id="form" enctype="multipart/form-data" method="post"
98 >{% csrf_token %}
99 >{% csrf_token %}
99 <div class="post-form-w">
100 <div class="post-form-w">
100 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
101 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
101 <div class="post-form">
102 <div class="post-form">
102 <div class="form-row">
103 <div class="form-row">
103 <div class="form-label">{% trans 'Title' %}</div>
104 <div class="form-label">{% trans 'Title' %}</div>
@@ -151,7 +152,7 b''
151
152
152 {% get_current_language as LANGUAGE_CODE %}
153 {% get_current_language as LANGUAGE_CODE %}
153
154
154 <span class="metapanel">
155 <span class="metapanel" data-last-update="{{ last_update }}">
155 {% cache 600 thread_meta posts.0.last_edit_time moderator LANGUAGE_CODE %}
156 {% cache 600 thread_meta posts.0.last_edit_time moderator LANGUAGE_CODE %}
156 {{ posts.0.get_reply_count }} {% trans 'replies' %},
157 {{ posts.0.get_reply_count }} {% trans 'replies' %},
157 {{ posts.0.get_images_count }} {% trans 'images' %}.
158 {{ posts.0.get_images_count }} {% trans 'images' %}.
@@ -53,4 +53,6 b" urlpatterns = patterns('',"
53
53
54 # API
54 # API
55 url(r'^api/post/(?P<post_id>\w+)/$', views.get_post, name="get_post"),
55 url(r'^api/post/(?P<post_id>\w+)/$', views.get_post, name="get_post"),
56 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
57 views.api_get_threaddiff, name="get_thread_diff"),
56 )
58 )
@@ -1,5 +1,11 b''
1 import hashlib
1 import hashlib
2 import json
2 import string
3 import string
4 import time
5 import calendar
6
7 from datetime import datetime
8
3 from django.core import serializers
9 from django.core import serializers
4 from django.core.urlresolvers import reverse
10 from django.core.urlresolvers import reverse
5 from django.http import HttpResponseRedirect
11 from django.http import HttpResponseRedirect
@@ -8,6 +14,7 b' from django.template import RequestConte'
8 from django.shortcuts import render, redirect, get_object_or_404
14 from django.shortcuts import render, redirect, get_object_or_404
9 from django.utils import timezone
15 from django.utils import timezone
10 from django.db import transaction
16 from django.db import transaction
17 import math
11
18
12 from boards import forms
19 from boards import forms
13 import boards
20 import boards
@@ -210,6 +217,7 b' def thread(request, post_id):'
210 context['bumplimit_progress'] = str(
217 context['bumplimit_progress'] = str(
211 float(context['posts_left']) /
218 float(context['posts_left']) /
212 neboard.settings.MAX_POSTS_PER_THREAD * 100)
219 neboard.settings.MAX_POSTS_PER_THREAD * 100)
220 context["last_update"] = _datetime_to_epoch(posts[0].last_edit_time)
213
221
214 return render(request, 'boards/thread.html', context)
222 return render(request, 'boards/thread.html', context)
215
223
@@ -245,23 +253,23 b' def settings(request):'
245 is_moderator = user.is_moderator()
253 is_moderator = user.is_moderator()
246
254
247 if request.method == 'POST':
255 if request.method == 'POST':
248 with transaction.commit_on_success():
256 with transaction.commit_on_success():
249 if is_moderator:
257 if is_moderator:
250 form = ModeratorSettingsForm(request.POST,
258 form = ModeratorSettingsForm(request.POST,
251 error_class=PlainErrorList)
259 error_class=PlainErrorList)
252 else:
260 else:
253 form = SettingsForm(request.POST, error_class=PlainErrorList)
261 form = SettingsForm(request.POST, error_class=PlainErrorList)
254
262
255 if form.is_valid():
263 if form.is_valid():
256 selected_theme = form.cleaned_data['theme']
264 selected_theme = form.cleaned_data['theme']
257
265
258 user.save_setting('theme', selected_theme)
266 user.save_setting('theme', selected_theme)
259
267
260 if is_moderator:
268 if is_moderator:
261 moderate = form.cleaned_data['moderate']
269 moderate = form.cleaned_data['moderate']
262 user.save_setting(SETTING_MODERATE, moderate)
270 user.save_setting(SETTING_MODERATE, moderate)
263
271
264 return redirect(settings)
272 return redirect(settings)
265 else:
273 else:
266 selected_theme = _get_theme(request)
274 selected_theme = _get_theme(request)
267
275
@@ -318,7 +326,7 b' def delete(request, post_id):'
318 if user.is_moderator():
326 if user.is_moderator():
319 # TODO Show confirmation page before deletion
327 # TODO Show confirmation page before deletion
320 Post.objects.delete_post(post)
328 Post.objects.delete_post(post)
321
329
322 if not post.thread:
330 if not post.thread:
323 return _redirect_to_next(request)
331 return _redirect_to_next(request)
324 else:
332 else:
@@ -408,13 +416,43 b' def api_get_post(request, post_id):'
408 return HttpResponse(content=json)
416 return HttpResponse(content=json)
409
417
410
418
419 def api_get_threaddiff(request, thread_id, last_update_time):
420 """Get posts that were changed or added since time"""
421
422 thread = get_object_or_404(Post, id=thread_id)
423
424 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
425 timezone.get_current_timezone())
426
427 json_data = {
428 'added': [],
429 'updated': [],
430 'last_update': None,
431 }
432 added_posts = Post.objects.filter(thread=thread, pub_time__gt=filter_time)
433 updated_posts = Post.objects.filter(thread=thread,
434 pub_time__lt=filter_time,
435 last_edit_time__gt=filter_time)
436 for post in added_posts:
437 json_data['added'].append(get_post(request, post.id).content.strip())
438 for post in updated_posts:
439 json_data['updated'].append(get_post(request, post.id).content.strip())
440 json_data['last_update'] = _datetime_to_epoch(thread.last_edit_time)
441
442 return HttpResponse(content=json.dumps(json_data))
443
444
411 def get_post(request, post_id):
445 def get_post(request, post_id):
412 """Get the html of a post. Used for popups."""
446 """Get the html of a post. Used for popups."""
413
447
414 post = get_object_or_404(Post, id=post_id)
448 post = get_object_or_404(Post, id=post_id)
449 thread = post.thread
415
450
416 context = RequestContext(request)
451 context = RequestContext(request)
417 context["post"] = post
452 context["post"] = post
453 context["can_bump"] = thread.can_bump()
454 if "truncated" in request.GET:
455 context["truncated"] = True
418
456
419 return render(request, 'boards/post.html', context)
457 return render(request, 'boards/post.html', context)
420
458
@@ -518,3 +556,9 b' def _remove_invalid_links(text):'
518 text = string.replace(text, '>>' + id, id)
556 text = string.replace(text, '>>' + id, id)
519
557
520 return text
558 return text
559
560
561 def _datetime_to_epoch(datetime):
562 return int(time.mktime(timezone.localtime(
563 datetime,timezone.get_current_timezone()).timetuple())
564 * 1000000 + datetime.microsecond) No newline at end of file
@@ -219,7 +219,7 b' LAST_REPLIES_COUNT = 3'
219 ENABLE_CAPTCHA = False
219 ENABLE_CAPTCHA = False
220 # if user tries to post before CAPTCHA_DEFAULT_SAFE_TIME. Captcha will be shown
220 # if user tries to post before CAPTCHA_DEFAULT_SAFE_TIME. Captcha will be shown
221 CAPTCHA_DEFAULT_SAFE_TIME = 30 # seconds
221 CAPTCHA_DEFAULT_SAFE_TIME = 30 # seconds
222 POSTING_DELAY = 30 # seconds
222 POSTING_DELAY = 20 # seconds
223
223
224
224
225 def custom_show_toolbar(request):
225 def custom_show_toolbar(request):
@@ -9,6 +9,8 b' denied". Use second only for autoban for'
9 [DONE] Clean up tests and make them run ALWAYS
9 [DONE] Clean up tests and make them run ALWAYS
10 [DONE] Use transactions in tests
10 [DONE] Use transactions in tests
11
11
12 [IN PROGRESS] Thread autoupdate (JS + API)
13
12 [NOT STARTED] Tree view (JS)
14 [NOT STARTED] Tree view (JS)
13 [NOT STARTED] Adding tags to images filename
15 [NOT STARTED] Adding tags to images filename
14 [NOT STARTED] Federative network for s2s communication
16 [NOT STARTED] Federative network for s2s communication
@@ -16,7 +18,6 b' denied". Use second only for autoban for'
16 [NOT STARTED] Bitmessage gate
18 [NOT STARTED] Bitmessage gate
17 [NOT STARTED] Notification engine
19 [NOT STARTED] Notification engine
18 [NOT STARTED] Javascript disabling engine
20 [NOT STARTED] Javascript disabling engine
19 [NOT STARTED] Thread autoupdate (JS + API)
20 [NOT STARTED] Group tags by first letter in all tags list
21 [NOT STARTED] Group tags by first letter in all tags list
21 [NOT STARTED] Show board speed in the lower panel (posts per day)
22 [NOT STARTED] Show board speed in the lower panel (posts per day)
22 [NOT STARTED] Character counter in the post field
23 [NOT STARTED] Character counter in the post field
@@ -30,6 +31,7 b' and move everything that is used only in'
30 post or its part (delimited by N characters) into quote of the new post.
31 post or its part (delimited by N characters) into quote of the new post.
31 [NOT STARTED] Ban confirmation page with reason
32 [NOT STARTED] Ban confirmation page with reason
32 [NOT STARTED] Post deletion confirmation page
33 [NOT STARTED] Post deletion confirmation page
34 [NOT STARTED] Moderating page. Tags editing and adding
33
35
34 = Bugs =
36 = Bugs =
35 [DONE] Fix bug with creating threads from tag view
37 [DONE] Fix bug with creating threads from tag view
General Comments 0
You need to be logged in to leave comments. Login now