+function getReply(replyId, callbackFunction) {
+ $.getJSON("getReply.ajax", { "reply" : replyId }, function(data, textStatus) {
+ if ((data != null) && data.success) {
+ callbackFunction(data.soneId, data.soneName, data.time, data.displayTime, data.text, data.html);
+ }
+ }, function(xmlHttpRequest, textStatus, error) {
+ /* ignore error. */
+ });
+}
+
+/**
+ * Ajaxifies the given post by enhancing all eligible elements with AJAX.
+ *
+ * @param postElement
+ * The post element to ajaxify
+ */
+function ajaxifyPost(postElement) {
+ $(postElement).find("form").submit(function() {
+ return false;
+ });
+ $(postElement).find(".create-reply button:submit").click(function() {
+ inputField = $(this.form).find(":input:enabled").get(0);
+ postId = getPostId(this);
+ text = $(inputField).val();
+ $(inputField).val("");
+ postReply(postId, text, function(success, error, replyId) {
+ if (success) {
+ loadNewReply(replyId);
+ markPostAsKnown(getPostElement(inputField));
+ $("#sone .post#" + postId + " .create-reply").addClass("hidden");
+ } else {
+ alert(error);
+ }
+ });
+ return false;
+ });
+
+ /* replace all “delete” buttons with javascript. */
+ (function(postElement) {
+ getTranslation("WebInterface.Confirmation.DeletePostButton", function(deletePostText) {
+ postId = getPostId(postElement);
+ enhanceDeletePostButton($(postElement).find(".delete-post button"), postId, deletePostText);
+ });
+ })(postElement);
+
+ /* convert all “like” buttons to javascript functions. */
+ $(postElement).find(".like-post").submit(function() {
+ likePost(getPostId(this));
+ markPostAsKnown(getPostElement(this));
+ return false;
+ });
+ $(postElement).find(".unlike-post").submit(function() {
+ unlikePost(getPostId(this));
+ markPostAsKnown(getPostElement(this));
+ return false;
+ });
+
+ /* add “comment” link. */
+ addCommentLink(getPostId(postElement), postElement, $(postElement).find(".post-status-line .time"));
+
+ /* process all replies. */
+ $(postElement).find(".reply").each(function() {
+ ajaxifyReply(this);
+ });
+
+ /* process reply input fields. */
+ getTranslation("WebInterface.DefaultText.Reply", function(text) {
+ $(postElement).find("input.reply-input").each(function() {
+ registerInputTextareaSwap(this, text, "text", false, false);
+ });
+ });
+
+ /* mark everything as known on click. */
+ $(postElement).click(function() {
+ markPostAsKnown(this);
+ });
+
+ /* hide reply input field. */
+ $(postElement).find(".create-reply").addClass("hidden");
+}
+
+/**
+ * Ajaxifies the given reply element.
+ *
+ * @param replyElement
+ * The reply element to ajaxify
+ */
+function ajaxifyReply(replyElement) {
+ $(replyElement).find(".like-reply").submit(function() {
+ likeReply(getReplyId(this));
+ markPostAsKnown(getPostElement(this));
+ return false;
+ });
+ $(replyElement).find(".unlike-reply").submit(function() {
+ unlikeReply(getReplyId(this));
+ markPostAsKnown(getPostElement(this));
+ return false;
+ });
+ (function(replyElement) {
+ getTranslation("WebInterface.Confirmation.DeleteReplyButton", function(deleteReplyText) {
+ $(replyElement).find(".delete-reply button").each(function() {
+ enhanceDeleteReplyButton(this, getReplyId(replyElement), deleteReplyText);
+ });
+ });
+ })(replyElement);
+ addCommentLink(getPostId(replyElement), replyElement, $(replyElement).find(".reply-status-line .time"));
+
+ /* mark post and all replies as known on click. */
+ $(replyElement).click(function() {
+ markPostAsKnown(getPostElement(replyElement));
+ });
+}
+
+/**
+ * Ajaxifies the given notification by replacing the form with AJAX.
+ *
+ * @param notification
+ * jQuery object representing the notification.
+ */
+function ajaxifyNotification(notification) {
+ notification.find("form.dismiss").submit(function() {
+ return false;
+ });
+ notification.find("form.dismiss button").click(function() {
+ $.getJSON("dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) {
+ /* dismiss in case of error, too. */
+ notification.slideUp();
+ }, function(xmlHttpRequest, textStatus, error) {
+ /* ignore error. */
+ });
+ });
+ return notification;
+}
+
+function getStatus() {
+ $.getJSON("getStatus.ajax", {"loadAllSones": isKnownSonesPage()}, function(data, textStatus) {
+ if ((data != null) && data.success) {
+ /* process Sone information. */
+ $.each(data.sones, function(index, value) {
+ updateSoneStatus(value.id, value.name, value.status, value.modified, value.locked, value.lastUpdated);
+ });
+ /* process notifications. */
+ $.each(data.notifications, function(index, value) {
+ oldNotification = $("#sone #notification-area .notification#" + value.id);
+ notification = ajaxifyNotification(createNotification(value.id, value.text, value.dismissable)).hide();
+ if (oldNotification.length != 0) {
+ oldNotification.replaceWith(notification.show());
+ } else {
+ $("#sone #notification-area").append(notification);
+ notification.slideDown();
+ }
+ setActivity();
+ });
+ $.each(data.removedNotifications, function(index, value) {
+ $("#sone #notification-area .notification#" + value.id).slideUp();
+ });
+ /* process new posts. */
+ $.each(data.newPosts, function(index, value) {
+ loadNewPost(value);
+ });
+ /* process new replies. */
+ $.each(data.newReplies, function(index, value) {
+ loadNewReply(value);
+ });
+ /* do it again in 5 seconds. */
+ setTimeout(getStatus, 5000);
+ } else {
+ /* data.success was false, wait 30 seconds. */
+ setTimeout(getStatus, 30000);
+ }
+ }, function(xmlHttpRequest, textStatus, error) {
+ /* something really bad happend, wait a minute. */
+ setTimeout(getStatus, 60000);
+ })
+}
+
+/**
+ * Returns the content of the page-id attribute.
+ *
+ * @returns The page ID
+ */
+function getPageId() {
+ return $("#sone .page-id").text();
+}
+
+/**
+ * Returns whether the current page is the index page.
+ *
+ * @returns {Boolean} <code>true</code> if the current page is the index page,
+ * <code>false</code> otherwise
+ */
+function isIndexPage() {
+ return getPageId() == "index";
+}
+
+/**
+ * Returns whether the current page is a “view Sone” page.
+ *
+ * @returns {Boolean} <code>true</code> if the current page is a “view Sone”
+ * page, <code>false</code> otherwise
+ */
+function isViewSonePage() {
+ return getPageId() == "view-sone";
+}
+
+/**
+ * Returns the ID of the currently shown Sone. This will only return a sensible
+ * value if isViewSonePage() returns <code>true</code>.
+ *
+ * @returns The ID of the currently shown Sone
+ */
+function getShownSoneId() {
+ return $("#sone .sone-id").text();
+}
+
+/**
+ * Returns whether the current page is a “view post” page.
+ *
+ * @returns {Boolean} <code>true</code> if the current page is a “view post”
+ * page, <code>false</code> otherwise
+ */
+function isViewPostPage() {
+ return getPageId() == "view-post";
+}
+
+/**
+ * Returns the ID of the currently shown post. This will only return a sensible
+ * value if isViewPostPage() returns <code>true</code>.
+ *
+ * @returns The ID of the currently shown post
+ */
+function getShownPostId() {
+ return $("#sone .post-id").text();
+}
+
+/**
+ * Returns whether the current page is the “known Sones” page.
+ *
+ * @returns {Boolean} <code>true</code> if the current page is the “known
+ * Sones” page, <code>false</code> otherwise
+ */
+function isKnownSonesPage() {
+ return getPageId() == "known-sones";
+}
+
+var loadedPosts = {};
+var loadedReplies = {};
+
+function loadNewPost(postId) {
+ if (postId in loadedPosts) {