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