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