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