##// END OF EJS Templates
Don't show 'replies' and 'images' texts in the thread
neko259 -
r1770:038b4c85 default
parent child Browse files
Show More
@@ -1,402 +1,399 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-2014 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 CLASS_POST = '.post';
27 27
28 28 var POST_ADDED = 0;
29 29 var POST_UPDATED = 1;
30 30
31 31 // TODO These need to be syncronized with board settings.
32 32 var JS_AUTOUPDATE_PERIOD = 20000;
33 33 // TODO This needs to be the same for attachment download time limit.
34 34 var POST_AJAX_TIMEOUT = 30000;
35 35 var BLINK_SPEED = 500;
36 36
37 37 var ALLOWED_FOR_PARTIAL_UPDATE = [
38 38 'refmap',
39 39 'post-info'
40 40 ];
41 41
42 42 var ATTR_CLASS = 'class';
43 43 var ATTR_UID = 'data-uid';
44 44
45 45 var unreadPosts = 0;
46 46 var documentOriginalTitle = '';
47 47
48 48 // Thread ID does not change, can be stored one time
49 49 var threadId = $('div.thread').children(CLASS_POST).first().attr('id');
50 50 var blinkColor = $('<div class="post-blink"></div>').css('background-color');
51 51
52 52 /**
53 53 * Get diff of the posts from the current thread timestamp.
54 54 * This is required if the browser was closed and some post updates were
55 55 * missed.
56 56 */
57 57 function getThreadDiff() {
58 58 var all_posts = $('.post');
59 59
60 60 var uids = '';
61 61 var posts = all_posts;
62 62 for (var i = 0; i < posts.length; i++) {
63 63 uids += posts[i].getAttribute('data-uid') + ' ';
64 64 }
65 65
66 66 var data = {
67 67 uids: uids,
68 68 thread: threadId
69 69 };
70 70
71 71 var diffUrl = '/api/diff_thread/';
72 72
73 73 $.post(diffUrl,
74 74 data,
75 75 function(data) {
76 76 var updatedPosts = data.updated;
77 77 var addedPostCount = 0;
78 78
79 79 for (var i = 0; i < updatedPosts.length; i++) {
80 80 var postText = updatedPosts[i];
81 81 var post = $(postText);
82 82
83 83 if (updatePost(post) == POST_ADDED) {
84 84 addedPostCount++;
85 85 }
86 86 }
87 87
88 88 var hasMetaUpdates = updatedPosts.length > 0;
89 89 if (hasMetaUpdates) {
90 90 updateMetadataPanel();
91 91 }
92 92
93 93 if (addedPostCount > 0) {
94 94 updateBumplimitProgress(addedPostCount);
95 95 }
96 96
97 97 if (updatedPosts.length > 0) {
98 98 showNewPostsTitle(addedPostCount);
99 99 }
100 100
101 101 // TODO Process removed posts if any
102 102 $('.metapanel').attr('data-last-update', data.last_update);
103 103
104 104 if (data.subscribed == 'True') {
105 105 var favButton = $('#thread-fav-button .not_fav');
106 106
107 107 if (favButton.length > 0) {
108 108 favButton.attr('value', 'unsubscribe');
109 109 favButton.removeClass('not_fav');
110 110 favButton.addClass('fav');
111 111 }
112 112 }
113 113 },
114 114 'json'
115 115 )
116 116 }
117 117
118 118 /**
119 119 * Add or update the post on html page.
120 120 */
121 121 function updatePost(postHtml) {
122 122 // This needs to be set on start because the page is scrolled after posts
123 123 // are added or updated
124 124 var bottom = isPageBottom();
125 125
126 126 var post = $(postHtml);
127 127
128 128 var threadBlock = $('div.thread');
129 129
130 130 var postId = post.attr('id');
131 131
132 132 // If the post already exists, replace it. Otherwise add as a new one.
133 133 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
134 134
135 135 var type;
136 136
137 137 if (existingPosts.size() > 0) {
138 138 replacePartial(existingPosts.first(), post, false);
139 139 post = existingPosts.first();
140 140
141 141 type = POST_UPDATED;
142 142 } else {
143 143 post.appendTo(threadBlock);
144 144
145 145 if (bottom) {
146 146 scrollToBottom();
147 147 }
148 148
149 149 type = POST_ADDED;
150 150 }
151 151
152 152 processNewPost(post);
153 153
154 154 return type;
155 155 }
156 156
157 157 /**
158 158 * Initiate a blinking animation on a node to show it was updated.
159 159 */
160 160 function blink(node) {
161 161 node.effect('highlight', { color: blinkColor }, BLINK_SPEED);
162 162 }
163 163
164 164 function isPageBottom() {
165 165 var scroll = $(window).scrollTop() / ($(document).height()
166 166 - $(window).height());
167 167
168 168 return scroll == 1
169 169 }
170 170
171 171 function enableJsUpdate() {
172 172 setInterval(getThreadDiff, JS_AUTOUPDATE_PERIOD);
173 173 return true;
174 174 }
175 175
176 176 function initAutoupdate() {
177 177 return enableJsUpdate();
178 178 }
179 179
180 180 function getReplyCount() {
181 181 return $('.thread').children(CLASS_POST).length
182 182 }
183 183
184 184 function getImageCount() {
185 185 return $('.thread').find('img').length
186 186 }
187 187
188 188 /**
189 189 * Update post count, images count and last update time in the metadata
190 190 * panel.
191 191 */
192 192 function updateMetadataPanel() {
193 193 var replyCountField = $('#reply-count');
194 194 var imageCountField = $('#image-count');
195 195
196 196 var replyCount = getReplyCount();
197 197 replyCountField.text(replyCount);
198 198 var imageCount = getImageCount();
199 199 imageCountField.text(imageCount);
200 200
201 201 var lastUpdate = $('.post:last').children('.post-info').first()
202 202 .children('.pub_time').first().html();
203 203 if (lastUpdate !== '') {
204 204 var lastUpdateField = $('#last-update');
205 205 lastUpdateField.html(lastUpdate);
206 206 blink(lastUpdateField);
207 207 }
208 208
209 209 blink(replyCountField);
210 210 blink(imageCountField);
211
212 $('#message-count-text').text(ngettext('message', 'messages', replyCount));
213 $('#image-count-text').text(ngettext('image', 'images', imageCount));
214 211 }
215 212
216 213 /**
217 214 * Update bumplimit progress bar
218 215 */
219 216 function updateBumplimitProgress(postDelta) {
220 217 var progressBar = $('#bumplimit_progress');
221 218 if (progressBar) {
222 219 var postsToLimitElement = $('#left_to_limit');
223 220
224 221 var oldPostsToLimit = parseInt(postsToLimitElement.text());
225 222 var postCount = getReplyCount();
226 223 var bumplimit = postCount - postDelta + oldPostsToLimit;
227 224
228 225 var newPostsToLimit = bumplimit - postCount;
229 226 if (newPostsToLimit <= 0) {
230 227 $('.bar-bg').remove();
231 228 } else {
232 229 postsToLimitElement.text(newPostsToLimit);
233 230 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
234 231 }
235 232 }
236 233 }
237 234
238 235 /**
239 236 * Show 'new posts' text in the title if the document is not visible to a user
240 237 */
241 238 function showNewPostsTitle(newPostCount) {
242 239 if (document.hidden) {
243 240 if (documentOriginalTitle === '') {
244 241 documentOriginalTitle = document.title;
245 242 }
246 243 unreadPosts = unreadPosts + newPostCount;
247 244
248 245 var newTitle = null;
249 246 if (unreadPosts > 0) {
250 247 newTitle = '[' + unreadPosts + '] ';
251 248 } else {
252 249 newTitle = '* ';
253 250 }
254 251 newTitle += documentOriginalTitle;
255 252
256 253 document.title = newTitle;
257 254
258 255 document.addEventListener('visibilitychange', function() {
259 256 if (documentOriginalTitle !== '') {
260 257 document.title = documentOriginalTitle;
261 258 documentOriginalTitle = '';
262 259 unreadPosts = 0;
263 260 }
264 261
265 262 document.removeEventListener('visibilitychange', null);
266 263 });
267 264 }
268 265 }
269 266
270 267 /**
271 268 * Clear all entered values in the form fields
272 269 */
273 270 function resetForm(form) {
274 271 form.find('input:text, input:password, input:file, select, textarea').val('');
275 272 form.find('input:radio, input:checkbox')
276 273 .removeAttr('checked').removeAttr('selected');
277 274 $('.file_wrap').find('.file-thumb').remove();
278 275 $('#preview-text').hide();
279 276 }
280 277
281 278 /**
282 279 * When the form is posted, this method will be run as a callback
283 280 */
284 281 function updateOnPost(response, statusText, xhr, form) {
285 282 var json = $.parseJSON(response);
286 283 var status = json.status;
287 284
288 285 showAsErrors(form, '');
289 286 $('.post-form-w').unblock();
290 287
291 288 if (status === 'ok') {
292 289 resetFormPosition();
293 290 resetForm(form);
294 291 getThreadDiff();
295 292 scrollToBottom();
296 293 } else {
297 294 var errors = json.errors;
298 295 for (var i = 0; i < errors.length; i++) {
299 296 var fieldErrors = errors[i];
300 297
301 298 var error = fieldErrors.errors;
302 299
303 300 showAsErrors(form, error);
304 301 }
305 302 }
306 303 }
307 304
308 305
309 306 /**
310 307 * Run js methods that are usually run on the document, on the new post
311 308 */
312 309 function processNewPost(post) {
313 310 addScriptsToPost(post);
314 311 blink(post);
315 312 }
316 313
317 314 function replacePartial(oldNode, newNode, recursive) {
318 315 if (!equalNodes(oldNode, newNode)) {
319 316 // Update parent node attributes
320 317 updateNodeAttr(oldNode, newNode, ATTR_CLASS);
321 318 updateNodeAttr(oldNode, newNode, ATTR_UID);
322 319
323 320 // Replace children
324 321 var children = oldNode.children();
325 322 if (children.length == 0) {
326 323 oldNode.replaceWith(newNode);
327 324 } else {
328 325 var newChildren = newNode.children();
329 326 newChildren.each(function(i) {
330 327 var newChild = newChildren.eq(i);
331 328 var newChildClass = newChild.attr(ATTR_CLASS);
332 329
333 330 // Update only certain allowed blocks (e.g. not images)
334 331 if (ALLOWED_FOR_PARTIAL_UPDATE.indexOf(newChildClass) > -1) {
335 332 var oldChild = oldNode.children('.' + newChildClass);
336 333
337 334 if (oldChild.length == 0) {
338 335 oldNode.append(newChild);
339 336 } else {
340 337 if (!equalNodes(oldChild, newChild)) {
341 338 if (recursive) {
342 339 replacePartial(oldChild, newChild, false);
343 340 } else {
344 341 oldChild.replaceWith(newChild);
345 342 }
346 343 }
347 344 }
348 345 }
349 346 });
350 347 }
351 348 }
352 349 }
353 350
354 351 /**
355 352 * Compare nodes by content
356 353 */
357 354 function equalNodes(node1, node2) {
358 355 return node1[0].outerHTML == node2[0].outerHTML;
359 356 }
360 357
361 358 /**
362 359 * Update attribute of a node if it has changed
363 360 */
364 361 function updateNodeAttr(oldNode, newNode, attrName) {
365 362 var oldAttr = oldNode.attr(attrName);
366 363 var newAttr = newNode.attr(attrName);
367 364 if (oldAttr != newAttr) {
368 365 oldNode.attr(attrName, newAttr);
369 366 }
370 367 }
371 368
372 369 $(document).ready(function() {
373 370 if (initAutoupdate()) {
374 371 // Post form data over AJAX
375 372 var threadId = $('div.thread').children('.post').first().attr('id');
376 373
377 374 var form = $('#form');
378 375
379 376 if (form.length > 0) {
380 377 var options = {
381 378 beforeSubmit: function(arr, form, options) {
382 379 $('.post-form-w').block({ message: gettext('Sending message...') });
383 380 },
384 381 success: updateOnPost,
385 382 error: function(xhr, textStatus, errorString) {
386 383 var errorText = gettext('Server error: ') + textStatus;
387 384 if (errorString) {
388 385 errorText += ' / ' + errorString;
389 386 }
390 387 showAsErrors(form, errorText);
391 388 $('.post-form-w').unblock();
392 389 },
393 390 url: '/api/add_post/' + threadId + '/',
394 391 timeout: POST_AJAX_TIMEOUT
395 392 };
396 393
397 394 form.ajaxForm(options);
398 395
399 396 resetForm(form);
400 397 }
401 398 }
402 399 });
@@ -1,38 +1,38 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load static from staticfiles %}
5 5 {% load board %}
6 6 {% load tz %}
7 7
8 8 {% block head %}
9 9 <title>{{ opening_post.get_title_or_text }} - {{ site_name }}</title>
10 10 {% endblock %}
11 11
12 12 {% block content %}
13 13 <div class="image-mode-tab">
14 14 <a {% ifequal mode 'normal' %}class="current_mode"{% endifequal %} href="{% url 'thread' opening_post.id %}">{% trans 'Normal' %}</a>,
15 15 <a {% ifequal mode 'gallery' %}class="current_mode"{% endifequal %} href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery' %}</a>,
16 16 <a {% ifequal mode 'tree' %}class="current_mode"{% endifequal %} href="{% url 'thread_tree' opening_post.id %}">{% trans 'Tree' %}</a>
17 17 </div>
18 18
19 19 {% block thread_content %}
20 20 {% endblock %}
21 21 {% endblock %}
22 22
23 23 {% block metapanel %}
24 24
25 25 <span class="metapanel"
26 26 data-last-update="{{ last_update }}"
27 27 data-ws-token-time="{{ ws_token_time }}">
28 28
29 29 {% with replies_count=thread.get_reply_count%}
30 30 <span id="reply-count">{{ thread.get_reply_count }}</span>{% if thread.has_post_limit %}/{{ thread.max_posts }}{% endif %}
31 31 {% endwith %}
32 32 {% with images_count=thread.get_images_count%}
33 <span id="image-count">{{ images_count }}</span> <span id="image-count-text">
33 <span id="image-count">{{ images_count }}</span>
34 34 {% endwith %}
35 35 {% trans 'Last update: ' %}<span id="last-update"><time datetime="{{ thread.last_edit_time|date:'c' }}">{{ thread.last_edit_time }}</time></span>
36 36 </span>
37 37
38 38 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now