1 /* Sone JavaScript functions. */
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);
9 if (jQuery.isFunction(data)) {
10 errorCallback = successCallback;
11 successCallback = data;
17 success: successCallback,
23 return $("#sone").hasClass("online");
26 function registerInputTextareaSwap(inputSelector, defaultText, inputFieldName, optional, dontUseTextarea) {
27 $(inputSelector).each(function() {
28 textarea = $(dontUseTextarea ? "<input type=\"text\" name=\"" + inputFieldName + "\">" : "<textarea name=\"" + inputFieldName + "\"></textarea>").blur(function() {
29 if ($(this).val() == "") {
31 inputField = $(this).data("inputField");
32 inputField.show().removeAttr("disabled").addClass("default");
33 inputField.val(defaultText);
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();
42 if (inputField.val() == "") {
43 inputField.addClass("default");
44 inputField.val(defaultText);
46 inputField.hide().attr("disabled", "disabled");
49 $(inputField.get(0).form).submit(function() {
50 if (!optional && (textarea.val() == "")) {
54 })($(this), textarea);
58 /* hide all the “create reply” forms until a link is clicked. */
59 function addCommentLinks() {
63 $("#sone .post").each(function() {
64 postId = $(this).attr("id");
65 addCommentLink(postId, $(this));
70 * Adds a “comment” link to all status lines contained in the given element.
75 * The element to add a “comment” link to
77 function addCommentLink(postId, element) {
78 commentElement = (function(postId) {
79 var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
80 replyElement = $("#sone .post#" + postId + " .create-reply");
81 replyElement.removeClass("hidden");
82 replyElement.removeClass("light");
83 (function(replyElement) {
84 replyElement.find("input.reply-input").blur(function() {
85 if ($(this).hasClass("default")) {
86 replyElement.addClass("light");
89 replyElement.removeClass("light");
92 replyElement.find("input.reply-input").focus();
94 return commentElement;
96 element.find(".create-reply").addClass("hidden");
97 element.find(".status-line .time").each(function() {
98 $(this).after(commentElement.clone(true));
103 * Retrieves the translation for the given key and calls the callback function.
104 * The callback function takes a single parameter, the translated string.
107 * The key of the translation string
109 * The callback function
111 function getTranslation(key, callback) {
112 $.getJSON("ajax/getTranslation.ajax", {"key": key}, function(data, textStatus) {
113 if ((data != null) && data.success) {
114 callback(data.value);
116 }, function(xmlHttpRequest, textStatus, error) {
122 * Fires off an AJAX request to retrieve the current status of a Sone.
127 * <code>true</code> if the Sone is local, <code>false</code>
130 function getSoneStatus(soneId, local) {
131 $.getJSON("ajax/getSoneStatus.ajax", {"sone": soneId}, function(data, textStatus) {
132 if ((data != null) && data.success) {
133 updateSoneStatus(soneId, data.name, data.status, data.modified, data.locked, data.lastUpdated);
137 if (local || (data!= null) && (data.modified || (data.status == "downloading") || (data.status == "inserting"))) {
140 setTimeout(function() {
141 getSoneStatus(soneId, local);
142 }, updateInterval * 1000);
143 }, function(xmlHttpRequest, textStatus, error) {
149 * Filters the given Sone ID, replacing all “~” characters by an underscore.
152 * The Sone ID to filter
153 * @returns The filtered Sone ID
155 function filterSoneId(soneId) {
156 return soneId.replace(/[^a-zA-Z0-9-]/g, "_");
160 * Updates the status of the given Sone.
163 * The ID of the Sone to update
165 * The status of the Sone (“idle”, “unknown”, “inserting”,
168 * Whether the Sone is modified
170 * Whether the Sone is locked
172 * The date and time of the last update (formatted for display)
174 function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated) {
175 $("#sone .sone." + filterSoneId(soneId)).
176 toggleClass("unknown", status == "unknown").
177 toggleClass("idle", status == "idle").
178 toggleClass("inserting", status == "inserting").
179 toggleClass("downloading", status == "downloading").
180 toggleClass("modified", modified);
181 $("#sone .sone." + filterSoneId(soneId) + " .lock").toggleClass("hidden", locked);
182 $("#sone .sone." + filterSoneId(soneId) + " .unlock").toggleClass("hidden", !locked);
183 $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(lastUpdated);
184 $("#sone .sone." + filterSoneId(soneId) + " .profile-link a").text(name);
187 var watchedSones = {};
190 * Watches this Sone for updates to its status.
193 * The ID of the Sone to watch
195 * <code>true</code> if the Sone is local, <code>false</code>
198 function watchSone(soneId, local) {
199 if (watchedSones[soneId]) {
202 watchedSones[soneId] = true;
204 setTimeout(function() {
205 getSoneStatus(soneId, local);
211 * Enhances a “delete” button so that the confirmation is done on the same page.
214 * The selector of the button
216 * The text to show on the button
217 * @param deleteCallback
218 * The callback that actually deletes something
220 function enhanceDeleteButton(buttonId, text, deleteCallback) {
221 button = $(buttonId);
223 newButton = $("<button></button>").addClass("confirm").hide().text(text).click(function() {
224 $(this).fadeOut("slow");
227 }).insertAfter(button);
228 (function(button, newButton) {
229 button.click(function() {
230 button.fadeOut("slow", function() {
231 newButton.fadeIn("slow");
232 $(document).one("click", function() {
233 if (this != newButton.get(0)) {
234 newButton.fadeOut(function() {
242 })(button, newButton);
247 * Enhances a post’s “delete” button.
250 * The selector of the button
252 * The ID of the post to delete
254 * The text to replace the button with
256 function enhanceDeletePostButton(buttonId, postId, text) {
257 enhanceDeleteButton(buttonId, text, function() {
258 $.getJSON("ajax/deletePost.ajax", { "post": postId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
263 $("#sone .post#" + postId).slideUp();
264 } else if (data.error == "invalid-post-id") {
265 alert("Invalid post ID given!");
266 } else if (data.error == "auth-required") {
267 alert("You need to be logged in.");
268 } else if (data.error == "not-authorized") {
269 alert("You are not allowed to delete this post.");
271 }, function(xmlHttpRequest, textStatus, error) {
278 * Enhances a reply’s “delete” button.
281 * The selector of the button
283 * The ID of the reply to delete
285 * The text to replace the button with
287 function enhanceDeleteReplyButton(buttonId, replyId, text) {
288 enhanceDeleteButton(buttonId, text, function() {
289 $.getJSON("ajax/deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
294 $("#sone .reply#" + replyId).slideUp();
295 } else if (data.error == "invalid-reply-id") {
296 alert("Invalid reply ID given!");
297 } else if (data.error == "auth-required") {
298 alert("You need to be logged in.");
299 } else if (data.error == "not-authorized") {
300 alert("You are not allowed to delete this reply.");
302 }, function(xmlHttpRequest, textStatus, error) {
308 function getFormPassword() {
309 return $("#sone #formPassword").text();
312 function getSoneElement(element) {
313 return $(element).parents(".sone");
317 * Generates a list of Sones by concatening the names of the given sones with a
318 * new line character (“\n”).
321 * The sones to format
322 * @returns {String} The created string
324 function generateSoneList(sones) {
326 $.each(sones, function() {
327 if (soneList != "") {
330 soneList += this.name;
336 * Returns the ID of the Sone that this element belongs to.
339 * The element to locate the matching Sone ID for
340 * @returns The ID of the Sone, or undefined
342 function getSoneId(element) {
343 return getSoneElement(element).find(".id").text();
346 function getPostElement(element) {
347 return $(element).parents(".post");
350 function getPostId(element) {
351 return getPostElement(element).attr("id");
354 function getReplyElement(element) {
355 return $(element).parents(".reply");
358 function getReplyId(element) {
359 return getReplyElement(element).attr("id");
362 function likePost(postId) {
363 $.getJSON("ajax/like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
364 if ((data == null) || !data.success) {
367 $("#sone .post#" + postId + " > .inner-part > .status-line .like").addClass("hidden");
368 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").removeClass("hidden");
369 updatePostLikes(postId);
370 }, function(xmlHttpRequest, textStatus, error) {
375 function unlikePost(postId) {
376 $.getJSON("ajax/unlike.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
377 if ((data == null) || !data.success) {
380 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").addClass("hidden");
381 $("#sone .post#" + postId + " > .inner-part > .status-line .like").removeClass("hidden");
382 updatePostLikes(postId);
383 }, function(xmlHttpRequest, textStatus, error) {
388 function updatePostLikes(postId) {
389 $.getJSON("ajax/getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) {
390 if ((data != null) && data.success) {
391 $("#sone .post#" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0)
392 $("#sone .post#" + postId + " > .inner-part > .status-line .likes span.like-count").text(data.likes);
393 $("#sone .post#" + postId + " > .inner-part > .status-line .likes > span").attr("title", generateSoneList(data.sones));
395 }, function(xmlHttpRequest, textStatus, error) {
400 function likeReply(replyId) {
401 $.getJSON("ajax/like.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
402 if ((data == null) || !data.success) {
405 $("#sone .reply#" + replyId + " .status-line .like").addClass("hidden");
406 $("#sone .reply#" + replyId + " .status-line .unlike").removeClass("hidden");
407 updateReplyLikes(replyId);
408 }, function(xmlHttpRequest, textStatus, error) {
413 function unlikeReply(replyId) {
414 $.getJSON("ajax/unlike.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
415 if ((data == null) || !data.success) {
418 $("#sone .reply#" + replyId + " .status-line .unlike").addClass("hidden");
419 $("#sone .reply#" + replyId + " .status-line .like").removeClass("hidden");
420 updateReplyLikes(replyId);
421 }, function(xmlHttpRequest, textStatus, error) {
426 function updateReplyLikes(replyId) {
427 $.getJSON("ajax/getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) {
428 if ((data != null) && data.success) {
429 $("#sone .reply#" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0)
430 $("#sone .reply#" + replyId + " .status-line .likes span.like-count").text(data.likes);
431 $("#sone .reply#" + replyId + " .status-line .likes > span").attr("title", generateSoneList(data.sones));
433 }, function(xmlHttpRequest, textStatus, error) {
439 * Posts a reply and calls the given callback when the request finishes.
442 * The ID of the post the reply refers to
445 * @param callbackFunction
446 * The callback function to call when the request finishes (takes 3
447 * parameters: success, error, replyId)
449 function postReply(postId, text, callbackFunction) {
450 $.getJSON("ajax/createReply.ajax", { "formPassword" : getFormPassword(), "post" : postId, "text": text }, function(data, textStatus) {
452 /* TODO - show error */
456 callbackFunction(true, null, data.reply);
458 callbackFunction(false, data.error);
460 }, function(xmlHttpRequest, textStatus, error) {
466 * Requests information about the reply with the given ID.
469 * The ID of the reply
470 * @param callbackFunction
471 * A callback function (parameters soneId, soneName, replyTime,
472 * replyDisplayTime, text, html)
474 function getReply(replyId, callbackFunction) {
475 $.getJSON("ajax/getReply.ajax", { "reply" : replyId }, function(data, textStatus) {
476 if ((data != null) && data.success) {
477 callbackFunction(data.soneId, data.soneName, data.time, data.displayTime, data.text, data.html);
479 }, function(xmlHttpRequest, textStatus, error) {
485 * Ajaxifies the given notification by replacing the form with AJAX.
487 * @param notification
488 * jQuery object representing the notification.
490 function ajaxifyNotification(notification) {
491 notification.find("form.dismiss").submit(function() {
494 notification.find("form.dismiss button").click(function() {
495 $.getJSON("ajax/dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) {
496 /* dismiss in case of error, too. */
497 notification.slideUp();
498 }, function(xmlHttpRequest, textStatus, error) {
506 * Retrieves all changed notifications.
508 function getNotifications() {
509 $.getJSON("ajax/getNotifications.ajax", {}, function(data, textStatus) {
510 if ((data != null) && data.success) {
511 $.each(data.notifications, function(index, value) {
512 oldNotification = $("#sone #notification-area .notification#" + value.id);
513 notification = ajaxifyNotification(createNotification(value.id, value.text, value.dismissable)).hide();
514 if (oldNotification.length != 0) {
515 oldNotification.replaceWith(notification.show());
517 $("#sone #notification-area").append(notification);
518 notification.slideDown();
521 $.each(data.removedNotifications, function(index, value) {
522 $("#sone #notification-area .notification#" + value.id).slideUp();
524 setTimeout(getNotifications, 5000);
526 setTimeout(getNotifications, 30000);
528 }, function(xmlHttpRequest, textStatus, error) {
534 * Creates a new notification.
537 * The ID of the notificaiton
539 * The text of the notification
541 * <code>true</code> if the notification can be dismissed by the
544 function createNotification(id, text, dismissable) {
545 notification = $("<div></div>").addClass("notification").attr("id", id);
547 dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id")
548 dismissForm.find("input[name=notification]").val(id);
549 notification.append(dismissForm);
551 notification.append(text);