Factor out method that adds comment links to a single element.
[Sone.git] / src / main / resources / static / javascript / sone.js
1 /* Sone JavaScript functions. */
2
3 function isOnline() {
4         return $("#sone").hasClass("online");
5 }
6
7 function registerInputTextareaSwap(inputSelector, defaultText, inputFieldName, optional, dontUseTextarea) {
8         $(inputSelector).each(function() {
9                 textarea = $(dontUseTextarea ? "<input type=\"text\" name=\"" + inputFieldName + "\">" : "<textarea name=\"" + inputFieldName + "\"></textarea>").blur(function() {
10                         if ($(this).val() == "") {
11                                 $(this).hide();
12                                 inputField = $(this).data("inputField");
13                                 inputField.show().removeAttr("disabled").addClass("default");
14                                 inputField.val(defaultText);
15                         }
16                 }).hide().data("inputField", $(this)).val($(this).val());
17                 $(this).after(textarea);
18                 (function(inputField, textarea) {
19                         inputField.focus(function() {
20                                 $(this).hide().attr("disabled", "disabled");
21                                 textarea.show().focus();
22                         });
23                         if (inputField.val() == "") {
24                                 inputField.addClass("default");
25                                 inputField.val(defaultText);
26                         } else {
27                                 inputField.hide().attr("disabled", "disabled");
28                                 textarea.show();
29                         }
30                         $(inputField.get(0).form).submit(function() {
31                                 if (!optional && (textarea.val() == "")) {
32                                         return false;
33                                 }
34                         });
35                 })($(this), textarea);
36         });
37 }
38
39 /* hide all the “create reply” forms until a link is clicked. */
40 function addCommentLinks() {
41         if (!isOnline()) {
42                 return;
43         }
44         $("#sone .post").each(function() {
45                 postId = $(this).attr("id");
46                 addCommentLink(postId, $(this));
47         });
48 }
49
50 /**
51  * Adds a “comment” link to all status lines contained in the given element.
52  *
53  * @param postId
54  *            The ID of the post
55  * @param element
56  *            The element to add a “comment” link to
57  */
58 function addCommentLink(postId, element) {
59         commentElement = (function(postId) {
60                 var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
61                         replyElement = $("#sone .post#" + postId + " .create-reply");
62                         replyElement.removeClass("hidden");
63                         replyElement.removeClass("light");
64                         (function(replyElement) {
65                                 replyElement.find("input.reply-input").blur(function() {
66                                         if ($(this).hasClass("default")) {
67                                                 replyElement.addClass("light");
68                                         }
69                                 }).focus(function() {
70                                         replyElement.removeClass("light");
71                                 });
72                         })(replyElement);
73                         replyElement.find("input.reply-input").focus();
74                 });
75                 return commentElement;
76         })(postId);
77         element.find(".create-reply").addClass("hidden");
78         element.find(".status-line .time").each(function() {
79                 $(this).after(commentElement.clone(true));
80         });
81 }
82
83 /**
84  * Retrieves the translation for the given key and calls the callback function.
85  * The callback function takes a single parameter, the translated string.
86  *
87  * @param key
88  *            The key of the translation string
89  * @param callback
90  *            The callback function
91  */
92 function getTranslation(key, callback) {
93         $.getJSON("ajax/getTranslation.ajax", {"key": key}, function(data, textStatus) {
94                 callback(data.value);
95         });
96 }
97
98 /**
99  * Fires off an AJAX request to retrieve the current status of a Sone.
100  *
101  * @param soneId
102  *            The ID of the Sone
103  */
104 function getSoneStatus(soneId) {
105         $.getJSON("ajax/getSoneStatus.ajax", {"sone": soneId}, function(data, textStatus) {
106                 updateSoneStatus(soneId, data.name, data.status, data.modified, data.lastUpdated);
107                 /* seconds! */
108                 updateInterval = 60;
109                 if (data.modified || (data.status == "downloading") || (data.status == "inserting")) {
110                         updateInterval = 5;
111                 }
112                 setTimeout(function() {
113                         getSoneStatus(soneId);
114                 }, updateInterval * 1000);
115         });
116 }
117
118 /**
119  * Filters the given Sone ID, replacing all “~” characters by an underscore.
120  *
121  * @param soneId
122  *            The Sone ID to filter
123  * @returns The filtered Sone ID
124  */
125 function filterSoneId(soneId) {
126         return soneId.replace(/[^a-zA-Z0-9-]/g, "_");
127 }
128
129 /**
130  * Updates the status of the given Sone.
131  *
132  * @param soneId
133  *            The ID of the Sone to update
134  * @param status
135  *            The status of the Sone (“idle”, “unknown”, “inserting”,
136  *            “downloading”)
137  * @param modified
138  *            Whether the Sone is modified
139  * @param lastUpdated
140  *            The date and time of the last update (formatted for display)
141  */
142 function updateSoneStatus(soneId, name, status, modified, lastUpdated) {
143         $("#sone .sone." + filterSoneId(soneId)).
144                 toggleClass("unknown", status == "unknown").
145                 toggleClass("idle", status == "idle").
146                 toggleClass("inserting", status == "inserting").
147                 toggleClass("downloading", status == "downloading").
148                 toggleClass("modified", modified);
149         $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(lastUpdated);
150         $("#sone .sone." + filterSoneId(soneId) + " .profile-link a").text(name);
151 }
152
153 var watchedSones = {};
154
155 /**
156  * Watches this Sone for updates to its status.
157  *
158  * @param soneId
159  *            The ID of the Sone to watch
160  */
161 function watchSone(soneId) {
162         if (watchedSones[soneId]) {
163                 return;
164         }
165         watchedSones[soneId] = true;
166         (function(soneId) {
167                 setTimeout(function() {
168                         getSoneStatus(soneId);
169                 }, 5000);
170         })(soneId);
171 }
172
173 /**
174  * Enhances a “delete” button so that the confirmation is done on the same page.
175  *
176  * @param buttonId
177  *            The selector of the button
178  * @param text
179  *            The text to show on the button
180  * @param deleteCallback
181  *            The callback that actually deletes something
182  */
183 function enhanceDeleteButton(buttonId, text, deleteCallback) {
184         button = $(buttonId);
185         (function(button) {
186                 newButton = $("<button></button>").addClass("confirm").hide().text(text).click(function() {
187                         $(this).fadeOut("slow");
188                         deleteCallback();
189                         return false;
190                 }).insertAfter(button);
191                 (function(button, newButton) {
192                         button.click(function() {
193                                 button.fadeOut("slow", function() {
194                                         newButton.fadeIn("slow");
195                                         $(document).one("click", function() {
196                                                 if (this != newButton.get(0)) {
197                                                         newButton.fadeOut(function() {
198                                                                 button.fadeIn();
199                                                         });
200                                                 }
201                                         });
202                                 });
203                                 return false;
204                         });
205                 })(button, newButton);
206         })(button);
207 }
208
209 /**
210  * Enhances a post’s “delete” button.
211  *
212  * @param buttonId
213  *            The selector of the button
214  * @param postId
215  *            The ID of the post to delete
216  * @param text
217  *            The text to replace the button with
218  */
219 function enhanceDeletePostButton(buttonId, postId, text) {
220         enhanceDeleteButton(buttonId, text, function() {
221                 $.getJSON("ajax/deletePost.ajax", { "post": postId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
222                         if (data.success) {
223                                 $("#sone .post#" + postId).slideUp();
224                         } else if (data.error == "invalid-post-id") {
225                                 alert("Invalid post ID given!");
226                         } else if (data.error == "auth-required") {
227                                 alert("You need to be logged in.");
228                         } else if (data.error == "not-authorized") {
229                                 alert("You are not allowed to delete this post.");
230                         }
231                 });
232         });
233 }
234
235 /**
236  * Enhances a reply’s “delete” button.
237  *
238  * @param buttonId
239  *            The selector of the button
240  * @param replyId
241  *            The ID of the reply to delete
242  * @param text
243  *            The text to replace the button with
244  */
245 function enhanceDeleteReplyButton(buttonId, replyId, text) {
246         enhanceDeleteButton(buttonId, text, function() {
247                 $.getJSON("ajax/deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
248                         if (data.success) {
249                                 $("#sone .reply#" + replyId).slideUp();
250                         } else if (data.error == "invalid-reply-id") {
251                                 alert("Invalid reply ID given!");
252                         } else if (data.error == "auth-required") {
253                                 alert("You need to be logged in.");
254                         } else if (data.error == "not-authorized") {
255                                 alert("You are not allowed to delete this reply.");
256                         }
257                 });
258         });
259 }
260
261 function getFormPassword() {
262         return $("#sone #formPassword").text();
263 }
264
265 function getSoneElement(element) {
266         return $(element).parents(".sone");
267 }
268
269 /**
270  * Generates a list of Sones by concatening the names of the given sones with a
271  * new line character (“\n”).
272  *
273  * @param sones
274  *            The sones to format
275  * @returns {String} The created string
276  */
277 function generateSoneList(sones) {
278         var soneList = "";
279         $.each(sones, function() {
280                 if (soneList != "") {
281                         soneList += "\n";
282                 }
283                 soneList += this.name;
284         });
285         return soneList;
286 }
287
288 /**
289  * Returns the ID of the Sone that this element belongs to.
290  *
291  * @param element
292  *            The element to locate the matching Sone ID for
293  * @returns The ID of the Sone, or undefined
294  */
295 function getSoneId(element) {
296         return getSoneElement(element).find(".id").text();
297 }
298
299 function getPostElement(element) {
300         return $(element).parents(".post");
301 }
302
303 function getPostId(element) {
304         return getPostElement(element).attr("id");
305 }
306
307 function getReplyElement(element) {
308         return $(element).parents(".reply");
309 }
310
311 function getReplyId(element) {
312         return getReplyElement(element).attr("id");
313 }
314
315 function likePost(postId) {
316         $.getJSON("ajax/like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function() {
317                 $("#sone .post#" + postId + " > .inner-part > .status-line .like").addClass("hidden");
318                 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").removeClass("hidden");
319                 updatePostLikes(postId);
320         });
321 }
322
323 function unlikePost(postId) {
324         $.getJSON("ajax/unlike.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function() {
325                 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").addClass("hidden");
326                 $("#sone .post#" + postId + " > .inner-part > .status-line .like").removeClass("hidden");
327                 updatePostLikes(postId);
328         });
329 }
330
331 function updatePostLikes(postId) {
332         $.getJSON("ajax/getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) {
333                 if (data.success) {
334                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0)
335                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes span.like-count").text(data.likes);
336                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes > span").attr("title", generateSoneList(data.sones));
337                 }
338         });
339 }
340
341 function likeReply(replyId) {
342         $.getJSON("ajax/like.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function() {
343                 $("#sone .reply#" + replyId + " .status-line .like").addClass("hidden");
344                 $("#sone .reply#" + replyId + " .status-line .unlike").removeClass("hidden");
345                 updateReplyLikes(replyId);
346         });
347 }
348
349 function unlikeReply(replyId) {
350         $.getJSON("ajax/unlike.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function() {
351                 $("#sone .reply#" + replyId + " .status-line .unlike").addClass("hidden");
352                 $("#sone .reply#" + replyId + " .status-line .like").removeClass("hidden");
353                 updateReplyLikes(replyId);
354         });
355 }
356
357 function updateReplyLikes(replyId) {
358         $.getJSON("ajax/getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) {
359                 if (data.success) {
360                         $("#sone .reply#" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0)
361                         $("#sone .reply#" + replyId + " .status-line .likes span.like-count").text(data.likes);
362                         $("#sone .reply#" + replyId + " .status-line .likes > span").attr("title", generateSoneList(data.sones));
363                 }
364         });
365 }