Add context menues when hovering over avatar images.
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 22 Jun 2011 19:36:38 +0000 (21:36 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 22 Jun 2011 19:36:38 +0000 (21:36 +0200)
This fixes #191.

20 files changed:
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/template/ParserFilter.java
src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java
src/main/java/net/pterodactylus/sone/web/OptionsPage.java
src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java
src/main/resources/i18n/sone.en.properties
src/main/resources/static/css/sone.css
src/main/resources/static/javascript/sone.js
src/main/resources/templates/include/viewPost.html
src/main/resources/templates/include/viewReply.html
src/main/resources/templates/knownSones.html
src/main/resources/templates/options.html

diff --git a/pom.xml b/pom.xml
index f88c9c7..c05b57a 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -2,12 +2,12 @@
        <modelVersion>4.0.0</modelVersion>
        <groupId>net.pterodactylus</groupId>
        <artifactId>sone</artifactId>
-       <version>0.6.4</version>
+       <version>0.6.5</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
                        <artifactId>utils</artifactId>
-                       <version>0.9.6-SNAPSHOT</version>
+                       <version>0.9.7-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <groupId>junit</groupId>
index 3c99eef..7ecf0fa 100644 (file)
@@ -55,7 +55,9 @@ import net.pterodactylus.util.config.ConfigurationException;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.thread.Ticker;
+import net.pterodactylus.util.validation.EqualityValidator;
 import net.pterodactylus.util.validation.IntegerRangeValidator;
+import net.pterodactylus.util.validation.OrValidator;
 import net.pterodactylus.util.validation.Validation;
 import net.pterodactylus.util.version.Version;
 import freenet.client.FetchResult;
@@ -1757,6 +1759,7 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos
                        configuration.getIntValue("Option/ConfigurationVersion").setValue(0);
                        configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal());
                        configuration.getIntValue("Option/PostsPerPage").setValue(options.getIntegerOption("PostsPerPage").getReal());
+                       configuration.getIntValue("Option/CharactersPerPost").setValue(options.getIntegerOption("CharactersPerPost").getReal());
                        configuration.getBooleanValue("Option/RequireFullAccess").setValue(options.getBooleanOption("RequireFullAccess").getReal());
                        configuration.getIntValue("Option/PositiveTrust").setValue(options.getIntegerOption("PositiveTrust").getReal());
                        configuration.getIntValue("Option/NegativeTrust").setValue(options.getIntegerOption("NegativeTrust").getReal());
@@ -1834,6 +1837,7 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos
 
                }));
                options.addIntegerOption("PostsPerPage", new DefaultOption<Integer>(10, new IntegerRangeValidator(1, Integer.MAX_VALUE)));
+               options.addIntegerOption("CharactersPerPost", new DefaultOption<Integer>(200, new OrValidator<Integer>(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator<Integer>(-1))));
                options.addBooleanOption("RequireFullAccess", new DefaultOption<Boolean>(false));
                options.addIntegerOption("PositiveTrust", new DefaultOption<Integer>(75, new IntegerRangeValidator(0, 100)));
                options.addIntegerOption("NegativeTrust", new DefaultOption<Integer>(-25, new IntegerRangeValidator(-100, 100)));
@@ -1872,6 +1876,7 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos
 
                loadConfigurationValue("InsertionDelay");
                loadConfigurationValue("PostsPerPage");
+               loadConfigurationValue("CharactersPerPost");
                options.getBooleanOption("RequireFullAccess").set(configuration.getBooleanValue("Option/RequireFullAccess").getValue(null));
                loadConfigurationValue("PositiveTrust");
                loadConfigurationValue("NegativeTrust");
