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