##// END OF EJS Templates
Updating last_update time if the thread in autoupdate
neko259 -
r536:886da067 default
parent child Browse files
Show More
@@ -1,241 +1,270 b''
1 1 /*
2 2 @licstart The following is the entire license notice for the
3 3 JavaScript code in this page.
4 4
5 5
6 6 Copyright (C) 2013 neko259
7 7
8 8 The JavaScript code in this page is free software: you can
9 9 redistribute it and/or modify it under the terms of the GNU
10 10 General Public License (GNU GPL) as published by the Free Software
11 11 Foundation, either version 3 of the License, or (at your option)
12 12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15 15
16 16 As additional permission under GNU GPL version 3 section 7, you
17 17 may distribute non-source (e.g., minimized or compacted) forms of
18 18 that code without the copy of the GNU GPL normally required by
19 19 section 4, provided you include this license notice and a URL
20 20 through which recipients can access the Corresponding Source.
21 21
22 22 @licend The above is the entire license notice
23 23 for the JavaScript code in this page.
24 24 */
25 25
26 26 var THREAD_UPDATE_DELAY = 10000;
27 27
28 28 var loading = false;
29 29 var lastUpdateTime = null;
30 30 var unreadPosts = 0
31 31
32 32 function blink(node) {
33 33 var blinkCount = 2;
34 34
35 35 var nodeToAnimate = node;
36 36 for (var i = 0; i < blinkCount; i++) {
37 37 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
38 38 }
39 39 }
40 40
41 41 function updateThread() {
42 42 if (loading) {
43 43 return;
44 44 }
45 45
46 46 loading = true;
47 47
48 48 var threadPosts = $('div.thread').children('.post');
49 49
50 50 var lastPost = threadPosts.last();
51 51 var threadId = threadPosts.first().attr('id');
52 52
53 53 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
54 54 $.getJSON(diffUrl)
55 55 .success(function(data) {
56 56 var bottom = isPageBottom();
57 57
58 var lastUpdate = '';
59
58 60 var addedPosts = data.added;
59 61 for (var i = 0; i < addedPosts.length; i++) {
60 62 var postText = addedPosts[i];
61 63
62 64 var post = $(postText);
65
66 if (lastUpdate === '') {
67 lastUpdate = post.find('.pub_time').text();
68 }
69
63 70 post.appendTo(lastPost.parent());
64 71 addRefLinkPreview(post[0]);
65 72
66 73 lastPost = post;
67 74 blink(post);
68 75 }
69 76
70 77 var updatedPosts = data.updated;
71 78 for (var i = 0; i < updatedPosts.length; i++) {
72 79 var postText = updatedPosts[i];
73 80
74 81 var post = $(postText);
82
83 if (lastUpdate === '') {
84 lastUpdate = post.find('.pub_time').text();
85 }
86
75 87 var postId = post.attr('id');
76 88
77 89 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
78 90
79 91 oldPost.replaceWith(post);
80 92 addRefLinkPreview(post[0]);
81 93
82 94 blink(post);
83 95 }
84
96
85 97 // TODO Process deleted posts
86 98
87 99 lastUpdateTime = data.last_update;
88 100 loading = false;
89 101
90 102 if (bottom) {
91 103 var $target = $('html,body');
92 104 $target.animate({scrollTop: $target.height()}, 1000);
93 105 }
94 106
95 $('#reply-count').text(getReplyCount());
96 $('#image-count').text(getImageCount());
107 var hasPostChanges = (updatedPosts.length > 0)
108 || (addedPosts.length > 0);
109 if (hasPostChanges) {
110 updateMetadataPanel(lastUpdate);
111 }
97 112
98 113 updateBumplimitProgress(data.added.length);
99 114 updatePostBumpableStatus();
100 115
101 116 if (data.added.length + data.updated.length > 0) {
102 117 showNewPostsTitle(data.added.length);
103 118 }
104 119 })
105 120 .error(function(data) {
106 121 // TODO Show error message that server is unavailable?
107 122
108 123 loading = false;
109 124 });
110 125 }
111 126
112 127 function isPageBottom() {
113 128 var scroll = $(window).scrollTop() / ($(document).height()
114 129 - $(window).height())
115 130
116 131 return scroll == 1
117 132 }
118 133
119 134 function initAutoupdate() {
120 135 loading = false;
121 136
122 137 lastUpdateTime = $('.metapanel').attr('data-last-update');
123 138
124 139 setInterval(updateThread, THREAD_UPDATE_DELAY);
125 140 }
126 141
127 142 function getReplyCount() {
128 143 return $('.thread').children('.post').length
129 144 }
130 145
131 146 function getImageCount() {
132 147 return $('.thread').find('img').length
133 148 }
134 149
150 function updateMetadataPanel(lastUpdate) {
151 var replyCountField = $('#reply-count');
152 var imageCountField = $('#image-count');
153
154 replyCountField.text(getReplyCount());
155 imageCountField.text(getImageCount());
156
157 if (lastUpdate !== '') {
158 var lastUpdateField = $('#last-update');
159 lastUpdateField.text(lastUpdate);
160 blink(lastUpdateField);
161 }
162
163 blink(replyCountField);
164 blink(imageCountField);
165 }
166
135 167 /**
136 168 * Update bumplimit progress bar
137 169 */
138 170 function updateBumplimitProgress(postDelta) {
139 171 var progressBar = $('#bumplimit_progress');
140 172 if (progressBar) {
141 173 var postsToLimitElement = $('#left_to_limit');
142 174
143 175 var oldPostsToLimit = parseInt(postsToLimitElement.text());
144 176 var postCount = getReplyCount();
145 177 var bumplimit = postCount - postDelta + oldPostsToLimit;
146 178
147 179 var newPostsToLimit = bumplimit - postCount;
148 180 if (newPostsToLimit <= 0) {
149 181 $('.bar-bg').remove();
150 182 } else {
151 183 postsToLimitElement.text(newPostsToLimit);
152 184 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
153 185 }
154 186 }
155 187 }
156 188
157 189 /**
158 190 * If the bumplimit is reached, add dead_post class to all posts
159 191 */
160 192 function updatePostBumpableStatus() {
161 var postCount = getReplyCount();
162 193 var postsToLimitElement = $('#left_to_limit');
163 var postsToLimit = parseInt(postsToLimitElement.text());
164 194
165 if (postsToLimit <= 0) {
166 $('.thread').find('.post').addClass('dead_post');
195 if (postsToLimitElement === null) {
196 $('.thread').children('.post').addClass('dead_post');
167 197 }
168 198 }
169 199
170 200 var documentOriginalTitle = '';
171 201 /**
172 202 * Show 'new posts' text in the title if the document is not visible to a user
173 203 */
174 204 function showNewPostsTitle(newPostCount) {
175 205 if (document.hidden) {
176 206 if (documentOriginalTitle === '') {
177 207 documentOriginalTitle = document.title;
178 208 }
179 209 unreadPosts = unreadPosts + newPostCount;
180 210 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
181 211
182 212 document.addEventListener('visibilitychange', function() {
183 213 if (documentOriginalTitle !== '') {
184 214 document.title = documentOriginalTitle;
185 215 documentOriginalTitle = '';
186 216 unreadPosts = 0;
187 217 }
188 218
189 219 document.removeEventListener('visibilitychange', null);
190 220 });
191 221 }
192 222 }
193 223
194 224 /**
195 225 * Clear all entered values in the form fields
196 226 */
197 227 function resetForm(form) {
198 228 form.find('input:text, input:password, input:file, select, textarea').val('');
199 229 form.find('input:radio, input:checkbox')
200 230 .removeAttr('checked').removeAttr('selected');
201 231 }
202 232
203 233
204 234 $(document).ready(function(){
205 235 initAutoupdate();
206 236
207 237 // Post form data over AJAX
208 238 var threadId = $('div.thread').children('.post').first().attr('id');;
209 239
210 240 var form = $('#form');
211 241 var options = {
212 242 success: updateOnPost,
213 243 url: '/api/add_post/' + threadId + '/',
214 244 };
215 245
216 246 form.ajaxForm(options);
217 247
218 248 function updateOnPost(response, statusText, xhr, $form) {
219 249 var json = $.parseJSON(response);
220 250 var status = json.status;
221 251
222 252 form.children('.form-errors').remove();
223 253
224 254 if (status === 'ok') {
225 255 resetForm(form);
226 256 updateThread();
227 257 } else {
228 258 var errors = json.errors;
229 259 for (var i = 0; i < errors.length; i++) {
230 var error = errors[i];
260 var fieldErrors = errors[i];
231 261
232 var fieldName = error.field;
233 var error = error.errors;
262 var error = fieldErrors.errors;
234 263
235 264 var errorList = $('<div class="form-errors">' + error
236 265 + '<div>');
237 266 errorList.appendTo(form);
238 267 }
239 268 }
240 269 }
241 270 });
@@ -1,74 +1,74 b''
1 1 {% load i18n %}
2 2 {% load board %}
3 3
4 4 {% spaceless %}
5 5 {% if post.thread_new.archived %}
6 6 <div class="post archive_post" id="{{ post.id }}">
7 7 {% elif post.thread_new.can_bump %}
8 8 <div class="post" id="{{ post.id }}">
9 9 {% else %}
10 10 <div class="post dead_post" id="{{ post.id }}">
11 11 {% endif %}
12 12
13 13 {% if post.image %}
14 14 <div class="image">
15 15 <a
16 16 class="thumb"
17 17 href="{{ post.image.url }}"><img
18 18 src="{{ post.image.url_200x150 }}"
19 19 alt="{{ post.id }}"
20 20 width="{{ post.image_pre_width }}"
21 21 height="{{ post.image_pre_height }}"
22 22 data-width="{{ post.image_width }}"
23 23 data-height="{{ post.image_height }}"/>
24 24 </a>
25 25 </div>
26 26 {% endif %}
27 27 <div class="message">
28 28 <div class="post-info">
29 29 <span class="title">{{ post.title }}</span>
30 30 <a class="post_id" href="{% post_url post.id %}">
31 ({{ post.id }})</a>
32 [{{ post.pub_time }}]
31 ({{ post.id }}) </a>
32 [<span class="pub_time">{{ post.pub_time }}</span>]
33 33 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
34 34 ; return false;">&gt;&gt;</a>]
35 35
36 36 {% if moderator %}
37 37 <span class="moderator_info">
38 38 [<a href="{% url 'delete' post_id=post.id %}"
39 39 >{% trans 'Delete' %}</a>]
40 40 ({{ post.poster_ip }})
41 41 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
42 42 >{% trans 'Ban IP' %}</a>]
43 43 </span>
44 44 {% endif %}
45 45 </div>
46 46 {% autoescape off %}
47 47 {% if truncated %}
48 48 {{ post.text.rendered|truncatewords_html:50 }}
49 49 {% else %}
50 50 {{ post.text.rendered }}
51 51 {% endif %}
52 52 {% endautoescape %}
53 53 {% if post.is_referenced %}
54 54 <div class="refmap">
55 55 {% trans "Replies" %}:
56 56 {% for ref_post in post.get_sorted_referenced_posts %}
57 57 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
58 58 >{% if not forloop.last %},{% endif %}
59 59 {% endfor %}
60 60 </div>
61 61 {% endif %}
62 62 </div>
63 63 {% if post.is_opening and post.thread_new.tags.exists %}
64 64 <div class="metadata">
65 65 <span class="tags">
66 66 {% for tag in post.thread_new.get_tags %}
67 67 <a class="tag" href="{% url 'tag' tag.name %}">
68 68 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
69 69 {% endfor %}
70 70 </span>
71 71 </div>
72 72 {% endif %}
73 73 </div>
74 74 {% endspaceless %}
@@ -1,145 +1,145 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load cache %}
5 5 {% load static from staticfiles %}
6 6 {% load board %}
7 7
8 8 {% block head %}
9 9 <title>{{ thread.get_opening_post.get_title|striptags|truncatewords:10 }} - Neboard</title>
10 10 {% endblock %}
11 11
12 12 {% block content %}
13 13 {% spaceless %}
14 14 {% get_current_language as LANGUAGE_CODE %}
15 15
16 16 {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
17 17
18 18 <div class="image-mode-tab">
19 19 <a class="current_mode" href="{% url 'thread' thread.get_opening_post.id %}">{% trans 'Normal mode' %}</a>,
20 20 <a href="{% url 'thread_mode' thread.get_opening_post.id 'gallery' %}">{% trans 'Gallery mode' %}</a>
21 21 </div>
22 22
23 23 {% if bumpable %}
24 24 <div class="bar-bg">
25 25 <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress">
26 26 </div>
27 27 <div class="bar-text">
28 28 <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %}
29 29 </div>
30 30 </div>
31 31 {% endif %}
32 32 <div class="thread">
33 33 {% for post in posts %}
34 34 {% if bumpable %}
35 35 <div class="post" id="{{ post.id }}">
36 36 {% elif thread.archived %}
37 37 <div class="post archive_post" id="{{ post.id }}">
38 38 {% else %}
39 39 <div class="post dead_post" id="{{ post.id }}">
40 40 {% endif %}
41 41 {% if post.image %}
42 42 <div class="image">
43 43 <a
44 44 class="thumb"
45 45 href="{{ post.image.url }}"><img
46 46 src="{{ post.image.url_200x150 }}"
47 47 alt="{{ post.id }}"
48 48 width="{{ post.image_pre_width }}"
49 49 height="{{ post.image_pre_height }}"
50 50 data-width="{{ post.image_width }}"
51 51 data-height="{{ post.image_height }}"/>
52 52 </a>
53 53 </div>
54 54 {% endif %}
55 55 <div class="message">
56 56 <div class="post-info">
57 57 <span class="title">{{ post.title }}</span>
58 58 <a class="post_id" href="#{{ post.id }}">
59 ({{ post.id }})</a>
60 [{{ post.pub_time }}]
59 ({{ post.id }}) </a>
60 [<span class="pub_time">{{ post.pub_time }}</span>]
61 61 {% if not thread.archived %}
62 62 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
63 63 ; return false;">&gt;&gt;</a>]
64 64 {% endif %}
65 65
66 66 {% if moderator %}
67 67 <span class="moderator_info">
68 68 [<a href="{% url 'delete' post_id=post.id %}"
69 69 >{% trans 'Delete' %}</a>]
70 70 ({{ post.poster_ip }})
71 71 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
72 72 >{% trans 'Ban IP' %}</a>]
73 73 </span>
74 74 {% endif %}
75 75 </div>
76 76 {% autoescape off %}
77 77 {{ post.text.rendered }}
78 78 {% endautoescape %}
79 79 {% if post.is_referenced %}
80 80 <div class="refmap">
81 81 {% trans "Replies" %}:
82 82 {% for ref_post in post.get_sorted_referenced_posts %}
83 83 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
84 84 >{% if not forloop.last %},{% endif %}
85 85 {% endfor %}
86 86 </div>
87 87 {% endif %}
88 88 </div>
89 89 {% if forloop.first %}
90 90 <div class="metadata">
91 91 <span class="tags">
92 92 {% for tag in thread.get_tags %}
93 93 <a class="tag" href="{% url 'tag' tag.name %}">
94 94 #{{ tag.name }}</a
95 95 >{% if not forloop.last %},{% endif %}
96 96 {% endfor %}
97 97 </span>
98 98 </div>
99 99 {% endif %}
100 100 </div>
101 101 {% endfor %}
102 102 </div>
103 103 {% endcache %}
104 104
105 105 {% if not thread.archived %}
106 106
107 107 <div class="post-form-w">
108 108 <script src="{% static 'js/panel.js' %}"></script>
109 109 <div class="form-title">{% trans "Reply to thread" %} #{{ thread.get_opening_post.id }}</div>
110 110 <div class="post-form">
111 111 <form id="form" enctype="multipart/form-data" method="post"
112 112 >{% csrf_token %}
113 113 {{ form.as_div }}
114 114 <div class="form-submit">
115 115 <input type="submit" value="{% trans "Post" %}"/>
116 116 </div>
117 117 </form>
118 118 <div><a href="{% url "staticpage" name="help" %}">
119 119 {% trans 'Text syntax' %}</a></div>
120 120 </div>
121 121 </div>
122 122
123 123 <script src="{% static 'js/jquery.form.min.js' %}"></script>
124 124 <script src="{% static 'js/thread_update.js' %}"></script>
125 125 {% endif %}
126 126
127 127 <script src="{% static 'js/thread.js' %}"></script>
128 128
129 129 {% endspaceless %}
130 130 {% endblock %}
131 131
132 132 {% block metapanel %}
133 133
134 134 {% get_current_language as LANGUAGE_CODE %}
135 135
136 136 <span class="metapanel" data-last-update="{{ last_update }}">
137 137 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
138 138 <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %},
139 139 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
140 {% trans 'Last update: ' %}{{ thread.last_edit_time }}
140 {% trans 'Last update: ' %}<span id="last-update">{{ thread.last_edit_time }}</span>
141 141 [<a href="rss/">RSS</a>]
142 142 {% endcache %}
143 143 </span>
144 144
145 145 {% endblock %}
@@ -1,48 +1,27 b''
1 1 = Features =
2 [DONE] Connecting tags to each other
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
5 [DONE] Regen script to update all posts
6 [DONE] Remove jump links from refmaps
7 [DONE] Ban reasons. Split bans into 2 types "read-only" and "read
8 denied". Use second only for autoban for spam
9 [DONE] Clean up tests and make them run ALWAYS
10 [DONE] Use transactions in tests
11 [DONE] Thread autoupdate (JS + API)
12 [DONE] Split up post model into post and thread,
13 and move everything that is used only in 1st post to thread model.
14 [DONE] Show board speed in the lower panel (posts per day)
15 [DONE] Save image thumbnails size to the separate field
16
17 2 [NOT STARTED] Tree view (JS)
18 3 [NOT STARTED] Adding tags to images filename
19 4 [NOT STARTED] Federative network for s2s communication
20 5 [NOT STARTED] XMPP gate
21 6 [NOT STARTED] Bitmessage gate
22 7 [NOT STARTED] Notification engine
23 8 [NOT STARTED] Javascript disabling engine
24 9 [NOT STARTED] Group tags by first letter in all tags list
25 10 [NOT STARTED] Character counter in the post field
26 11 [NOT STARTED] Whitelist functionality. Permin autoban of an address
27 12 [NOT STARTED] Statistics module. Count views (optional, may result in bad
28 13 performance), posts per day/week/month, users (or IPs)
29 14 [NOT STARTED] Quote button next to "reply" for posts in thread to include full
30 15 post or its part (delimited by N characters) into quote of the new post.
31 16 [NOT STARTED] Ban confirmation page with reason
32 17 [NOT STARTED] Post deletion confirmation page
33 18 [NOT STARTED] Moderating page. Tags editing and adding
34 19 [NOT STARTED] Get thread graph image using pygraphviz
35 [NOT STARTED] Creating post via AJAX without reloading page
36 20 [NOT STARTED] Subscribing to tag via AJAX
37 21
38 22 = Bugs =
39 [DONE] Fix bug with creating threads from tag view
40 [DONE] Quote characters within quote causes quote parsing to fail
41
42 [IN PROGRESS] Replies, images, last update time in bottom panel doesn't change when
43 thread updates (last update changing left)
44 23
45 24 = Testing =
46 25 [NOT STARTED] Make tests for every view
47 26 [NOT STARTED] Make tests for every model
48 27 [NOT STARTED] Make tests for every form
General Comments 0
You need to be logged in to leave comments. Login now