@@ -2168,6 +2173,41 @@ public class Core implements IdentityListener, UpdateListener, SoneProvider, Pos
                }
 
                /**
+                * Returns the number of characters per post, or <code>-1</code> if the
+                * posts should not be cut off.
+                *
+                * @return The numbers of characters per post
+                */
+               public int getCharactersPerPost() {
+                       return options.getIntegerOption("CharactersPerPost").get();
+               }
+
+               /**
+                * Validates the number of characters per post.
+                *
+                * @param charactersPerPost
+                *            The number of characters per post
+                * @return {@code true} if the number of characters per post was valid,
+                *         {@code false} otherwise
+                */
+               public boolean validateCharactersPerPost(Integer charactersPerPost) {
+                       return options.getIntegerOption("CharactersPerPost").validate(charactersPerPost);
+               }
+
+               /**
+                * Sets the number of characters per post.
+                *
+                * @param charactersPerPost
+                *            The number of characters per post, or <code>-1</code> to
+                *            not cut off the posts
+                * @return This preferences objects
+                */
+               public Preferences setCharactersPerPost(Integer charactersPerPost) {
+                       options.getIntegerOption("CharactersPerPost").set(charactersPerPost);
+                       return this;
+               }
+
+               /**
                 * Returns whether Sone requires full access to be even visible.
                 *
                 * @return {@code true} if Sone requires full access, {@code false}
index cc5f689..aa2062c 100644 (file)
@@ -347,6 +347,7 @@ public class SoneInserter extends AbstractService {
                        }
 
                        TemplateContext templateContext = templateContextFactory.createTemplateContext();
+                       templateContext.set("core", core);
                        templateContext.set("currentSone", soneProperties);
                        templateContext.set("currentEdition", core.getUpdateChecker().getLatestEdition());
                        templateContext.set("version", SonePlugin.VERSION);
index e1c0391..ca87856 100644 (file)
@@ -60,6 +60,17 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
 
        };
 
+       /**
+        * Comparator that sorts Sones by last activity (least recent active first).
+        */
+       public static final Comparator<Sone> LAST_ACTIVITY_COMPARATOR = new Comparator<Sone>() {
+
+               @Override
+               public int compare(Sone firstSone, Sone secondSone) {
+                       return (int) Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, secondSone.getTime() - firstSone.getTime()));
+               }
+       };
+
        /** Filter to remove Sones that have not been downloaded. */
        public static final Filter<Sone> EMPTY_SONE_FILTER = new Filter<Sone>() {
 
index 6259fd3..b5a7fcc 100644 (file)
@@ -83,7 +83,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 6, 4);
+       public static final Version VERSION = new Version(0, 6, 5);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
index 26afe2e..74641e9 100644 (file)
@@ -21,6 +21,8 @@ import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 import net.pterodactylus.sone.core.Core;
@@ -85,6 +87,19 @@ public class ParserFilter implements Filter {
        @Override
        public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
                String text = String.valueOf(data);
+               int length = -1;
+               try {
+                       length = Integer.parseInt(parameters.get("length"));
+               } catch (NumberFormatException nfe1) {
+                       /* ignore. */
+               }
+               if ((length == -1) && (parameters.get("length") != null)) {
+                       try {
+                               length = Integer.parseInt(String.valueOf(templateContext.get(parameters.get("length"))));
+                       } catch (NumberFormatException nfe1) {
+                               /* ignore. */
+                       }
+               }
                String soneKey = parameters.get("sone");
                if (soneKey == null) {
                        soneKey = "sone";
@@ -97,7 +112,31 @@ public class ParserFilter implements Filter {
                SoneTextParserContext context = new SoneTextParserContext(request, sone);
                StringWriter parsedTextWriter = new StringWriter();
                try {
-                       render(parsedTextWriter, soneTextParser.parse(context, new StringReader(text)));
+                       Iterable<Part> parts = soneTextParser.parse(context, new StringReader(text));
+                       if (length > -1) {
+                               List<Part> shortenedParts = new ArrayList<Part>();
+                               for (Part part : parts) {
+                                       if (part instanceof PlainTextPart) {
+                                               String longText = ((PlainTextPart) part).getText();
+                                               if (length >= longText.length()) {
+                                                       shortenedParts.add(part);
+                                               } else {
+                                                       shortenedParts.add(new PlainTextPart(longText.substring(0, length) + "…"));
+                                               }
+                                               length -= longText.length();
+                                       } else if (part instanceof LinkPart) {
+                                               shortenedParts.add(part);
+                                               length -= ((LinkPart) part).getText().length();
+                                       } else {
+                                               shortenedParts.add(part);
+                                       }
+                                       if (length <= 0) {
+                                               break;
+                                       }
+                               }
+                               parts = shortenedParts;
+                       }
+                       render(parsedTextWriter, parts);
                } catch (IOException ioe1) {
                        /* no exceptions in a StringReader or StringWriter, ignore. */
                }
index 7c8f9ad..0eedec6 100644 (file)
@@ -23,6 +23,8 @@ import java.util.List;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.collection.Pagination;
+import net.pterodactylus.util.collection.ReverseComparator;
+import net.pterodactylus.util.filter.Filter;
 import net.pterodactylus.util.filter.Filters;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Template;
@@ -57,11 +59,46 @@ public class KnownSonesPage extends SoneTemplatePage {
        @Override
        protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
+               String sortField = request.getHttpRequest().getParam("sort");
+               String sortOrder = request.getHttpRequest().getParam("order");
+               String followedSones = request.getHttpRequest().getParam("followedSones");
+               templateContext.set("sort", (sortField != null) ? sortField : "name");
+               templateContext.set("order", (sortOrder != null) ? sortOrder : "asc");
+               templateContext.set("followedSones", followedSones);
+               final Sone currentSone = getCurrentSone(request.getToadletContext(), false);
                List<Sone> knownSones = Filters.filteredList(new ArrayList<Sone>(webInterface.getCore().getSones()), Sone.EMPTY_SONE_FILTER);
-               Collections.sort(knownSones, Sone.NICE_NAME_COMPARATOR);
+               if ((currentSone != null) && "show-only".equals(followedSones)) {
+                       knownSones = Filters.filteredList(knownSones, new Filter<Sone>() {
+
+                               @Override
+                               public boolean filterObject(Sone sone) {
+                                       return currentSone.hasFriend(sone.getId());
+                               }
+                       });
+               } else if ((currentSone != null) && "hide".equals(followedSones)) {
+                       knownSones = Filters.filteredList(knownSones, new Filter<Sone>() {
+
+                               @Override
+                               public boolean filterObject(Sone sone) {
+                                       return !currentSone.hasFriend(sone.getId());
+                               }
+                       });
+               }
+               if ("activity".equals(sortField)) {
+                       if ("asc".equals(sortOrder)) {
+                               Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.LAST_ACTIVITY_COMPARATOR));
+                       } else {
+                               Collections.sort(knownSones, Sone.LAST_ACTIVITY_COMPARATOR);
+                       }
+               } else {
+                       if ("desc".equals(sortOrder)) {
+                               Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.NICE_NAME_COMPARATOR));
+                       } else {
+                               Collections.sort(knownSones, Sone.NICE_NAME_COMPARATOR);
+                       }
+               }
                Pagination<Sone> sonePagination = new Pagination<Sone>(knownSones, 25).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
                templateContext.set("pagination", sonePagination);
                templateContext.set("knownSones", sonePagination.getItems());
        }
