Merge branch 'release-0.6.2' 0.6.2
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 17 Apr 2011 11:47:39 +0000 (13:47 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 17 Apr 2011 11:47:39 +0000 (13:47 +0200)
57 files changed:
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/FreenetInterface.java
src/main/java/net/pterodactylus/sone/core/Options.java
src/main/java/net/pterodactylus/sone/core/SoneDownloader.java
src/main/java/net/pterodactylus/sone/core/SoneException.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/data/Post.java
src/main/java/net/pterodactylus/sone/data/Profile.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java
src/main/java/net/pterodactylus/sone/freenet/PluginStoreConfigurationBackend.java
src/main/java/net/pterodactylus/sone/freenet/StringBucket.java
src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/notify/ListNotification.java
src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java
src/main/java/net/pterodactylus/sone/template/SoneAccessor.java
src/main/java/net/pterodactylus/sone/text/ParserContext.java
src/main/java/net/pterodactylus/sone/web/CreateSonePage.java
src/main/java/net/pterodactylus/sone/web/DeleteSonePage.java
src/main/java/net/pterodactylus/sone/web/DistrustPage.java
src/main/java/net/pterodactylus/sone/web/IndexPage.java
src/main/java/net/pterodactylus/sone/web/LikePage.java
src/main/java/net/pterodactylus/sone/web/LoginPage.java
src/main/java/net/pterodactylus/sone/web/LogoutPage.java
src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java
src/main/java/net/pterodactylus/sone/web/SearchPage.java
src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java
src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java
src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java
src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java
src/main/java/net/pterodactylus/sone/web/UntrustPage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java
src/main/java/net/pterodactylus/sone/web/page/Page.java
src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java
src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java
src/main/java/net/pterodactylus/sone/web/page/StaticPage.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/head.html
src/main/resources/templates/include/pagination.html
src/main/resources/templates/include/viewPost.html
src/main/resources/templates/include/viewReply.html
src/main/resources/templates/include/viewSone.html
src/main/resources/templates/index.html
src/main/resources/templates/viewSone.html

diff --git a/pom.xml b/pom.xml
index 33fd6be..daacd28 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.1</version>
+       <version>0.6.2</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
                        <artifactId>utils</artifactId>
-                       <version>0.9.3</version>
+                       <version>0.9.4</version>
                </dependency>
                <dependency>
                        <groupId>junit</groupId>
index 1fe843e..fcbc889 100644 (file)
@@ -876,6 +876,8 @@ public class Core implements IdentityListener, UpdateListener {
                        return null;
                }
                Sone sone = addLocalSone(ownIdentity);
+               sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
+               saveSone(sone);
                return sone;
        }
 
@@ -1493,6 +1495,7 @@ public class Core implements IdentityListener, UpdateListener {
                synchronized (posts) {
                        posts.remove(post.getId());
                }
+               coreListenerManager.firePostRemoved(post);
                synchronized (newPosts) {
                        markPostKnown(post);
                        knownPosts.remove(post.getId());
@@ -1902,6 +1905,7 @@ public class Core implements IdentityListener, UpdateListener {
                        public void run() {
                                Sone sone = getRemoteSone(identity.getId());
                                sone.setIdentity(identity);
+                               sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), sone.getLatestEdition()));
                                soneDownloader.addSone(sone);
                                soneDownloader.fetchSone(sone);
                        }
index e22f407..20c8da7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - FreenetInterface.java - Copyright © 2010 David Roden
+ * Sone - FreenetInterface.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b7ece81..94fbec1 100644 (file)
@@ -1,3 +1,20 @@
+/*
+ * Sone - Options.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
 package net.pterodactylus.sone.core;
 
 import java.util.ArrayList;
index 7022f5e..56c1e4a 100644 (file)
@@ -119,7 +119,7 @@ public class SoneDownloader extends AbstractService {
         *            The Sone to fetch
         */
        public void fetchSone(Sone sone) {
-               fetchSone(sone, sone.getRequestUri());
+               fetchSone(sone, sone.getRequestUri().sskForUSK());
        }
 
        /**
index 74f257d..c786661 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - SoneException.java - Copyright © 2010 David Roden
+ * Sone - SoneException.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 15e054f..3936975 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - SoneInserter.java - Copyright © 2010 David Roden
+ * Sone - SoneInserter.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index bb431d0..a28bf9f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - StatusUpdate.java - Copyright © 2010 David Roden
+ * Sone - Post.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 8d4306d..6b44d10 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - Profile.java - Copyright © 2010 David Roden
+ * Sone - Profile.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2dd4bc1..715d95b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - Sone.java - Copyright © 2010 David Roden
+ * Sone - Sone.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ef1a183..fa7385c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - L10nFilter.java - Copyright © 2010 David Roden
+ * Sone - L10nFilter.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5e30de1..5aa68cf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - PluginStoreConfigurationBackend.java - Copyright © 2010 David Roden
+ * Sone - PluginStoreConfigurationBackend.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ebe036a..2993b2f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - TemplateBucket.java - Copyright © 2010 David Roden
+ * Sone - StringBucket.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 71710e4..3da08f3 100644 (file)
@@ -19,7 +19,6 @@ package net.pterodactylus.sone.freenet.plugin;
 
 import java.util.EventListener;
 
-
 import freenet.support.SimpleFieldSet;
 import freenet.support.api.Bucket;
 
index de4fee8..7ada162 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - SonePlugin.java - Copyright © 2010 David Roden
+ * Sone - SonePlugin.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -78,7 +78,7 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 6, 1);
+       public static final Version VERSION = new Version(0, 6, 2);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
index c66d376..3a79c93 100644 (file)
@@ -101,7 +101,8 @@ public class ListNotification<T> extends TemplateNotification {
        }
 
        /**
-        * Sets the elements to show in this notification.
+        * Sets the elements to show in this notification. This method will not call
+        * {@link #touch()}.
         *
         * @param elements
         *            The elements to show
@@ -109,7 +110,6 @@ public class ListNotification<T> extends TemplateNotification {
        public void setElements(Collection<? extends T> elements) {
                this.elements.clear();
                this.elements.addAll(elements);
-               touch();
        }
 
        /**
index 2a98ec8..fe024d6 100644 (file)
@@ -84,7 +84,7 @@ public class ListNotificationFilters {
         * @return The filtered new-post notification, or {@code null} if the
         *         notification should be removed
         */
