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