-
 }
index 6785e81..4f76fda 100644 (file)
@@ -78,6 +78,12 @@ public class OptionsPage extends SoneTemplatePage {
                        } else {
                                preferences.setPostsPerPage(postsPerPage);
                        }
+                       Integer charactersPerPost = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("characters-per-post", 10), null);
+                       if (!preferences.validateCharactersPerPost(charactersPerPost)) {
+                               fieldErrors.add("characters-per-post");
+                       } else {
+                               preferences.setCharactersPerPost(charactersPerPost);
+                       }
                        boolean requireFullAccess = request.getHttpRequest().isPartSet("require-full-access");
                        preferences.setRequireFullAccess(requireFullAccess);
                        Integer positiveTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("positive-trust", 3));
@@ -119,6 +125,7 @@ public class OptionsPage extends SoneTemplatePage {
                }
                templateContext.set("insertion-delay", preferences.getInsertionDelay());
                templateContext.set("posts-per-page", preferences.getPostsPerPage());
+               templateContext.set("characters-per-post", preferences.getCharactersPerPost());
                templateContext.set("require-full-access", preferences.isRequireFullAccess());
                templateContext.set("positive-trust", preferences.getPositiveTrust());
                templateContext.set("negative-trust", preferences.getNegativeTrust());
index a0174a9..b7e36f4 100644 (file)
@@ -250,6 +250,7 @@ public class SoneTemplatePage extends FreenetTemplatePage {
        protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                Sone currentSone = getCurrentSone(request.getToadletContext(), false);
+               templateContext.set("core", webInterface.getCore());
                templateContext.set("currentSone", currentSone);
                templateContext.set("localSones", webInterface.getCore().getLocalSones());
                templateContext.set("request", request);
index f9b6929..73436c7 100644 (file)
@@ -242,11 +242,15 @@ public class WebInterface implements CoreListener {
 
                Template newPostNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newPostNotification.html"));
                newPostNotification = new ListNotification<Post>("new-post-notification", "posts", newPostNotificationTemplate, false);
-               localPostNotification = new ListNotification<Post>("local-post-notification", "posts", newPostNotificationTemplate, false);
+
+               Template localPostNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newPostNotification.html"));
+               localPostNotification = new ListNotification<Post>("local-post-notification", "posts", localPostNotificationTemplate, false);
 
                Template newReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html"));
                newReplyNotification = new ListNotification<Reply>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
-               localReplyNotification = new ListNotification<Reply>("local-reply-notification", "replies", newReplyNotificationTemplate, false);
+
+               Template localReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html"));
+               localReplyNotification = new ListNotification<Reply>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
 
                Template mentionNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/mentionNotification.html"));
                mentionNotification = new ListNotification<Post>("mention-notification", "posts", mentionNotificationTemplate, false);
index 1406c3a..03cecee 100644 (file)
@@ -115,6 +115,7 @@ public class GetNotificationAjaxPage extends JsonPage {
                try {
                        if (notification instanceof TemplateNotification) {
                                TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext().mergeContext(((TemplateNotification) notification).getTemplateContext());
+                               templateContext.set("core", webInterface.getCore());
                                templateContext.set("currentSone", webInterface.getCurrentSone(request.getToadletContext(), false));
                                templateContext.set("localSones", webInterface.getCore().getLocalSones());
                                templateContext.set("request", request);
index f46ec6b..f56c5b7 100644 (file)
@@ -97,6 +97,7 @@ public class GetPostAjaxPage extends JsonPage {
                jsonPost.put("time", post.getTime());
                StringWriter stringWriter = new StringWriter();
                TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext();
+               templateContext.set("core", webInterface.getCore());
                templateContext.set("request", request);
                templateContext.set("post", post);
                templateContext.set("currentSone", currentSone);
index 24bbbe2..6cc7d47 100644 (file)
@@ -99,6 +99,7 @@ public class GetReplyAjaxPage extends JsonPage {
                jsonReply.put("time", reply.getTime());
                StringWriter stringWriter = new StringWriter();
                TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext();
+               templateContext.set("core", webInterface.getCore());
                templateContext.set("request", request);
                templateContext.set("reply", reply);
                templateContext.set("currentSone", currentSone);
index d6098c8..4c36d4e 100644 (file)
@@ -38,6 +38,7 @@ Page.Options.Option.AutoFollow.Description=If a new Sone is discovered, follow i
 Page.Options.Section.RuntimeOptions.Title=Runtime Behaviour
 Page.Options.Option.InsertionDelay.Description=The number of seconds the Sone inserter waits after a modification of a Sone before it is being inserted.
 Page.Options.Option.PostsPerPage.Description=The number of posts to display on a page before pagination controls are being shown.
+Page.Options.Option.CharactersPerPost.Description=The number of characters to display from a post before cutting it off and showing a link to expand it (-1 to disable).
 Page.Options.Option.RequireFullAccess.Description=Whether to deny access to Sone to any host that has not been granted full access.
 Page.Options.Section.TrustOptions.Title=Trust Settings
 Page.Options.Option.PositiveTrust.Description=The amount of positive trust you want to assign to other Sones by clicking the checkmark below a post or reply.
@@ -247,6 +248,8 @@ View.Post.LikeLink=Like
 View.Post.UnlikeLink=Unlike
 View.Post.ShowSource=Toggle Parser
 View.Post.NotDownloaded=This post has not yet been downloaded, or it has been deleted.
+View.Post.ShowMore=show more
+View.Post.ShowLess=show less
 
 View.UpdateStatus.Text.ChooseSenderIdentity=Choose the sender identity
 
@@ -283,6 +286,7 @@ WebInterface.DefaultText.BirthYear=Year
 WebInterface.DefaultText.FieldName=Field name
 WebInterface.DefaultText.Option.InsertionDelay=Time to wait after a Sone is modified before insert (in seconds)
 WebInterface.DefaultText.Option.PostsPerPage=Number of posts to show on a page
+WebInterface.DefaultText.Option.CharactersPerPost=Number of characters per post after which to cut the post off
 WebInterface.DefaultText.Option.PositiveTrust=The positive trust to assign
 WebInterface.DefaultText.Option.NegativeTrust=The negative trust to assign
 WebInterface.DefaultText.Option.TrustComment=The comment to set in the web of trust
index 96ca0d3..13aa26e 100644 (file)
@@ -145,7 +145,7 @@ textarea {
 
 #sone #notification-area #local-post-notification, #sone #notification-area #local-reply-notification {
        display: none;
-} 
+}
 
 #sone #plugin-warning {
        border: solid 0.5em red;
@@ -297,15 +297,28 @@ textarea {
        font-size: 90%;
 }
 
-#sone .post .text, #sone .post .raw-text {
+#sone .post .text, #sone .post .raw-text, #sone .post .short-text {
        display: inline;
        white-space: pre-wrap;
+       word-wrap: break-word;
 }
 
-#sone .post .text.hidden, #sone .post .raw-text.hidden {
+#sone .post .text.hidden, #sone .post .raw-text.hidden, #sone .post .short-text.hidden {
        display: none;
 }
 
+#sone .post .expand-post-text:before, #sone .post .expand-reply-text:before {
+       content: "» ";
+}
+
+#sone .post .shrink-post-text:before, #sone .post .shrink-reply-text:before {
+       content: "« ";
+}
+
+#sone .post .shrink-post-text {
+       cursor: pointer;
+}
+
 #sone .post .status-line {
        margin-top: 0.5ex;
        font-size: 85%;
@@ -715,3 +728,7 @@ textarea {
        color: red;
        font-style: italic;
 }
+
+#sone #sort-options {
+       margin-bottom: 1em;
+}
index 3fcf907..4f8ff2b 100644 (file)
@@ -771,11 +771,43 @@ function ajaxifyPost(postElement) {
        /* convert “show source” link into javascript function. */
        $(postElement).find(".show-source").each(function() {
                $("a", this).click(function() {
+                       post = getPostElement(this);
+                       rawPostText = $(".post-text.raw-text", post);
+                       rawPostText.toggleClass("hidden");
+                       if (rawPostText.hasClass("hidden")) {
+                               $(".post-text.short-text", post).removeClass("hidden");
+                               $(".post-text.text", post).addClass("hidden");
+                               $(".expand-post-text", post).removeClass("hidden");
+                               $(".shrink-post-text", post).addClass("hidden");
+                       } else {
+                               $(".post-text.short-text", post).addClass("hidden");
+                               $(".post-text.text", post).addClass("hidden");
+                               $(".expand-post-text", post).addClass("hidden");
+                               $(".shrink-post-text", post).addClass("hidden");
+                       }
+                       return false;
+               });
+       });
+
+       /* convert “show more” link into javascript function. */
+       $(postElement).find(".expand-post-text").each(function() {
+               $(this).click(function() {
                        $(".post-text.text", getPostElement(this)).toggleClass("hidden");
-                       $(".post-text.raw-text", getPostElement(this)).toggleClass("hidden");
+                       $(".post-text.short-text", getPostElement(this)).toggleClass("hidden");
+                       $(".expand-post-text", getPostElement(this)).toggleClass("hidden");
+                       $(".shrink-post-text", getPostElement(this)).toggleClass("hidden");
                        return false;
                });
        });
+       $(postElement).find(".shrink-post-text").each(function() {
+               $(this).click(function() {
+                       $(".post-text.text", getPostElement(this)).toggleClass("hidden");
+                       $(".post-text.short-text", getPostElement(this)).toggleClass("hidden");
+                       $(".expand-post-text", getPostElement(this)).toggleClass("hidden");
+                       $(".shrink-post-text", getPostElement(this)).toggleClass("hidden");
+                       return false;
+               })
+       });
 
        /* add “comment” link. */
        addCommentLink(getPostId(postElement), getPostAuthor(postElement), postElement, $(postElement).find(".post-status-line .permalink-author"));
@@ -882,8 +914,40 @@ function ajaxifyReply(replyElement) {
        /* convert “show source” link into javascript function. */
        $(replyElement).find(".show-reply-source").each(function() {
                $("a", this).click(function() {
+                       reply = getReplyElement(this);
+                       rawReplyText = $(".reply-text.raw-text", reply);
+                       rawReplyText.toggleClass("hidden");
+                       if (rawReplyText.hasClass("hidden")) {
+                               $(".reply-text.short-text", reply).removeClass("hidden");
+                               $(".reply-text.text", reply).addClass("hidden");
+                               $(".expand-reply-text", reply).removeClass("hidden");
+                               $(".shrink-reply-text", reply).addClass("hidden");
+                       } else {
+                               $(".reply-text.short-text", reply).addClass("hidden");
+                               $(".reply-text.text", reply).addClass("hidden");
+                               $(".expand-reply-text", reply).addClass("hidden");
+                               $(".shrink-reply-text", reply).addClass("hidden");
+                       }
+                       return false;
+               });
+       });
+
+       /* convert “show more” link into javascript function. */
+       $(replyElement).find(".expand-reply-text").each(function() {
+               $(this).click(function() {
+                       $(".reply-text.text", getReplyElement(this)).toggleClass("hidden");
+                       $(".reply-text.short-text", getReplyElement(this)).toggleClass("hidden");
+                       $(".expand-reply-text", getReplyElement(this)).toggleClass("hidden");
+                       $(".shrink-reply-text", getReplyElement(this)).toggleClass("hidden");
+                       return false;
+               });
+       });
+       $(replyElement).find(".shrink-reply-text").each(function() {
+               $(this).click(function() {
                        $(".reply-text.text", getReplyElement(this)).toggleClass("hidden");
-                       $(".reply-text.raw-text", getReplyElement(this)).toggleClass("hidden");
+                       $(".reply-text.short-text", getReplyElement(this)).toggleClass("hidden");
+                       $(".expand-reply-text", getReplyElement(this)).toggleClass("hidden");
+                       $(".shrink-reply-text", getReplyElement(this)).toggleClass("hidden");
                        return false;
                });
        });
index 43f9981..ec1c299 100644 (file)
                        <%/if>
                        <% post.text|html|store key=originalText text=true>
                        <% post.text|parse sone=post.sone|store key=parsedText text=true>
+                       <% post.text|parse sone=post.sone length=core.preferences.charactersPerPost|store key=shortText text=true>
                        <div class="post-text raw-text<%if !raw> hidden<%/if>"><% originalText></div>
-                       <div class="post-text text<%if raw> hidden<%/if>"><% parsedText></div>
+                       <div class="post-text text<%if raw> hidden<%/if><%if !shortText|match key=parsedText> hidden<%/if>"><% parsedText></div>
+                       <div class="post-text short-text<%if raw> hidden<%/if><%if shortText|match key=parsedText> hidden<%/if>"><% shortText></div>
+                       <%if !shortText|match key=parsedText><%if !raw><a class="expand-post-text" href="viewPost.html?post=<% post.id|html>&amp;raw=true"><%= View.Post.ShowMore|l10n|html></a><%/if><%/if>
+                       <%if !shortText|match key=parsedText><%if !raw><a class="shrink-post-text hidden"><%= View.Post.ShowLess|l10n|html></a><%/if><%/if>
                </div>
                <div class="post-status-line status-line<%if !post.loaded> hidden<%/if>">
                        <div class="bookmarks">
index 51fd98f..abf3463 100644 (file)
                        <div class="author profile-link"><a href="viewSone.html?sone=<% reply.sone.id|html>"><% reply.sone.niceName|html></a></div>
                        <% reply.text|html|store key=originalText text=true>
                        <% reply.text|parse sone=reply.sone|store key=parsedText text=true>
+                       <% reply.text|parse sone=reply.sone length=core.preferences.charactersPerPost|store key=shortText text=true>
                        <div class="reply-text raw-text<%if !raw> hidden<%/if>"><% originalText></div>
-                       <div class="reply-text text<%if raw> hidden<%/if>"><% parsedText></div>
+                       <div class="reply-text text<%if raw> hidden<%/if><%if !shortText|match key=parsedText> hidden<%/if>"><% parsedText></div>
+                       <div class="reply-text short-text<%if raw> hidden<%/if><%if shortText|match key=parsedText> hidden<%/if>"><% shortText></div>
+                       <%if !shortText|match key=parsedText><%if !raw><a class="expand-reply-text" href="viewPost.html?post=<% reply.post.id|html>&amp;raw=true"><%= View.Post.ShowMore|l10n|html></a><%/if><%/if>
+                       <%if !shortText|match key=parsedText><%if !raw><a class="shrink-reply-text hidden"><%= View.Post.ShowLess|l10n|html></a><%/if><%/if>
                </div>
                <div class="reply-status-line status-line">
                        <div class="time"><% reply.time|date format="MMM d, yyyy, HH:mm:ss"></div>
index 33f0e27..ab36861 100644 (file)
@@ -1,8 +1,55 @@
 <%include include/head.html>
 
        <div class="page-id hidden">known-sones</div>
+       
+       <script language="javascript">
+
+               $(document).ready(function() {
+                       $("select[name=sort]").change(function() {
+                               value = $(this).val();
+                               if (value == "activity") {
+                                       $("select[name=order]").val("desc");
+                               } else if (value == "name") {
+                                       $("select[name=order]").val("asc");
+                               } 
+                       });
+                       $("#sort-options select").change(function() {
+                               this.form.submit();
+                       });
+               });
+       
+       </script>
 
        <h1><%= Page.KnownSones.Page.Title|l10n|html></h1>
+       
+       <div id="sort-options">
+               <form action="knownSones.html" method="get">
+                       <div>
+                               Sort:
+                               <select name="sort">
+                                       <option value="name"<%if sort|match value="name"> selected="selected"<%/if>>Name</option>
+                                       <option value="activity"<%if sort|match value="activity"> selected="selected"<%/if>>Last activity</option>
+                               </select>
+                               <select name="order">
+                                       <option value="asc"<%if order|match value="asc"> selected="selected"<%/if>>Ascending</option>
+                                       <option value="desc"<%if order|match value="desc"> selected="selected"<%/if>>Descending</option>
+                               </select>
+                       </div>
+                       <%ifnull !currentSone>
+                               <div>
+                                       Followed Sones:
+                                       <select name="followedSones">
+                                               <option value="none"></option>
+                                               <option value="show-only"<%if followedSones|match value="show-only"> selected="selected"<%/if>>Show only followed Sones</option>
+                                               <option value="hide"<%if followedSones|match value="hide"> selected="selected"<%/if>>Hide followed Sones</option>
+                                       </select>
+                               </div>
+                       <%/if>
+                       <div>
+                               <button type="submit">Apply</button>
+                       </div>
+               </form>
+       </div>
 
        <div id="known-sones">
                <%= page|store key=pageParameter>
index 368c14e..2bf98fd 100644 (file)
@@ -8,6 +8,9 @@
                        getTranslation("WebInterface.DefaultText.Option.PostsPerPage", function(postsPerPageText) {
                                registerInputTextareaSwap("#sone #options input[name=posts-per-page]", postsPerPageText, "posts-per-page", true, true);
                        });
+                       getTranslation("WebInterface.DefaultText.Option.CharactersPerPost", function(postsPerPageText) {
+                               registerInputTextareaSwap("#sone #options input[name=characters-per-post]", postsPerPageText, "characters-per-post", true, true);
+                       });
                        getTranslation("WebInterface.DefaultText.Option.PositiveTrust", function(positiveTrustText) {
                                registerInputTextareaSwap("#sone #options input[name=positive-trust]", positiveTrustText, "positive-trust", true, true);
                        });
                <%/if>
                <p><input type="text" name="posts-per-page" value="<% posts-per-page|html>" /></p>
 
+               <p><%= Page.Options.Option.CharactersPerPost.Description|l10n|html></p>
+               <%if =characters-per-post|in collection=fieldErrors>
+                       <p class="warning"><%= Page.Options.Warnings.ValueNotChanged|l10n|html></p>
+               <%/if>
+               <p><input type="text" name="characters-per-post" value="<% characters-per-post|html>" /></p>
+
                <p>
                        <input type="checkbox" name="require-full-access"<%if require-full-access> checked="checked"<%/if> />
                        <%= Page.Options.Option.RequireFullAccess.Description|l10n|html></p>