-       private static ListNotification<Post> filterNewPostNotification(ListNotification<Post> newPostNotification, Sone currentSone) {
+       public static ListNotification<Post> filterNewPostNotification(ListNotification<Post> newPostNotification, Sone currentSone) {
                if (currentSone == null) {
                        return null;
                }
@@ -119,13 +119,13 @@ public class ListNotificationFilters {
         * @return The filtered new-reply notification, or {@code null} if the
         *         notification should be removed
         */
-       private static ListNotification<Reply> filterNewReplyNotification(ListNotification<Reply> newReplyNotification, Sone currentSone) {
+       public static ListNotification<Reply> filterNewReplyNotification(ListNotification<Reply> newReplyNotification, Sone currentSone) {
                if (currentSone == null) {
                        return null;
                }
                List<Reply> newReplies = new ArrayList<Reply>();
                for (Reply reply : newReplyNotification.getElements()) {
-                       if (currentSone.hasFriend(reply.getPost().getSone().getId()) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient())) {
+                       if (((reply.getPost().getSone() != null) && currentSone.hasFriend(reply.getPost().getSone().getId())) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient())) {
                                newReplies.add(reply);
                        }
                }
index 23b2227..76fdcc1 100644 (file)
@@ -25,6 +25,8 @@ import net.pterodactylus.sone.core.Core.SoneStatus;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.wot.Trust;
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.template.Accessor;
 import net.pterodactylus.util.template.ReflectionAccessor;
@@ -97,6 +99,8 @@ public class SoneAccessor extends ReflectionAccessor {
                        return core.isNewSone(sone.getId());
                } else if (member.equals("locked")) {
                        return core.isLocked(sone);
+               } else if (member.equals("lastUpdatedText")) {
+                       return GetTimesAjaxPage.getTime((WebInterface) templateContext.get("webInterface"), System.currentTimeMillis() - sone.getTime());
                } else if (member.equals("trust")) {
                        Sone currentSone = (Sone) templateContext.get("currentSone");
                        if (currentSone == null) {
index 7f38ad8..ee61b6f 100644 (file)
@@ -1,3 +1,19 @@
+/*
+ * Sone - ParserContext.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
 
 package net.pterodactylus.sone.text;
 
index cc40320..2e2fc41 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - CreateSonePage.java - Copyright © 2010 David Roden
+ * Sone - CreateSonePage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 54d77b8..21979b8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - DeleteSonePage.java - Copyright © 2010 David Roden
+ * Sone - DeleteSonePage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c055545..37a792c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustPage.java - Copyright © 2011 David Roden
+ * Sone - DistrustPage.java - Copyright © 2011 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e675311..922d5ef 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - IndexPage.java - Copyright © 2010 David Roden
+ * Sone - IndexPage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 73fdd3c..56f55ef 100644 (file)
@@ -53,7 +53,7 @@ public class LikePage extends SoneTemplatePage {
        protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.POST) {
-                       String type=request.getHttpRequest().getPartAsStringFailsafe("type", 16);
+                       String type = request.getHttpRequest().getPartAsStringFailsafe("type", 16);
                        String id = request.getHttpRequest().getPartAsStringFailsafe(type, 36);
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                        Sone currentSone = getCurrentSone(request.getToadletContext());
index eaa8351..321193b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - LoginPage.java - Copyright © 2010 David Roden
+ * Sone - LoginPage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f388368..4510bc1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - LogoutPage.java - Copyright © 2010 David Roden
+ * Sone - LogoutPage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4ecf6e0..9f91c84 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkReadPage.java - Copyright © 2011 David Roden
+ * Sone - MarkAsKnownPage.java - Copyright © 2011 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1d1aa36..74bfd10 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OptionsPage.java - Copyright © 2010 David Roden
+ * Sone - SearchPage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,6 +24,8 @@ import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Profile;
@@ -35,6 +37,7 @@ import net.pterodactylus.util.collection.Converters;
 import net.pterodactylus.util.collection.Pagination;
 import net.pterodactylus.util.filter.Filter;
 import net.pterodactylus.util.filter.Filters;
+import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
@@ -49,6 +52,9 @@ import net.pterodactylus.util.text.TextException;
  */
 public class SearchPage extends SoneTemplatePage {
 
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(SearchPage.class);
+
        /**
         * Creates a new search page.
         *
@@ -137,7 +143,7 @@ public class SearchPage extends SoneTemplatePage {
                Set<Hit<T>> hits = new HashSet<Hit<T>>();
                for (T object : objects) {
                        String objectString = stringGenerator.generateString(object);
-                       int score = calculateScore(phrases, objectString);
+                       double score = calculateScore(phrases, objectString);
                        hits.add(new Hit<T>(object, score));
                }
                return hits;
@@ -185,9 +191,10 @@ public class SearchPage extends SoneTemplatePage {
         *            The expression to search
         * @return The score of the expression
         */
-       private int calculateScore(List<Phrase> phrases, String expression) {
-               int optionalHits = 0;
-               int requiredHits = 0;
+       private double calculateScore(List<Phrase> phrases, String expression) {
+               logger.log(Level.FINEST, "Calculating Score for “%s”…", expression);
+               double optionalHits = 0;
+               double requiredHits = 0;
                int forbiddenHits = 0;
                int requiredPhrases = 0;
                for (Phrase phrase : phrases) {
@@ -197,22 +204,26 @@ public class SearchPage extends SoneTemplatePage {
                        }
                        int matches = 0;
                        int index = 0;
+                       double score = 0;
                        while (index < expression.length()) {
                                int position = expression.toLowerCase().indexOf(phraseString, index);
                                if (position == -1) {
                                        break;
                                }
+                               score += Math.pow(1 - position / (double) expression.length(), 2);
                                index = position + phraseString.length();
+                               logger.log(Level.FINEST, "Got hit at position %d.", position);
                                ++matches;
                        }
+                       logger.log(Level.FINEST, "Score: %f", score);
                        if (matches == 0) {
                                continue;
                        }
                        if (phrase.getOptionality() == Phrase.Optionality.REQUIRED) {
-                               requiredHits += matches;
+                               requiredHits += score;
                        }
                        if (phrase.getOptionality() == Phrase.Optionality.OPTIONAL) {
-                               optionalHits += matches;
+                               optionalHits += score;
                        }
                        if (phrase.getOptionality() == Phrase.Optionality.FORBIDDEN) {
                                forbiddenHits += matches;
@@ -418,7 +429,7 @@ public class SearchPage extends SoneTemplatePage {
 
                        @Override
                        public int compare(Hit<?> leftHit, Hit<?> rightHit) {
-                               return rightHit.getScore() - leftHit.getScore();
+                               return (rightHit.getScore() < leftHit.getScore()) ? -1 : ((rightHit.getScore() > leftHit.getScore()) ? 1 : 0);
                        }
 
                };
@@ -427,7 +438,7 @@ public class SearchPage extends SoneTemplatePage {
                private final T object;
 
                /** The score of the object. */
-               private final int score;
+               private final double score;
 
                /**
                 * Creates a new hit.
@@ -437,7 +448,7 @@ public class SearchPage extends SoneTemplatePage {
                 * @param score
                 *            The score of the object
                 */
-               public Hit(T object, int score) {
+               public Hit(T object, double score) {
                        this.object = object;
                        this.score = score;
                }
@@ -456,7 +467,7 @@ public class SearchPage extends SoneTemplatePage {
                 *
                 * @return The score of the object
                 */
-               public int getScore() {
+               public double getScore() {
                        return score;
                }
 
index d6c41c1..9eacaf8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Freetalk - FreetalkTemplatePage.java - Copyright © 2010 David Roden
+ * Sone - SoneTemplatePage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,8 +26,8 @@ import java.util.Map;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.main.SonePlugin;
-import net.pterodactylus.sone.web.page.Page;
 import net.pterodactylus.sone.web.page.FreenetTemplatePage;
+import net.pterodactylus.sone.web.page.Page;
 import net.pterodactylus.util.collection.ListBuilder;
 import net.pterodactylus.util.collection.MapBuilder;
 import net.pterodactylus.util.template.Template;
@@ -37,7 +37,7 @@ import freenet.clients.http.ToadletContext;
 import freenet.support.api.HTTPRequest;
 
 /**
- * Base page for the Freetalk web interface.
+ * Base page for the Sone web interface.
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
@@ -53,8 +53,8 @@ public class SoneTemplatePage extends FreenetTemplatePage {
        private final boolean requireLogin;
 
        /**
-        * Creates a new template page for Freetalk that does not require the user
-        * to be logged in.
+        * Creates a new template page for Sone that does not require the user to be
+        * logged in.
         *
         * @param path
         *            The path of the page
@@ -68,8 +68,8 @@ public class SoneTemplatePage extends FreenetTemplatePage {
        }
 
        /**
-        * Creates a new template page for Freetalk that does not require the user
-        * to be logged in.
+        * Creates a new template page for Sone that does not require the user to be
+        * logged in.
         *
         * @param path
         *            The path of the page
@@ -85,7 +85,7 @@ public class SoneTemplatePage extends FreenetTemplatePage {
        }
 
        /**
-        * Creates a new template page for Freetalk.
+        * Creates a new template page for Sone.
         *
         * @param path
         *            The path of the page
@@ -101,7 +101,7 @@ public class SoneTemplatePage extends FreenetTemplatePage {
        }
 
        /**
-        * Creates a new template page for Freetalk.
+        * Creates a new template page for Sone.
         *
         * @param path
         *            The path of the page
index 85f46f0..60165b2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - BookmarkPage.java - Copyright © 2011 David Roden
+ * Sone - UnbookmarkPage.java - Copyright © 2011 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e026a9a..a7836f5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FollowSonePage.java - Copyright © 2010 David Roden
+ * Sone - UnfollowSonePage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5408f20..ccb5959 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LockSonePage.java - Copyright © 2010 David Roden
+ * Sone - UnlockSonePage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0e7e198..c5e7dec 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustPage.java - Copyright © 2011 David Roden
+ * Sone - UntrustPage.java - Copyright © 2011 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b3772f9..fd09f33 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - WebInterface.java - Copyright © 2010 David Roden
+ * Sone - WebInterface.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -70,6 +70,7 @@ import net.pterodactylus.sone.web.ajax.DistrustAjaxPage;
 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
+import net.pterodactylus.sone.web.ajax.GetNotificationAjaxPage;
 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
@@ -586,6 +587,7 @@ public class WebInterface implements CoreListener {
                pageToadlets.add(pageToadletFactory.createPageToadlet(new TemplatePage("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new GetTranslationPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new GetStatusAjaxPage(this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetNotificationAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyAjaxPage(this)));
index f34d202..e12b2cf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteReplysAjaxPage.java - Copyright © 2010 David Roden
+ * Sone - DeleteReplyAjaxPage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1a6d28f..ca770e4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustAjaxPage.java - Copyright © 2011 David Roden
+ * Sone - DistrustAjaxPage.java - Copyright © 2011 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java
new file mode 100644 (file)
index 0000000..650a9ae
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Sone - GetNotificationAjaxPage.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.main.SonePlugin;
+import net.pterodactylus.sone.notify.ListNotification;
+import net.pterodactylus.sone.notify.ListNotificationFilters;
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.util.json.JsonObject;
+import net.pterodactylus.util.notify.Notification;
+import net.pterodactylus.util.notify.TemplateNotification;
+import net.pterodactylus.util.template.TemplateContext;
+
+/**
+ * The “get notification” AJAX handler returns a number of rendered
+ * notifications.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class GetNotificationAjaxPage extends JsonPage {
+
+       /**
+        * Creates a new “get notification” AJAX page.
+        *
+        * @param webInterface
+        *            The Sone web interface
+        */
+       public GetNotificationAjaxPage(WebInterface webInterface) {
+               super("getNotification.ajax", webInterface);
+       }
+
+       //
+       // JSONPAGE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected boolean needsFormPassword() {
+               return false;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected boolean requiresLogin() {
+               return false;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       protected JsonObject createJsonObject(Request request) {
+               String[] notificationIds = request.getHttpRequest().getParam("notifications").split(",");
+               JsonObject jsonNotifications = new JsonObject();
+               Sone currentSone = webInterface.getCurrentSone(request.getToadletContext(), false);
+               for (String notificationId : notificationIds) {
+                       Notification notification = webInterface.getNotifications().getNotification(notificationId);
+                       ListNotificationFilters.filterNotifications(new ArrayList<Notification>(), currentSone);
+                       if ("new-post-notification".equals(notificationId)) {
+                               notification = ListNotificationFilters.filterNewPostNotification((ListNotification<Post>) notification, currentSone);
+                       } else if ("new-reply-notification".equals(notificationId)) {
+                               notification = ListNotificationFilters.filterNewReplyNotification((ListNotification<Reply>) notification, currentSone);
+                       }
+                       if (notification == null) {
+                               // TODO - show error
+                               continue;
+                       }
+                       jsonNotifications.put(notificationId, createJsonNotification(request, notification));
+               }
+               return createSuccessJsonObject().put("notifications", jsonNotifications);
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Creates a JSON object from the given notification.
+        *
+        * @param request
+        *            The request to load the session from
+        * @param notification
+        *            The notification to create a JSON object
+        * @return The JSON object
+        */
+       private JsonObject createJsonNotification(Request request, Notification notification) {
+               JsonObject jsonNotification = new JsonObject();
+               jsonNotification.put("id", notification.getId());
+               StringWriter notificationWriter = new StringWriter();
+               try {
+                       if (notification instanceof TemplateNotification) {
+                               TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext().mergeContext(((TemplateNotification) notification).getTemplateContext());
+                               templateContext.set("currentSone", webInterface.getCurrentSone(request.getToadletContext(), false));
+                               templateContext.set("localSones", webInterface.getCore().getLocalSones());
+                               templateContext.set("request", request);
+                               templateContext.set("currentVersion", SonePlugin.VERSION);
+                               templateContext.set("hasLatestVersion", webInterface.getCore().getUpdateChecker().hasLatestVersion());
+                               templateContext.set("latestEdition", webInterface.getCore().getUpdateChecker().getLatestEdition());
+                               templateContext.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion());
+                               templateContext.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate());
+                               templateContext.set("notification", notification);
+                               ((TemplateNotification) notification).render(templateContext, notificationWriter);
+                       } else {
+                               notification.render(notificationWriter);
+                       }
+               } catch (IOException ioe1) {
+                       /* StringWriter never throws, ignore. */
+               }
+               jsonNotification.put("text", notificationWriter.toString());
+               jsonNotification.put("createdTime", notification.getCreatedTime());
+               jsonNotification.put("lastUpdatedTime", notification.getLastUpdatedTime());
+               jsonNotification.put("dismissable", notification.isDismissable());
+               return jsonNotification;
+       }
+
+}
index 426c08f..7d581d7 100644 (file)
@@ -17,8 +17,6 @@
 
 package net.pterodactylus.sone.web.ajax;
 
-import java.io.IOException;
-import java.io.StringWriter;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -39,8 +37,6 @@ import net.pterodactylus.util.filter.Filters;
 import net.pterodactylus.util.json.JsonArray;
 import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.notify.Notification;
-import net.pterodactylus.util.notify.TemplateNotification;
-import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * The “get status” AJAX handler returns all information that is necessary to
@@ -86,9 +82,9 @@ public class GetStatusAjaxPage extends JsonPage {
                /* load notifications. */
                List<Notification> notifications = ListNotificationFilters.filterNotifications(new ArrayList<Notification>(webInterface.getNotifications().getNotifications()), currentSone);
                Collections.sort(notifications, Notification.LAST_UPDATED_TIME_SORTER);
-               JsonArray jsonNotifications = new JsonArray();
+               JsonArray jsonNotificationInformations = new JsonArray();
                for (Notification notification : notifications) {
-                       jsonNotifications.add(createJsonNotification(notification));
+                       jsonNotificationInformations.add(createJsonNotificationInformation(notification));
                }
                /* load new posts. */
                Set<Post> newPosts = webInterface.getNewPosts();
@@ -132,7 +128,7 @@ public class GetStatusAjaxPage extends JsonPage {
                        jsonReply.put("postSone", reply.getPost().getSone().getId());
                        jsonReplies.add(jsonReply);
                }
-               return createSuccessJsonObject().put("sones", jsonSones).put("notifications", jsonNotifications).put("newPosts", jsonPosts).put("newReplies", jsonReplies);
+               return createSuccessJsonObject().put("sones", jsonSones).put("notifications", jsonNotificationInformations).put("newPosts", jsonPosts).put("newReplies", jsonReplies);
        }
 
        /**
@@ -174,36 +170,25 @@ public class GetStatusAjaxPage extends JsonPage {
                synchronized (dateFormat) {
                        jsonSone.put("lastUpdated", dateFormat.format(new Date(sone.getTime())));
                }
-               jsonSone.put("age", (System.currentTimeMillis() - sone.getTime()) / 1000);
+               jsonSone.put("lastUpdatedText", GetTimesAjaxPage.getTime(webInterface, System.currentTimeMillis() - sone.getTime()).getText());
                return jsonSone;
        }
 
        /**
-        * Creates a JSON object from the given notification.
+        * Creates a JSON object that only contains the ID and the last-updated time
+        * of the given notification.
         *
+        * @see Notification#getId()
+        * @see Notification#getLastUpdatedTime()
         * @param notification
-        *            The notification to create a JSON object
-        * @return The JSON object
+        *            The notification
+        * @return A JSON object containing the notification ID and last-updated
+        *         time
         */
-       private JsonObject createJsonNotification(Notification notification) {
+       private JsonObject createJsonNotificationInformation(Notification notification) {
                JsonObject jsonNotification = new JsonObject();
                jsonNotification.put("id", notification.getId());
-               StringWriter notificationWriter = new StringWriter();
-               try {
-                       if (notification instanceof TemplateNotification) {
-                               TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext().mergeContext(((TemplateNotification) notification).getTemplateContext());
-                               templateContext.set("notification", notification);
-                               ((TemplateNotification) notification).render(templateContext, notificationWriter);
-                       } else {
-                               notification.render(notificationWriter);
-                       }
-               } catch (IOException ioe1) {
-                       /* StringWriter never throws, ignore. */
-               }
-               jsonNotification.put("text", notificationWriter.toString());
-               jsonNotification.put("createdTime", notification.getCreatedTime());
                jsonNotification.put("lastUpdatedTime", notification.getLastUpdatedTime());
-               jsonNotification.put("dismissable", notification.isDismissable());
                return jsonNotification;
        }
 
index b17e4a2..f90806a 100644 (file)
@@ -120,6 +120,23 @@ public class GetTimesAjaxPage extends JsonPage {
         * @return The formatted age
         */
        private Time getTime(long age) {
+               return getTime(webInterface, age);
+       }
+
+       //
+       // STATIC METHODS
+       //
+
+       /**
+        * Returns the formatted relative time for a given age.
+        *
+        * @param webInterface
+        *            The Sone web interface (for l10n access)
+        * @param age
+        *            The age to format (in milliseconds)
+        * @return The formatted age
+        */
+       public static Time getTime(WebInterface webInterface, long age) {
                String text;
                long refresh;
                if (age < 0) {
@@ -179,7 +196,7 @@ public class GetTimesAjaxPage extends JsonPage {
         *
         * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
-       private static class Time {
+       public static class Time {
 
                /** Number of milliseconds in a second. */
                private static final long SECOND = 1000;
@@ -239,6 +256,14 @@ public class GetTimesAjaxPage extends JsonPage {
                        return refresh;
                }
 
+               /**
+                * {@inheritDoc}
+                */
+               @Override
+               public String toString() {
+                       return text;
+               }
+
        }
 
 }
index ad1689e..f07dcb0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - BookmarkAjaxPage.java - Copyright © 2011 David Roden
+ * Sone - UnbookmarkAjaxPage.java - Copyright © 2011 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e1c408c..3160db0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LockSoneAjaxPage.java - Copyright © 2010 David Roden
+ * Sone - UnlockSoneAjaxPage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 32c6229..ad613ff 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustAjaxPage.java - Copyright © 2011 David Roden
+ * Sone - UntrustAjaxPage.java - Copyright © 2011 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0668154..6e7812f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * shortener - TemplatePage.java - Copyright © 2010 David Roden
+ * Sone - FreenetTemplatePage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index dd54f41..297081e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * shortener - Page.java - Copyright © 2010 David Roden
+ * Sone - Page.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f7f59a6..f594e58 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * shortener - PageToadlet.java - Copyright © 2010 David Roden
+ * Sone - PageToadlet.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index af87470..bd0adb9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * shortener - PageToadletFactory.java - Copyright © 2010 David Roden
+ * Sone - PageToadletFactory.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a840958..3d24fdd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * shortener - CSSPage.java - Copyright © 2010 David Roden
+ * Sone - StaticPage.java - Copyright © 2010 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a73432d..06d036a 100644 (file)
@@ -134,7 +134,7 @@ Page.ViewSone.PostList.Title=Posts by {sone}
 Page.ViewSone.PostList.Text.NoPostYet=This Sone has not yet posted anything.
 Page.ViewSone.Profile.Title=Profile
 Page.ViewSone.Profile.Label.Name=Name
-Page.ViewSone.Replies.Title=Replies to Posts
+Page.ViewSone.Replies.Title=Posts {sone} has replied to
 
 Page.ViewPost.Title=View Post - Sone
 Page.ViewPost.Page.Title=View Post by {sone}
@@ -224,6 +224,8 @@ View.Sone.Status.Downloading=This Sone is currently being downloaded.
 View.Sone.Status.Inserting=This Sone is currently being inserted.
 
 View.Post.UnknownAuthor=(unknown)
+View.Post.Permalink=link post
+View.Post.PermalinkAuthor=link author
 View.Post.Bookmarks.PostIsBookmarked=Post is bookmarked, click to remove from bookmarks
 View.Post.Bookmarks.PostIsNotBookmarked=Post is not bookmarked, click to bookmark
 View.Post.DeleteLink=Delete
@@ -251,7 +253,7 @@ View.Time.XHoursAgo=${hour} hours ago
 View.Time.ADayAgo=about a day ago
 View.Time.XDaysAgo=${day} days ago
 View.Time.AWeekAgo=about a week ago
-View.Time.XWeeksAgo=${week} week ago
+View.Time.XWeeksAgo=${week} weeks ago
 View.Time.AMonthAgo=about a month ago
 View.Time.XMonthsAgo=${month} months ago
 View.Time.AYearAgo=about a year ago
index f6bb62d..2621b50 100644 (file)
@@ -266,6 +266,10 @@ textarea {
        display: inline;
 }
 
+#sone .permalink {
+       display: inline;
+}
+
 #sone .post .bookmarks {
        display: inline;
        color: rgb(28, 131, 191);
index d60ee23..3088aff 100644 (file)
@@ -33,7 +33,7 @@ function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, op
                                inputField.val(defaultText);
                        }
                }).hide().data("inputField", $(this)).val($(this).val());
-               $(this).after(textarea);
+               $(this).data("textarea", textarea).after(textarea);
                (function(inputField, textarea) {
                        inputField.focus(function() {
                                $(this).hide().attr("disabled", "disabled");
@@ -66,11 +66,11 @@ function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, op
  * @param element
  *            The element to add a “comment” link to
  */
-function addCommentLink(postId, element, insertAfterThisElement) {
+function addCommentLink(postId, author, element, insertAfterThisElement) {
        if (($(element).find(".show-reply-form").length > 0) || (getPostElement(element).find(".create-reply").length == 0)) {
                return;
        }
-       commentElement = (function(postId) {
+       commentElement = (function(postId, author) {
                separator = $("<span> · </span>").addClass("separator");
                var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
                        replyElement = $("#sone .post#" + postId + " .create-reply");
@@ -85,10 +85,11 @@ function addCommentLink(postId, element, insertAfterThisElement) {
                                        replyElement.removeClass("light");
                                });
                        })(replyElement);
-                       replyElement.find("input.reply-input").focus();
+                       textArea = replyElement.find("input.reply-input").focus().data("textarea");
+                       textArea.val(textArea.val() + "@sone://" + author + " ");
                });
                return commentElement;
-       })(postId);
+       })(postId, author);
        $(insertAfterThisElement).after(commentElement.clone(true));
        $(insertAfterThisElement).after(separator);
 }
@@ -145,7 +146,7 @@ function filterSoneId(soneId) {
  * @param lastUpdated
  *            The date and time of the last update (formatted for display)
  */
-function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated) {
+function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated, lastUpdatedText) {
        $("#sone .sone." + filterSoneId(soneId)).
                toggleClass("unknown", status == "unknown").
                toggleClass("idle", status == "idle").
@@ -155,7 +156,7 @@ function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated) {
        $("#sone .sone." + filterSoneId(soneId) + " .lock").toggleClass("hidden", locked);
        $("#sone .sone." + filterSoneId(soneId) + " .unlock").toggleClass("hidden", !locked);
        if (lastUpdated != null) {
-               $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(lastUpdated);
+               $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").attr("title", lastUpdated).text(lastUpdatedText);
        } else {
                getTranslation("View.Sone.Text.UnknownDate", function(unknown) {
                        $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(unknown);
@@ -276,7 +277,7 @@ function getFormPassword() {
  */
 function getSone(soneId) {
        return $("#sone .sone").filter(function(index) {
-               return $(".id").text() == soneId;
+               return $(".id", this).text() == soneId;
        });
 }
 
@@ -415,6 +416,17 @@ function getNotificationId(notificationElement) {
        return $(notificationElement).attr("id");
 }
 
+/**
+ * Returns the time the notification was last updated.
+ *
+ * @param notificationElement
+ *            The notification element
+ * @returns The last update time of the notification
+ */
+function getNotificationLastUpdatedTime(notificationElement) {
+       return $(notificationElement).attr("lastUpdatedTime");
+}
+
 function likePost(postId) {
        $.getJSON("like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
                if ((data == null) || !data.success) {
@@ -666,7 +678,7 @@ function ajaxifySone(soneElement) {
 
        /* mark Sone as known when clicking it. */
        $(soneElement).click(function() {
-               markSoneAsKnown(soneElement);
+               markSoneAsKnown(this);
        });
 }
 
@@ -757,7 +769,7 @@ function ajaxifyPost(postElement) {
        });
 
        /* add “comment” link. */
-       addCommentLink(getPostId(postElement), postElement, $(postElement).find(".post-status-line .time"));
+       addCommentLink(getPostId(postElement), getPostAuthor(postElement), postElement, $(postElement).find(".post-status-line .permalink-author"));
 
        /* process all replies. */
        replyIds = [];
@@ -817,7 +829,7 @@ function ajaxifyReply(replyElement) {
                        });
                });
        })(replyElement);
-       addCommentLink(getPostId(replyElement), replyElement, $(replyElement).find(".reply-status-line .time"));
+       addCommentLink(getPostId(replyElement), getReplyAuthor(replyElement), replyElement, $(replyElement).find(".reply-status-line .permalink-author"));
 
        /* convert “show source” link into javascript function. */
        $(replyElement).find(".show-reply-source").each(function() {
@@ -970,7 +982,7 @@ function getStatus() {
                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.lastUpdatedUnknown ? null : value.lastUpdated);
+                               updateSoneStatus(value.id, value.name, value.status, value.modified, value.locked, value.lastUpdatedUnknown ? null : value.lastUpdated, value.lastUpdatedText);
                        });
                        /* search for removed notifications. */
                        $("#sone #notification-area .notification").each(function() {
@@ -1009,25 +1021,16 @@ function getStatus() {
                                }
                        });
                        /* process notifications. */
+                       notificationIds = [];
                        $.each(data.notifications, function(index, value) {
                                oldNotification = getNotification(value.id);
-                               notification = ajaxifyNotification(createNotification(value.id, value.text, value.dismissable)).hide();
-                               if (oldNotification.length != 0) {
-                                       if ((oldNotification.find(".short-text").length > 0) && (notification.find(".short-text").length > 0)) {
-                                               opened = oldNotification.is(":visible") && oldNotification.find(".short-text").hasClass("hidden");
-                                               notification.find(".short-text").toggleClass("hidden", opened);
-                                               notification.find(".text").toggleClass("hidden", !opened);
-                                       }
-                                       checkForRemovedSones(oldNotification, notification);
-                                       checkForRemovedPosts(oldNotification, notification);
-                                       checkForRemovedReplies(oldNotification, notification);
-                                       oldNotification.replaceWith(notification.show());
-                               } else {
-                                       $("#sone #notification-area").append(notification);
-                                       notification.slideDown();
-                                       setActivity();
+                               if ((oldNotification.length == 0) || (value.lastUpdatedTime > getNotificationLastUpdatedTime(oldNotification))) {
+                                       notificationIds.push(value.id);
                                }
                        });
+                       if (notificationIds.length > 0) {
+                               loadNotifications(notificationIds);
+                       }
                        /* process new posts. */
                        $.each(data.newPosts, function(index, value) {
                                loadNewPost(value.id, value.sone, value.recipient, value.time);
@@ -1049,6 +1052,40 @@ function getStatus() {
 }
 
 /**
+ * Requests multiple notifications from Sone and displays them.
+ *
+ * @param notificationIds
+ *            Array of IDs of the notifications to load
+ */
+function loadNotifications(notificationIds) {
+       $.getJSON("getNotification.ajax", {"notifications": notificationIds.join(",")}, function(data, textStatus) {
+               if (!data || !data.success) {
+                       // TODO - show error
+                       return;
+               }
+               $.each(data.notifications, function(index, value) {
+                       oldNotification = getNotification(value.id);
+                       notification = ajaxifyNotification(createNotification(value.id, value.lastUpdatedTime, value.text, value.dismissable)).hide();
+                       if (oldNotification.length != 0) {
+                               if ((oldNotification.find(".short-text").length > 0) && (notification.find(".short-text").length > 0)) {
+                                       opened = oldNotification.is(":visible") && oldNotification.find(".short-text").hasClass("hidden");
+                                       notification.find(".short-text").toggleClass("hidden", opened);
+                                       notification.find(".text").toggleClass("hidden", !opened);
+                               }
+                               checkForRemovedSones(oldNotification, notification);
+                               checkForRemovedPosts(oldNotification, notification);
+                               checkForRemovedReplies(oldNotification, notification);
+                               oldNotification.replaceWith(notification.show());
+                       } else {
+                               $("#sone #notification-area").append(notification);
+                               notification.slideDown();
+                               setActivity();
+                       }
+               })
+       });
+}
+
+/**
  * 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
@@ -1078,6 +1115,22 @@ function isIndexPage() {
 }
 
 /**
+ * Returns the current page of the selected pagination. If no pagination can be
+ * found with the given selector, {@code 1} is returned.
+ *
+ * @param paginationSelector
+ *            The pagination selector
+ * @returns The current page of the pagination
+ */
+function getPage(paginationSelector) {
+       pagination = $(paginationSelector);
+       if (pagination.length > 0) {
+               return $(".current-page", paginationSelector).text();
+       }
+       return 1;
+}
+
+/**
  * Returns whether the current page is a “view Sone” page.
  *
  * @returns {Boolean} <code>true</code> if the current page is a “view Sone”
@@ -1155,7 +1208,7 @@ function loadNewPost(postId, soneId, recipientId, time) {
        if (hasPost(postId)) {
                return;
        }
-       if (!isIndexPage()) {
+       if (!isIndexPage() || (getPage(".pagination-index") > 1)) {
                if (!isViewPostPage() || (getShownPostId() != postId)) {
                        if (!isViewSonePage() || ((getShownSoneId() != soneId) && (getShownSoneId() != recipientId))) {
                                return;
@@ -1170,7 +1223,7 @@ function loadNewPost(postId, soneId, recipientId, time) {
                        if (hasPost(data.post.id)) {
                                return;
                        }
-                       if (!isIndexPage() && !(isViewSonePage() && ((getShownSoneId() == data.post.sone) || (getShownSoneId() == data.post.recipient)))) {
+                       if ((!isIndexPage() || (getPage(".pagination-index") > 1)) && !(isViewSonePage() && ((getShownSoneId() == data.post.sone) || (getShownSoneId() == data.post.recipient)))) {
                                return;
                        }
                        var firstOlderPost = null;
@@ -1243,11 +1296,10 @@ function loadNewReply(replyId, soneId, postId, postSoneId) {
  *            request
  */
 function markSoneAsKnown(soneElement, skipRequest) {
-       if ($(".new", soneElement).length > 0) {
-               if ((typeof skipRequest != "undefined") && !skipRequest) {
-                       $.getJSON("maskAsKnown.ajax", {"formPassword": getFormPassword(), "type": "sone", "id": getSoneId(soneElement)}, function(data, textStatus) {
-                               $(soneElement).removeClass("new");
-                       });
+       if ($(soneElement).is(".new")) {
+               $(soneElement).removeClass("new");
+               if ((typeof skipRequest == "undefined") || !skipRequest) {
+                       $.getJSON("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "sone", "id": getSoneId(soneElement)});
                }
        }
 }
@@ -1439,8 +1491,8 @@ function changeIcon(iconUrl) {
  *            <code>true</code> if the notification can be dismissed by the
  *            user
  */
-function createNotification(id, text, dismissable) {
-       notification = $("<div></div>").addClass("notification").attr("id", id);
+function createNotification(id, lastUpdatedTime, text, dismissable) {
+       notification = $("<div></div>").addClass("notification").attr("id", id).attr("lastUpdatedTime", lastUpdatedTime);
        if (dismissable) {
                dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id")
                dismissForm.find("input[name=notification]").val(id);
@@ -1655,6 +1707,11 @@ $(document).ready(function() {
                ajaxifyNotification($(this));
        });
 
+       /* disable all permalinks. */
+       $(".permalink").click(function() {
+               return false;
+       });
+
        /* activate status polling. */
        setTimeout(getStatus, 5000);
 
index fe9cf34..ef3b34b 100644 (file)
@@ -19,7 +19,7 @@
                        </form>
 
                        <%foreach webInterface.notifications.all notification>
-                               <div class="notification" id="<% notification.id|html>">
+                               <div class="notification" id="<% notification.id|html>" lastUpdatedTime="<%notification.lastUpdatedTime|html>">
                                        <%if notification.dismissable>
                                                <form class="dismiss" action="dismissNotification.html" method="post">
                                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
index a5efc97..f8e6f11 100644 (file)
@@ -1,5 +1,5 @@
 <%if pagination.necessary>
-       <div class="navigation">
+       <div class="navigation <%paginationName|html>">
                <div class="first"><%if ! pagination.first><a href="<% request|change nameKey=pageParameter value=0>">«</a><%else><span>«</span><%/if></div>
                <div class="previous"><%if ! pagination.first><a href="<% request|change nameKey=pageParameter key=pagination.previousPage>">‹</a><%else><span>‹</span><%/if></div>
                <div class="current-page"><% pagination.pageNumber></div>
index 4be1bcc..8b7ccb8 100644 (file)
                        </div>
                        <span class='separator'>·</span>
                        <div class="time"><a href="viewPost.html?post=<% post.id|html>"><% post.time|date format="MMM d, yyyy, HH:mm:ss"></a></div>
+                       <span class='separator'>·</span>
+                       <div class="permalink permalink-post"><a href="post://<%post.id|html>">[<%= View.Post.Permalink|l10n|html>]</a></div>
+                       <span class='separator'>·</span>
+                       <div class="permalink permalink-author"><a href="sone://<%post.sone.id|html>">[<%= View.Post.PermalinkAuthor|l10n|html>]</a></div>
                        <%if ! originalText|match key=parsedText>
                                <span class='separator'>·</span>
                                <div class="show-source"><a href="viewPost.html?post=<% post.id|html>&amp;raw=<%if raw>false<%else>true<%/if>"><%= View.Post.ShowSource|l10n|html></a></div>
index 5dc26fb..2fa4885 100644 (file)
@@ -15,6 +15,8 @@
                </div>
                <div class="reply-status-line status-line">
                        <div class="time"><% reply.time|date format="MMM d, yyyy, HH:mm:ss"></div>
+                       <span class='separator'>·</span>
+                       <div class="permalink permalink-author"><a href="sone://<%reply.sone.id|html>">[<%= View.Post.PermalinkAuthor|l10n|html>]</a></div>
                        <%if ! originalText|match key=parsedText>
                                <span class='separator'>·</span>
                                <div class="show-reply-source"><a href="viewPost.html?post=<% post.id|html>&amp;raw=<%if raw>false<%else>true<%/if>"><%= View.Post.ShowSource|l10n|html></a></div>
index c403b0d..91c921d 100644 (file)
@@ -5,7 +5,7 @@
        <div class="download-marker" title="<%= View.Sone.Status.Downloading|l10n|html>">⬊</div>
        <div class="insert-marker" title="<%= View.Sone.Status.Inserting|l10n|html>">⬈</div>
        <div class="idle-marker" title="<%= View.Sone.Status.Idle|l10n|html>">✔</div>
-       <div class="last-update"><%= View.Sone.Label.LastUpdate|l10n|html> <span class="time"><% sone.time|unknown|date format="MMM d, yyyy, HH:mm:ss"></span></div>
+       <div class="last-update"><%= View.Sone.Label.LastUpdate|l10n|html> <span class="time" title="<% sone.time|unknown|date format="MMM d, yyyy, HH:mm:ss">"><%sone.lastUpdatedText|html></span></div>
        <div class="profile-link"><a href="viewSone.html?sone=<% sone.id|html>" title="<% sone.requestUri|html>"><% sone.niceName|html></a></div>
        <div class="short-request-uri"><% sone.requestUri|substring start=4 length=43|html></div>
        <div class="hidden"><% sone.blacklisted></div>
index 59bfee7..f79e52d 100644 (file)
@@ -8,7 +8,7 @@
 
        <div id="posts">
                <%= page|store key=pageParameter>
-               <%include include/pagination.html>
+               <%include include/pagination.html paginationName==pagination-index>
                <%foreach pagination.items post>
                        <%include include/viewPost.html>
                <%foreachelse>
index 066adef..27b156d 100644 (file)
@@ -76,7 +76,7 @@
 
                <%foreach repliedPosts post>
                        <%first>
-                               <h2><%= Page.ViewSone.Replies.Title|l10n|html></h2>
+                               <h2><%= Page.ViewSone.Replies.Title|l10n|html|replace needle="{sone}" replacementKey=sone.niceName></h2>
                                <div id="replied-posts">
                                        <%include include/pagination.html pagination=repliedPostPagination pageParameter==repliedPostPage>
                        <%/first>