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