##// END OF EJS Templates
Update post's uid too
neko259 -
r1316:872ff2a9 default
parent child Browse files
Show More
@@ -1,447 +1,456 b''
1 1 /*
2 2 @licstart The following is the entire license notice for the
3 3 JavaScript code in this page.
4 4
5 5
6 6 Copyright (C) 2013-2014 neko259
7 7
8 8 The JavaScript code in this page is free software: you can
9 9 redistribute it and/or modify it under the terms of the GNU
10 10 General Public License (GNU GPL) as published by the Free Software
11 11 Foundation, either version 3 of the License, or (at your option)
12 12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15 15
16 16 As additional permission under GNU GPL version 3 section 7, you
17 17 may distribute non-source (e.g., minimized or compacted) forms of
18 18 that code without the copy of the GNU GPL normally required by
19 19 section 4, provided you include this license notice and a URL
20 20 through which recipients can access the Corresponding Source.
21 21
22 22 @licend The above is the entire license notice
23 23 for the JavaScript code in this page.
24 24 */
25 25
26 26 var CLASS_POST = '.post'
27 27
28 28 var POST_ADDED = 0;
29 29 var POST_UPDATED = 1;
30 30
31 31 var JS_AUTOUPDATE_PERIOD = 20000;
32 32
33 33 var ALLOWED_FOR_PARTIAL_UPDATE = [
34 34 'refmap',
35 35 'post-info'
36 36 ];
37 37
38 38 var ATTR_CLASS = 'class';
39 var ATTR_UID = 'data-uid';
39 40
40 41 var wsUser = '';
41 42
42 43 var unreadPosts = 0;
43 44 var documentOriginalTitle = '';
44 45
45 46 // Thread ID does not change, can be stored one time
46 47 var threadId = $('div.thread').children(CLASS_POST).first().attr('id');
47 48
48 49 /**
49 50 * Connect to websocket server and subscribe to thread updates. On any update we
50 51 * request a thread diff.
51 52 *
52 53 * @returns {boolean} true if connected, false otherwise
53 54 */
54 55 function connectWebsocket() {
55 56 var metapanel = $('.metapanel')[0];
56 57
57 58 var wsHost = metapanel.getAttribute('data-ws-host');
58 59 var wsPort = metapanel.getAttribute('data-ws-port');
59 60
60 61 if (wsHost.length > 0 && wsPort.length > 0) {
61 62 var centrifuge = new Centrifuge({
62 63 "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket",
63 64 "project": metapanel.getAttribute('data-ws-project'),
64 65 "user": wsUser,
65 66 "timestamp": metapanel.getAttribute('data-ws-token-time'),
66 67 "token": metapanel.getAttribute('data-ws-token'),
67 68 "debug": false
68 69 });
69 70
70 71 centrifuge.on('error', function(error_message) {
71 72 console.log("Error connecting to websocket server.");
72 73 console.log(error_message);
73 74 console.log("Using javascript update instead.");
74 75
75 76 // If websockets don't work, enable JS update instead
76 77 enableJsUpdate()
77 78 });
78 79
79 80 centrifuge.on('connect', function() {
80 81 var channelName = 'thread:' + threadId;
81 82 centrifuge.subscribe(channelName, function(message) {
82 83 getThreadDiff();
83 84 });
84 85
85 86 // For the case we closed the browser and missed some updates
86 87 getThreadDiff();
87 88 $('#autoupdate').hide();
88 89 });
89 90
90 91 centrifuge.connect();
91 92
92 93 return true;
93 94 } else {
94 95 return false;
95 96 }
96 97 }
97 98
98 99 /**
99 100 * Get diff of the posts from the current thread timestamp.
100 101 * This is required if the browser was closed and some post updates were
101 102 * missed.
102 103 */
103 104 function getThreadDiff() {
104 105 var lastUpdateTime = $('.metapanel').attr('data-last-update');
105 106 var lastPostId = $('.post').last().attr('id');
106 107
107 108 var uids = '';
108 109 var posts = $('.post');
109 110 for (var i = 0; i < posts.length; i++) {
110 111 uids += posts[i].getAttribute('data-uid') + ' ';
111 112 }
112 113
113 114 var data = {
114 115 uids: uids,
115 116 thread: threadId
116 117 }
117 118
118 119 var diffUrl = '/api/diff_thread/';
119 120
120 121 $.post(diffUrl,
121 122 data,
122 123 function(data) {
123 124 var updatedPosts = data.updated;
124 125 var addedPostCount = 0;
125 126
126 127 for (var i = 0; i < updatedPosts.length; i++) {
127 128 var postText = updatedPosts[i];
128 129 var post = $(postText);
129 130
130 131 if (updatePost(post) == POST_ADDED) {
131 132 addedPostCount++;
132 133 }
133 134 }
134 135
135 136 var hasMetaUpdates = updatedPosts.length > 0;
136 137 if (hasMetaUpdates) {
137 138 updateMetadataPanel();
138 139 }
139 140
140 141 if (addedPostCount > 0) {
141 142 updateBumplimitProgress(addedPostCount);
142 143 }
143 144
144 145 if (updatedPosts.length > 0) {
145 146 showNewPostsTitle(addedPostCount);
146 147 }
147 148
148 149 // TODO Process removed posts if any
149 150 $('.metapanel').attr('data-last-update', data.last_update);
150 151 },
151 152 'json'
152 153 )
153 154 }
154 155
155 156 /**
156 157 * Add or update the post on html page.
157 158 */
158 159 function updatePost(postHtml) {
159 160 // This needs to be set on start because the page is scrolled after posts
160 161 // are added or updated
161 162 var bottom = isPageBottom();
162 163
163 164 var post = $(postHtml);
164 165
165 166 var threadBlock = $('div.thread');
166 167
167 168 var postId = post.attr('id');
168 169
169 170 // If the post already exists, replace it. Otherwise add as a new one.
170 171 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
171 172
172 173 var type;
173 174
174 175 if (existingPosts.size() > 0) {
175 176 replacePartial(existingPosts.first(), post, false);
176 177 post = existingPosts.first();
177 178
178 179 type = POST_UPDATED;
179 180 } else {
180 181 post.appendTo(threadBlock);
181 182
182 183 if (bottom) {
183 184 scrollToBottom();
184 185 }
185 186
186 187 type = POST_ADDED;
187 188 }
188 189
189 190 processNewPost(post);
190 191
191 192 return type;
192 193 }
193 194
194 195 /**
195 196 * Initiate a blinking animation on a node to show it was updated.
196 197 */
197 198 function blink(node) {
198 199 var blinkCount = 2;
199 200
200 201 var nodeToAnimate = node;
201 202 for (var i = 0; i < blinkCount; i++) {
202 203 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
203 204 }
204 205 }
205 206
206 207 function isPageBottom() {
207 208 var scroll = $(window).scrollTop() / ($(document).height()
208 209 - $(window).height());
209 210
210 211 return scroll == 1
211 212 }
212 213
213 214 function enableJsUpdate() {
214 215 setInterval(getThreadDiff, JS_AUTOUPDATE_PERIOD);
215 216 return true;
216 217 }
217 218
218 219 function initAutoupdate() {
219 220 if (location.protocol === 'https:') {
220 221 return enableJsUpdate();
221 222 } else {
222 223 if (connectWebsocket()) {
223 224 return true;
224 225 } else {
225 226 return enableJsUpdate();
226 227 }
227 228 }
228 229 }
229 230
230 231 function getReplyCount() {
231 232 return $('.thread').children(CLASS_POST).length
232 233 }
233 234
234 235 function getImageCount() {
235 236 return $('.thread').find('img').length
236 237 }
237 238
238 239 /**
239 240 * Update post count, images count and last update time in the metadata
240 241 * panel.
241 242 */
242 243 function updateMetadataPanel() {
243 244 var replyCountField = $('#reply-count');
244 245 var imageCountField = $('#image-count');
245 246
246 247 replyCountField.text(getReplyCount());
247 248 imageCountField.text(getImageCount());
248 249
249 250 var lastUpdate = $('.post:last').children('.post-info').first()
250 251 .children('.pub_time').first().html();
251 252 if (lastUpdate !== '') {
252 253 var lastUpdateField = $('#last-update');
253 254 lastUpdateField.html(lastUpdate);
254 255 blink(lastUpdateField);
255 256 }
256 257
257 258 blink(replyCountField);
258 259 blink(imageCountField);
259 260 }
260 261
261 262 /**
262 263 * Update bumplimit progress bar
263 264 */
264 265 function updateBumplimitProgress(postDelta) {
265 266 var progressBar = $('#bumplimit_progress');
266 267 if (progressBar) {
267 268 var postsToLimitElement = $('#left_to_limit');
268 269
269 270 var oldPostsToLimit = parseInt(postsToLimitElement.text());
270 271 var postCount = getReplyCount();
271 272 var bumplimit = postCount - postDelta + oldPostsToLimit;
272 273
273 274 var newPostsToLimit = bumplimit - postCount;
274 275 if (newPostsToLimit <= 0) {
275 276 $('.bar-bg').remove();
276 277 } else {
277 278 postsToLimitElement.text(newPostsToLimit);
278 279 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
279 280 }
280 281 }
281 282 }
282 283
283 284 /**
284 285 * Show 'new posts' text in the title if the document is not visible to a user
285 286 */
286 287 function showNewPostsTitle(newPostCount) {
287 288 if (document.hidden) {
288 289 if (documentOriginalTitle === '') {
289 290 documentOriginalTitle = document.title;
290 291 }
291 292 unreadPosts = unreadPosts + newPostCount;
292 293
293 294 var newTitle = '* ';
294 295 if (unreadPosts > 0) {
295 296 newTitle += '[' + unreadPosts + '] ';
296 297 }
297 298 newTitle += documentOriginalTitle;
298 299
299 300 document.title = newTitle;
300 301
301 302 document.addEventListener('visibilitychange', function() {
302 303 if (documentOriginalTitle !== '') {
303 304 document.title = documentOriginalTitle;
304 305 documentOriginalTitle = '';
305 306 unreadPosts = 0;
306 307 }
307 308
308 309 document.removeEventListener('visibilitychange', null);
309 310 });
310 311 }
311 312 }
312 313
313 314 /**
314 315 * Clear all entered values in the form fields
315 316 */
316 317 function resetForm(form) {
317 318 form.find('input:text, input:password, input:file, select, textarea').val('');
318 319 form.find('input:radio, input:checkbox')
319 320 .removeAttr('checked').removeAttr('selected');
320 321 $('.file_wrap').find('.file-thumb').remove();
321 322 $('#preview-text').hide();
322 323 }
323 324
324 325 /**
325 326 * When the form is posted, this method will be run as a callback
326 327 */
327 328 function updateOnPost(response, statusText, xhr, form) {
328 329 var json = $.parseJSON(response);
329 330 var status = json.status;
330 331
331 332 showAsErrors(form, '');
332 333
333 334 if (status === 'ok') {
334 335 resetFormPosition();
335 336 resetForm(form);
336 337 getThreadDiff();
337 338 scrollToBottom();
338 339 } else {
339 340 var errors = json.errors;
340 341 for (var i = 0; i < errors.length; i++) {
341 342 var fieldErrors = errors[i];
342 343
343 344 var error = fieldErrors.errors;
344 345
345 346 showAsErrors(form, error);
346 347 }
347 348 }
348 349 }
349 350
350 351 /**
351 352 * Show text in the errors row of the form.
352 353 * @param form
353 354 * @param text
354 355 */
355 356 function showAsErrors(form, text) {
356 357 form.children('.form-errors').remove();
357 358
358 359 if (text.length > 0) {
359 360 var errorList = $('<div class="form-errors">' + text + '<div>');
360 361 errorList.appendTo(form);
361 362 }
362 363 }
363 364
364 365 /**
365 366 * Run js methods that are usually run on the document, on the new post
366 367 */
367 368 function processNewPost(post) {
368 369 addRefLinkPreview(post[0]);
369 370 highlightCode(post);
370 371 blink(post);
371 372 }
372 373
373 374 function replacePartial(oldNode, newNode, recursive) {
374 375 if (!equalNodes(oldNode, newNode)) {
375 // Update parent node class attribute
376 var oldClass = oldNode.attr(ATTR_CLASS);
377 var newClass = newNode.attr(ATTR_CLASS);
378 if (oldClass != newClass) {
379 oldNode.attr(ATTR_CLASS, newClass);
380 };
376 // Update parent node attributes
377 updateNodeAttr(oldNode, newNode, ATTR_CLASS);
378 updateNodeAttr(oldNode, newNode, ATTR_UID);
381 379
382 380 // Replace children
383 381 var children = oldNode.children();
384 382 if (children.length == 0) {
385 383 console.log(oldContent);
386 384 console.log(newContent)
387 385
388 386 oldNode.replaceWith(newNode);
389 387 } else {
390 388 var newChildren = newNode.children();
391 389 newChildren.each(function(i) {
392 390 var newChild = newChildren.eq(i);
393 391 var newChildClass = newChild.attr(ATTR_CLASS);
394 392
395 393 // Update only certain allowed blocks (e.g. not images)
396 394 if (ALLOWED_FOR_PARTIAL_UPDATE.indexOf(newChildClass) > -1) {
397 395 var oldChild = oldNode.children('.' + newChildClass);
398 396
399 397 if (oldChild.length == 0) {
400 398 oldNode.append(newChild);
401 399 } else {
402 400 if (!equalNodes(oldChild, newChild)) {
403 401 if (recursive) {
404 402 replacePartial(oldChild, newChild, false);
405 403 } else {
406 404 oldChild.replaceWith(newChild);
407 405 }
408 406 }
409 407 }
410 408 }
411 409 });
412 410 }
413 411 }
414 412 }
415 413
416 414 /**
417 415 * Compare nodes by content
418 416 */
419 417 function equalNodes(node1, node2) {
420 418 return node1[0].outerHTML == node2[0].outerHTML;
421 419 }
422 420
421 /**
422 * Update attribute of a node if it has changed
423 */
424 function updateNodeAttr(oldNode, newNode, attrName) {
425 var oldAttr = oldNode.attr(attrName);
426 var newAttr = newNode.attr(attrName);
427 if (oldAttr != newAttr) {
428 oldNode.attr(attrName, newAttr);
429 };
430 }
431
423 432 $(document).ready(function(){
424 433 if (initAutoupdate()) {
425 434 // Post form data over AJAX
426 435 var threadId = $('div.thread').children('.post').first().attr('id');
427 436
428 437 var form = $('#form');
429 438
430 439 if (form.length > 0) {
431 440 var options = {
432 441 beforeSubmit: function(arr, $form, options) {
433 442 showAsErrors($('form'), gettext('Sending message...'));
434 443 },
435 444 success: updateOnPost,
436 445 error: function() {
437 446 showAsErrors($('form'), gettext('Server error!'));
438 447 },
439 448 url: '/api/add_post/' + threadId + '/'
440 449 };
441 450
442 451 form.ajaxForm(options);
443 452
444 453 resetForm(form);
445 454 }
446 455 }
447 456 });
General Comments 0
You need to be logged in to leave comments. Login now