##// END OF EJS Templates
Blink with fade out
neko259 -
r2134:d8cfb857 default
parent child Browse files
Show More
@@ -1,358 +1,357 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 // TODO These need to be syncronized with board settings.
31 // TODO These need to be syncronized with board settings.
32 var JS_AUTOUPDATE_PERIOD = 20000;
32 var JS_AUTOUPDATE_PERIOD = 20000;
33 var BLINK_SPEED = 500;
33 var BLINK_SPEED = 500;
34
34
35 var ALLOWED_FOR_PARTIAL_UPDATE = [
35 var ALLOWED_FOR_PARTIAL_UPDATE = [
36 'refmap',
36 'refmap',
37 'post-info'
37 'post-info'
38 ];
38 ];
39
39
40 var ATTR_CLASS = 'class';
40 var ATTR_CLASS = 'class';
41 var ATTR_UID = 'data-uid';
41 var ATTR_UID = 'data-uid';
42
42
43 var unreadPosts = 0;
43 var unreadPosts = 0;
44 var documentOriginalTitle = '';
44 var documentOriginalTitle = '';
45
45
46 // Thread ID does not change, can be stored one time
46 // Thread ID does not change, can be stored one time
47 var threadId = $('div.thread').children(CLASS_POST).first().attr('id');
47 var threadId = $('div.thread').children(CLASS_POST).first().attr('id');
48 var blinkColor = $('<div class="post-blink"></div>').css('background-color');
49
48
50 /**
49 /**
51 * Get diff of the posts from the current thread timestamp.
50 * Get diff of the posts from the current thread timestamp.
52 * This is required if the browser was closed and some post updates were
51 * This is required if the browser was closed and some post updates were
53 * missed.
52 * missed.
54 */
53 */
55 function getThreadDiff() {
54 function getThreadDiff() {
56 var all_posts = $('.post');
55 var all_posts = $('.post');
57
56
58 var uids = '';
57 var uids = '';
59 var posts = all_posts;
58 var posts = all_posts;
60 for (var i = 0; i < posts.length; i++) {
59 for (var i = 0; i < posts.length; i++) {
61 uids += posts[i].getAttribute('data-uid') + ' ';
60 uids += posts[i].getAttribute('data-uid') + ' ';
62 }
61 }
63
62
64 var data = {
63 var data = {
65 uids: uids,
64 uids: uids,
66 thread: threadId
65 thread: threadId
67 };
66 };
68
67
69 var diffUrl = '/api/diff_thread/';
68 var diffUrl = '/api/diff_thread/';
70
69
71 $.post(diffUrl,
70 $.post(diffUrl,
72 data,
71 data,
73 function(data) {
72 function(data) {
74 var updatedPosts = data.updated;
73 var updatedPosts = data.updated;
75 var addedPostCount = 0;
74 var addedPostCount = 0;
76
75
77 for (var i = 0; i < updatedPosts.length; i++) {
76 for (var i = 0; i < updatedPosts.length; i++) {
78 var postText = updatedPosts[i];
77 var postText = updatedPosts[i];
79 var post = $(postText);
78 var post = $(postText);
80
79
81 if (updatePost(post) == POST_ADDED) {
80 if (updatePost(post) == POST_ADDED) {
82 addedPostCount++;
81 addedPostCount++;
83 }
82 }
84 }
83 }
85
84
86 var hasMetaUpdates = updatedPosts.length > 0;
85 var hasMetaUpdates = updatedPosts.length > 0;
87 if (hasMetaUpdates) {
86 if (hasMetaUpdates) {
88 updateMetadataPanel();
87 updateMetadataPanel();
89 }
88 }
90
89
91 if (addedPostCount > 0) {
90 if (addedPostCount > 0) {
92 updateBumplimitProgress(addedPostCount);
91 updateBumplimitProgress(addedPostCount);
93 }
92 }
94
93
95 if (updatedPosts.length > 0) {
94 if (updatedPosts.length > 0) {
96 showNewPostsTitle(addedPostCount);
95 showNewPostsTitle(addedPostCount);
97 }
96 }
98
97
99 // TODO Process removed posts if any
98 // TODO Process removed posts if any
100 $('.metapanel').attr('data-last-update', data.last_update);
99 $('.metapanel').attr('data-last-update', data.last_update);
101
100
102 if (data.subscribed == 'True') {
101 if (data.subscribed == 'True') {
103 var favButton = $('#thread-fav-button .not_fav');
102 var favButton = $('#thread-fav-button .not_fav');
104
103
105 if (favButton.length > 0) {
104 if (favButton.length > 0) {
106 favButton.attr('value', 'unsubscribe');
105 favButton.attr('value', 'unsubscribe');
107 favButton.removeClass('not_fav');
106 favButton.removeClass('not_fav');
108 favButton.addClass('fav');
107 favButton.addClass('fav');
109 }
108 }
110 }
109 }
111 },
110 },
112 'json'
111 'json'
113 )
112 )
114 }
113 }
115
114
116 /**
115 /**
117 * Add or update the post on html page.
116 * Add or update the post on html page.
118 */
117 */
119 function updatePost(postHtml) {
118 function updatePost(postHtml) {
120 // 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
121 // are added or updated
120 // are added or updated
122 var bottom = isPageBottom();
121 var bottom = isPageBottom();
123
122
124 var post = $(postHtml);
123 var post = $(postHtml);
125
124
126 var threadBlock = $('div.thread');
125 var threadBlock = $('div.thread');
127
126
128 var postId = post.attr('id');
127 var postId = post.attr('id');
129
128
130 // If the post already exists, replace it. Otherwise add as a new one.
129 // If the post already exists, replace it. Otherwise add as a new one.
131 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
130 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
132
131
133 var type;
132 var type;
134
133
135 if (existingPosts.size() > 0) {
134 if (existingPosts.size() > 0) {
136 replacePartial(existingPosts.first(), post, false);
135 replacePartial(existingPosts.first(), post, false);
137 post = existingPosts.first();
136 post = existingPosts.first();
138
137
139 type = POST_UPDATED;
138 type = POST_UPDATED;
140 } else {
139 } else {
141 var postPubTime = new Date(post.find('time').attr('datetime'));
140 var postPubTime = new Date(post.find('time').attr('datetime'));
142 var allPosts = $('.post');
141 var allPosts = $('.post');
143 var previousPost = null;
142 var previousPost = null;
144
143
145 allPosts.each(function(i) {
144 allPosts.each(function(i) {
146 var curPost = $(this);
145 var curPost = $(this);
147 var curPostTime = new Date(curPost.find('time').attr('datetime'));
146 var curPostTime = new Date(curPost.find('time').attr('datetime'));
148
147
149 if (i + 0 && curPostTime > postPubTime) {
148 if (i + 0 && curPostTime > postPubTime) {
150 previousPost = allPosts.get(i - 1);
149 previousPost = allPosts.get(i - 1);
151 return false;
150 return false;
152 };
151 };
153 });
152 });
154
153
155 if (previousPost == null) {
154 if (previousPost == null) {
156 previousPost = allPosts.last();
155 previousPost = allPosts.last();
157 }
156 }
158
157
159 post.insertAfter(previousPost);
158 post.insertAfter(previousPost);
160
159
161 if (bottom) {
160 if (bottom) {
162 scrollToBottom();
161 scrollToBottom();
163 }
162 }
164
163
165 type = POST_ADDED;
164 type = POST_ADDED;
166 }
165 }
167
166
168 processNewPost(post);
167 processNewPost(post);
169
168
170 return type;
169 return type;
171 }
170 }
172
171
173 /**
172 /**
174 * 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.
175 */
174 */
176 function blink(node) {
175 function blink(node) {
177 node.effect('highlight', { color: blinkColor }, BLINK_SPEED);
176 node.fadeOut(BLINK_SPEED).fadeIn(BLINK_SPEED);
178 }
177 }
179
178
180 function isPageBottom() {
179 function isPageBottom() {
181 var scroll = $(window).scrollTop() / ($(document).height()
180 var scroll = $(window).scrollTop() / ($(document).height()
182 - $(window).height());
181 - $(window).height());
183
182
184 return scroll == 1
183 return scroll == 1
185 }
184 }
186
185
187 function enableJsUpdate() {
186 function enableJsUpdate() {
188 setInterval(getThreadDiff, JS_AUTOUPDATE_PERIOD);
187 setInterval(getThreadDiff, JS_AUTOUPDATE_PERIOD);
189 return true;
188 return true;
190 }
189 }
191
190
192 function initAutoupdate() {
191 function initAutoupdate() {
193 return enableJsUpdate();
192 return enableJsUpdate();
194 }
193 }
195
194
196 function getReplyCount() {
195 function getReplyCount() {
197 return $('.thread').children(CLASS_POST).length
196 return $('.thread').children(CLASS_POST).length
198 }
197 }
199
198
200 function getImageCount() {
199 function getImageCount() {
201 return $('.thread').find('.image').length
200 return $('.thread').find('.image').length
202 }
201 }
203
202
204 /**
203 /**
205 * 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
206 * panel.
205 * panel.
207 */
206 */
208 function updateMetadataPanel() {
207 function updateMetadataPanel() {
209 var replyCountField = $('#reply-count');
208 var replyCountField = $('#reply-count');
210 var imageCountField = $('#image-count');
209 var imageCountField = $('#image-count');
211
210
212 var replyCount = getReplyCount();
211 var replyCount = getReplyCount();
213 replyCountField.text(replyCount);
212 replyCountField.text(replyCount);
214 var imageCount = getImageCount();
213 var imageCount = getImageCount();
215 imageCountField.text(imageCount);
214 imageCountField.text(imageCount);
216
215
217 var lastUpdate = $('.post:last').children('.post-info').first()
216 var lastUpdate = $('.post:last').children('.post-info').first()
218 .children('.pub_time').first().html();
217 .children('.pub_time').first().html();
219 if (lastUpdate !== '') {
218 if (lastUpdate !== '') {
220 var lastUpdateField = $('#last-update');
219 var lastUpdateField = $('#last-update');
221 lastUpdateField.html(lastUpdate);
220 lastUpdateField.html(lastUpdate);
222 blink(lastUpdateField);
221 blink(lastUpdateField);
223 }
222 }
224
223
225 blink(replyCountField);
224 blink(replyCountField);
226 blink(imageCountField);
225 blink(imageCountField);
227 }
226 }
228
227
229 /**
228 /**
230 * Update bumplimit progress bar
229 * Update bumplimit progress bar
231 */
230 */
232 function updateBumplimitProgress(postDelta) {
231 function updateBumplimitProgress(postDelta) {
233 var progressBar = $('#bumplimit_progress');
232 var progressBar = $('#bumplimit_progress');
234 if (progressBar) {
233 if (progressBar) {
235 var postsToLimitElement = $('#left_to_limit');
234 var postsToLimitElement = $('#left_to_limit');
236
235
237 var oldPostsToLimit = parseInt(postsToLimitElement.text());
236 var oldPostsToLimit = parseInt(postsToLimitElement.text());
238 var postCount = getReplyCount();
237 var postCount = getReplyCount();
239 var bumplimit = postCount - postDelta + oldPostsToLimit;
238 var bumplimit = postCount - postDelta + oldPostsToLimit;
240
239
241 var newPostsToLimit = bumplimit - postCount;
240 var newPostsToLimit = bumplimit - postCount;
242 if (newPostsToLimit <= 0) {
241 if (newPostsToLimit <= 0) {
243 $('.bar-bg').remove();
242 $('.bar-bg').remove();
244 } else {
243 } else {
245 postsToLimitElement.text(newPostsToLimit);
244 postsToLimitElement.text(newPostsToLimit);
246 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
245 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
247 }
246 }
248 }
247 }
249 }
248 }
250
249
251 /**
250 /**
252 * Show 'new posts' text in the title if the document is not visible to a user
251 * Show 'new posts' text in the title if the document is not visible to a user
253 */
252 */
254 function showNewPostsTitle(newPostCount) {
253 function showNewPostsTitle(newPostCount) {
255 if (document.hidden) {
254 if (document.hidden) {
256 if (documentOriginalTitle === '') {
255 if (documentOriginalTitle === '') {
257 documentOriginalTitle = document.title;
256 documentOriginalTitle = document.title;
258 }
257 }
259 unreadPosts = unreadPosts + newPostCount;
258 unreadPosts = unreadPosts + newPostCount;
260
259
261 var newTitle = null;
260 var newTitle = null;
262 if (unreadPosts > 0) {
261 if (unreadPosts > 0) {
263 newTitle = '[' + unreadPosts + '] ';
262 newTitle = '[' + unreadPosts + '] ';
264 } else {
263 } else {
265 newTitle = '* ';
264 newTitle = '* ';
266 }
265 }
267 newTitle += documentOriginalTitle;
266 newTitle += documentOriginalTitle;
268
267
269 document.title = newTitle;
268 document.title = newTitle;
270
269
271 document.addEventListener('visibilitychange', function() {
270 document.addEventListener('visibilitychange', function() {
272 if (documentOriginalTitle !== '') {
271 if (documentOriginalTitle !== '') {
273 document.title = documentOriginalTitle;
272 document.title = documentOriginalTitle;
274 documentOriginalTitle = '';
273 documentOriginalTitle = '';
275 unreadPosts = 0;
274 unreadPosts = 0;
276 }
275 }
277
276
278 document.removeEventListener('visibilitychange', null);
277 document.removeEventListener('visibilitychange', null);
279 });
278 });
280 }
279 }
281 }
280 }
282
281
283 /**
282 /**
284 * Run js methods that are usually run on the document, on the new post
283 * Run js methods that are usually run on the document, on the new post
285 */
284 */
286 function processNewPost(post) {
285 function processNewPost(post) {
287 addScriptsToPost(post);
286 addScriptsToPost(post);
288 blink(post);
287 blink(post);
289 }
288 }
290
289
291 function replacePartial(oldNode, newNode, recursive) {
290 function replacePartial(oldNode, newNode, recursive) {
292 if (!equalNodes(oldNode, newNode)) {
291 if (!equalNodes(oldNode, newNode)) {
293 // Update parent node attributes
292 // Update parent node attributes
294 updateNodeAttr(oldNode, newNode, ATTR_CLASS);
293 updateNodeAttr(oldNode, newNode, ATTR_CLASS);
295 updateNodeAttr(oldNode, newNode, ATTR_UID);
294 updateNodeAttr(oldNode, newNode, ATTR_UID);
296
295
297 // Replace children
296 // Replace children
298 var children = oldNode.children();
297 var children = oldNode.children();
299 if (children.length == 0) {
298 if (children.length == 0) {
300 oldNode.replaceWith(newNode);
299 oldNode.replaceWith(newNode);
301 } else {
300 } else {
302 var newChildren = newNode.children();
301 var newChildren = newNode.children();
303 newChildren.each(function(i) {
302 newChildren.each(function(i) {
304 var newChild = newChildren.eq(i);
303 var newChild = newChildren.eq(i);
305 var newChildClass = newChild.attr(ATTR_CLASS);
304 var newChildClass = newChild.attr(ATTR_CLASS);
306
305
307 // Update only certain allowed blocks (e.g. not images)
306 // Update only certain allowed blocks (e.g. not images)
308 if (ALLOWED_FOR_PARTIAL_UPDATE.indexOf(newChildClass) > -1) {
307 if (ALLOWED_FOR_PARTIAL_UPDATE.indexOf(newChildClass) > -1) {
309 var oldChild = oldNode.children('.' + newChildClass);
308 var oldChild = oldNode.children('.' + newChildClass);
310
309
311 if (oldChild.length == 0) {
310 if (oldChild.length == 0) {
312 oldNode.append(newChild);
311 oldNode.append(newChild);
313 } else {
312 } else {
314 if (!equalNodes(oldChild, newChild)) {
313 if (!equalNodes(oldChild, newChild)) {
315 if (recursive) {
314 if (recursive) {
316 replacePartial(oldChild, newChild, false);
315 replacePartial(oldChild, newChild, false);
317 } else {
316 } else {
318 oldChild.replaceWith(newChild);
317 oldChild.replaceWith(newChild);
319 }
318 }
320 }
319 }
321 }
320 }
322 }
321 }
323 });
322 });
324 }
323 }
325 }
324 }
326 }
325 }
327
326
328 /**
327 /**
329 * Compare nodes by content
328 * Compare nodes by content
330 */
329 */
331 function equalNodes(node1, node2) {
330 function equalNodes(node1, node2) {
332 return node1[0].outerHTML == node2[0].outerHTML;
331 return node1[0].outerHTML == node2[0].outerHTML;
333 }
332 }
334
333
335 /**
334 /**
336 * Update attribute of a node if it has changed
335 * Update attribute of a node if it has changed
337 */
336 */
338 function updateNodeAttr(oldNode, newNode, attrName) {
337 function updateNodeAttr(oldNode, newNode, attrName) {
339 var oldAttr = oldNode.attr(attrName);
338 var oldAttr = oldNode.attr(attrName);
340 var newAttr = newNode.attr(attrName);
339 var newAttr = newNode.attr(attrName);
341 if (oldAttr != newAttr) {
340 if (oldAttr != newAttr) {
342 oldNode.attr(attrName, newAttr);
341 oldNode.attr(attrName, newAttr);
343 }
342 }
344 }
343 }
345
344
346 $(document).ready(function() {
345 $(document).ready(function() {
347 if (initAutoupdate()) {
346 if (initAutoupdate()) {
348 // Post form data over AJAX
347 // Post form data over AJAX
349 var threadId = $('div.thread').children('.post').first().attr('id');
348 var threadId = $('div.thread').children('.post').first().attr('id');
350
349
351 var form = $('#form');
350 var form = $('#form');
352
351
353 initAjaxForm(threadId);
352 initAjaxForm(threadId);
354 if (form.length > 0) {
353 if (form.length > 0) {
355 resetForm();
354 resetForm();
356 }
355 }
357 }
356 }
358 });
357 });
General Comments 0
You need to be logged in to leave comments. Login now