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