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 | 48 | def create_post(self, title, text, image=None, thread=None, |
|
49 | 49 | ip=NO_IP, tags=None, user=None): |
|
50 | posting_time = timezone.now() | |
|
51 | ||
|
50 | 52 | post = self.create(title=title, |
|
51 | 53 | text=text, |
|
52 |
pub_time=ti |
|
|
54 | pub_time=posting_time, | |
|
53 | 55 | thread=thread, |
|
54 | 56 | image=image, |
|
55 | 57 | poster_ip=ip, |
|
56 | 58 | poster_user_agent=UNKNOWN_UA, |
|
57 |
last_edit_time=ti |
|
|
58 |
bump_time=ti |
|
|
59 | last_edit_time=posting_time, | |
|
60 | bump_time=posting_time, | |
|
59 | 61 | user=user) |
|
60 | 62 | |
|
61 | 63 | if tags: |
@@ -66,7 +68,7 b' class PostManager(models.Manager):' | |||
|
66 | 68 | if thread: |
|
67 | 69 | thread.replies.add(post) |
|
68 | 70 | thread.bump() |
|
69 |
thread.last_edit_time = ti |
|
|
71 | thread.last_edit_time = posting_time | |
|
70 | 72 | thread.save() |
|
71 | 73 | |
|
72 | 74 | #cache_key = thread.get_cache_key() |
@@ -179,7 +181,10 b' class PostManager(models.Manager):' | |||
|
179 | 181 | id = reply_number.group(1) |
|
180 | 182 | ref_post = self.filter(id=id) |
|
181 | 183 | if ref_post.count() > 0: |
|
182 |
ref_post[0] |
|
|
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 | 189 | def _get_page_count(self, thread_count): |
|
185 | 190 | return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE))) |
@@ -312,7 +317,7 b' class Post(models.Model):' | |||
|
312 | 317 | def can_bump(self): |
|
313 | 318 | """Check if the thread can be bumped by replying""" |
|
314 | 319 | |
|
315 |
post_count = self.get_reply_count() |
|
|
320 | post_count = self.get_reply_count() | |
|
316 | 321 | |
|
317 | 322 | return post_count <= settings.MAX_POSTS_PER_THREAD |
|
318 | 323 |
@@ -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 | 63 | if($('div[id='+pNum+']').length > 0) { |
@@ -72,7 +72,7 b' function showPostPreview(e) {' | |||
|
72 | 72 | //ajax api |
|
73 | 73 | else { |
|
74 | 74 | $.ajax({ |
|
75 |
|
|
|
75 | url: '/api/post/' + pNum + '/?truncated' | |
|
76 | 76 | }) |
|
77 | 77 |
|
|
78 | 78 |
|
@@ -83,7 +83,8 b' function showPostPreview(e) {' | |||
|
83 | 83 | |
|
84 | 84 | }) |
|
85 | 85 |
|
|
86 | cln.innerHTML = gettext('Post not found'); | |
|
86 | cln.innerHTML = "<div class=\"post\">" | |
|
87 | + gettext('Post not found') + "</div>"; | |
|
87 | 88 | }); |
|
88 | 89 | } |
|
89 | 90 |
@@ -77,4 +77,5 b' function addQuickReply(postId) {' | |||
|
77 | 77 | |
|
78 | 78 | $(document).ready(function(){ |
|
79 | 79 | addGalleryPanel(); |
|
80 | initAutoupdate(); | |
|
80 | 81 | }); |
@@ -1,14 +1,19 b'' | |||
|
1 | 1 | {% load i18n %} |
|
2 | 2 | {% load board %} |
|
3 | 3 | |
|
4 | {% if can_bump %} | |
|
4 | 5 | <div class="post" id="{{ post.id }}"> |
|
6 | {% else %} | |
|
7 | <div class="post dead_post" id="{{ post.id }}"> | |
|
8 | {% endif %} | |
|
9 | ||
|
5 | 10 | {% if post.image %} |
|
6 | 11 | <div class="image"> |
|
7 | 12 | <a |
|
8 | 13 | class="thumb" |
|
9 | 14 | href="{{ post.image.url }}"><img |
|
10 | 15 | src="{{ post.image.url_200x150 }}" |
|
11 |
alt="{ |
|
|
16 | alt="{{ post.id }}" | |
|
12 | 17 | data-width="{{ post.image_width }}" |
|
13 | 18 | data-height="{{ post.image_height }}"/> |
|
14 | 19 | </a> |
@@ -18,8 +23,10 b'' | |||
|
18 | 23 | <div class="post-info"> |
|
19 | 24 | <span class="title">{{ post.title }}</span> |
|
20 | 25 | <a class="post_id" href="#{{ post.id }}"> |
|
21 |
( |
|
|
26 | ({{ post.id }})</a> | |
|
22 | 27 | [{{ post.pub_time }}] |
|
28 | [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}') | |
|
29 | ; return false;">>></a>] | |
|
23 | 30 | |
|
24 | 31 | {% if moderator %} |
|
25 | 32 | <span class="moderator_info"> |
@@ -32,8 +39,13 b'' | |||
|
32 | 39 | {% endif %} |
|
33 | 40 | </div> |
|
34 | 41 | {% autoescape off %} |
|
42 | {% if truncated %} | |
|
43 | {{ post.text.rendered|truncatewords_html:50 }} | |
|
44 | {% else %} | |
|
35 | 45 | {{ post.text.rendered }} |
|
46 | {% endif %} | |
|
36 | 47 | {% endautoescape %} |
|
48 | {% if post.is_referenced %} | |
|
37 | 49 | <div class="refmap"> |
|
38 | 50 | {% trans "Replies" %}: |
|
39 | 51 | {% for ref_post in post.get_sorted_referenced_posts %} |
@@ -41,6 +53,7 b'' | |||
|
41 | 53 | >{% if not forloop.last %},{% endif %} |
|
42 | 54 | {% endfor %} |
|
43 | 55 | </div> |
|
56 | {% endif %} | |
|
44 | 57 | </div> |
|
45 | 58 | {% if post.tags.exists %} |
|
46 | 59 | <div class="metadata"> |
@@ -13,6 +13,7 b'' | |||
|
13 | 13 | {% block content %} |
|
14 | 14 | {% get_current_language as LANGUAGE_CODE %} |
|
15 | 15 | |
|
16 | <script src="{% static 'js/thread_update.js' %}"></script> | |
|
16 | 17 | <script src="{% static 'js/thread.js' %}"></script> |
|
17 | 18 | |
|
18 | 19 |
|
@@ -151,7 +152,7 b'' | |||
|
151 | 152 | |
|
152 | 153 | {% get_current_language as LANGUAGE_CODE %} |
|
153 | 154 | |
|
154 | <span class="metapanel"> | |
|
155 | <span class="metapanel" data-last-update="{{ last_update }}"> | |
|
155 | 156 | {% cache 600 thread_meta posts.0.last_edit_time moderator LANGUAGE_CODE %} |
|
156 | 157 | {{ posts.0.get_reply_count }} {% trans 'replies' %}, |
|
157 | 158 | {{ posts.0.get_images_count }} {% trans 'images' %}. |
@@ -53,4 +53,6 b" urlpatterns = patterns(''," | |||
|
53 | 53 | |
|
54 | 54 | # API |
|
55 | 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 | 1 | import hashlib |
|
2 | import json | |
|
2 | 3 | import string |
|
4 | import time | |
|
5 | import calendar | |
|
6 | ||
|
7 | from datetime import datetime | |
|
8 | ||
|
3 | 9 | from django.core import serializers |
|
4 | 10 | from django.core.urlresolvers import reverse |
|
5 | 11 | from django.http import HttpResponseRedirect |
@@ -8,6 +14,7 b' from django.template import RequestConte' | |||
|
8 | 14 | from django.shortcuts import render, redirect, get_object_or_404 |
|
9 | 15 | from django.utils import timezone |
|
10 | 16 | from django.db import transaction |
|
17 | import math | |
|
11 | 18 | |
|
12 | 19 | from boards import forms |
|
13 | 20 | import boards |
@@ -210,6 +217,7 b' def thread(request, post_id):' | |||
|
210 | 217 | context['bumplimit_progress'] = str( |
|
211 | 218 | float(context['posts_left']) / |
|
212 | 219 | neboard.settings.MAX_POSTS_PER_THREAD * 100) |
|
220 | context["last_update"] = _datetime_to_epoch(posts[0].last_edit_time) | |
|
213 | 221 | |
|
214 | 222 | return render(request, 'boards/thread.html', context) |
|
215 | 223 | |
@@ -408,13 +416,43 b' def api_get_post(request, post_id):' | |||
|
408 | 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 | 445 | def get_post(request, post_id): |
|
412 | 446 | """Get the html of a post. Used for popups.""" |
|
413 | 447 | |
|
414 | 448 | post = get_object_or_404(Post, id=post_id) |
|
449 | thread = post.thread | |
|
415 | 450 | |
|
416 | 451 | context = RequestContext(request) |
|
417 | 452 | context["post"] = post |
|
453 | context["can_bump"] = thread.can_bump() | |
|
454 | if "truncated" in request.GET: | |
|
455 | context["truncated"] = True | |
|
418 | 456 | |
|
419 | 457 | return render(request, 'boards/post.html', context) |
|
420 | 458 | |
@@ -518,3 +556,9 b' def _remove_invalid_links(text):' | |||
|
518 | 556 | text = string.replace(text, '>>' + id, id) |
|
519 | 557 | |
|
520 | 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 | 219 | ENABLE_CAPTCHA = False |
|
220 | 220 | # if user tries to post before CAPTCHA_DEFAULT_SAFE_TIME. Captcha will be shown |
|
221 | 221 | CAPTCHA_DEFAULT_SAFE_TIME = 30 # seconds |
|
222 |
POSTING_DELAY = |
|
|
222 | POSTING_DELAY = 20 # seconds | |
|
223 | 223 | |
|
224 | 224 | |
|
225 | 225 | def custom_show_toolbar(request): |
@@ -9,6 +9,8 b' denied". Use second only for autoban for' | |||
|
9 | 9 | [DONE] Clean up tests and make them run ALWAYS |
|
10 | 10 | [DONE] Use transactions in tests |
|
11 | 11 | |
|
12 | [IN PROGRESS] Thread autoupdate (JS + API) | |
|
13 | ||
|
12 | 14 | [NOT STARTED] Tree view (JS) |
|
13 | 15 | [NOT STARTED] Adding tags to images filename |
|
14 | 16 | [NOT STARTED] Federative network for s2s communication |
@@ -16,7 +18,6 b' denied". Use second only for autoban for' | |||
|
16 | 18 | [NOT STARTED] Bitmessage gate |
|
17 | 19 | [NOT STARTED] Notification engine |
|
18 | 20 | [NOT STARTED] Javascript disabling engine |
|
19 | [NOT STARTED] Thread autoupdate (JS + API) | |
|
20 | 21 | [NOT STARTED] Group tags by first letter in all tags list |
|
21 | 22 | [NOT STARTED] Show board speed in the lower panel (posts per day) |
|
22 | 23 | [NOT STARTED] Character counter in the post field |
@@ -30,6 +31,7 b' and move everything that is used only in' | |||
|
30 | 31 | post or its part (delimited by N characters) into quote of the new post. |
|
31 | 32 | [NOT STARTED] Ban confirmation page with reason |
|
32 | 33 | [NOT STARTED] Post deletion confirmation page |
|
34 | [NOT STARTED] Moderating page. Tags editing and adding | |
|
33 | 35 | |
|
34 | 36 | = Bugs = |
|
35 | 37 | [DONE] Fix bug with creating threads from tag view |
General Comments 0
You need to be logged in to leave comments.
Login now