8a401312ca2bb18968b7c31cf6688ad9b29015dc
[Sone.git] / src / main / resources / static / javascript / sone.js
1 /* Sone JavaScript functions. */
2
3 /* jQuery overrides. */
4 oldGetJson = jQuery.prototype.getJSON;
5 jQuery.prototype.getJSON = function(url, data, successCallback, errorCallback) {
6         if (typeof errorCallback == "undefined") {
7                 return oldGetJson(url, data, successCallback);
8         }
9         if (jQuery.isFunction(data)) {
10                 errorCallback = successCallback;
11                 successCallback = data;
12                 data = null;
13         }
14         return jQuery.ajax({
15                 data: data,
16                 error: errorCallback,
17                 success: successCallback,
18                 url: url
19         });
20 }
21
22 function isOnline() {
23         return $("#sone").hasClass("online");
24 }
25
26 function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, optional, dontUseTextarea) {
27         $(inputElement).each(function() {
28                 textarea = $(dontUseTextarea ? "<input type=\"text\" name=\"" + inputFieldName + "\">" : "<textarea name=\"" + inputFieldName + "\"></textarea>").blur(function() {
29                         if ($(this).val() == "") {
30                                 $(this).hide();
31                                 inputField = $(this).data("inputField");
32                                 inputField.show().removeAttr("disabled").addClass("default");
33                                 inputField.val(defaultText);
34                         }
35                 }).hide().data("inputField", $(this)).val($(this).val());
36                 $(this).after(textarea);
37                 (function(inputField, textarea) {
38                         inputField.focus(function() {
39                                 $(this).hide().attr("disabled", "disabled");
40                                 textarea.show().focus();
41                         });
42                         if (inputField.val() == "") {
43                                 inputField.addClass("default");
44                                 inputField.val(defaultText);
45                         } else {
46                                 inputField.hide().attr("disabled", "disabled");
47                                 textarea.show();
48                         }
49                         $(inputField.get(0).form).submit(function() {
50                                 if (!optional && (textarea.val() == "")) {
51                                         return false;
52                                 }
53                         });
54                 })($(this), textarea);
55         });
56 }
57
58 /**
59  * Adds a “comment” link to all status lines contained in the given element.
60  *
61  * @param postId
62  *            The ID of the post
63  * @param element
64  *            The element to add a “comment” link to
65  */
66 function addCommentLink(postId, element) {
67         if ($(element).find(".show-reply-form").length > 0) {
68                 return;
69         }
70         commentElement = (function(postId) {
71                 var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
72                         replyElement = $("#sone .post#" + postId + " .create-reply");
73                         replyElement.removeClass("hidden");
74                         replyElement.removeClass("light");
75                         (function(replyElement) {
76                                 replyElement.find("input.reply-input").blur(function() {
77                                         if ($(this).hasClass("default")) {
78                                                 replyElement.addClass("light");
79                                         }
80                                 }).focus(function() {
81                                         replyElement.removeClass("light");
82                                 });
83                         })(replyElement);
84                         replyElement.find("input.reply-input").focus();
85                 });
86                 return commentElement;
87         })(postId);
88         $(element).find(".status-line .time").each(function() {
89                 $(this).after(commentElement.clone(true));
90         });
91 }
92
93 var translations = {};
94
95 /**
96  * Retrieves the translation for the given key and calls the callback function.
97  * The callback function takes a single parameter, the translated string.
98  *
99  * @param key
100  *            The key of the translation string
101  * @param callback
102  *            The callback function
103  */
104 function getTranslation(key, callback) {
105         if (key in translations) {
106                 callback(translations[key]);
107                 return;
108         }
109         $.getJSON("ajax/getTranslation.ajax", {"key": key}, function(data, textStatus) {
110                 if ((data != null) && data.success) {
111                         translations[key] = data.value;
112                         callback(data.value);
113                 }
114         }, function(xmlHttpRequest, textStatus, error) {
115                 /* ignore error. */
116         });
117 }
118
119 /**
120  * Filters the given Sone ID, replacing all “~” characters by an underscore.
121  *
122  * @param soneId
123  *            The Sone ID to filter
124  * @returns The filtered Sone ID
125  */
126 function filterSoneId(soneId) {
127         return soneId.replace(/[^a-zA-Z0-9-]/g, "_");
128 }
129
130 /**
131  * Updates the status of the given Sone.
132  *
133  * @param soneId
134  *            The ID of the Sone to update
135  * @param status
136  *            The status of the Sone (“idle”, “unknown”, “inserting”,
137  *            “downloading”)
138  * @param modified
139  *            Whether the Sone is modified
140  * @param locked
141  *            Whether the Sone is locked
142  * @param lastUpdated
143  *            The date and time of the last update (formatted for display)
144  */
145 function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated) {
146         $("#sone .sone." + filterSoneId(soneId)).
147                 toggleClass("unknown", status == "unknown").
148                 toggleClass("idle", status == "idle").
149                 toggleClass("inserting", status == "inserting").
150                 toggleClass("downloading", status == "downloading").
151                 toggleClass("modified", modified);
152         $("#sone .sone." + filterSoneId(soneId) + " .lock").toggleClass("hidden", locked);
153         $("#sone .sone." + filterSoneId(soneId) + " .unlock").toggleClass("hidden", !locked);
154         $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(lastUpdated);
155         $("#sone .sone." + filterSoneId(soneId) + " .profile-link a").text(name);
156 }
157
158 /**
159  * Enhances a “delete” button so that the confirmation is done on the same page.
160  *
161  * @param button
162  *            The button element
163  * @param text
164  *            The text to show on the button
165  * @param deleteCallback
166  *            The callback that actually deletes something
167  */
168 function enhanceDeleteButton(button, text, deleteCallback) {
169         (function(button) {
170                 newButton = $("<button></button>").addClass("confirm").hide().text(text).click(function() {
171                         $(this).fadeOut("slow");
172                         deleteCallback();
173                         return false;
174                 }).insertAfter(button);
175                 (function(button, newButton) {
176                         button.click(function() {
177                                 button.fadeOut("slow", function() {
178                                         newButton.fadeIn("slow");
179                                         $(document).one("click", function() {
180                                                 if (this != newButton.get(0)) {
181                                                         newButton.fadeOut(function() {
182                                                                 button.fadeIn();
183                                                         });
184                                                 }
185                                         });
186                                 });
187                                 return false;
188                         });
189                 })(button, newButton);
190         })($(button));
191 }
192
193 /**
194  * Enhances a post’s “delete” button.
195  *
196  * @param button
197  *            The button element
198  * @param postId
199  *            The ID of the post to delete
200  * @param text
201  *            The text to replace the button with
202  */
203 function enhanceDeletePostButton(button, postId, text) {
204         enhanceDeleteButton(button, text, function() {
205                 $.getJSON("ajax/deletePost.ajax", { "post": postId, "formPassword": getFormPassword() }, function(data, textStatus) {
206                         if (data == null) {
207                                 return;
208                         }
209                         if (data.success) {
210                                 $("#sone .post#" + postId).slideUp();
211                         } else if (data.error == "invalid-post-id") {
212                                 alert("Invalid post ID given!");
213                         } else if (data.error == "auth-required") {
214                                 alert("You need to be logged in.");
215                         } else if (data.error == "not-authorized") {
216                                 alert("You are not allowed to delete this post.");
217                         }
218                 }, function(xmlHttpRequest, textStatus, error) {
219                         /* ignore error. */
220                 });
221         });
222 }
223
224 /**
225  * Enhances a reply’s “delete” button.
226  *
227  * @param button
228  *            The button element
229  * @param replyId
230  *            The ID of the reply to delete
231  * @param text
232  *            The text to replace the button with
233  */
234 function enhanceDeleteReplyButton(button, replyId, text) {
235         enhanceDeleteButton(button, text, function() {
236                 $.getJSON("ajax/deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
237                         if (data == null) {
238                                 return;
239                         }
240                         if (data.success) {
241                                 $("#sone .reply#" + replyId).slideUp();
242                         } else if (data.error == "invalid-reply-id") {
243                                 alert("Invalid reply ID given!");
244                         } else if (data.error == "auth-required") {
245                                 alert("You need to be logged in.");
246                         } else if (data.error == "not-authorized") {
247                                 alert("You are not allowed to delete this reply.");
248                         }
249                 }, function(xmlHttpRequest, textStatus, error) {
250                         /* ignore error. */
251                 });
252         });
253 }
254
255 function getFormPassword() {
256         return $("#sone #formPassword").text();
257 }
258
259 function getSoneElement(element) {
260         return $(element).parents(".sone");
261 }
262
263 /**
264  * Generates a list of Sones by concatening the names of the given sones with a
265  * new line character (“\n”).
266  *
267  * @param sones
268  *            The sones to format
269  * @returns {String} The created string
270  */
271 function generateSoneList(sones) {
272         var soneList = "";
273         $.each(sones, function() {
274                 if (soneList != "") {
275                         soneList += ", ";
276                 }
277                 soneList += this.name;
278         });
279         return soneList;
280 }
281
282 /**
283  * Returns the ID of the Sone that this element belongs to.
284  *
285  * @param element
286  *            The element to locate the matching Sone ID for
287  * @returns The ID of the Sone, or undefined
288  */
289 function getSoneId(element) {
290         return getSoneElement(element).find(".id").text();
291 }
292
293 function getPostElement(element) {
294         return $(element).closest(".post");
295 }
296
297 function getPostId(element) {
298         return getPostElement(element).attr("id");
299 }
300
301 function getPostTime(element) {
302         return getPostElement(element).find(".post-time").text();
303 }
304
305 function getReplyElement(element) {
306         return $(element).closest(".reply");
307 }
308
309 function getReplyId(element) {
310         return getReplyElement(element).attr("id");
311 }
312
313 function getReplyTime(element) {
314         return getReplyElement(element).find(".reply-time").text();
315 }
316
317 function likePost(postId) {
318         $.getJSON("ajax/like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
319                 if ((data == null) || !data.success) {
320                         return;
321                 }
322                 $("#sone .post#" + postId + " > .inner-part > .status-line .like").addClass("hidden");
323                 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").removeClass("hidden");
324                 updatePostLikes(postId);
325         }, function(xmlHttpRequest, textStatus, error) {
326                 /* ignore error. */
327         });
328 }
329
330 function unlikePost(postId) {
331         $.getJSON("ajax/unlike.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
332                 if ((data == null) || !data.success) {
333                         return;
334                 }
335                 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").addClass("hidden");
336                 $("#sone .post#" + postId + " > .inner-part > .status-line .like").removeClass("hidden");
337                 updatePostLikes(postId);
338         }, function(xmlHttpRequest, textStatus, error) {
339                 /* ignore error. */
340         });
341 }
342
343 function updatePostLikes(postId) {
344         $.getJSON("ajax/getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) {
345                 if ((data != null) && data.success) {
346                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0)
347                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes span.like-count").text(data.likes);
348                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes > span").attr("title", generateSoneList(data.sones));
349                 }
350         }, function(xmlHttpRequest, textStatus, error) {
351                 /* ignore error. */
352         });
353 }
354
355 function likeReply(replyId) {
356         $.getJSON("ajax/like.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
357                 if ((data == null) || !data.success) {
358                         return;
359                 }
360                 $("#sone .reply#" + replyId + " .status-line .like").addClass("hidden");
361                 $("#sone .reply#" + replyId + " .status-line .unlike").removeClass("hidden");
362                 updateReplyLikes(replyId);
363         }, function(xmlHttpRequest, textStatus, error) {
364                 /* ignore error. */
365         });
366 }
367
368 function unlikeReply(replyId) {
369         $.getJSON("ajax/unlike.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
370                 if ((data == null) || !data.success) {
371                         return;
372                 }
373                 $("#sone .reply#" + replyId + " .status-line .unlike").addClass("hidden");
374                 $("#sone .reply#" + replyId + " .status-line .like").removeClass("hidden");
375                 updateReplyLikes(replyId);
376         }, function(xmlHttpRequest, textStatus, error) {
377                 /* ignore error. */
378         });
379 }
380
381 function updateReplyLikes(replyId) {
382         $.getJSON("ajax/getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) {
383                 if ((data != null) && data.success) {
384                         $("#sone .reply#" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0)
385                         $("#sone .reply#" + replyId + " .status-line .likes span.like-count").text(data.likes);
386                         $("#sone .reply#" + replyId + " .status-line .likes > span").attr("title", generateSoneList(data.sones));
387                 }
388         }, function(xmlHttpRequest, textStatus, error) {
389                 /* ignore error. */
390         });
391 }
392
393 /**
394  * Posts a reply and calls the given callback when the request finishes.
395  *
396  * @param postId
397  *            The ID of the post the reply refers to
398  * @param text
399  *            The text to post
400  * @param callbackFunction
401  *            The callback function to call when the request finishes (takes 3
402  *            parameters: success, error, replyId)
403  */
404 function postReply(postId, text, callbackFunction) {
405         $.getJSON("ajax/createReply.ajax", { "formPassword" : getFormPassword(), "post" : postId, "text": text }, function(data, textStatus) {
406                 if (data == null) {
407                         /* TODO - show error */
408                         return;
409                 }
410                 if (data.success) {
411                         callbackFunction(true, null, data.reply);
412                 } else {
413                         callbackFunction(false, data.error);
414                 }
415         }, function(xmlHttpRequest, textStatus, error) {
416                 /* ignore error. */
417         });
418 }
419
420 /**
421  * Requests information about the reply with the given ID.
422  *
423  * @param replyId
424  *            The ID of the reply
425  * @param callbackFunction
426  *            A callback function (parameters soneId, soneName, replyTime,
427  *            replyDisplayTime, text, html)
428  */
429 function getReply(replyId, callbackFunction) {
430         $.getJSON("ajax/getReply.ajax", { "reply" : replyId }, function(data, textStatus) {
431                 if ((data != null) && data.success) {
432                         callbackFunction(data.soneId, data.soneName, data.time, data.displayTime, data.text, data.html);
433                 }
434         }, function(xmlHttpRequest, textStatus, error) {
435                 /* ignore error. */
436         });
437 }
438
439 /**
440  * Ajaxifies the given post by enhancing all eligible elements with AJAX.
441  *
442  * @param postElement
443  *            The post element to ajaxify
444  */
445 function ajaxifyPost(postElement) {
446         $(postElement).find("form").submit(function() {
447                 return false;
448         });
449         $(postElement).find(".create-reply button:submit").click(function() {
450                 inputField = $(this.form).find(":input:enabled").get(0);
451                 postId = getPostId(this);
452                 text = $(inputField).val();
453                 $(inputField).val("");
454                 postReply(postId, text, function(success, error, replyId) {
455                         if (success) {
456                                 loadNewReply(replyId);
457                                 markPostAsKnown(getPostElement(inputField));
458                                 $("#sone .post#" + postId + " .create-reply").addClass("hidden");
459                         } else {
460                                 alert(error);
461                         }
462                 });
463                 return false;
464         });
465
466         /* replace all “delete” buttons with javascript. */
467         (function(postElement) {
468                 getTranslation("WebInterface.Confirmation.DeletePostButton", function(deletePostText) {
469                         postId = getPostId(postElement);
470                         enhanceDeletePostButton($(postElement).find(".delete-post button"), postId, deletePostText);
471                 });
472         })(postElement);
473
474         /* convert all “like” buttons to javascript functions. */
475         $(postElement).find(".like-post").submit(function() {
476                 likePost(getPostId(this));
477                 return false;
478         });
479         $(postElement).find(".unlike-post").submit(function() {
480                 unlikePost(getPostId(this));
481                 return false;
482         });
483
484         /* process all replies. */
485         $(postElement).find(".reply").each(function() {
486                 ajaxifyReply(this);
487         });
488
489         /* process reply input fields. */
490         getTranslation("WebInterface.DefaultText.Reply", function(text) {
491                 $(postElement).find("input.reply-input").each(function() {
492                         registerInputTextareaSwap(this, text, "text", false, false);
493                         addCommentLink(getPostId(postElement), postElement);
494                 });
495         });
496
497         /* add “comment” link. */
498         addCommentLink(getPostId(postElement), postElement);
499
500         /* hide reply input field. */
501         $(postElement).find(".create-reply").addClass("hidden");
502 }
503
504 /**
505  * Ajaxifies the given reply element.
506  *
507  * @param replyElement
508  *            The reply element to ajaxify
509  */
510 function ajaxifyReply(replyElement) {
511         $(replyElement).find(".like-reply").submit(function() {
512                 likeReply(getReplyId(this));
513                 return false;
514         });
515         $(replyElement).find(".unlike-reply").submit(function() {
516                 unlikeReply(getReplyId(this));
517                 return false;
518         });
519         (function(replyElement) {
520                 getTranslation("WebInterface.Confirmation.DeleteReplyButton", function(deleteReplyText) {
521                         $(replyElement).find(".delete-reply button").each(function() {
522                                 enhanceDeleteReplyButton(this, getReplyId(replyElement), deleteReplyText);
523                         });
524                 });
525         })(replyElement);
526         addCommentLink(getPostId(replyElement), replyElement);
527 }
528
529 /**
530  * Ajaxifies the given notification by replacing the form with AJAX.
531  *
532  * @param notification
533  *            jQuery object representing the notification.
534  */
535 function ajaxifyNotification(notification) {
536         notification.find("form.dismiss").submit(function() {
537                 return false;
538         });
539         notification.find("form.dismiss button").click(function() {
540                 $.getJSON("ajax/dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) {
541                         /* dismiss in case of error, too. */
542                         notification.slideUp();
543                 }, function(xmlHttpRequest, textStatus, error) {
544                         /* ignore error. */
545                 });
546         });
547         return notification;
548 }
549
550 function getStatus() {
551         $.getJSON("ajax/getStatus.ajax", {}, function(data, textStatus) {
552                 if ((data != null) && data.success) {
553                         /* process Sone information. */
554                         $.each(data.sones, function(index, value) {
555                                 updateSoneStatus(value.id, value.name, value.status, value.modified, value.locked, value.lastUpdated);
556                         });
557                         /* process notifications. */
558                         $.each(data.notifications, function(index, value) {
559                                 oldNotification = $("#sone #notification-area .notification#" + value.id);
560                                 notification = ajaxifyNotification(createNotification(value.id, value.text, value.dismissable)).hide();
561                                 if (oldNotification.length != 0) {
562                                         oldNotification.replaceWith(notification.show());
563                                 } else {
564                                         $("#sone #notification-area").append(notification);
565                                         notification.slideDown();
566                                 }
567                         });
568                         $.each(data.removedNotifications, function(index, value) {
569                                 $("#sone #notification-area .notification#" + value.id).slideUp();
570                         });
571                         /* process new posts. */
572                         $.each(data.newPosts, function(index, value) {
573                                 loadNewPost(value);
574                         });
575                         /* process new replies. */
576                         $.each(data.newReplies, function(index, value) {
577                                 loadNewReply(value);
578                         });
579                         /* do it again in 5 seconds. */
580                         setTimeout(getStatus, 5000);
581                 } else {
582                         /* data.success was false, wait 30 seconds. */
583                         setTimeout(getStatus, 30000);
584                 }
585         }, function(xmlHttpRequest, textStatus, error) {
586                 /* something really bad happend, wait a minute. */
587                 setTimeout(getStatus, 60000);
588         })
589 }
590
591 var loadedPosts = {};
592 var loadedReplies = {};
593
594 function loadNewPost(postId) {
595         if (postId in loadedPosts) {
596                 return;
597         }
598         loadedPosts[postId] = true;
599         $.getJSON("ajax/getPost.ajax", { "post" : postId }, function(data, textStatus) {
600                 if ((data != null) && data.success) {
601                         var firstOlderPost = null;
602                         $("#sone .post").each(function() {
603                                 if (getPostTime(this) < data.post.time) {
604                                         firstOlderPost = $(this);
605                                         return false;
606                                 }
607                         });
608                         newPost = $(data.post.html).addClass("hidden");
609                         if (firstOlderPost != null) {
610                                 newPost.insertBefore(firstOlderPost);
611                         } else {
612                                 $("#sone #posts").append(newPost);
613                         }
614                         ajaxifyPost(newPost);
615                         newPost.slideDown();
616                 }
617         });
618 }
619
620 function loadNewReply(replyId) {
621         if (replyId in loadedReplies) {
622                 return;
623         }
624         loadedReplies[replyId] = true;
625         $.getJSON("ajax/getReply.ajax", { "reply": replyId }, function(data, textStatus) {
626                 /* find post. */
627                 if ((data != null) && data.success) {
628                         $("#sone .post#" + data.reply.postId).each(function() {
629                                 var firstNewerReply = null;
630                                 $(this).find(".replies .reply").each(function() {
631                                         if (getReplyTime(this) > data.reply.time) {
632                                                 firstNewerReply = $(this);
633                                                 return false;
634                                         }
635                                 });
636                                 newReply = $(data.reply.html).addClass("hidden");
637                                 if (firstNewerReply != null) {
638                                         newReply.insertBefore(firstNewerReply);
639                                 } else {
640                                         if ($(this).find(".replies .create-reply")) {
641                                                 $(this).find(".replies .create-reply").before(newReply);
642                                         } else {
643                                                 $(this).find(".replies").append(newReply);
644                                         }
645                                 }
646                                 ajaxifyReply(newReply);
647                                 newReply.slideDown();
648                         });
649                 }
650         });
651 }
652
653 function markPostAsKnown(postElements) {
654         $(postElements).each(function() {
655                 postElement = this;
656                 if ($(postElement).hasClass("new")) {
657                         $.getJSON("ajax/markPostAsKnown.ajax", {"formPassword": getFormPassword(), "post": getPostId(postElement)}, function(data, textStatus) {
658                                 $(postElement).removeClass("new");
659                         });
660                 }
661         });
662         markReplyAsKnown($(postElements).find(".reply"));
663 }
664
665 function markReplyAsKnown(replyElements) {
666         $(replyElements).each(function() {
667                 replyElement = this;
668                 if ($(replyElement).hasClass("new")) {
669                         $.getJSON("ajax/markReplyAsKnown.ajax", {"formPassword": getFormPassword(), "reply": getReplyId(replyElement)}, function(data, textStatus) {
670                                 $(replyElement).removeClass("new");
671                         });
672                 }
673         });
674 }
675
676 /**
677  * Creates a new notification.
678  *
679  * @param id
680  *            The ID of the notificaiton
681  * @param text
682  *            The text of the notification
683  * @param dismissable
684  *            <code>true</code> if the notification can be dismissed by the
685  *            user
686  */
687 function createNotification(id, text, dismissable) {
688         notification = $("<div></div>").addClass("notification").attr("id", id);
689         if (dismissable) {
690                 dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id")
691                 dismissForm.find("input[name=notification]").val(id);
692                 notification.append(dismissForm);
693         }
694         notification.append(text);
695         return notification;
696 }
697
698 //
699 // EVERYTHING BELOW HERE IS EXECUTED AFTER LOADING THE PAGE
700 //
701
702 $(document).ready(function() {
703
704         /* this initializes the status update input field. */
705         getTranslation("WebInterface.DefaultText.StatusUpdate", function(defaultText) {
706                 registerInputTextareaSwap("#sone #update-status .status-input", defaultText, "text", false, false);
707                 $("#sone #update-status").submit(function() {
708                         text = $(this).find(":input:enabled").val();
709                         $.getJSON("ajax/createPost.ajax", { "formPassword": getFormPassword(), "text": text }, function(data, textStatus) {
710                                 if ((data != null) && data.success) {
711                                         loadNewPost(data.postId);
712                                 }
713                         });
714                         $(this).find(":input:enabled").val("").blur();
715                         return false;
716                 });
717         });
718
719         /* Ajaxifies all posts. */
720         /* calling getTranslation here will cache the necessary values. */
721         getTranslation("WebInterface.Confirmation.DeletePostButton", function(text) {
722                 getTranslation("WebInterface.Confirmation.DeleteReplyButton", function(text) {
723                         getTranslation("WebInterface.DefaultText.Reply", function(text) {
724                                 $("#sone .post").each(function() {
725                                         ajaxifyPost(this);
726                                 });
727                         });
728                 });
729         });
730
731         /* hides all replies but the latest two. */
732         getTranslation("WebInterface.ClickToShow.Replies", function(text) {
733                 $("#sone .post .replies").each(function() {
734                         allReplies = $(this).find(".reply");
735                         if (allReplies.length > 2) {
736                                 newHidden = false;
737                                 for (replyIndex = 0; replyIndex < (allReplies.length - 2); ++replyIndex) {
738                                         $(allReplies[replyIndex]).addClass("hidden");
739                                         newHidden |= $(allReplies[replyIndex]).hasClass("new");
740                                 }
741                                 clickToShowElement = $("<div></div>").addClass("click-to-show");
742                                 if (newHidden) {
743                                         clickToShowElement.addClass("new");
744                                 }
745                                 (function(clickToShowElement, allReplies, text) {
746                                         clickToShowElement.text(text);
747                                         clickToShowElement.click(function() {
748                                                 allReplies.removeClass("hidden");
749                                                 clickToShowElement.addClass("hidden");
750                                         });
751                                 })(clickToShowElement, allReplies, text);
752                                 $(allReplies[0]).before(clickToShowElement);
753                         }
754                 });
755         });
756
757         /*
758          * convert all “follow”, “unfollow”, “lock”, and “unlock” links to something
759          * nicer.
760          */
761         $("#sone .follow").submit(function() {
762                 var followElement = this;
763                 $.getJSON("ajax/followSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
764                         $(followElement).addClass("hidden");
765                         $(followElement).parent().find(".unfollow").removeClass("hidden");
766                 });
767                 return false;
768         });
769         $("#sone .unfollow").submit(function() {
770                 var unfollowElement = this;
771                 $.getJSON("ajax/unfollowSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
772                         $(unfollowElement).addClass("hidden");
773                         $(unfollowElement).parent().find(".follow").removeClass("hidden");
774                 });
775                 return false;
776         });
777         $("#sone .lock").submit(function() {
778                 var lockElement = this;
779                 $.getJSON("ajax/lockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
780                         $(lockElement).addClass("hidden");
781                         $(lockElement).parent().find(".unlock").removeClass("hidden");
782                 });
783                 return false;
784         });
785         $("#sone .unlock").submit(function() {
786                 var unlockElement = this;
787                 $.getJSON("ajax/unlockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
788                         $(unlockElement).addClass("hidden");
789                         $(unlockElement).parent().find(".lock").removeClass("hidden");
790                 });
791                 return false;
792         });
793
794         /* process all existing notifications, ajaxify dismiss buttons. */
795         $("#sone #notification-area .notification").each(function() {
796                 ajaxifyNotification($(this));
797         });
798
799         /* activate status polling. */
800         setTimeout(getStatus, 5000);
801 });