##// END OF EJS Templates
Increased posting AJAX timeout to prevent server errors when downloading a...
neko259 -
r1410:451d5821 default
parent child Browse files
Show More
@@ -1,457 +1,461 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 // TODO These need to be syncronized with board settings.
31 32 var JS_AUTOUPDATE_PERIOD = 20000;
33 // TODO This needs to be the same for attachment download time limit.
34 var POST_AJAX_TIMEOUT = 30000;
32 35
33 36 var ALLOWED_FOR_PARTIAL_UPDATE = [
34 37 'refmap',
35 38 'post-info'
36 39 ];
37 40
38 41 var ATTR_CLASS = 'class';
39 42 var ATTR_UID = 'data-uid';
40 43
41 44 var wsUser = '';
42 45
43 46 var unreadPosts = 0;
44 47 var documentOriginalTitle = '';
45 48
46 49 // Thread ID does not change, can be stored one time
47 50 var threadId = $('div.thread').children(CLASS_POST).first().attr('id');
48 51
49 52 /**
50 53 * Connect to websocket server and subscribe to thread updates. On any update we
51 54 * request a thread diff.
52 55 *
53 56 * @returns {boolean} true if connected, false otherwise
54 57 */
55 58 function connectWebsocket() {
56 59 var metapanel = $('.metapanel')[0];
57 60
58 61 var wsHost = metapanel.getAttribute('data-ws-host');
59 62 var wsPort = metapanel.getAttribute('data-ws-port');
60 63
61 64 if (wsHost.length > 0 && wsPort.length > 0) {
62 65 var centrifuge = new Centrifuge({
63 66 "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket",
64 67 "project": metapanel.getAttribute('data-ws-project'),
65 68 "user": wsUser,
66 69 "timestamp": metapanel.getAttribute('data-ws-token-time'),
67 70 "token": metapanel.getAttribute('data-ws-token'),
68 71 "debug": false
69 72 });
70 73
71 74 centrifuge.on('error', function(error_message) {
72 75 console.log("Error connecting to websocket server.");
73 76 console.log(error_message);
74 77 console.log("Using javascript update instead.");
75 78
76 79 // If websockets don't work, enable JS update instead
77 80 enableJsUpdate()
78 81 });
79 82
80 83 centrifuge.on('connect', function() {
81 84 var channelName = 'thread:' + threadId;
82 85 centrifuge.subscribe(channelName, function(message) {
83 86 getThreadDiff();
84 87 });
85 88
86 89 // For the case we closed the browser and missed some updates
87 90 getThreadDiff();
88 91 $('#autoupdate').hide();
89 92 });
90 93
91 94 centrifuge.connect();
92 95
93 96 return true;
94 97 } else {
95 98 return false;
96 99 }
97 100 }
98 101
99 102 /**
100 103 * Get diff of the posts from the current thread timestamp.
101 104 * This is required if the browser was closed and some post updates were
102 105 * missed.
103 106 */
104 107 function getThreadDiff() {
105 108 var all_posts = $('.post');
106 109
107 110 var uids = '';
108 111 var posts = all_posts;
109 112 for (var i = 0; i < posts.length; i++) {
110 113 uids += posts[i].getAttribute('data-uid') + ' ';
111 114 }
112 115
113 116 var data = {
114 117 uids: uids,
115 118 thread: threadId
116 119 };
117 120
118 121 var diffUrl = '/api/diff_thread/';
119 122
120 123 $.post(diffUrl,
121 124 data,
122 125 function(data) {
123 126 var updatedPosts = data.updated;
124 127 var addedPostCount = 0;
125 128
126 129 for (var i = 0; i < updatedPosts.length; i++) {
127 130 var postText = updatedPosts[i];
128 131 var post = $(postText);
129 132
130 133 if (updatePost(post) == POST_ADDED) {
131 134 addedPostCount++;
132 135 }
133 136 }
134 137
135 138 var hasMetaUpdates = updatedPosts.length > 0;
136 139 if (hasMetaUpdates) {
137 140 updateMetadataPanel();
138 141 }
139 142
140 143 if (addedPostCount > 0) {
141 144 updateBumplimitProgress(addedPostCount);
142 145 }
143 146
144 147 if (updatedPosts.length > 0) {
145 148 showNewPostsTitle(addedPostCount);
146 149 }
147 150
148 151 // TODO Process removed posts if any
149 152 $('.metapanel').attr('data-last-update', data.last_update);
150 153 },
151 154 'json'
152 155 )
153 156 }
154 157
155 158 /**
156 159 * Add or update the post on html page.
157 160 */
158 161 function updatePost(postHtml) {
159 162 // This needs to be set on start because the page is scrolled after posts
160 163 // are added or updated
161 164 var bottom = isPageBottom();
162 165
163 166 var post = $(postHtml);
164 167
165 168 var threadBlock = $('div.thread');
166 169
167 170 var postId = post.attr('id');
168 171
169 172 // If the post already exists, replace it. Otherwise add as a new one.
170 173 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
171 174
172 175 var type;
173 176
174 177 if (existingPosts.size() > 0) {
175 178 replacePartial(existingPosts.first(), post, false);
176 179 post = existingPosts.first();
177 180
178 181 type = POST_UPDATED;
179 182 } else {
180 183 post.appendTo(threadBlock);
181 184
182 185 if (bottom) {
183 186 scrollToBottom();
184 187 }
185 188
186 189 type = POST_ADDED;
187 190 }
188 191
189 192 processNewPost(post);
190 193
191 194 return type;
192 195 }
193 196
194 197 /**
195 198 * Initiate a blinking animation on a node to show it was updated.
196 199 */
197 200 function blink(node) {
198 201 var blinkCount = 2;
199 202
200 203 var nodeToAnimate = node;
201 204 for (var i = 0; i < blinkCount; i++) {
202 205 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
203 206 }
204 207 }
205 208
206 209 function isPageBottom() {
207 210 var scroll = $(window).scrollTop() / ($(document).height()
208 211 - $(window).height());
209 212
210 213 return scroll == 1
211 214 }
212 215
213 216 function enableJsUpdate() {
214 217 setInterval(getThreadDiff, JS_AUTOUPDATE_PERIOD);
215 218 return true;
216 219 }
217 220
218 221 function initAutoupdate() {
219 222 if (location.protocol === 'https:') {
220 223 return enableJsUpdate();
221 224 } else {
222 225 if (connectWebsocket()) {
223 226 return true;
224 227 } else {
225 228 return enableJsUpdate();
226 229 }
227 230 }
228 231 }
229 232
230 233 function getReplyCount() {
231 234 return $('.thread').children(CLASS_POST).length
232 235 }
233 236
234 237 function getImageCount() {
235 238 return $('.thread').find('img').length
236 239 }
237 240
238 241 /**
239 242 * Update post count, images count and last update time in the metadata
240 243 * panel.
241 244 */
242 245 function updateMetadataPanel() {
243 246 var replyCountField = $('#reply-count');
244 247 var imageCountField = $('#image-count');
245 248
246 249 var replyCount = getReplyCount();
247 250 replyCountField.text(replyCount);
248 251 var imageCount = getImageCount();
249 252 imageCountField.text(imageCount);
250 253
251 254 var lastUpdate = $('.post:last').children('.post-info').first()
252 255 .children('.pub_time').first().html();
253 256 if (lastUpdate !== '') {
254 257 var lastUpdateField = $('#last-update');
255 258 lastUpdateField.html(lastUpdate);
256 259 blink(lastUpdateField);
257 260 }
258 261
259 262 blink(replyCountField);
260 263 blink(imageCountField);
261 264
262 265 $('#message-count-text').text(ngettext('message', 'messages', replyCount));
263 266 $('#image-count-text').text(ngettext('image', 'images', imageCount));
264 267 }
265 268
266 269 /**
267 270 * Update bumplimit progress bar
268 271 */
269 272 function updateBumplimitProgress(postDelta) {
270 273 var progressBar = $('#bumplimit_progress');
271 274 if (progressBar) {
272 275 var postsToLimitElement = $('#left_to_limit');
273 276
274 277 var oldPostsToLimit = parseInt(postsToLimitElement.text());
275 278 var postCount = getReplyCount();
276 279 var bumplimit = postCount - postDelta + oldPostsToLimit;
277 280
278 281 var newPostsToLimit = bumplimit - postCount;
279 282 if (newPostsToLimit <= 0) {
280 283 $('.bar-bg').remove();
281 284 } else {
282 285 postsToLimitElement.text(newPostsToLimit);
283 286 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
284 287 }
285 288 }
286 289 }
287 290
288 291 /**
289 292 * Show 'new posts' text in the title if the document is not visible to a user
290 293 */
291 294 function showNewPostsTitle(newPostCount) {
292 295 if (document.hidden) {
293 296 if (documentOriginalTitle === '') {
294 297 documentOriginalTitle = document.title;
295 298 }
296 299 unreadPosts = unreadPosts + newPostCount;
297 300
298 301 var newTitle = '* ';
299 302 if (unreadPosts > 0) {
300 303 newTitle += '[' + unreadPosts + '] ';
301 304 }
302 305 newTitle += documentOriginalTitle;
303 306
304 307 document.title = newTitle;
305 308
306 309 document.addEventListener('visibilitychange', function() {
307 310 if (documentOriginalTitle !== '') {
308 311 document.title = documentOriginalTitle;
309 312 documentOriginalTitle = '';
310 313 unreadPosts = 0;
311 314 }
312 315
313 316 document.removeEventListener('visibilitychange', null);
314 317 });
315 318 }
316 319 }
317 320
318 321 /**
319 322 * Clear all entered values in the form fields
320 323 */
321 324 function resetForm(form) {
322 325 form.find('input:text, input:password, input:file, select, textarea').val('');
323 326 form.find('input:radio, input:checkbox')
324 327 .removeAttr('checked').removeAttr('selected');
325 328 $('.file_wrap').find('.file-thumb').remove();
326 329 $('#preview-text').hide();
327 330 }
328 331
329 332 /**
330 333 * When the form is posted, this method will be run as a callback
331 334 */
332 335 function updateOnPost(response, statusText, xhr, form) {
333 336 var json = $.parseJSON(response);
334 337 var status = json.status;
335 338
336 339 showAsErrors(form, '');
337 340
338 341 if (status === 'ok') {
339 342 resetFormPosition();
340 343 resetForm(form);
341 344 getThreadDiff();
342 345 scrollToBottom();
343 346 } else {
344 347 var errors = json.errors;
345 348 for (var i = 0; i < errors.length; i++) {
346 349 var fieldErrors = errors[i];
347 350
348 351 var error = fieldErrors.errors;
349 352
350 353 showAsErrors(form, error);
351 354 }
352 355 }
353 356 }
354 357
355 358 /**
356 359 * Show text in the errors row of the form.
357 360 * @param form
358 361 * @param text
359 362 */
360 363 function showAsErrors(form, text) {
361 364 form.children('.form-errors').remove();
362 365
363 366 if (text.length > 0) {
364 367 var errorList = $('<div class="form-errors">' + text + '<div>');
365 368 errorList.appendTo(form);
366 369 }
367 370 }
368 371
369 372 /**
370 373 * Run js methods that are usually run on the document, on the new post
371 374 */
372 375 function processNewPost(post) {
373 376 addRefLinkPreview(post[0]);
374 377 highlightCode(post);
375 378 blink(post);
376 379 }
377 380
378 381 function replacePartial(oldNode, newNode, recursive) {
379 382 if (!equalNodes(oldNode, newNode)) {
380 383 // Update parent node attributes
381 384 updateNodeAttr(oldNode, newNode, ATTR_CLASS);
382 385 updateNodeAttr(oldNode, newNode, ATTR_UID);
383 386
384 387 // Replace children
385 388 var children = oldNode.children();
386 389 if (children.length == 0) {
387 390 oldNode.replaceWith(newNode);
388 391 } else {
389 392 var newChildren = newNode.children();
390 393 newChildren.each(function(i) {
391 394 var newChild = newChildren.eq(i);
392 395 var newChildClass = newChild.attr(ATTR_CLASS);
393 396
394 397 // Update only certain allowed blocks (e.g. not images)
395 398 if (ALLOWED_FOR_PARTIAL_UPDATE.indexOf(newChildClass) > -1) {
396 399 var oldChild = oldNode.children('.' + newChildClass);
397 400
398 401 if (oldChild.length == 0) {
399 402 oldNode.append(newChild);
400 403 } else {
401 404 if (!equalNodes(oldChild, newChild)) {
402 405 if (recursive) {
403 406 replacePartial(oldChild, newChild, false);
404 407 } else {
405 408 oldChild.replaceWith(newChild);
406 409 }
407 410 }
408 411 }
409 412 }
410 413 });
411 414 }
412 415 }
413 416 }
414 417
415 418 /**
416 419 * Compare nodes by content
417 420 */
418 421 function equalNodes(node1, node2) {
419 422 return node1[0].outerHTML == node2[0].outerHTML;
420 423 }
421 424
422 425 /**
423 426 * Update attribute of a node if it has changed
424 427 */
425 428 function updateNodeAttr(oldNode, newNode, attrName) {
426 429 var oldAttr = oldNode.attr(attrName);
427 430 var newAttr = newNode.attr(attrName);
428 431 if (oldAttr != newAttr) {
429 432 oldNode.attr(attrName, newAttr);
430 433 }
431 434 }
432 435
433 436 $(document).ready(function(){
434 437 if (initAutoupdate()) {
435 438 // Post form data over AJAX
436 439 var threadId = $('div.thread').children('.post').first().attr('id');
437 440
438 441 var form = $('#form');
439 442
440 443 if (form.length > 0) {
441 444 var options = {
442 445 beforeSubmit: function(arr, $form, options) {
443 446 showAsErrors($('#form'), gettext('Sending message...'));
444 447 },
445 448 success: updateOnPost,
446 449 error: function() {
447 450 showAsErrors($('#form'), gettext('Server error!'));
448 451 },
449 url: '/api/add_post/' + threadId + '/'
452 url: '/api/add_post/' + threadId + '/',
453 timeout: POST_AJAX_TIMEOUT
450 454 };
451 455
452 456 form.ajaxForm(options);
453 457
454 458 resetForm(form);
455 459 }
456 460 }
457 461 });
General Comments 0
You need to be logged in to leave comments. Login now