##// END OF EJS Templates
Update reply and image count after thread update
neko259 -
r391:0a2454a1 default
parent child Browse files
Show More
@@ -1,116 +1,127 b''
1 /*
1 /*
2 @licstart The following is the entire license notice for the
2 @licstart The following is the entire license notice for the
3 JavaScript code in this page.
3 JavaScript code in this page.
4
4
5
5
6 Copyright (C) 2013 neko259
6 Copyright (C) 2013 neko259
7
7
8 The JavaScript code in this page is free software: you can
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
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
10 General Public License (GNU GPL) as published by the Free Software
11 Foundation, either version 3 of the License, or (at your option)
11 Foundation, either version 3 of the License, or (at your option)
12 any later version. The code is distributed WITHOUT ANY WARRANTY;
12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 without even the implied warranty of MERCHANTABILITY or FITNESS
13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15
15
16 As additional permission under GNU GPL version 3 section 7, you
16 As additional permission under GNU GPL version 3 section 7, you
17 may distribute non-source (e.g., minimized or compacted) forms of
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
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
19 section 4, provided you include this license notice and a URL
20 through which recipients can access the Corresponding Source.
20 through which recipients can access the Corresponding Source.
21
21
22 @licend The above is the entire license notice
22 @licend The above is the entire license notice
23 for the JavaScript code in this page.
23 for the JavaScript code in this page.
24 */
24 */
25
25
26 var THREAD_UPDATE_DELAY = 10000;
26 var THREAD_UPDATE_DELAY = 10000;
27
27
28 var loading = false;
28 var loading = false;
29 var lastUpdateTime = null;
29 var lastUpdateTime = null;
30
30
31 function blink(node) {
31 function blink(node) {
32 var blinkCount = 2;
32 var blinkCount = 2;
33 var blinkDelay = 250;
33 var blinkDelay = 250;
34
34
35 var nodeToAnimate = node;
35 var nodeToAnimate = node;
36 for (var i = 0; i < blinkCount; i++) {
36 for (var i = 0; i < blinkCount; i++) {
37 nodeToAnimate = nodeToAnimate.fadeOut(blinkDelay).fadeIn(blinkDelay);
37 nodeToAnimate = nodeToAnimate.fadeOut(blinkDelay).fadeIn(blinkDelay);
38 }
38 }
39 }
39 }
40
40
41 function updateThread() {
41 function updateThread() {
42 if (loading) {
42 if (loading) {
43 return;
43 return;
44 }
44 }
45
45
46 loading = true;
46 loading = true;
47
47
48 var threadPosts = $('div.thread').children('.post');
48 var threadPosts = $('div.thread').children('.post');
49
49
50 var lastPost = threadPosts.last();
50 var lastPost = threadPosts.last();
51 var threadId = threadPosts.first().attr('id');
51 var threadId = threadPosts.first().attr('id');
52
52
53 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
53 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
54 $.getJSON(diffUrl)
54 $.getJSON(diffUrl)
55 .success(function(data) {
55 .success(function(data) {
56 var bottom = isPageBottom();
56 var bottom = isPageBottom();
57
57
58 var addedPosts = data.added;
58 var addedPosts = data.added;
59
59
60 for (var i = 0; i < addedPosts.length; i++) {
60 for (var i = 0; i < addedPosts.length; i++) {
61 var postText = addedPosts[i];
61 var postText = addedPosts[i];
62
62
63 var post = $(postText);
63 var post = $(postText);
64 post.appendTo(lastPost.parent());
64 post.appendTo(lastPost.parent());
65 addRefLinkPreview(post[0]);
65 addRefLinkPreview(post[0]);
66
66
67 lastPost = post;
67 lastPost = post;
68 blink(post);
68 blink(post);
69 }
69 }
70
70
71 var updatedPosts = data.updated;
71 var updatedPosts = data.updated;
72 for (var i = 0; i < updatedPosts.length; i++) {
72 for (var i = 0; i < updatedPosts.length; i++) {
73 var postText = updatedPosts[i];
73 var postText = updatedPosts[i];
74
74
75 var post = $(postText);
75 var post = $(postText);
76 var postId = post.attr('id');
76 var postId = post.attr('id');
77
77
78 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
78 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
79
79
80 oldPost.replaceWith(post);
80 oldPost.replaceWith(post);
81 addRefLinkPreview(post[0]);
81 addRefLinkPreview(post[0]);
82
82
83 blink(post);
83 blink(post);
84 }
84 }
85
85
86 // TODO Process deleted posts
86 // TODO Process deleted posts
87
87
88 lastUpdateTime = data.last_update;
88 lastUpdateTime = data.last_update;
89 loading = false;
89 loading = false;
90
90
91 if (bottom) {
91 if (bottom) {
92 var $target = $('html,body');
92 var $target = $('html,body');
93 $target.animate({scrollTop: $target.height()}, 1000);
93 $target.animate({scrollTop: $target.height()}, 1000);
94 }
94 }
95
96 $('#reply-count').text(getReplyCount());
97 $('#image-count').text(getImageCount());
95 })
98 })
96 .error(function(data) {
99 .error(function(data) {
97 // TODO Show error message that server is unavailable?
100 // TODO Show error message that server is unavailable?
98
101
99 loading = false;
102 loading = false;
100 });
103 });
101 }
104 }
102
105
103 function isPageBottom() {
106 function isPageBottom() {
104 var scroll = $(window).scrollTop() / ($(document).height()
107 var scroll = $(window).scrollTop() / ($(document).height()
105 - $(window).height())
108 - $(window).height())
106
109
107 return scroll == 1
110 return scroll == 1
108 }
111 }
109
112
110 function initAutoupdate() {
113 function initAutoupdate() {
111 loading = false;
114 loading = false;
112
115
113 lastUpdateTime = $('.metapanel').attr('data-last-update');
116 lastUpdateTime = $('.metapanel').attr('data-last-update');
114
117
115 setInterval(updateThread, THREAD_UPDATE_DELAY);
118 setInterval(updateThread, THREAD_UPDATE_DELAY);
116 }
119 }
120
121 function getReplyCount() {
122 return $('.thread').children('.post').length - 1
123 }
124
125 function getImageCount() {
126 return $('.thread').find('img').length
127 }
@@ -1,164 +1,164 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5 {% load cache %}
5 {% load cache %}
6 {% load static from staticfiles %}
6 {% load static from staticfiles %}
7 {% load board %}
7 {% load board %}
8
8
9 {% block head %}
9 {% block head %}
10 <title>Neboard - {{ posts.0.get_title }}</title>
10 <title>Neboard - {{ posts.0.get_title }}</title>
11 {% endblock %}
11 {% endblock %}
12
12
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_update.js' %}"></script>
17 <script src="{% static 'js/thread.js' %}"></script>
17 <script src="{% static 'js/thread.js' %}"></script>
18
18
19 {% if posts %}
19 {% if posts %}
20 {% 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 %}
21 {% if bumpable %}
21 {% if bumpable %}
22 <div class="bar-bg">
22 <div class="bar-bg">
23 <div class="bar-value" style="width:{{ bumplimit_progress }}%">
23 <div class="bar-value" style="width:{{ bumplimit_progress }}%">
24 </div>
24 </div>
25 <div class="bar-text">
25 <div class="bar-text">
26 {{ posts_left }} {% trans 'posts to bumplimit' %}
26 {{ posts_left }} {% trans 'posts to bumplimit' %}
27 </div>
27 </div>
28 </div>
28 </div>
29 {% endif %}
29 {% endif %}
30 <div class="thread">
30 <div class="thread">
31 {% for post in posts %}
31 {% for post in posts %}
32 {% if bumpable %}
32 {% if bumpable %}
33 <div class="post" id="{{ post.id }}">
33 <div class="post" id="{{ post.id }}">
34 {% else %}
34 {% else %}
35 <div class="post dead_post" id="{{ post.id }}">
35 <div class="post dead_post" id="{{ post.id }}">
36 {% endif %}
36 {% endif %}
37 {% if post.image %}
37 {% if post.image %}
38 <div class="image">
38 <div class="image">
39 <a
39 <a
40 class="thumb"
40 class="thumb"
41 href="{{ post.image.url }}"><img
41 href="{{ post.image.url }}"><img
42 src="{{ post.image.url_200x150 }}"
42 src="{{ post.image.url_200x150 }}"
43 alt="{{ post.id }}"
43 alt="{{ post.id }}"
44 data-width="{{ post.image_width }}"
44 data-width="{{ post.image_width }}"
45 data-height="{{ post.image_height }}"/>
45 data-height="{{ post.image_height }}"/>
46 </a>
46 </a>
47 </div>
47 </div>
48 {% endif %}
48 {% endif %}
49 <div class="message">
49 <div class="message">
50 <div class="post-info">
50 <div class="post-info">
51 <span class="title">{{ post.title }}</span>
51 <span class="title">{{ post.title }}</span>
52 <a class="post_id" href="#{{ post.id }}">
52 <a class="post_id" href="#{{ post.id }}">
53 ({{ post.id }})</a>
53 ({{ post.id }})</a>
54 [{{ post.pub_time }}]
54 [{{ post.pub_time }}]
55 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
55 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
56 ; return false;">&gt;&gt;</a>]
56 ; return false;">&gt;&gt;</a>]
57
57
58 {% if moderator %}
58 {% if moderator %}
59 <span class="moderator_info">
59 <span class="moderator_info">
60 [<a href="{% url 'delete' post_id=post.id %}"
60 [<a href="{% url 'delete' post_id=post.id %}"
61 >{% trans 'Delete' %}</a>]
61 >{% trans 'Delete' %}</a>]
62 ({{ post.poster_ip }})
62 ({{ post.poster_ip }})
63 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
63 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
64 >{% trans 'Ban IP' %}</a>]
64 >{% trans 'Ban IP' %}</a>]
65 </span>
65 </span>
66 {% endif %}
66 {% endif %}
67 </div>
67 </div>
68 {% autoescape off %}
68 {% autoescape off %}
69 {{ post.text.rendered }}
69 {{ post.text.rendered }}
70 {% endautoescape %}
70 {% endautoescape %}
71 {% if post.is_referenced %}
71 {% if post.is_referenced %}
72 <div class="refmap">
72 <div class="refmap">
73 {% trans "Replies" %}:
73 {% trans "Replies" %}:
74 {% for ref_post in post.get_sorted_referenced_posts %}
74 {% for ref_post in post.get_sorted_referenced_posts %}
75 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
75 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
76 >{% if not forloop.last %},{% endif %}
76 >{% if not forloop.last %},{% endif %}
77 {% endfor %}
77 {% endfor %}
78 </div>
78 </div>
79 {% endif %}
79 {% endif %}
80 </div>
80 </div>
81 {% if forloop.first %}
81 {% if forloop.first %}
82 <div class="metadata">
82 <div class="metadata">
83 <span class="tags">
83 <span class="tags">
84 {% for tag in post.get_tags %}
84 {% for tag in post.get_tags %}
85 <a class="tag" href="{% url 'tag' tag.name %}">
85 <a class="tag" href="{% url 'tag' tag.name %}">
86 #{{ tag.name }}</a
86 #{{ tag.name }}</a
87 >{% if not forloop.last %},{% endif %}
87 >{% if not forloop.last %},{% endif %}
88 {% endfor %}
88 {% endfor %}
89 </span>
89 </span>
90 </div>
90 </div>
91 {% endif %}
91 {% endif %}
92 </div>
92 </div>
93 {% endfor %}
93 {% endfor %}
94 </div>
94 </div>
95 {% endcache %}
95 {% endcache %}
96 {% endif %}
96 {% endif %}
97
97
98 <form id="form" enctype="multipart/form-data" method="post"
98 <form id="form" enctype="multipart/form-data" method="post"
99 >{% csrf_token %}
99 >{% csrf_token %}
100 <div class="post-form-w">
100 <div class="post-form-w">
101 <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>
102 <div class="post-form">
102 <div class="post-form">
103 <div class="form-row">
103 <div class="form-row">
104 <div class="form-label">{% trans 'Title' %}</div>
104 <div class="form-label">{% trans 'Title' %}</div>
105 <div class="form-input">{{ form.title }}</div>
105 <div class="form-input">{{ form.title }}</div>
106 <div class="form-errors">{{ form.title.errors }}</div>
106 <div class="form-errors">{{ form.title.errors }}</div>
107 </div>
107 </div>
108 <div class="form-row">
108 <div class="form-row">
109 <div class="form-label">{% trans 'Formatting' %}</div>
109 <div class="form-label">{% trans 'Formatting' %}</div>
110 <div class="form-input" id="mark_panel">
110 <div class="form-input" id="mark_panel">
111 <span class="mark_btn" id="quote"><span class="quote">&gt;{% trans 'quote' %}</span></span>
111 <span class="mark_btn" id="quote"><span class="quote">&gt;{% trans 'quote' %}</span></span>
112 <span class="mark_btn" id="italic"><i>{% trans 'italic' %}</i></span>
112 <span class="mark_btn" id="italic"><i>{% trans 'italic' %}</i></span>
113 <span class="mark_btn" id="bold"><b>{% trans 'bold' %}</b></span>
113 <span class="mark_btn" id="bold"><b>{% trans 'bold' %}</b></span>
114 <span class="mark_btn" id="spoiler"><span class="spoiler">{% trans 'spoiler' %}</span></span>
114 <span class="mark_btn" id="spoiler"><span class="spoiler">{% trans 'spoiler' %}</span></span>
115 <span class="mark_btn" id="comment"><span class="comment">// {% trans 'comment' %}</span></span>
115 <span class="mark_btn" id="comment"><span class="comment">// {% trans 'comment' %}</span></span>
116 </div>
116 </div>
117 </div>
117 </div>
118 <div class="form-row">
118 <div class="form-row">
119 <div class="form-label">{% trans 'Text' %}</div>
119 <div class="form-label">{% trans 'Text' %}</div>
120 <div class="form-input">{{ form.text }}</div>
120 <div class="form-input">{{ form.text }}</div>
121 <div class="form-errors">{{ form.text.errors }}</div>
121 <div class="form-errors">{{ form.text.errors }}</div>
122 </div>
122 </div>
123 <div class="form-row">
123 <div class="form-row">
124 <div class="form-label">{% trans 'Image' %}</div>
124 <div class="form-label">{% trans 'Image' %}</div>
125 <div class="form-input">{{ form.image }}</div>
125 <div class="form-input">{{ form.image }}</div>
126 <div class="form-errors">{{ form.image.errors }}</div>
126 <div class="form-errors">{{ form.image.errors }}</div>
127 </div>
127 </div>
128 <div class="form-row form-email">
128 <div class="form-row form-email">
129 <div class="form-label">{% trans 'e-mail' %}</div>
129 <div class="form-label">{% trans 'e-mail' %}</div>
130 <div class="form-input">{{ form.email }}</div>
130 <div class="form-input">{{ form.email }}</div>
131 <div class="form-errors">{{ form.email.errors }}</div>
131 <div class="form-errors">{{ form.email.errors }}</div>
132 </div>
132 </div>
133 <div class="form-row">
133 <div class="form-row">
134 {{ form.captcha }}
134 {{ form.captcha }}
135 <div class="form-errors">{{ form.captcha.errors }}</div>
135 <div class="form-errors">{{ form.captcha.errors }}</div>
136 </div>
136 </div>
137 <div class="form-row">
137 <div class="form-row">
138 <div class="form-errors">{{ form.other.errors }}</div>
138 <div class="form-errors">{{ form.other.errors }}</div>
139 </div>
139 </div>
140 </div>
140 </div>
141
141
142 <div class="form-submit"><input type="submit"
142 <div class="form-submit"><input type="submit"
143 value="{% trans "Post" %}"/></div>
143 value="{% trans "Post" %}"/></div>
144 <div><a href="{% url "staticpage" name="help" %}">
144 <div><a href="{% url "staticpage" name="help" %}">
145 {% trans 'Text syntax' %}</a></div>
145 {% trans 'Text syntax' %}</a></div>
146 </div>
146 </div>
147 </form>
147 </form>
148
148
149 {% endblock %}
149 {% endblock %}
150
150
151 {% block metapanel %}
151 {% block metapanel %}
152
152
153 {% get_current_language as LANGUAGE_CODE %}
153 {% get_current_language as LANGUAGE_CODE %}
154
154
155 <span class="metapanel" data-last-update="{{ last_update }}">
155 <span class="metapanel" data-last-update="{{ last_update }}">
156 {% 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 %}
157 {{ posts.0.get_reply_count }} {% trans 'replies' %},
157 <span id="reply-count">{{ posts.0.get_reply_count }}</span> {% trans 'replies' %},
158 {{ posts.0.get_images_count }} {% trans 'images' %}.
158 <span id="image-count">{{ posts.0.get_images_count }}</span> {% trans 'images' %}.
159 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
159 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
160 [<a href="rss/">RSS</a>]
160 [<a href="rss/">RSS</a>]
161 {% endcache %}
161 {% endcache %}
162 </span>
162 </span>
163
163
164 {% endblock %}
164 {% endblock %}
@@ -1,48 +1,48 b''
1 = Features =
1 = Features =
2 [DONE] Connecting tags to each other
2 [DONE] Connecting tags to each other
3 [DONE] Connect posts to the replies (in messages), get rid of the JS reply map
3 [DONE] Connect posts to the replies (in messages), get rid of the JS reply map
4 [DONE] Better django admin pages to simplify admin operations
4 [DONE] Better django admin pages to simplify admin operations
5 [DONE] Regen script to update all posts
5 [DONE] Regen script to update all posts
6 [DONE] Remove jump links from refmaps
6 [DONE] Remove jump links from refmaps
7 [DONE] Ban reasons. Split bans into 2 types "read-only" and "read
7 [DONE] Ban reasons. Split bans into 2 types "read-only" and "read
8 denied". Use second only for autoban for spam
8 denied". Use second only for autoban for spam
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 [DONE] Thread autoupdate (JS + API)
11 [DONE] Thread autoupdate (JS + API)
12
12
13 [NOT STARTED] Tree view (JS)
13 [NOT STARTED] Tree view (JS)
14 [NOT STARTED] Adding tags to images filename
14 [NOT STARTED] Adding tags to images filename
15 [NOT STARTED] Federative network for s2s communication
15 [NOT STARTED] Federative network for s2s communication
16 [NOT STARTED] XMPP gate
16 [NOT STARTED] XMPP gate
17 [NOT STARTED] Bitmessage gate
17 [NOT STARTED] Bitmessage gate
18 [NOT STARTED] Notification engine
18 [NOT STARTED] Notification engine
19 [NOT STARTED] Javascript disabling engine
19 [NOT STARTED] Javascript disabling engine
20 [NOT STARTED] Group tags by first letter in all tags list
20 [NOT STARTED] Group tags by first letter in all tags list
21 [NOT STARTED] Show board speed in the lower panel (posts per day)
21 [NOT STARTED] Show board speed in the lower panel (posts per day)
22 [NOT STARTED] Character counter in the post field
22 [NOT STARTED] Character counter in the post field
23 [NOT STARTED] Save image thumbnails size to the separate field
23 [NOT STARTED] Save image thumbnails size to the separate field
24 [NOT STARTED] Whitelist functionality. Permin autoban of an address
24 [NOT STARTED] Whitelist functionality. Permin autoban of an address
25 [NOT STARTED] Split up post model into post and thread,
25 [NOT STARTED] Split up post model into post and thread,
26 and move everything that is used only in 1st post to thread model.
26 and move everything that is used only in 1st post to thread model.
27 [NOT STARTED] Statistics module. Count views (optional, may result in bad
27 [NOT STARTED] Statistics module. Count views (optional, may result in bad
28 performance), posts per day/week/month, users (or IPs)
28 performance), posts per day/week/month, users (or IPs)
29 [NOT STARTED] Quote button next to "reply" for posts in thread to include full
29 [NOT STARTED] Quote button next to "reply" for posts in thread to include full
30 post or its part (delimited by N characters) into quote of the new post.
30 post or its part (delimited by N characters) into quote of the new post.
31 [NOT STARTED] Ban confirmation page with reason
31 [NOT STARTED] Ban confirmation page with reason
32 [NOT STARTED] Post deletion confirmation page
32 [NOT STARTED] Post deletion confirmation page
33 [NOT STARTED] Moderating page. Tags editing and adding
33 [NOT STARTED] Moderating page. Tags editing and adding
34 [NOT STARTED] Get thread graph image using pygraphviz
34 [NOT STARTED] Get thread graph image using pygraphviz
35 [NOT STARTED] Creating post via AJAX without reloading page
35 [NOT STARTED] Creating post via AJAX without reloading page
36 [NOT STARTED] Subscribing to tag via AJAX
36 [NOT STARTED] Subscribing to tag via AJAX
37
37
38 = Bugs =
38 = Bugs =
39 [DONE] Fix bug with creating threads from tag view
39 [DONE] Fix bug with creating threads from tag view
40 [DONE] Quote characters within quote causes quote parsing to fail
40 [DONE] Quote characters within quote causes quote parsing to fail
41
41
42 [NOT STARTED] Replies, images, last update time in bottom panel doesn't change when
42 [IN PROGRESS] Replies, images, last update time in bottom panel doesn't change when
43 thread updates
43 thread updates
44
44
45 = Testing =
45 = Testing =
46 [NOT STARTED] Make tests for every view
46 [NOT STARTED] Make tests for every view
47 [NOT STARTED] Make tests for every model
47 [NOT STARTED] Make tests for every model
48 [NOT STARTED] Make tests for every form
48 [NOT STARTED] Make tests for every form
General Comments 0
You need to be logged in to leave comments. Login now