Merge branch 'edit-wot-trust' into next
[Sone.git] / src / main / resources / static / javascript / sone.js
index 39999e9..80d039c 100644 (file)
@@ -63,11 +63,12 @@ function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, op
  * @param element
  *            The element to add a “comment” link to
  */
-function addCommentLink(postId, element) {
+function addCommentLink(postId, element, insertAfterThisElement) {
        if ($(element).find(".show-reply-form").length > 0) {
                return;
        }
        commentElement = (function(postId) {
+               separator = $("<span> · </span>").addClass("separator");
                var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
                        markPostAsKnown(getPostElement(this));
                        replyElement = $("#sone .post#" + postId + " .create-reply");
@@ -86,9 +87,8 @@ function addCommentLink(postId, element) {
                });
                return commentElement;
        })(postId);
-       $(element).find(".status-line .time").each(function() {
-               $(this).after(commentElement.clone(true));
-       });
+       $(insertAfterThisElement).after(commentElement.clone(true));
+       $(insertAfterThisElement).after(separator);
 }
 
 var translations = {};
@@ -107,7 +107,7 @@ function getTranslation(key, callback) {
                callback(translations[key]);
                return;
        }
-       $.getJSON("ajax/getTranslation.ajax", {"key": key}, function(data, textStatus) {
+       $.getJSON("getTranslation.ajax", {"key": key}, function(data, textStatus) {
                if ((data != null) && data.success) {
                        translations[key] = data.value;
                        callback(data.value);
@@ -203,7 +203,7 @@ function enhanceDeleteButton(button, text, deleteCallback) {
  */
 function enhanceDeletePostButton(button, postId, text) {
        enhanceDeleteButton(button, text, function() {
-               $.getJSON("ajax/deletePost.ajax", { "post": postId, "formPassword": getFormPassword() }, function(data, textStatus) {
+               $.getJSON("deletePost.ajax", { "post": postId, "formPassword": getFormPassword() }, function(data, textStatus) {
                        if (data == null) {
                                return;
                        }
@@ -234,7 +234,7 @@ function enhanceDeletePostButton(button, postId, text) {
  */
 function enhanceDeleteReplyButton(button, replyId, text) {
        enhanceDeleteButton(button, text, function() {
-               $.getJSON("ajax/deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
+               $.getJSON("deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
                        if (data == null) {
                                return;
                        }
@@ -258,7 +258,7 @@ function getFormPassword() {
 }
 
 function getSoneElement(element) {
-       return $(element).parents(".sone");
+       return $(element).closest(".sone");
 }
 
 /**
@@ -303,6 +303,17 @@ function getPostTime(element) {
        return getPostElement(element).find(".post-time").text();
 }
 
+/**
+ * Returns the author of the post the given element belongs to.
+ *
+ * @param element
+ *            The element whose post to get the author for
+ * @returns The ID of the authoring Sone
+ */
+function getPostAuthor(element) {
+       return getPostElement(element).find(".post-author").text();
+}
+
 function getReplyElement(element) {
        return $(element).closest(".reply");
 }
@@ -315,8 +326,19 @@ function getReplyTime(element) {
        return getReplyElement(element).find(".reply-time").text();
 }
 
+/**
+ * Returns the author of the reply the given element belongs to.
+ *
+ * @param element
+ *            The element whose reply to get the author for
+ * @returns The ID of the authoring Sone
+ */
+function getReplyAuthor(element) {
+       return getReplyElement(element).find(".reply-author").text();
+}
+
 function likePost(postId) {
-       $.getJSON("ajax/like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
+       $.getJSON("like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
                if ((data == null) || !data.success) {
                        return;
                }
@@ -329,7 +351,7 @@ function likePost(postId) {
 }
 
 function unlikePost(postId) {
-       $.getJSON("ajax/unlike.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
+       $.getJSON("unlike.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
                if ((data == null) || !data.success) {
                        return;
                }
@@ -342,7 +364,7 @@ function unlikePost(postId) {
 }
 
 function updatePostLikes(postId) {
-       $.getJSON("ajax/getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) {
+       $.getJSON("getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) {
                if ((data != null) && data.success) {
                        $("#sone .post#" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0)
                        $("#sone .post#" + postId + " > .inner-part > .status-line .likes span.like-count").text(data.likes);
@@ -354,7 +376,7 @@ function updatePostLikes(postId) {
 }
 
 function likeReply(replyId) {
-       $.getJSON("ajax/like.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
+       $.getJSON("like.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
                if ((data == null) || !data.success) {
                        return;
                }
@@ -367,7 +389,7 @@ function likeReply(replyId) {
 }
 
 function unlikeReply(replyId) {
-       $.getJSON("ajax/unlike.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
+       $.getJSON("unlike.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
                if ((data == null) || !data.success) {
                        return;
                }
@@ -379,8 +401,76 @@ function unlikeReply(replyId) {
        });
 }
 
+/**
+ * Trusts the Sone with the given ID.
+ *
+ * @param soneId
+ *            The ID of the Sone to trust
+ */
+function trustSone(soneId) {
+       $.getJSON("trustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) {
+               if ((data != null) && data.success) {
+                       updateTrustControls(soneId, data.trustValue);
+               }
+       });
+}
+
+/**
+ * Distrusts the Sone with the given ID, i.e. assigns a negative trust value.
+ *
+ * @param soneId
+ *            The ID of the Sone to distrust
+ */
+function distrustSone(soneId) {
+       $.getJSON("distrustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) {
+               if ((data != null) && data.success) {
+                       updateTrustControls(soneId, data.trustValue);
+               }
+       });
+}
+
+/**
+ * Untrusts the Sone with the given ID, i.e. removes any trust assignment.
+ *
+ * @param soneId
+ *            The ID of the Sone to untrust
+ */
+function untrustSone(soneId) {
+       $.getJSON("untrustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) {
+               if ((data != null) && data.success) {
+                       updateTrustControls(soneId, data.trustValue);
+               }
+       });
+}
+
+/**
+ * Updates the trust controls for all posts and replies of the given Sone,
+ * according to the given trust value.
+ *
+ * @param soneId
+ *            The ID of the Sone to update all trust controls for
+ * @param trustValue
+ *            The trust value for the Sone
+ */
+function updateTrustControls(soneId, trustValue) {
+       $("#sone .post").each(function() {
+               if (getPostAuthor(this) == soneId) {
+                       getPostElement(this).find(".post-trust").toggleClass("hidden", trustValue != null);
+                       getPostElement(this).find(".post-distrust").toggleClass("hidden", (trustValue != null) && (trustValue < 0));
+                       getPostElement(this).find(".post-untrust").toggleClass("hidden", trustValue == null);
+               }
+       });
+       $("#sone .reply").each(function() {
+               if (getReplyAuthor(this) == soneId) {
+                       getReplyElement(this).find(".reply-trust").toggleClass("hidden", trustValue != null);
+                       getReplyElement(this).find(".reply-distrust").toggleClass("hidden", (trustValue != null) && (trustValue < 0));
+                       getReplyElement(this).find(".reply-untrust").toggleClass("hidden", trustValue == null);
+               }
+       });
+}
+
 function updateReplyLikes(replyId) {
-       $.getJSON("ajax/getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) {
+       $.getJSON("getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) {
                if ((data != null) && data.success) {
                        $("#sone .reply#" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0)
                        $("#sone .reply#" + replyId + " .status-line .likes span.like-count").text(data.likes);
@@ -403,7 +493,7 @@ function updateReplyLikes(replyId) {
  *            parameters: success, error, replyId)
  */
 function postReply(postId, text, callbackFunction) {
-       $.getJSON("ajax/createReply.ajax", { "formPassword" : getFormPassword(), "post" : postId, "text": text }, function(data, textStatus) {
+       $.getJSON("createReply.ajax", { "formPassword" : getFormPassword(), "post" : postId, "text": text }, function(data, textStatus) {
                if (data == null) {
                        /* TODO - show error */
                        return;
@@ -428,7 +518,7 @@ function postReply(postId, text, callbackFunction) {
  *            replyDisplayTime, text, html)
  */
 function getReply(replyId, callbackFunction) {
-       $.getJSON("ajax/getReply.ajax", { "reply" : replyId }, function(data, textStatus) {
+       $.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);
                }
@@ -451,16 +541,18 @@ function ajaxifyPost(postElement) {
                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);
-                       }
-               });
+               (function(postId, text, inputField) {
+                       postReply(postId, text, function(success, error, replyId) {
+                               if (success) {
+                                       $(inputField).val("");
+                                       loadNewReply(replyId, getCurrentSoneId(), postId);
+                                       markPostAsKnown(getPostElement(inputField));
+                                       $("#sone .post#" + postId + " .create-reply").addClass("hidden");
+                               } else {
+                                       alert(error);
+                               }
+                       });
+               })(postId, text, inputField);
                return false;
        });
 
@@ -484,6 +576,20 @@ function ajaxifyPost(postElement) {
                return false;
        });
 
+       /* convert trust control buttons to javascript functions. */
+       $(postElement).find(".post-trust").submit(function() {
+               trustSone(getPostAuthor(this));
+               return false;
+       });
+       $(postElement).find(".post-distrust").submit(function() {
+               distrustSone(getPostAuthor(this));
+               return false;
+       });
+       $(postElement).find(".post-untrust").submit(function() {
+               untrustSone(getPostAuthor(this));
+               return false;
+       });
+
        /* add “comment” link. */
        addCommentLink(getPostId(postElement), postElement, $(postElement).find(".post-status-line .time"));
 
@@ -499,6 +605,14 @@ function ajaxifyPost(postElement) {
                });
        });
 
+       /* mark everything as known on click. */
+       $(postElement).click(function(event) {
+               if ($(event.target).hasClass("click-to-show")) {
+                       return false;
+               }
+               markPostAsKnown(this);
+       });
+
        /* hide reply input field. */
        $(postElement).find(".create-reply").addClass("hidden");
 }
@@ -527,7 +641,26 @@ function ajaxifyReply(replyElement) {
                        });
                });
        })(replyElement);
-       addCommentLink(getPostId(replyElement), replyElement);
+       addCommentLink(getPostId(replyElement), replyElement, $(replyElement).find(".reply-status-line .time"));
+
+       /* convert trust control buttons to javascript functions. */
+       $(replyElement).find(".reply-trust").submit(function() {
+               trustSone(getReplyAuthor(this));
+               return false;
+       });
+       $(replyElement).find(".reply-distrust").submit(function() {
+               distrustSone(getReplyAuthor(this));
+               return false;
+       });
+       $(replyElement).find(".reply-untrust").submit(function() {
+               untrustSone(getReplyAuthor(this));
+               return false;
+       });
+
+       /* mark post and all replies as known on click. */
+       $(replyElement).click(function() {
+               markPostAsKnown(getPostElement(this));
+       });
 }
 
 /**
@@ -541,7 +674,7 @@ function ajaxifyNotification(notification) {
                return false;
        });
        notification.find("form.dismiss button").click(function() {
-               $.getJSON("ajax/dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) {
+               $.getJSON("dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) {
                        /* dismiss in case of error, too. */
                        notification.slideUp();
                }, function(xmlHttpRequest, textStatus, error) {
@@ -552,7 +685,7 @@ function ajaxifyNotification(notification) {
 }
 
 function getStatus() {
-       $.getJSON("ajax/getStatus.ajax", {}, function(data, textStatus) {
+       $.getJSON("getStatus.ajax", {"loadAllSones": isKnownSonesPage()}, function(data, textStatus) {
                if ((data != null) && data.success) {
                        /* process Sone information. */
                        $.each(data.sones, function(index, value) {
@@ -575,11 +708,11 @@ function getStatus() {
                        });
                        /* process new posts. */
                        $.each(data.newPosts, function(index, value) {
-                               loadNewPost(value);
+                               loadNewPost(value.id, value.sone, value.recipient, value.time);
                        });
                        /* process new replies. */
                        $.each(data.newReplies, function(index, value) {
-                               loadNewReply(value);
+                               loadNewReply(value.id, value.sone, value.post, value.postSone);
                        });
                        /* do it again in 5 seconds. */
                        setTimeout(getStatus, 5000);
@@ -593,16 +726,131 @@ function getStatus() {
        })
 }
 
-var loadedPosts = {};
-var loadedReplies = {};
+/**
+ * Returns the ID of the currently logged in Sone.
+ *
+ * @return The ID of the current Sone, or an empty string if no Sone is logged
+ *         in
+ */
+function getCurrentSoneId() {
+       return $("#currentSoneId").text();
+}
+
+/**
+ * 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";
+}
+
+/**
+ * Returns whether a post with the given ID exists on the current page.
+ *
+ * @param postId
+ *            The post ID to check for
+ * @returns {Boolean} <code>true</code> if a post with the given ID already
+ *          exists on the page, <code>false</code> otherwise
+ */
+function hasPost(postId) {
+       return $(".post#" + postId).length > 0;
+}
+
+/**
+ * Returns whether a reply with the given ID exists on the current page.
+ *
+ * @param replyId
+ *            The reply ID to check for
+ * @returns {Boolean} <code>true</code> if a reply with the given ID already
+ *          exists on the page, <code>false</code> otherwise
+ */
+function hasReply(replyId) {
+       return $("#sone .reply#" + replyId).length > 0;
+}
 
-function loadNewPost(postId) {
-       if (postId in loadedPosts) {
+function loadNewPost(postId, soneId, recipientId, time) {
+       if (hasPost(postId)) {
                return;
        }
-       loadedPosts[postId] = true;
-       $.getJSON("ajax/getPost.ajax", { "post" : postId }, function(data, textStatus) {
+       if (!isIndexPage()) {
+               if (!isViewPostPage() || (getShownPostId() != postId)) {
+                       if (!isViewSonePage() || ((getShownSoneId() != soneId) && (getShownSoneId() != recipientId))) {
+                               return;
+                       }
+               }
+       }
+       if (getPostTime($("#sone .post").last()) > time) {
+               return;
+       }
+       $.getJSON("getPost.ajax", { "post" : postId }, function(data, textStatus) {
                if ((data != null) && data.success) {
+                       if (hasPost(data.post.id)) {
+                               return;
+                       }
+                       if (!isIndexPage() && !(isViewSonePage() && ((getShownSoneId() == data.post.sone) || (getShownSoneId() == data.post.recipient)))) {
+                               return;
+                       }
                        var firstOlderPost = null;
                        $("#sone .post").each(function() {
                                if (getPostTime(this) < data.post.time) {
@@ -613,8 +861,6 @@ function loadNewPost(postId) {
                        newPost = $(data.post.html).addClass("hidden");
                        if (firstOlderPost != null) {
                                newPost.insertBefore(firstOlderPost);
-                       } else {
-                               $("#sone #posts").append(newPost);
                        }
                        ajaxifyPost(newPost);
                        newPost.slideDown();
@@ -623,14 +869,19 @@ function loadNewPost(postId) {
        });
 }
 
-function loadNewReply(replyId) {
-       if (replyId in loadedReplies) {
+function loadNewReply(replyId, soneId, postId, postSoneId) {
+       if (hasReply(replyId)) {
+               return;
+       }
+       if (!hasPost(postId)) {
                return;
        }
-       loadedReplies[replyId] = true;
-       $.getJSON("ajax/getReply.ajax", { "reply": replyId }, function(data, textStatus) {
+       $.getJSON("getReply.ajax", { "reply": replyId }, function(data, textStatus) {
                /* find post. */
                if ((data != null) && data.success) {
+                       if (hasReply(data.reply.id)) {
+                               return;
+                       }
                        $("#sone .post#" + data.reply.postId).each(function() {
                                var firstNewerReply = null;
                                $(this).find(".replies .reply").each(function() {
@@ -652,6 +903,7 @@ function loadNewReply(replyId) {
                                ajaxifyReply(newReply);
                                newReply.slideDown();
                                setActivity();
+                               return false;
                        });
                }
        });
@@ -662,8 +914,9 @@ function markPostAsKnown(postElements) {
                postElement = this;
                if ($(postElement).hasClass("new")) {
                        (function(postElement) {
-                               $.getJSON("ajax/markPostAsKnown.ajax", {"formPassword": getFormPassword(), "post": getPostId(postElement)}, function(data, textStatus) {
+                               $.getJSON("markPostAsKnown.ajax", {"formPassword": getFormPassword(), "post": getPostId(postElement)}, function(data, textStatus) {
                                        $(postElement).removeClass("new");
+                                       $(".click-to-show", postElement).removeClass("new");
                                });
                        })(postElement);
                }
@@ -676,7 +929,7 @@ function markReplyAsKnown(replyElements) {
                replyElement = this;
                if ($(replyElement).hasClass("new")) {
                        (function(replyElement) {
-                               $.getJSON("ajax/markReplyAsKnown.ajax", {"formPassword": getFormPassword(), "reply": getReplyId(replyElement)}, function(data, textStatus) {
+                               $.getJSON("markReplyAsKnown.ajax", {"formPassword": getFormPassword(), "reply": getReplyId(replyElement)}, function(data, textStatus) {
                                        $(replyElement).removeClass("new");
                                });
                        })(replyElement);
@@ -692,9 +945,11 @@ function resetActivity() {
 }
 
 function setActivity() {
-       title = document.title;
-       if (title.indexOf('(') != 0) {
-               document.title = "(!) " + title;
+       if (!focus) {
+               title = document.title;
+               if (title.indexOf('(') != 0) {
+                       document.title = "(!) " + title;
+               }
        }
 }
 
@@ -720,20 +975,125 @@ function createNotification(id, text, dismissable) {
        return notification;
 }
 
+/**
+ * Shows the details of the notification with the given ID.
+ *
+ * @param notificationId
+ *            The ID of the notification
+ */
+function showNotificationDetails(notificationId) {
+       $("#sone .notification#" + notificationId + " .text").show();
+       $("#sone .notification#" + notificationId + " .short-text").hide();
+}
+
+/**
+ * Deletes the field with the given ID from the profile.
+ *
+ * @param fieldId
+ *            The ID of the field to delete
+ */
+function deleteProfileField(fieldId) {
+       $.getJSON("deleteProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId}, function(data, textStatus) {
+               if (data && data.success) {
+                       $("#sone .profile-field#" + data.field.id).slideUp();
+               }
+       });
+}
+
+/**
+ * Renames a profile field.
+ *
+ * @param fieldId
+ *            The ID of the field to rename
+ * @param newName
+ *            The new name of the field
+ * @param successFunction
+ *            Called when the renaming was successful
+ */
+function editProfileField(fieldId, newName, successFunction) {
+       $.getJSON("editProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "name": newName}, function(data, textStatus) {
+               if (data && data.success) {
+                       successFunction();
+               }
+       });
+}
+
+/**
+ * Moves the profile field with the given ID one slot in the given direction.
+ *
+ * @param fieldId
+ *            The ID of the field to move
+ * @param direction
+ *            The direction to move in (“up” or “down”)
+ * @param successFunction
+ *            Function to call on success
+ */
+function moveProfileField(fieldId, direction, successFunction) {
+       $.getJSON("moveProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "direction": direction}, function(data, textStatus) {
+               if (data && data.success) {
+                       successFunction();
+               }
+       });
+}
+
+/**
+ * Moves the profile field with the given ID up one slot.
+ *
+ * @param fieldId
+ *            The ID of the field to move
+ * @param successFunction
+ *            Function to call on success
+ */
+function moveProfileFieldUp(fieldId, successFunction) {
+       moveProfileField(fieldId, "up", successFunction);
+}
+
+/**
+ * Moves the profile field with the given ID down one slot.
+ *
+ * @param fieldId
+ *            The ID of the field to move
+ * @param successFunction
+ *            Function to call on success
+ */
+function moveProfileFieldDown(fieldId, successFunction) {
+       moveProfileField(fieldId, "down", successFunction);
+}
+
 //
 // EVERYTHING BELOW HERE IS EXECUTED AFTER LOADING THE PAGE
 //
 
+var focus = true;
+
 $(document).ready(function() {
 
        /* this initializes the status update input field. */
        getTranslation("WebInterface.DefaultText.StatusUpdate", function(defaultText) {
                registerInputTextareaSwap("#sone #update-status .status-input", defaultText, "text", false, false);
                $("#sone #update-status").submit(function() {
+                       if ($(this).find(":input.default:enabled").length > 0) {
+                               return false;
+                       }
                        text = $(this).find(":input:enabled").val();
-                       $.getJSON("ajax/createPost.ajax", { "formPassword": getFormPassword(), "text": text }, function(data, textStatus) {
+                       $.getJSON("createPost.ajax", { "formPassword": getFormPassword(), "text": text }, function(data, textStatus) {
                                if ((data != null) && data.success) {
-                                       loadNewPost(data.postId);
+                                       loadNewPost(data.postId, getCurrentSoneId());
+                               }
+                       });
+                       $(this).find(":input:enabled").val("").blur();
+                       return false;
+               });
+       });
+
+       /* ajaxify input field on “view Sone” page. */
+       getTranslation("WebInterface.DefaultText.Message", function(defaultText) {
+               registerInputTextareaSwap("#sone #post-message input[name=text]", defaultText, "text", false, false);
+               $("#sone #post-message").submit(function() {
+                       text = $(this).find(":input:enabled").val();
+                       $.getJSON("createPost.ajax", { "formPassword": getFormPassword(), "recipient": getShownSoneId(), "text": text }, function(data, textStatus) {
+                               if ((data != null) && data.success) {
+                                       loadNewPost(data.postId, getCurrentSoneId());
                                }
                        });
                        $(this).find(":input:enabled").val("").blur();
@@ -754,30 +1114,32 @@ $(document).ready(function() {
        });
 
        /* hides all replies but the latest two. */
-       getTranslation("WebInterface.ClickToShow.Replies", function(text) {
-               $("#sone .post .replies").each(function() {
-                       allReplies = $(this).find(".reply");
-                       if (allReplies.length > 2) {
-                               newHidden = false;
-                               for (replyIndex = 0; replyIndex < (allReplies.length - 2); ++replyIndex) {
-                                       $(allReplies[replyIndex]).addClass("hidden");
-                                       newHidden |= $(allReplies[replyIndex]).hasClass("new");
-                               }
-                               clickToShowElement = $("<div></div>").addClass("click-to-show");
-                               if (newHidden) {
-                                       clickToShowElement.addClass("new");
+       if (!isViewPostPage()) {
+               getTranslation("WebInterface.ClickToShow.Replies", function(text) {
+                       $("#sone .post .replies").each(function() {
+                               allReplies = $(this).find(".reply");
+                               if (allReplies.length > 2) {
+                                       newHidden = false;
+                                       for (replyIndex = 0; replyIndex < (allReplies.length - 2); ++replyIndex) {
+                                               $(allReplies[replyIndex]).addClass("hidden");
+                                               newHidden |= $(allReplies[replyIndex]).hasClass("new");
+                                       }
+                                       clickToShowElement = $("<div></div>").addClass("click-to-show");
+                                       if (newHidden) {
+                                               clickToShowElement.addClass("new");
+                                       }
+                                       (function(clickToShowElement, allReplies, text) {
+                                               clickToShowElement.text(text);
+                                               clickToShowElement.click(function() {
+                                                       allReplies.removeClass("hidden");
+                                                       clickToShowElement.addClass("hidden");
+                                               });
+                                       })(clickToShowElement, allReplies, text);
+                                       $(allReplies[0]).before(clickToShowElement);
                                }
-                               (function(clickToShowElement, allReplies, text) {
-                                       clickToShowElement.text(text);
-                                       clickToShowElement.click(function() {
-                                               allReplies.removeClass("hidden");
-                                               clickToShowElement.addClass("hidden");
-                                       });
-                               })(clickToShowElement, allReplies, text);
-                               $(allReplies[0]).before(clickToShowElement);
-                       }
+                       });
                });
-       });
+       }
 
        /*
         * convert all “follow”, “unfollow”, “lock”, and “unlock” links to something
@@ -785,7 +1147,7 @@ $(document).ready(function() {
         */
        $("#sone .follow").submit(function() {
                var followElement = this;
-               $.getJSON("ajax/followSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
+               $.getJSON("followSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
                        $(followElement).addClass("hidden");
                        $(followElement).parent().find(".unfollow").removeClass("hidden");
                });
@@ -793,7 +1155,7 @@ $(document).ready(function() {
        });
        $("#sone .unfollow").submit(function() {
                var unfollowElement = this;
-               $.getJSON("ajax/unfollowSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
+               $.getJSON("unfollowSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
                        $(unfollowElement).addClass("hidden");
                        $(unfollowElement).parent().find(".follow").removeClass("hidden");
                });
@@ -801,7 +1163,7 @@ $(document).ready(function() {
        });
        $("#sone .lock").submit(function() {
                var lockElement = this;
-               $.getJSON("ajax/lockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
+               $.getJSON("lockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
                        $(lockElement).addClass("hidden");
                        $(lockElement).parent().find(".unlock").removeClass("hidden");
                });
@@ -809,7 +1171,7 @@ $(document).ready(function() {
        });
        $("#sone .unlock").submit(function() {
                var unlockElement = this;
-               $.getJSON("ajax/unlockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
+               $.getJSON("unlockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
                        $(unlockElement).addClass("hidden");
                        $(unlockElement).parent().find(".lock").removeClass("hidden");
                });
@@ -826,7 +1188,10 @@ $(document).ready(function() {
 
        /* reset activity counter when the page has focus. */
        $(window).focus(function() {
+               focus = true;
                resetActivity();
-       });
+       }).blur(function() {
+               focus = false;
+       })
 
 });