Add online/offline marker in Sone top <div>.
[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) {
8         $(inputSelector).each(function() {
9                 textarea = $("<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                                 (function(inputField) {
15                                         getTranslation(defaultText, function(translation) {
16                                                 inputField.val(translation);
17                                         });
18                                 })(inputField);
19                         }
20                 }).hide().data("inputField", $(this)).val($(this).val());
21                 $(this).after(textarea);
22                 (function(inputField, textarea) {
23                         inputField.focus(function() {
24                                 $(this).hide().attr("disabled", "disabled");
25                                 textarea.show().focus();
26                         });
27                         if (inputField.val() == "") {
28                                 inputField.addClass("default");
29                                 (function(inputField) {
30                                         getTranslation(defaultText, function(translation) {
31                                                 inputField.val(translation);
32                                         });
33                                 })(inputField);
34                         } else {
35                                 inputField.hide().attr("disabled", "disabled");
36                                 textarea.show();
37                         }
38                         $(inputField.get(0).form).submit(function() {
39                                 if (!optional && (textarea.val() == "")) {
40                                         return false;
41                                 }
42                         });
43                 })($(this), textarea);
44         });
45 }
46
47 /* hide all the “create reply” forms until a link is clicked. */
48 function addCommentLinks() {
49         $("#sone .post").each(function() {
50                 postId = $(this).attr("id");
51                 commentElement = (function(postId) {
52                         var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
53                                 replyElement = $("#sone .post#" + postId + " .create-reply");
54                                 replyElement.removeClass("hidden");
55                                 replyElement.removeClass("light");
56                                 (function(replyElement) {
57                                         replyElement.find("input.reply-input").blur(function() {
58                                                 if ($(this).hasClass("default")) {
59                                                         replyElement.addClass("light");
60                                                 }
61                                         }).focus(function() {
62                                                 replyElement.removeClass("light");
63                                         });
64                                 })(replyElement);
65                                 replyElement.find("input.reply-input").focus();
66                         });
67                         return commentElement;
68                 })(postId);
69                 $(this).find(".create-reply").addClass("hidden");
70                 $(this).find(".status-line .time").each(function() {
71                         $(this).after(commentElement.clone(true));
72                 });
73         });
74 }
75
76 /**
77  * Retrieves the translation for the given key and calls the callback function.
78  * The callback function takes a single parameter, the translated string.
79  *
80  * @param key
81  *            The key of the translation string
82  * @param callback
83  *            The callback function
84  */
85 function getTranslation(key, callback) {
86         $.getJSON("ajax/getTranslation.ajax", {"key": key}, function(data, textStatus) {
87                 callback(data.value);
88         });
89 }
90
91 /**
92  * Fires off an AJAX request to retrieve the current status of a Sone.
93  *
94  * @param soneId
95  *            The ID of the Sone
96  */
97 function getSoneStatus(soneId) {
98         $.getJSON("ajax/getSoneStatus.ajax", {"sone": soneId}, function(data, textStatus) {
99                 updateSoneStatus(soneId, data.status, data.modified, data.lastUpdated);
100                 /* seconds! */
101                 updateInterval = 60;
102                 if (data.modified || (data.status == "downloading") || (data.status == "inserting")) {
103                         updateInterval = 5;
104                 }
105                 setTimeout(function() {
106                         getSoneStatus(soneId);
107                 }, updateInterval * 1000);
108         });
109 }
110
111 /**
112  * Updates the status of the given Sone.
113  *
114  * @param soneId
115  *            The ID of the Sone to update
116  * @param status
117  *            The status of the Sone (“idle”, “unknown”, “inserting”,
118  *            “downloading”)
119  * @param modified
120  *            Whether the Sone is modified
121  * @param lastUpdated
122  *            The date and time of the last update (formatted for display)
123  */
124 function updateSoneStatus(soneId, status, modified, lastUpdated) {
125         $("#sone .sone." + soneId).
126                 toggleClass("unknown", status == "unknown").
127                 toggleClass("idle", status == "idle").
128                 toggleClass("inserting", status == "inserting").
129                 toggleClass("downloading", status == "downloading").
130                 toggleClass("modified", modified);
131         $("#sone .sone." + soneId + " .last-update span.time").text(lastUpdated);
132 }
133
134 var watchedSones = {};
135
136 /**
137  * Watches this Sone for updates to its status.
138  *
139  * @param soneId
140  *            The ID of the Sone to watch
141  */
142 function watchSone(soneId) {
143         if (watchedSones[soneId]) {
144                 return;
145         }
146         watchedSones[soneId] = true;
147         (function(soneId) {
148                 setTimeout(function() {
149                         getSoneStatus(soneId);
150                 }, 5000);
151         })(soneId);
152 }
153
154 /**
155  * Enhances a “delete” button so that the confirmation is done on the same page.
156  *
157  * @param buttonId
158  *            The selector of the button
159  * @param translationKey
160  *            The translation key of the text to show on the button
161  * @param deleteCallback
162  *            The callback that actually deletes something
163  */
164 function enhanceDeleteButton(buttonId, translationKey, deleteCallback) {
165         button = $(buttonId);
166         (function(button) {
167                 getTranslation(translationKey, function(translation) {
168                         newButton = $("<button></button>").addClass("confirm").hide().text(translation).click(function() {
169                                 $(this).fadeOut("slow");
170                                 deleteCallback();
171                                 return false;
172                         }).insertAfter(button);
173                         (function(button, newButton) {
174                                 button.click(function() {
175                                         button.fadeOut("slow", function() {
176                                                 newButton.fadeIn("slow");
177                                                 $(document).one("click", function() {
178                                                         if (this != newButton.get(0)) {
179                                                                 newButton.fadeOut(function() {
180                                                                         button.fadeIn();
181                                                                 });
182                                                         }
183                                                 });
184                                         });
185                                         return false;
186                                 });
187                         })(button, newButton);
188                 });
189         })(button);
190 }
191
192 /**
193  * Enhances a post’s “delete” button.
194  *
195  * @param buttonId
196  *            The selector of the button
197  * @param postId
198  *            The ID of the post to delete
199  */
200 function enhanceDeletePostButton(buttonId, postId) {
201         enhanceDeleteButton(buttonId, "WebInterface.Confirmation.DeletePostButton", function() {
202                 $.getJSON("ajax/deletePost.ajax", { "post": postId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
203                         if (data.success) {
204                                 $("#sone .post#" + postId).slideUp();
205                         } else if (data.error == "invalid-post-id") {
206                                 alert("Invalid post ID given!");
207                         } else if (data.error == "auth-required") {
208                                 alert("You need to be logged in.");
209                         } else if (data.error == "not-authorized") {
210                                 alert("You are not allowed to delete this post.");
211                         }
212                 });
213         });
214 }
215
216 /**
217  * Enhances a reply’s “delete” button.
218  *
219  * @param buttonId
220  *            The selector of the button
221  * @param replyId
222  *            The ID of the reply to delete
223  */
224 function enhanceDeleteReplyButton(buttonId, replyId) {
225         enhanceDeleteButton(buttonId, "WebInterface.Confirmation.DeleteReplyButton", function() {
226                 $.getJSON("ajax/deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
227                         if (data.success) {
228                                 $("#sone .reply#" + replyId).slideUp();
229                         } else if (data.error == "invalid-reply-id") {
230                                 alert("Invalid reply ID given!");
231                         } else if (data.error == "auth-required") {
232                                 alert("You need to be logged in.");
233                         } else if (data.error == "not-authorized") {
234                                 alert("You are not allowed to delete this reply.");
235                         }
236                 });
237         });
238 }
239
240 function getFormPassword() {
241         return $("#sone #formPassword").text();
242 }
243
244 function getSoneElement(element) {
245         return $(element).parents(".sone");
246 }
247
248 /**
249  * Returns the ID of the Sone that this element belongs to.
250  *
251  * @param element
252  *            The element to locate the matching Sone ID for
253  * @returns The ID of the Sone, or undefined
254  */
255 function getSoneId(element) {
256         return getSoneElement(element).find(".id").text();
257 }
258
259 function getPostElement(element) {
260         return $(element).parents(".post");
261 }
262
263 function getPostId(element) {
264         return getPostElement(element).attr("id");
265 }
266
267 function likePost(postId) {
268         $.getJSON("ajax/likePost.ajax", { "post" : postId, "formPassword": getFormPassword() }, function() {
269                 $("#sone .post#" + postId + " > .status-line .like").addClass("hidden");
270                 $("#sone .post#" + postId + " > .status-line .unlike").removeClass("hidden");
271                 updatePostLikes(postId);
272         });
273 }
274
275 function unlikePost(postId) {
276         $.getJSON("ajax/unlikePost.ajax", { "post" : postId, "formPassword": getFormPassword() }, function() {
277                 $("#sone .post#" + postId + " > .status-line .unlike").addClass("hidden");
278                 $("#sone .post#" + postId + " > .status-line .like").removeClass("hidden");
279                 updatePostLikes(postId);
280         });
281 }
282
283 function updatePostLikes(postId) {
284         $.getJSON("ajax/getPostLikes.ajax", { "post": postId }, function(data, textStatus) {
285                 if (data.success) {
286                         $("#sone .post#" + postId + " > .status-line .likes span.like-count").text(data.likes);
287                 }
288         });
289 }