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