Merge branch 'release-0.4.2' 0.4.2
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Tue, 25 Jan 2011 08:21:27 +0000 (09:21 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Tue, 25 Jan 2011 08:21:27 +0000 (09:21 +0100)
84 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/SoneDownloader.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java
src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java
src/main/java/net/pterodactylus/sone/freenet/wot/Trust.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/template/CollectionAccessor.java
src/main/java/net/pterodactylus/sone/template/CssClassNameFilter.java
src/main/java/net/pterodactylus/sone/template/GetPagePlugin.java
src/main/java/net/pterodactylus/sone/template/IdentityAccessor.java
src/main/java/net/pterodactylus/sone/template/JavascriptFilter.java
src/main/java/net/pterodactylus/sone/template/NotificationManagerAccessor.java
src/main/java/net/pterodactylus/sone/template/ParserFilter.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/template/PostAccessor.java
src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java
src/main/java/net/pterodactylus/sone/template/RequestChangeFilter.java
src/main/java/net/pterodactylus/sone/template/SoneAccessor.java
src/main/java/net/pterodactylus/sone/template/SubstringFilter.java
src/main/java/net/pterodactylus/sone/template/TrustAccessor.java
src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/text/FreenetLinkParser.java
src/main/java/net/pterodactylus/sone/text/TemplatePart.java
src/main/java/net/pterodactylus/sone/web/AboutPage.java
src/main/java/net/pterodactylus/sone/web/CreatePostPage.java
src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java
src/main/java/net/pterodactylus/sone/web/CreateSonePage.java
src/main/java/net/pterodactylus/sone/web/DeletePostPage.java
src/main/java/net/pterodactylus/sone/web/DeleteProfileFieldPage.java
src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java
src/main/java/net/pterodactylus/sone/web/DeleteSonePage.java
src/main/java/net/pterodactylus/sone/web/DismissNotificationPage.java
src/main/java/net/pterodactylus/sone/web/DistrustPage.java
src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java
src/main/java/net/pterodactylus/sone/web/EditProfilePage.java
src/main/java/net/pterodactylus/sone/web/FollowSonePage.java
src/main/java/net/pterodactylus/sone/web/IndexPage.java
src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java
src/main/java/net/pterodactylus/sone/web/LikePage.java
src/main/java/net/pterodactylus/sone/web/LockSonePage.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 [new file with mode: 0644]
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/TrustPage.java
src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java
src/main/java/net/pterodactylus/sone/web/UnlikePage.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/ViewPostPage.java
src/main/java/net/pterodactylus/sone/web/ViewSonePage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/web/ajax/MarkPostAsKnownPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/MarkReplyAsKnownPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/page/TemplatePage.java
src/main/resources/i18n/sone.en.properties
src/main/resources/static/css/sone.css
src/main/resources/static/javascript/jquery.url.js [new file with mode: 0644]
src/main/resources/static/javascript/sone.js
src/main/resources/templates/editProfile.html
src/main/resources/templates/include/head.html
src/main/resources/templates/include/updateStatus.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/knownSones.html
src/main/resources/templates/notify/newPostNotification.html
src/main/resources/templates/notify/newReplyNotification.html
src/main/resources/templates/notify/newSoneNotification.html
src/main/resources/templates/viewPost.html
src/main/resources/templates/viewSone.html

diff --git a/pom.xml b/pom.xml
index ca493d8..4a5a06e 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.4.1</version>
+       <version>0.4.2</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
                        <artifactId>utils</artifactId>
-                       <version>0.7.8</version>
+                       <version>0.8</version>
                </dependency>
                <dependency>
                        <groupId>junit</groupId>
@@ -18,7 +18,7 @@
                <dependency>
                        <groupId>org.freenetproject</groupId>
                        <artifactId>fred</artifactId>
-                       <version>0.7.5.1311</version>
+                       <version>0.7.5.1336</version>
                        <scope>provided</scope>
                </dependency>
                <dependency>
index 27e8db8..dbda28d 100644 (file)
@@ -415,6 +415,7 @@ public class Core implements IdentityListener, UpdateListener {
                        if ((sone == null) && create) {
                                sone = new Sone(id);
                                localSones.put(id, sone);
+                               setSoneStatus(sone, SoneStatus.unknown);
                        }
                        return sone;
                }
@@ -458,6 +459,7 @@ public class Core implements IdentityListener, UpdateListener {
                        if ((sone == null) && create) {
                                sone = new Sone(id);
                                remoteSones.put(id, sone);
+                               setSoneStatus(sone, SoneStatus.unknown);
                        }
                        return sone;
                }
@@ -492,22 +494,15 @@ public class Core implements IdentityListener, UpdateListener {
        }
 
        /**
-        * Returns whether the given Sone is a new Sone. After this check, the Sone
-        * is marked as known, i.e. a second call with the same parameters will
-        * always yield {@code false}.
+        * Returns whether the Sone with the given ID is a new Sone.
         *
-        * @param sone
-        *            The sone to check for
+        * @param soneId
+        *            The ID of the sone to check for
         * @return {@code true} if the given Sone is new, false otherwise
         */
-       public boolean isNewSone(Sone sone) {
+       public boolean isNewSone(String soneId) {
                synchronized (newSones) {
-                       boolean isNew = !knownSones.contains(sone.getId()) && newSones.remove(sone.getId());
-                       knownSones.add(sone.getId());
-                       if (isNew) {
-                               coreListenerManager.fireMarkSoneKnown(sone);
-                       }
-                       return isNew;
+                       return !knownSones.contains(soneId) && newSones.contains(soneId);
                }
        }
 
@@ -569,8 +564,7 @@ public class Core implements IdentityListener, UpdateListener {
        }
 
        /**
-        * Returns whether the given post ID is new. After this method returns it is
-        * marked a known post ID.
+        * Returns whether the given post ID is new.
         *
         * @param postId
         *            The post ID
@@ -578,32 +572,8 @@ public class Core implements IdentityListener, UpdateListener {
         *         otherwise
         */
        public boolean isNewPost(String postId) {
-               return isNewPost(postId, true);
-       }
-
-       /**
-        * Returns whether the given post ID is new. If {@code markAsKnown} is
-        * {@code true} then after this method returns the post ID is marked a known
-        * post ID.
-        *
-        * @param postId
-        *            The post ID
-        * @param markAsKnown
-        *            {@code true} to mark the post ID as known, {@code false} to
-        *            not to mark it as known
-        * @return {@code true} if the post is considered to be new, {@code false}
-        *         otherwise
-        */
-       public boolean isNewPost(String postId, boolean markAsKnown) {
                synchronized (newPosts) {
-                       boolean isNew = !knownPosts.contains(postId) && newPosts.contains(postId);
-                       if (markAsKnown) {
-                               Post post = getPost(postId, false);
-                               if (post != null) {
-                                       markPostKnown(post);
-                               }
-                       }
-                       return isNew;
+                       return !knownPosts.contains(postId) && newPosts.contains(postId);
                }
        }
 
@@ -672,30 +642,8 @@ public class Core implements IdentityListener, UpdateListener {
         *         otherwise
         */
        public boolean isNewReply(String replyId) {
-               return isNewReply(replyId, true);
-       }
-
-       /**
-        * Returns whether the reply with the given ID is new.
-        *
-        * @param replyId
-        *            The ID of the reply to check
-        * @param markAsKnown
-        *            {@code true} to mark the reply as known, {@code false} to not
-        *            to mark it as known
-        * @return {@code true} if the reply is considered to be new, {@code false}
-        *         otherwise
-        */
-       public boolean isNewReply(String replyId, boolean markAsKnown) {
                synchronized (newReplies) {
-                       boolean isNew = !knownReplies.contains(replyId) && newReplies.contains(replyId);
-                       if (markAsKnown) {
-                               Reply reply = getReply(replyId, false);
-                               if (reply != null) {
-                                       markReplyKnown(reply);
-                               }
-                       }
-                       return isNew;
+                       return !knownReplies.contains(replyId) && newReplies.contains(replyId);
                }
        }
 
@@ -1027,10 +975,11 @@ public class Core implements IdentityListener, UpdateListener {
                                                }
                                        }
                                }
+                               List<Post> storedPosts = storedSone.getPosts();
                                synchronized (newPosts) {
                                        for (Post post : sone.getPosts()) {
-                                               post.setSone(getSone(post.getSone().getId()));
-                                               if (!storedSone.getPosts().contains(post) && !knownPosts.contains(post.getId())) {
+                                               post.setSone(storedSone);
+                                               if (!storedPosts.contains(post) && !knownPosts.contains(post.getId())) {
                                                        newPosts.add(post.getId());
                                                        coreListenerManager.fireNewPostFound(post);
                                                }
@@ -1047,10 +996,11 @@ public class Core implements IdentityListener, UpdateListener {
                                                }
                                        }
                                }
+                               Set<Reply> storedReplies = sone.getReplies();
                                synchronized (newReplies) {
                                        for (Reply reply : sone.getReplies()) {
-                                               reply.setSone(getSone(reply.getSone().getId()));
-                                               if (!storedSone.getReplies().contains(reply) && !knownReplies.contains(reply.getId())) {
+                                               reply.setSone(storedSone);
+                                               if (!storedReplies.contains(reply) && !knownReplies.contains(reply.getId())) {
                                                        newReplies.add(reply.getId());
                                                        coreListenerManager.fireNewReplyFound(reply);
                                                }
@@ -1123,6 +1073,23 @@ public class Core implements IdentityListener, UpdateListener {
        }
 
        /**
+        * Marks the given Sone as known. If the Sone was {@link #isNewPost(String)
+        * new} before, a {@link CoreListener#markSoneKnown(Sone)} event is fired.
+        *
+        * @param sone
+        *            The Sone to mark as known
+        */
+       public void markSoneKnown(Sone sone) {
+               synchronized (newSones) {
+                       if (newSones.remove(sone.getId())) {
+                               knownSones.add(sone.getId());
+                               coreListenerManager.fireMarkSoneKnown(sone);
+                               saveConfiguration();
+                       }
+               }
+       }
+
+       /**
         * Loads and updates the given Sone from the configuration. If any error is
         * encountered, loading is aborted and the given Sone is not changed.
         *
index 52c2857..3ccda5b 100644 (file)
@@ -153,8 +153,8 @@ public class FreenetInterface {
                                @SuppressWarnings("synthetic-access")
                                public void onFoundEdition(long edition, USK key, ObjectContainer objectContainer, ClientContext clientContext, boolean metadata, short codec, byte[] data, boolean newKnownGood, boolean newSlotToo) {
                                        logger.log(Level.FINE, "Found USK update for Sone “%s” at %s, new known good: %s, new slot too: %s.", new Object[] { sone, key, newKnownGood, newSlotToo });
-                                       if (newKnownGood) {
-                                               sone.setLatestEdition(key.suggestedEdition);
+                                       if (edition > sone.getLatestEdition()) {
+                                               sone.setLatestEdition(edition);
                                                new Thread(new Runnable() {
 
                                                        @Override
index af4155d..c883301 100644 (file)
@@ -285,7 +285,7 @@ public class SoneDownloader extends AbstractService {
                if ((soneInsertUri != null) && (sone.getInsertUri() == null)) {
                        try {
                                sone.setInsertUri(new FreenetURI(soneInsertUri));
-                               sone.setLatestEdition(Math.max(sone.getRequestUri().getSuggestedEdition(), sone.getInsertUri().getSuggestedEdition()));
+                               sone.setLatestEdition(Math.max(sone.getRequestUri().getEdition(), sone.getInsertUri().getEdition()));
                        } catch (MalformedURLException mue1) {
                                /* TODO - mark Sone as bad. */
                                logger.log(Level.WARNING, "Downloaded Sone " + sone + " has invalid insert URI: " + soneInsertUri, mue1);
index fec4e8f..c31be8a 100644 (file)
@@ -36,10 +36,13 @@ import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.service.AbstractService;
-import net.pterodactylus.util.template.DefaultTemplateFactory;
+import net.pterodactylus.util.template.HtmlFilter;
 import net.pterodactylus.util.template.ReflectionAccessor;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.template.TemplateContextFactory;
 import net.pterodactylus.util.template.TemplateException;
+import net.pterodactylus.util.template.TemplateParser;
 import net.pterodactylus.util.template.XmlFilter;
 import freenet.client.async.ManifestElement;
 import freenet.keys.FreenetURI;
@@ -58,11 +61,12 @@ public class SoneInserter extends AbstractService {
        private static volatile int insertionDelay = 60;
 
        /** The template factory used to create the templates. */
-       private static final DefaultTemplateFactory templateFactory = new DefaultTemplateFactory();
+       private static final TemplateContextFactory templateContextFactory = new TemplateContextFactory();
 
        static {
-               templateFactory.addAccessor(Object.class, new ReflectionAccessor());
-               templateFactory.addFilter("xml", new XmlFilter());
+               templateContextFactory.addAccessor(Object.class, new ReflectionAccessor());
+               templateContextFactory.addFilter("xml", new XmlFilter());
+               templateContextFactory.addFilter("html", new HtmlFilter());
        }
 
        /** The UTF-8 charset. */
@@ -182,7 +186,6 @@ public class SoneInserter extends AbstractService {
                                        } else {
                                                lastModificationTime = System.currentTimeMillis();
                                                modified = true;
-                                               sone.setTime(lastModificationTime);
                                                logger.log(Level.FINE, "Sone %s has been modified, waiting %d seconds before inserting.", new Object[] { sone.getName(), insertionDelay });
                                        }
                                        lastFingerprint = fingerprint;
@@ -199,12 +202,15 @@ public class SoneInserter extends AbstractService {
                                boolean success = false;
                                try {
                                        core.setSoneStatus(sone, SoneStatus.inserting);
+                                       long insertTime = System.currentTimeMillis();
+                                       insertInformation.setTime(insertTime);
                                        FreenetURI finalUri = freenetInterface.insertDirectory(insertInformation.getInsertUri().setKeyType("USK").setSuggestedEdition(0), insertInformation.generateManifestEntries(), "index.html");
                                        /* at this point we might already be stopped. */
                                        if (shouldStop()) {
                                                /* if so, bail out, don’t change anything. */
                                                break;
                                        }
+                                       sone.setTime(insertTime);
                                        sone.setLatestEdition(finalUri.getEdition());
                                        success = true;
                                        logger.log(Level.INFO, "Inserted Sone “%s” at %s.", new Object[] { sone.getName(), finalUri });
@@ -276,6 +282,16 @@ public class SoneInserter extends AbstractService {
                        return (FreenetURI) soneProperties.get("insertUri");
                }
 
+               /**
+                * Sets the time of the Sone at the time of the insert.
+                *
+                * @param time
+                *            The time of the Sone
+                */
+               public void setTime(long time) {
+                       soneProperties.put("time", time);
+               }
+
                //
                // ACTIONS
                //
@@ -314,10 +330,11 @@ public class SoneInserter extends AbstractService {
                 */
                @SuppressWarnings("synthetic-access")
                private ManifestElement createManifestElement(String name, String contentType, String templateName) {
-                       InputStreamReader templateInputStreamReader;
-                       Template template = templateFactory.createTemplate(templateInputStreamReader = new InputStreamReader(getClass().getResourceAsStream(templateName), utf8Charset));
+                       InputStreamReader templateInputStreamReader = null;
+                       Template template;
                        try {
-                               template.parse();
+                               templateInputStreamReader = new InputStreamReader(getClass().getResourceAsStream(templateName), utf8Charset);
+                               template = TemplateParser.parse(templateInputStreamReader);
                        } catch (TemplateException te1) {
                                logger.log(Level.SEVERE, "Could not parse template “" + templateName + "”!", te1);
                                return null;
@@ -325,12 +342,13 @@ public class SoneInserter extends AbstractService {
                                Closer.close(templateInputStreamReader);
                        }
 
-                       template.set("currentSone", soneProperties);
-                       template.set("version", SonePlugin.VERSION);
+                       TemplateContext templateContext = templateContextFactory.createTemplateContext();
+                       templateContext.set("currentSone", soneProperties);
+                       templateContext.set("version", SonePlugin.VERSION);
                        StringWriter writer = new StringWriter();
                        StringBucket bucket = null;
                        try {
-                               template.render(writer);
+                               template.render(templateContext, writer);
                                bucket = new StringBucket(writer.toString(), utf8Charset);
                                return new ManifestElement(name, bucket, contentType, bucket.size());
                        } catch (TemplateException te1) {
index 4d6a744..ef1a183 100644 (file)
@@ -19,8 +19,8 @@ package net.pterodactylus.sone.freenet;
 
 import java.util.Map;
 
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.TemplateContext;
 import freenet.l10n.BaseL10n;
 
 /**
@@ -48,7 +48,7 @@ public class L10nFilter implements Filter {
         * {@inheritDoc}
         */
        @Override
-       public String format(DataProvider dataProvider, Object data, Map<String, String> parameters) {
+       public String format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
                return l10n.getString(String.valueOf(data));
        }
 
index 3f4e66a..3c04823 100644 (file)
@@ -76,7 +76,7 @@ public class DefaultIdentity implements Identity {
                        }
                }
 
-       }, new TimedMap<OwnIdentity, CacheItem<Trust>>(60000));
+       }, new TimedMap<OwnIdentity, CacheItem<Trust>>(60 * 60 * 1000));
 
        /**
         * Creates a new identity.
index 975dd3f..5891c04 100644 (file)
@@ -79,4 +79,12 @@ public class Trust {
                return distance;
        }
 
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String toString() {
+               return getClass().getName() + "[explicit=" + explicit + ",implicit=" + implicit + ",distance=" + distance + "]";
+       }
+
 }
index 2d53e8b..4be076d 100644 (file)
@@ -78,7 +78,7 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 4, 1);
+       public static final Version VERSION = new Version(0, 4, 2);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
index 7d3e3c7..c7e5b7c 100644 (file)
@@ -48,8 +48,7 @@ public class ListNotification<T> extends TemplateNotification {
         */
        public ListNotification(String id, String key, Template template) {
                super(id, template);
-               template.set(key, elements);
-               template.set("notification", this);
+               template.getInitialContext().set(key, elements);
        }
 
        //
index 10e3ea3..0fa9129 100644 (file)
@@ -24,8 +24,8 @@ import java.util.List;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.template.Accessor;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.ReflectionAccessor;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * {@link Accessor} for {@link Collection}s that adds a couple of specialized
@@ -44,7 +44,7 @@ public class CollectionAccessor extends ReflectionAccessor {
         * {@inheritDoc}
         */
        @Override
-       public Object get(DataProvider dataProvider, Object object, String member) {
+       public Object get(TemplateContext templateContext, Object object, String member) {
                if (object == null) {
                        return null;
                }
@@ -67,7 +67,7 @@ public class CollectionAccessor extends ReflectionAccessor {
                        }
                        return soneNames.toString();
                }
-               return super.get(dataProvider, object, member);
+               return super.get(templateContext, object, member);
        }
 
 }
index 6ba5d6b..611c6d1 100644 (file)
@@ -19,8 +19,8 @@ package net.pterodactylus.sone.template;
 
 import java.util.Map;
 
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Converts the {@link String} {@link String#valueOf(Object) representation} of
@@ -35,7 +35,7 @@ public class CssClassNameFilter implements Filter {
         * {@inheritDoc}
         */
        @Override
-       public Object format(DataProvider dataProvider, Object data, Map<String, String> parameters) {
+       public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
                return String.valueOf(data).replaceAll("[^a-zA-Z0-9-]", "_");
        }
 
index c57932e..a25d619 100644 (file)
@@ -20,12 +20,12 @@ package net.pterodactylus.sone.template;
 import java.util.Map;
 
 import net.pterodactylus.sone.web.page.Page.Request;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Plugin;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
- * Extracts a page number from a {@link Request}’s parameters and stores it in a
- * {@link DataProvider}.
+ * Extracts a page number from a {@link Request}’s parameters and stores it in
+ * the {@link TemplateContext}.
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
@@ -35,7 +35,7 @@ public class GetPagePlugin implements Plugin {
         * {@inheritDoc}
         */
        @Override
-       public void execute(DataProvider dataProvider, Map<String, String> parameters) {
+       public void execute(TemplateContext templateContext, Map<String, String> parameters) {
                String requestKey = parameters.get("request");
                String parameter = parameters.get("parameter");
                String pageKey = parameters.get("key");
@@ -50,7 +50,7 @@ public class GetPagePlugin implements Plugin {
                        pageKey = "page";
                }
 
-               Request request = (Request) dataProvider.getData(requestKey);
+               Request request = (Request) templateContext.get(requestKey);
                String pageString = request.getHttpRequest().getParam(parameter);
                int page = 0;
                try {
@@ -58,7 +58,7 @@ public class GetPagePlugin implements Plugin {
                } catch (NumberFormatException nfe1) {
                        /* ignore. */
                }
-               dataProvider.setData(pageKey, page);
+               templateContext.set(pageKey, page);
        }
 
 }
index 84989b3..eec9347 100644 (file)
@@ -23,8 +23,8 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.freenet.wot.Identity;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.util.template.Accessor;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.ReflectionAccessor;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * {@link Accessor} implementation that adds a “uniqueNickname” member to an
@@ -51,7 +51,7 @@ public class IdentityAccessor extends ReflectionAccessor {
         * {@inheritDoc}
         */
        @Override
-       public Object get(DataProvider dataProvider, Object object, String member) {
+       public Object get(TemplateContext templateContext, Object object, String member) {
                Identity identity = (Identity) object;
                if ("uniqueNickname".equals(member)) {
                        int minLength = -1;
@@ -75,7 +75,7 @@ public class IdentityAccessor extends ReflectionAccessor {
                        } while (!found && (minLength < 43));
                        return getAbbreviatedNickname(identity, minLength);
                }
-               return super.get(dataProvider, object, member);
+               return super.get(templateContext, object, member);
        }
 
        //
index 0cd55de..f852202 100644 (file)
@@ -20,8 +20,8 @@ package net.pterodactylus.sone.template;
 import java.util.Map;
 
 import net.pterodactylus.util.number.Hex;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Escapes double quotes, backslashes, carriage returns and line feeds, and
@@ -36,7 +36,7 @@ public class JavascriptFilter implements Filter {
         * {@inheritDoc}
         */
        @Override
-       public Object format(DataProvider dataProvider, Object data, Map<String, String> parameters) {
+       public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
                StringBuilder javascriptString = new StringBuilder();
                javascriptString.append('"');
                for (char c : String.valueOf(data).toCharArray()) {
index 1c27b29..a8f34a8 100644 (file)
@@ -23,8 +23,8 @@ import java.util.List;
 
 import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.notify.NotificationManager;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.ReflectionAccessor;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Adds additional properties to a {@link NotificationManager}.
@@ -44,7 +44,7 @@ public class NotificationManagerAccessor extends ReflectionAccessor {
         * {@inheritDoc}
         */
        @Override
-       public Object get(DataProvider dataProvider, Object object, String member) {
+       public Object get(TemplateContext templateContext, Object object, String member) {
                NotificationManager notificationManager = (NotificationManager) object;
                if ("all".equals(member)) {
                        List<Notification> notifications = new ArrayList<Notification>(notificationManager.getNotifications());
@@ -55,7 +55,7 @@ public class NotificationManagerAccessor extends ReflectionAccessor {
                        Collections.sort(notifications, Notification.LAST_UPDATED_TIME_SORTER);
                        return notifications;
                }
-               return super.get(dataProvider, object, member);
+               return super.get(templateContext, object, member);
        }
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/template/ParserFilter.java b/src/main/java/net/pterodactylus/sone/template/ParserFilter.java
new file mode 100644 (file)
index 0000000..f1190e0
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Sone - ParserFilter.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
+ * 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.template;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Map;
+
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.text.FreenetLinkParser;
+import net.pterodactylus.sone.text.FreenetLinkParserContext;
+import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.template.TemplateContextFactory;
+
+/**
+ * Filter that filters a given text through a {@link FreenetLinkParser}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ParserFilter implements Filter {
+
+       /** The link parser. */
+       private final FreenetLinkParser linkParser;
+
+       /**
+        * Creates a new filter that runs its input through a
+        * {@link FreenetLinkParser}.
+        *
+        * @param templateContextFactory
+        *            The context factory for rendering the parts
+        */
+       public ParserFilter(TemplateContextFactory templateContextFactory) {
+               linkParser = new FreenetLinkParser(templateContextFactory);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
+               String text = String.valueOf(data);
+               String soneKey = parameters.get("sone");
+               if (soneKey == null) {
+                       soneKey = "sone";
+               }
+               Sone sone = (Sone) templateContext.get(soneKey);
+               FreenetLinkParserContext context = new FreenetLinkParserContext(sone);
+               try {
+                       return linkParser.parse(context, new StringReader(text));
+               } catch (IOException ioe1) {
+                       /* no exceptions in a StringReader, ignore. */
+               }
+               return null;
+       }
+
+}
index 95dc614..f6a9c06 100644 (file)
 
 package net.pterodactylus.sone.template;
 
-import java.io.IOException;
-import java.io.StringReader;
-
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.text.FreenetLinkParser;
-import net.pterodactylus.sone.text.FreenetLinkParserContext;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.ReflectionAccessor;
-import net.pterodactylus.util.template.TemplateFactory;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Accessor for {@link Post} objects that adds additional properties:
@@ -40,9 +34,6 @@ import net.pterodactylus.util.template.TemplateFactory;
  */
 public class PostAccessor extends ReflectionAccessor {
 
-       /** Parser for Freenet links. */
-       private final FreenetLinkParser linkParser;
-
        /** The core to get the replies from. */
        private final Core core;
 
@@ -51,41 +42,28 @@ public class PostAccessor extends ReflectionAccessor {
         *
         * @param core
         *            The core to get the replies from
-        * @param templateFactory
-        *            The template factory for the text parser
         */
-       public PostAccessor(Core core, TemplateFactory templateFactory) {
+       public PostAccessor(Core core) {
                this.core = core;
-               linkParser = new FreenetLinkParser(templateFactory);
        }
 
        /**
         * {@inheritDoc}
         */
        @Override
-       public Object get(DataProvider dataProvider, Object object, String member) {
+       public Object get(TemplateContext templateContext, Object object, String member) {
                Post post = (Post) object;
                if ("replies".equals(member)) {
                        return core.getReplies(post);
                } else if (member.equals("likes")) {
                        return core.getLikes(post);
                } else if (member.equals("liked")) {
-                       Sone currentSone = (Sone) dataProvider.getData("currentSone");
+                       Sone currentSone = (Sone) templateContext.get("currentSone");
                        return (currentSone != null) && (currentSone.isLikedPostId(post.getId()));
                } else if (member.equals("new")) {
-                       return core.isNewPost(post.getId(), false);
-               } else if (member.equals("text")) {
-                       String text = post.getText();
-                       if (text == null) {
-                               return null;
-                       }
-                       try {
-                               return linkParser.parse(new FreenetLinkParserContext(post.getSone()), new StringReader(text));
-                       } catch (IOException ioe1) {
-                               /* ignore. */
-                       }
+                       return core.isNewPost(post.getId());
                }
-               return super.get(dataProvider, object, member);
+               return super.get(templateContext, object, member);
        }
 
 }
index db6c528..f439abe 100644 (file)
 
 package net.pterodactylus.sone.template;
 
-import java.io.IOException;
-import java.io.StringReader;
-
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.text.FreenetLinkParser;
-import net.pterodactylus.sone.text.FreenetLinkParserContext;
 import net.pterodactylus.util.template.Accessor;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.ReflectionAccessor;
-import net.pterodactylus.util.template.TemplateFactory;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * {@link Accessor} implementation that adds a couple of properties to
@@ -38,9 +32,6 @@ import net.pterodactylus.util.template.TemplateFactory;
  */
 public class ReplyAccessor extends ReflectionAccessor {
 
-       /** Parser for Freenet links. */
-       private final FreenetLinkParser linkParser;
-
        /** The core. */
        private final Core core;
 
@@ -49,36 +40,26 @@ public class ReplyAccessor extends ReflectionAccessor {
         *
         * @param core
         *            The core
-        * @param templateFactory
-        *            The template factory for the text parser
         */
-       public ReplyAccessor(Core core, TemplateFactory templateFactory) {
+       public ReplyAccessor(Core core) {
                this.core = core;
-               linkParser = new FreenetLinkParser(templateFactory);
        }
 
        /**
         * {@inheritDoc}
         */
        @Override
-       public Object get(DataProvider dataProvider, Object object, String member) {
+       public Object get(TemplateContext templateContext, Object object, String member) {
                Reply reply = (Reply) object;
                if ("likes".equals(member)) {
                        return core.getLikes(reply);
                } else if (member.equals("liked")) {
-                       Sone currentSone = (Sone) dataProvider.getData("currentSone");
+                       Sone currentSone = (Sone) templateContext.get("currentSone");
                        return (currentSone != null) && (currentSone.isLikedReplyId(reply.getId()));
                } else if (member.equals("new")) {
-                       return core.isNewReply(reply.getId(), false);
-               } else if (member.equals("text")) {
-                       String text = reply.getText();
-                       try {
-                               return linkParser.parse(new FreenetLinkParserContext(reply.getSone()), new StringReader(text));
-                       } catch (IOException ioe1) {
-                               /* ignore. */
-                       }
+                       return core.isNewReply(reply.getId());
                }
-               return super.get(dataProvider, object, member);
+               return super.get(templateContext, object, member);
        }
 
 }
index 380027b..fb31719 100644 (file)
@@ -27,8 +27,8 @@ import java.util.Map;
 import java.util.Map.Entry;
 
 import net.pterodactylus.sone.web.page.Page.Request;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This filter expects a {@link Request} as input and outputs a {@link URI} that
@@ -44,17 +44,17 @@ public class RequestChangeFilter implements Filter {
         * {@inheritDoc}
         */
        @Override
-       public Object format(DataProvider dataProvider, Object data, Map<String, String> parameters) {
+       public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
                Request request = (Request) data;
                String name = parameters.get("name");
                String nameKey = parameters.get("nameKey");
                if (nameKey != null) {
-                       name = String.valueOf(dataProvider.getData(nameKey));
+                       name = String.valueOf(templateContext.get(nameKey));
                }
                String key = parameters.get("key");
                String value = null;
                if (key != null) {
-                       value = String.valueOf(dataProvider.getData(key));
+                       value = String.valueOf(templateContext.get(key));
                }
                if (value == null) {
                        value = parameters.get("value");
index 0cb92ff..23b2227 100644 (file)
 
 package net.pterodactylus.sone.template;
 
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
 import net.pterodactylus.sone.core.Core;
 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.util.logging.Logging;
 import net.pterodactylus.util.template.Accessor;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.ReflectionAccessor;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * {@link Accessor} for {@link Sone}s that adds a couple of properties to Sones.
@@ -35,7 +39,7 @@ import net.pterodactylus.util.template.ReflectionAccessor;
  * <dt>friend</dt>
  * <dd>Will return {@code true} if the sone in question is a friend of the
  * currently logged in Sone (as determined by accessing the “currentSone”
- * variable of the given {@link DataProvider}).</dd>
+ * variable of the given {@link TemplateContext}).</dd>
  * <dt>current</dt>
  * <dd>Will return {@code true} if the sone in question is the currently logged
  * in Sone.</dd>
@@ -45,6 +49,9 @@ import net.pterodactylus.util.template.ReflectionAccessor;
  */
 public class SoneAccessor extends ReflectionAccessor {
 
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(SoneAccessor.class);
+
        /** The core. */
        private final Core core;
 
@@ -62,17 +69,17 @@ public class SoneAccessor extends ReflectionAccessor {
         * {@inheritDoc}
         */
        @Override
-       public Object get(DataProvider dataProvider, Object object, String member) {
+       public Object get(TemplateContext templateContext, Object object, String member) {
                Sone sone = (Sone) object;
                if (member.equals("niceName")) {
                        return getNiceName(sone);
                } else if (member.equals("local")) {
-                       return sone.getInsertUri() != null;
+                       return core.isLocalSone(sone);
                } else if (member.equals("friend")) {
-                       Sone currentSone = (Sone) dataProvider.getData("currentSone");
+                       Sone currentSone = (Sone) templateContext.get("currentSone");
                        return (currentSone != null) && currentSone.hasFriend(sone.getId());
                } else if (member.equals("current")) {
-                       Sone currentSone = (Sone) dataProvider.getData("currentSone");
+                       Sone currentSone = (Sone) templateContext.get("currentSone");
                        return (currentSone != null) && currentSone.equals(sone);
                } else if (member.equals("modified")) {
                        return core.isModifiedSone(sone);
@@ -87,17 +94,22 @@ public class SoneAccessor extends ReflectionAccessor {
                } else if (member.equals("downloading")) {
                        return core.getSoneStatus(sone) == SoneStatus.downloading;
                } else if (member.equals("new")) {
-                       return core.isNewSone(sone);
+                       return core.isNewSone(sone.getId());
                } else if (member.equals("locked")) {
                        return core.isLocked(sone);
                } else if (member.equals("trust")) {
-                       Sone currentSone = (Sone) dataProvider.getData("currentSone");
+                       Sone currentSone = (Sone) templateContext.get("currentSone");
+                       if (currentSone == null) {
+                               return null;
+                       }
                        Trust trust = core.getTrust(currentSone, sone);
+                       logger.log(Level.FINEST, "Trust for %s by %s: %s", new Object[] { sone, currentSone, trust });
                        if (trust == null) {
                                return new Trust(null, null, null);
                        }
+                       return trust;
                }
-               return super.get(dataProvider, object, member);
+               return super.get(templateContext, object, member);
        }
 
        //
index f0075e4..857017c 100644 (file)
@@ -19,8 +19,8 @@ package net.pterodactylus.sone.template;
 
 import java.util.Map;
 
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * {@link Filter} implementation that executes
@@ -37,7 +37,7 @@ public class SubstringFilter implements Filter {
         * {@inheritDoc}
         */
        @Override
-       public Object format(DataProvider dataProvider, Object data, Map<String, String> parameters) {
+       public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
                String startString = parameters.get("start");
                String lengthString = parameters.get("length");
                int start = 0;
index 4dfd81e..4aad3c9 100644 (file)
@@ -19,8 +19,8 @@ package net.pterodactylus.sone.template;
 
 import net.pterodactylus.sone.freenet.wot.Trust;
 import net.pterodactylus.util.template.Accessor;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.ReflectionAccessor;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * {@link Accessor} implementation for {@link Trust} values, adding the
@@ -39,7 +39,7 @@ public class TrustAccessor extends ReflectionAccessor {
         * {@inheritDoc}
         */
        @Override
-       public Object get(DataProvider dataProvider, Object object, String member) {
+       public Object get(TemplateContext templateContext, Object object, String member) {
                Trust trust = (Trust) object;
                if ("assigned".equals(member)) {
                        return trust.getExplicit() != null;
@@ -48,7 +48,7 @@ public class TrustAccessor extends ReflectionAccessor {
                } else if ("hasDistance".equals(member)) {
                        return (trust.getDistance() != null) && (trust.getDistance() != Integer.MAX_VALUE);
                }
-               return super.get(dataProvider, object, member);
+               return super.get(templateContext, object, member);
        }
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java b/src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java
new file mode 100644 (file)
index 0000000..586e90c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Sone - UnknownDateFilter.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
+ * 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.template;
+
+import java.util.Map;
+
+import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.TemplateContext;
+import freenet.l10n.BaseL10n;
+
+/**
+ * {@link Filter} implementation that replaces a {@link Long} with a value of
+ * {@code 0} by a {@link String} from an {@link BaseL10n l10n handler}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UnknownDateFilter implements Filter {
+
+       /** The l10n handler. */
+       private BaseL10n l10nHandler;
+
+       /** The key for the text to show. */
+       private final String unknownKey;
+
+       /**
+        * Creates a new unknown date filter.
+        *
+        * @param l10nHandler
+        *            The l10n handler
+        * @param unknownKey
+        *            The key of the text to show
+        */
+       public UnknownDateFilter(BaseL10n l10nHandler, String unknownKey) {
+               this.l10nHandler = l10nHandler;
+               this.unknownKey = unknownKey;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
+               if (data instanceof Long) {
+                       if ((Long) data == 0) {
+                               return l10nHandler.getString(unknownKey);
+                       }
+               }
+               return data;
+       }
+
+}
index 1ef77d2..704792c 100644 (file)
@@ -28,7 +28,8 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.template.TemplateFactory;
+import net.pterodactylus.util.template.TemplateContextFactory;
+import net.pterodactylus.util.template.TemplateParser;
 import freenet.keys.FreenetURI;
 
 /**
@@ -72,16 +73,16 @@ public class FreenetLinkParser implements Parser<FreenetLinkParserContext> {
        }
 
        /** The template factory. */
-       private final TemplateFactory templateFactory;
+       private final TemplateContextFactory templateContextFactory;
 
        /**
         * Creates a new freenet link parser.
         *
-        * @param templateFactory
-        *            The template factory
+        * @param templateContextFactory
+        *            The template context factory
         */
-       public FreenetLinkParser(TemplateFactory templateFactory) {
-               this.templateFactory = templateFactory;
+       public FreenetLinkParser(TemplateContextFactory templateContextFactory) {
+               this.templateContextFactory = templateContextFactory;
        }
 
        //
@@ -218,7 +219,7 @@ public class FreenetLinkParser implements Parser<FreenetLinkParserContext> {
         * @return The part that displays the given text
         */
        private Part createPlainTextPart(String text) {
-               return new TemplatePart(templateFactory.createTemplate(new StringReader("<% text|html>"))).set("text", text);
+               return new TemplatePart(templateContextFactory, TemplateParser.parse(new StringReader("<% text|html>"))).set("text", text);
        }
 
        /**
@@ -232,7 +233,7 @@ public class FreenetLinkParser implements Parser<FreenetLinkParserContext> {
         * @return The part that displays the link
         */
        private Part createInternetLinkPart(String link, String name) {
-               return new TemplatePart(templateFactory.createTemplate(new StringReader("<a class=\"internet\" href=\"/<% link|html>\" title=\"<% link|html>\"><% name|html></a>"))).set("link", link).set("name", name);
+               return new TemplatePart(templateContextFactory, TemplateParser.parse(new StringReader("<a class=\"internet\" href=\"/<% link|html>\" title=\"<% link|html>\"><% name|html></a>"))).set("link", link).set("name", name);
        }
 
        /**
@@ -246,7 +247,7 @@ public class FreenetLinkParser implements Parser<FreenetLinkParserContext> {
         * @return The part that displays the link
         */
        private Part createFreenetLinkPart(String link, String name) {
-               return new TemplatePart(templateFactory.createTemplate(new StringReader("<a class=\"freenet\" href=\"/<% link|html>\" title=\"<% link|html>\"><% name|html></a>"))).set("link", link).set("name", name);
+               return new TemplatePart(templateContextFactory, TemplateParser.parse(new StringReader("<a class=\"freenet\" href=\"/<% link|html>\" title=\"<% link|html>\"><% name|html></a>"))).set("link", link).set("name", name);
        }
 
        /**
@@ -260,7 +261,7 @@ public class FreenetLinkParser implements Parser<FreenetLinkParserContext> {
         * @return The part that displays the link
         */
        private Part createTrustedFreenetLinkPart(String link, String name) {
-               return new TemplatePart(templateFactory.createTemplate(new StringReader("<a class=\"freenet-trusted\" href=\"/<% link|html>\" title=\"<% link|html>\"><% name|html></a>"))).set("link", link).set("name", name);
+               return new TemplatePart(templateContextFactory, TemplateParser.parse(new StringReader("<a class=\"freenet-trusted\" href=\"/<% link|html>\" title=\"<% link|html>\"><% name|html></a>"))).set("link", link).set("name", name);
        }
 
 }
index 1663e08..1ac1fdd 100644 (file)
@@ -21,13 +21,19 @@ import java.io.IOException;
 import java.io.Writer;
 
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.template.TemplateContextFactory;
+import net.pterodactylus.util.template.TemplateException;
 
 /**
  * {@link Part} implementation that is rendered using a {@link Template}.
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class TemplatePart implements Part {
+public class TemplatePart implements Part, net.pterodactylus.util.template.Part {
+
+       /** The template context factory. */
+       private final TemplateContextFactory templateContextFactory;
 
        /** The template to render for this part. */
        private final Template template;
@@ -35,10 +41,13 @@ public class TemplatePart implements Part {
        /**
         * Creates a new template part.
         *
+        * @param templateContextFactory
+        *            The template context factory
         * @param template
         *            The template to render
         */
-       public TemplatePart(Template template) {
+       public TemplatePart(TemplateContextFactory templateContextFactory, Template template) {
+               this.templateContextFactory = templateContextFactory;
                this.template = template;
        }
 
@@ -56,7 +65,7 @@ public class TemplatePart implements Part {
         * @return This template part (for method chaining)
         */
        public TemplatePart set(String key, Object value) {
-               template.set(key, value);
+               template.getInitialContext().set(key, value);
                return this;
        }
 
@@ -69,7 +78,15 @@ public class TemplatePart implements Part {
         */
        @Override
        public void render(Writer writer) throws IOException {
-               template.render(writer);
+               template.render(templateContextFactory.createTemplateContext().mergeContext(template.getInitialContext()), writer);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void render(TemplateContext templateContext, Writer writer) throws TemplateException {
+               template.render(templateContext.mergeContext(template.getInitialContext()), writer);
        }
 
 }
index 4a0dd22..4c14457 100644 (file)
@@ -17,8 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.version.Version;
 
 /**
@@ -54,9 +54,9 @@ public class AboutPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
-               dataProvider.set("version", version);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
+               templateContext.set("version", version);
        }
 
 }
index aba4700..57eb18a 100644 (file)
@@ -20,8 +20,8 @@ package net.pterodactylus.sone.web;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page lets the user create a new {@link Post}.
@@ -50,21 +50,26 @@ public class CreatePostPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
                        String text = request.getHttpRequest().getPartAsStringFailsafe("text", 65536).trim();
                        if (text.length() != 0) {
+                               String senderId = request.getHttpRequest().getPartAsStringFailsafe("sender", 43);
                                String recipientId = request.getHttpRequest().getPartAsStringFailsafe("recipient", 43);
-                               Sone recipient = webInterface.getCore().getSone(recipientId, false);
                                Sone currentSone = getCurrentSone(request.getToadletContext());
-                               webInterface.getCore().createPost(currentSone, recipient, System.currentTimeMillis(), text);
+                               Sone sender = webInterface.getCore().getLocalSone(senderId, false);
+                               if (sender == null) {
+                                       sender = currentSone;
+                               }
+                               Sone recipient = webInterface.getCore().getSone(recipientId, false);
+                               webInterface.getCore().createPost(sender, recipient, System.currentTimeMillis(), text);
                                throw new RedirectException(returnPage);
                        }
-                       dataProvider.set("errorTextEmpty", true);
+                       templateContext.set("errorTextEmpty", true);
                }
-               dataProvider.set("returnPage", returnPage);
+               templateContext.set("returnPage", returnPage);
        }
 
 }
index d4a9775..aae5e83 100644 (file)
@@ -20,8 +20,8 @@ package net.pterodactylus.sone.web;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page lets the user post a reply to a post.
@@ -50,23 +50,27 @@ public class CreateReplyPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                String postId = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
                String text = request.getHttpRequest().getPartAsStringFailsafe("text", 65536).trim();
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
                        Post post = webInterface.getCore().getPost(postId);
                        if (text.length() > 0) {
-                               Sone currentSone = getCurrentSone(request.getToadletContext());
-                               webInterface.getCore().createReply(currentSone, post, text);
+                               String senderId = request.getHttpRequest().getPartAsStringFailsafe("sender", 43);
+                               Sone sender = webInterface.getCore().getLocalSone(senderId, false);
+                               if (sender == null) {
+                                       sender = getCurrentSone(request.getToadletContext());
+                               }
+                               webInterface.getCore().createReply(sender, post, text);
                                throw new RedirectException(returnPage);
                        }
-                       dataProvider.set("errorTextEmpty", true);
+                       templateContext.set("errorTextEmpty", true);
                }
-               dataProvider.set("postId", postId);
-               dataProvider.set("text", text);
-               dataProvider.set("returnPage", returnPage);
+               templateContext.set("postId", postId);
+               templateContext.set("text", text);
+               templateContext.set("returnPage", returnPage);
        }
 
 }
index 46302b0..cc40320 100644 (file)
@@ -30,8 +30,8 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
 import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 import freenet.clients.http.ToadletContext;
 
 /**
@@ -94,10 +94,10 @@ public class CreateSonePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                List<OwnIdentity> ownIdentitiesWithoutSone = getOwnIdentitiesWithoutSone(webInterface.getCore());
-               dataProvider.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
+               templateContext.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
                if (request.getMethod() == Method.POST) {
                        String id = request.getHttpRequest().getPartAsStringFailsafe("identity", 44);
                        OwnIdentity selectedIdentity = null;
@@ -108,7 +108,7 @@ public class CreateSonePage extends SoneTemplatePage {
                                }
                        }
                        if (selectedIdentity == null) {
-                               dataProvider.set("errorNoIdentity", true);
+                               templateContext.set("errorNoIdentity", true);
                                return;
                        }
                        /* create Sone. */
@@ -129,7 +129,7 @@ public class CreateSonePage extends SoneTemplatePage {
         */
        @Override
        public boolean isEnabled(ToadletContext toadletContext) {
-               return getCurrentSone(toadletContext) == null;
+               return (getCurrentSone(toadletContext, false) == null) || (webInterface.getCore().getLocalSones().size() == 1);
        }
 
 }
index 5722ee3..7a36d02 100644 (file)
 package net.pterodactylus.sone.web;
 
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Lets the user delete a post they made.
@@ -50,31 +49,30 @@ public class DeletePostPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.GET) {
                        String postId = request.getHttpRequest().getParam("post");
                        String returnPage = request.getHttpRequest().getParam("returnPage");
                        Post post = webInterface.getCore().getPost(postId);
-                       dataProvider.set("post", post);
-                       dataProvider.set("returnPage", returnPage);
+                       templateContext.set("post", post);
+                       templateContext.set("returnPage", returnPage);
                        return;
                } else if (request.getMethod() == Method.POST) {
                        String postId = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                        Post post = webInterface.getCore().getPost(postId);
-                       Sone currentSone = getCurrentSone(request.getToadletContext());
-                       if (!post.getSone().equals(currentSone)) {
+                       if (!webInterface.getCore().isLocalSone(post.getSone())) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("confirmDelete")) {
-                               currentSone.removePost(post);
+                               webInterface.getCore().deletePost(post);
                                throw new RedirectException(returnPage);
                        } else if (request.getHttpRequest().isPartSet("abortDelete")) {
                                throw new RedirectException(returnPage);
                        }
-                       dataProvider.set("post", post);
-                       dataProvider.set("returnPage", returnPage);
+                       templateContext.set("post", post);
+                       templateContext.set("returnPage", returnPage);
                }
        }
 
index 9b52b04..82f2ace 100644 (file)
@@ -21,8 +21,8 @@ import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Page that lets the user confirm the deletion of a profile field.
@@ -51,8 +51,8 @@ public class DeleteProfileFieldPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                Sone currentSone = getCurrentSone(request.getToadletContext());
                Profile profile = currentSone.getProfile();
 
@@ -78,7 +78,7 @@ public class DeleteProfileFieldPage extends SoneTemplatePage {
                }
 
                /* set current values in template. */
-               dataProvider.set("field", field);
+               templateContext.set("field", field);
        }
 
 }
index 8d90474..782589f 100644 (file)
 package net.pterodactylus.sone.web;
 
 import net.pterodactylus.sone.data.Reply;
-import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page lets the user delete a reply.
@@ -50,14 +49,13 @@ public class DeleteReplyPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                String replyId = request.getHttpRequest().getPartAsStringFailsafe("reply", 36);
                Reply reply = webInterface.getCore().getReply(replyId);
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
-                       Sone currentSone = getCurrentSone(request.getToadletContext());
-                       if (!reply.getSone().equals(currentSone)) {
+                       if (!webInterface.getCore().isLocalSone(reply.getSone())) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("confirmDelete")) {
@@ -67,8 +65,8 @@ public class DeleteReplyPage extends SoneTemplatePage {
                                throw new RedirectException(returnPage);
                        }
                }
-               dataProvider.set("reply", reply);
-               dataProvider.set("returnPage", returnPage);
+               templateContext.set("reply", reply);
+               templateContext.set("returnPage", returnPage);
        }
 
 }
index abe26ea..54d77b8 100644 (file)
@@ -19,8 +19,8 @@ package net.pterodactylus.sone.web;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Lets the user delete a Sone. Of course the Sone is not really deleted from
@@ -51,8 +51,8 @@ public class DeleteSonePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.POST) {
                        if (request.getHttpRequest().isPartSet("deleteSone")) {
                                Sone currentSone = getCurrentSone(request.getToadletContext());
index 2d61a95..15c5675 100644 (file)
@@ -18,8 +18,8 @@
 package net.pterodactylus.sone.web;
 
 import net.pterodactylus.util.notify.Notification;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Page that lets the user dismiss a notification.
@@ -48,8 +48,8 @@ public class DismissNotificationPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                String notificationId = request.getHttpRequest().getPartAsStringFailsafe("notification", 36);
                Notification notification = webInterface.getNotifications().getNotification(notificationId);
                if ((notification != null) && notification.isDismissable()) {
index 9cbbb55..c055545 100644 (file)
@@ -20,8 +20,8 @@ package net.pterodactylus.sone.web;
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Page that lets the user distrust another Sone. This will assign a
@@ -52,17 +52,17 @@ public class DistrustPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.POST) {
-                       String returnPath = request.getHttpRequest().getPartAsStringFailsafe("returnPath", 256);
+                       String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                        String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                        Sone currentSone = getCurrentSone(request.getToadletContext());
                        Sone sone = webInterface.getCore().getSone(identity, false);
                        if (sone != null) {
                                webInterface.getCore().distrustSone(currentSone, sone);
                        }
-                       throw new RedirectException(returnPath);
+                       throw new RedirectException(returnPage);
                }
        }
 
index 1b64b08..219bdc5 100644 (file)
@@ -21,8 +21,8 @@ import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Page that lets the user edit the name of a profile field.
@@ -51,8 +51,8 @@ public class EditProfileFieldPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                Sone currentSone = getCurrentSone(request.getToadletContext());
                Profile profile = currentSone.getProfile();
 
@@ -80,11 +80,11 @@ public class EditProfileFieldPage extends SoneTemplatePage {
                                currentSone.setProfile(profile);
                                throw new RedirectException("editProfile.html#profile-fields");
                        }
-                       dataProvider.set("duplicateFieldName", true);
+                       templateContext.set("duplicateFieldName", true);
                }
 
                /* store current values in template. */
-               dataProvider.set("field", field);
+               templateContext.set("field", field);
        }
 
 }
index 380e054..52468a3 100644 (file)
@@ -24,8 +24,8 @@ import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
 import net.pterodactylus.util.number.Numbers;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 import freenet.clients.http.ToadletContext;
 
 /**
@@ -55,8 +55,8 @@ public class EditProfilePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                ToadletContext toadletContenxt = request.getToadletContext();
                Sone currentSone = getCurrentSone(toadletContenxt);
                Profile profile = currentSone.getProfile();
@@ -95,8 +95,8 @@ public class EditProfilePage extends SoneTemplatePage {
                                        webInterface.getCore().saveSone(currentSone);
                                        throw new RedirectException("editProfile.html#profile-fields");
                                } catch (IllegalArgumentException iae1) {
-                                       dataProvider.set("fieldName", fieldName);
-                                       dataProvider.set("duplicateFieldName", true);
+                                       templateContext.set("fieldName", fieldName);
+                                       templateContext.set("duplicateFieldName", true);
                                }
                        } else {
                                String id = getFieldId(request, "delete-field-");
@@ -129,13 +129,13 @@ public class EditProfilePage extends SoneTemplatePage {
                                }
                        }
                }
-               dataProvider.set("firstName", firstName);
-               dataProvider.set("middleName", middleName);
-               dataProvider.set("lastName", lastName);
-               dataProvider.set("birthDay", birthDay);
-               dataProvider.set("birthMonth", birthMonth);
-               dataProvider.set("birthYear", birthYear);
-               dataProvider.set("fields", fields);
+               templateContext.set("firstName", firstName);
+               templateContext.set("middleName", middleName);
+               templateContext.set("lastName", lastName);
+               templateContext.set("birthDay", birthDay);
+               templateContext.set("birthMonth", birthMonth);
+               templateContext.set("birthYear", birthYear);
+               templateContext.set("fields", fields);
        }
 
        //
index 864480a..9cd1f2a 100644 (file)
@@ -19,8 +19,8 @@ package net.pterodactylus.sone.web;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page lets the user follow another Sone.
@@ -47,8 +47,8 @@ public class FollowSonePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.POST) {
                        String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
index b0a27f7..2d85e02 100644 (file)
@@ -26,8 +26,8 @@ import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.collection.Pagination;
 import net.pterodactylus.util.number.Numbers;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * The index page shows the main page of Sone. This page will contain the posts
@@ -55,8 +55,8 @@ public class IndexPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                Sone currentSone = getCurrentSone(request.getToadletContext());
                List<Post> allPosts = new ArrayList<Post>();
                allPosts.addAll(currentSone.getPosts());
@@ -75,17 +75,17 @@ public class IndexPage extends SoneTemplatePage {
                }
                Collections.sort(allPosts, Post.TIME_COMPARATOR);
                Pagination<Post> pagination = new Pagination<Post>(allPosts, 25).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
-               dataProvider.set("pagination", pagination);
-               dataProvider.set("posts", pagination.getItems());
+               templateContext.set("pagination", pagination);
+               templateContext.set("posts", pagination.getItems());
        }
 
        /**
         * {@inheritDoc}
         */
        @Override
-       protected void postProcess(Request request, DataProvider dataProvider) {
+       protected void postProcess(Request request, TemplateContext templateContext) {
                @SuppressWarnings("unchecked")
-               List<Post> posts = (List<Post>) dataProvider.get("posts");
+               List<Post> posts = (List<Post>) templateContext.get("posts");
                for (Post post : posts) {
                        webInterface.getCore().markPostKnown(post);
                        for (Reply reply : webInterface.getCore().getReplies(post)) {
index 774f410..b08b6e3 100644 (file)
@@ -22,8 +22,10 @@ import java.util.Collections;
 import java.util.List;
 
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.template.DataProvider;
+import net.pterodactylus.util.collection.Pagination;
+import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page shows all known Sones.
@@ -52,11 +54,26 @@ public class KnownSonesPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                List<Sone> knownSones = new ArrayList<Sone>(webInterface.getCore().getSones());
                Collections.sort(knownSones, Sone.NICE_NAME_COMPARATOR);
-               dataProvider.set("knownSones", knownSones);
+               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());
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected void postProcess(Request request, TemplateContext templateContext) {
+               super.postProcess(request, templateContext);
+               @SuppressWarnings("unchecked")
+               List<Sone> sones = (List<Sone>) templateContext.get("knownSones");
+               for (Sone sone : sones) {
+                       webInterface.getCore().markSoneKnown(sone);
+               }
        }
 
 }
index c84ea57..73fdd3c 100644 (file)
@@ -20,8 +20,8 @@ package net.pterodactylus.sone.web;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Page that lets the user like a {@link Post}.
@@ -50,8 +50,8 @@ public class LikePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       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 id = request.getHttpRequest().getPartAsStringFailsafe(type, 36);
index 7888659..d09de66 100644 (file)
@@ -18,8 +18,8 @@
 package net.pterodactylus.sone.web;
 
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page lets the user lock a {@link Sone} to prevent it from being
@@ -49,8 +49,8 @@ public class LockSonePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                Sone sone = webInterface.getCore().getLocalSone(soneId, false);
                if (sone != null) {
index ff81562..1126f7b 100644 (file)
@@ -26,8 +26,8 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
 import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 import freenet.clients.http.ToadletContext;
 
 /**
@@ -61,12 +61,12 @@ public class LoginPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                /* get all own identities. */
                List<Sone> localSones = new ArrayList<Sone>(webInterface.getCore().getLocalSones());
                Collections.sort(localSones, Sone.NICE_NAME_COMPARATOR);
-               dataProvider.set("sones", localSones);
+               templateContext.set("sones", localSones);
                if (request.getMethod() == Method.POST) {
                        String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone-id", 100);
                        Sone selectedSone = webInterface.getCore().getLocalSone(soneId, false);
@@ -76,7 +76,7 @@ public class LoginPage extends SoneTemplatePage {
                        }
                }
                List<OwnIdentity> ownIdentitiesWithoutSone = CreateSonePage.getOwnIdentitiesWithoutSone(webInterface.getCore());
-               dataProvider.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
+               templateContext.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
        }
 
        /**
@@ -84,7 +84,7 @@ public class LoginPage extends SoneTemplatePage {
         */
        @Override
        protected String getRedirectTarget(Request request) {
-               if (getCurrentSone(request.getToadletContext()) != null) {
+               if (getCurrentSone(request.getToadletContext(), false) != null) {
                        return "index.html";
                }
                return null;
@@ -99,7 +99,7 @@ public class LoginPage extends SoneTemplatePage {
         */
        @Override
        public boolean isEnabled(ToadletContext toadletContext) {
-               return getCurrentSone(toadletContext) == null;
+               return getCurrentSone(toadletContext, false) == null;
        }
 
 }
index 0b6e6e7..f388368 100644 (file)
@@ -17,8 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 import freenet.clients.http.ToadletContext;
 
 /**
@@ -46,9 +46,9 @@ public class LogoutPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
                setCurrentSone(request.getToadletContext(), null);
-               super.processTemplate(request, dataProvider);
+               super.processTemplate(request, templateContext);
                throw new RedirectException("index.html");
        }
 
@@ -57,7 +57,7 @@ public class LogoutPage extends SoneTemplatePage {
         */
        @Override
        public boolean isEnabled(ToadletContext toadletContext) {
-               return getCurrentSone(toadletContext) != null;
+               return (getCurrentSone(toadletContext, false) != null) && (webInterface.getCore().getLocalSones().size() != 1);
        }
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java b/src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java
new file mode 100644 (file)
index 0000000..4ecf6e0
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Sone - MarkReadPage.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
+ * 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;
+
+import java.util.StringTokenizer;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+
+/**
+ * Page that lets the user mark a number of {@link Sone}s, {@link Post}s, or
+ * {@link Reply Replie}s as known.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class MarkAsKnownPage extends SoneTemplatePage {
+
+       /**
+        * Creates a new “mark as known” page.
+        *
+        * @param template
+        *            The template to render
+        * @param webInterface
+        *            The Sone web interface
+        */
+       public MarkAsKnownPage(Template template, WebInterface webInterface) {
+               super("markAsKnown.html", template, "Page.MarkAsKnown.Title", webInterface);
+       }
+
+       //
+       // SONETEMPLATEPAGE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
+               String type = request.getHttpRequest().getPartAsStringFailsafe("type", 5);
+               if (!type.equals("sone") && !type.equals("post") && !type.equals("reply")) {
+                       throw new RedirectException("invalid.html");
+               }
+               String ids = request.getHttpRequest().getPartAsStringFailsafe("id", 65536);
+               for (StringTokenizer idTokenizer = new StringTokenizer(ids); idTokenizer.hasMoreTokens();) {
+                       String id = idTokenizer.nextToken();
+                       if (type.equals("post")) {
+                               Post post = webInterface.getCore().getPost(id, false);
+                               if (post == null) {
+                                       continue;
+                               }
+                               webInterface.getCore().markPostKnown(post);
+                       } else if (type.equals("reply")) {
+                               Reply reply = webInterface.getCore().getReply(id, false);
+                               if (reply == null) {
+                                       continue;
+                               }
+                               webInterface.getCore().markReplyKnown(reply);
+                       } else if (type.equals("sone")) {
+                               Sone sone = webInterface.getCore().getSone(id, false);
+                               if (sone == null) {
+                                       continue;
+                               }
+                               webInterface.getCore().markSoneKnown(sone);
+                       }
+               }
+               String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
+               throw new RedirectException(returnPage);
+       }
+
+}
index 0739ae7..421b2a2 100644 (file)
@@ -20,8 +20,8 @@ package net.pterodactylus.sone.web;
 import net.pterodactylus.sone.core.Options;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
 import net.pterodactylus.util.number.Numbers;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page lets the user edit the options of the Sone plugin.
@@ -50,8 +50,8 @@ public class OptionsPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                Options options = webInterface.getCore().getOptions();
                if (request.getMethod() == Method.POST) {
                        Integer insertionDelay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16));
@@ -74,13 +74,13 @@ public class OptionsPage extends SoneTemplatePage {
                        webInterface.getCore().saveConfiguration();
                        throw new RedirectException(getPath());
                }
-               dataProvider.set("insertion-delay", options.getIntegerOption("InsertionDelay").get());
-               dataProvider.set("positive-trust", options.getIntegerOption("PositiveTrust").get());
-               dataProvider.set("negative-trust", options.getIntegerOption("NegativeTrust").get());
-               dataProvider.set("trust-comment", options.getStringOption("TrustComment").get());
-               dataProvider.set("sone-rescue-mode", options.getBooleanOption("SoneRescueMode").get());
-               dataProvider.set("clear-on-next-restart", options.getBooleanOption("ClearOnNextRestart").get());
-               dataProvider.set("really-clear-on-next-restart", options.getBooleanOption("ReallyClearOnNextRestart").get());
+               templateContext.set("insertion-delay", options.getIntegerOption("InsertionDelay").get());
+               templateContext.set("positive-trust", options.getIntegerOption("PositiveTrust").get());
+               templateContext.set("negative-trust", options.getIntegerOption("NegativeTrust").get());
+               templateContext.set("trust-comment", options.getStringOption("TrustComment").get());
+               templateContext.set("sone-rescue-mode", options.getBooleanOption("SoneRescueMode").get());
+               templateContext.set("clear-on-next-restart", options.getBooleanOption("ClearOnNextRestart").get());
+               templateContext.set("really-clear-on-next-restart", options.getBooleanOption("ReallyClearOnNextRestart").get());
        }
 
 }
index dc41865..fa43945 100644 (file)
@@ -24,8 +24,8 @@ 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.TemplatePage;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 import freenet.clients.http.SessionManager.Session;
 import freenet.clients.http.ToadletContext;
 
@@ -74,10 +74,10 @@ public class SoneTemplatePage extends TemplatePage {
         *            Whether this page requires a login
         */
        public SoneTemplatePage(String path, Template template, String pageTitleKey, WebInterface webInterface, boolean requireLogin) {
-               super(path, template, webInterface.getL10n(), pageTitleKey, "noPermission.html");
+               super(path, webInterface.getTemplateContextFactory(), template, webInterface.getL10n(), pageTitleKey, "noPermission.html");
                this.webInterface = webInterface;
                this.requireLogin = requireLogin;
-               template.set("webInterface", webInterface);
+               template.getInitialContext().set("webInterface", webInterface);
        }
 
        //
@@ -186,14 +186,15 @@ public class SoneTemplatePage extends TemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
-               dataProvider.set("currentSone", getCurrentSone(request.getToadletContext(), false));
-               dataProvider.set("request", request);
-               dataProvider.set("currentVersion", SonePlugin.VERSION);
-               dataProvider.set("hasLatestVersion", webInterface.getCore().getUpdateChecker().hasLatestVersion());
-               dataProvider.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion());
-               dataProvider.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate());
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
+               templateContext.set("currentSone", 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("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion());
+               templateContext.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate());
        }
 
        /**
index 85341ad..b0dadef 100644 (file)
@@ -20,8 +20,8 @@ package net.pterodactylus.sone.web;
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Page that lets the user trust another Sone. This will assign a configurable
@@ -52,17 +52,17 @@ public class TrustPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.POST) {
-                       String returnPath = request.getHttpRequest().getPartAsStringFailsafe("returnPath", 256);
+                       String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                        String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                        Sone currentSone = getCurrentSone(request.getToadletContext());
                        Sone sone = webInterface.getCore().getSone(identity, false);
                        if (sone != null) {
                                webInterface.getCore().trustSone(currentSone, sone);
                        }
-                       throw new RedirectException(returnPath);
+                       throw new RedirectException(returnPage);
                }
        }
 
index 316e408..e026a9a 100644 (file)
@@ -19,8 +19,8 @@ package net.pterodactylus.sone.web;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page lets the user unfollow another Sone.
@@ -47,8 +47,8 @@ public class UnfollowSonePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.POST) {
                        String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
index d08689f..24ff3ca 100644 (file)
@@ -20,8 +20,8 @@ package net.pterodactylus.sone.web;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Page that lets the user unlike a {@link Post}.
@@ -50,8 +50,8 @@ public class UnlikePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       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 id = request.getHttpRequest().getPartAsStringFailsafe(type, 36);
index 35d69ff..5408f20 100644 (file)
@@ -18,8 +18,8 @@
 package net.pterodactylus.sone.web;
 
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page lets the user unlock a {@link Sone} to allow its insertion.
@@ -48,8 +48,8 @@ public class UnlockSonePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                Sone sone = webInterface.getCore().getLocalSone(soneId, false);
                if (sone != null) {
index cc9e26d..0e7e198 100644 (file)
@@ -20,8 +20,8 @@ package net.pterodactylus.sone.web;
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.Page.Request.Method;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Page that lets the user untrust another Sone. This will remove all trust
@@ -52,17 +52,17 @@ public class UntrustPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.POST) {
-                       String returnPath = request.getHttpRequest().getPartAsStringFailsafe("returnPath", 256);
+                       String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                        String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                        Sone currentSone = getCurrentSone(request.getToadletContext());
                        Sone sone = webInterface.getCore().getSone(identity, false);
                        if (sone != null) {
                                webInterface.getCore().untrustSone(currentSone, sone);
                        }
-                       throw new RedirectException(returnPath);
+                       throw new RedirectException(returnPage);
                }
        }
 
index 3c4eddf..864dc84 100644 (file)
@@ -19,8 +19,8 @@ package net.pterodactylus.sone.web;
 
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Reply;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * This page lets the user view a post and all its replies.
@@ -49,19 +49,22 @@ public class ViewPostPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                String postId = request.getHttpRequest().getParam("post");
                Post post = webInterface.getCore().getPost(postId);
-               dataProvider.set("post", post);
+               templateContext.set("post", post);
        }
 
        /**
         * {@inheritDoc}
         */
        @Override
-       protected void postProcess(Request request, DataProvider dataProvider) {
-               Post post = (Post) dataProvider.get("post");
+       protected void postProcess(Request request, TemplateContext templateContext) {
+               Post post = (Post) templateContext.get("post");
+               if (post == null) {
+                       return;
+               }
                webInterface.getCore().markPostKnown(post);
                for (Reply reply : webInterface.getCore().getReplies(post)) {
                        webInterface.getCore().markReplyKnown(reply);
index 9746afd..0792a6f 100644 (file)
@@ -22,8 +22,8 @@ import java.util.List;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Lets the user browser another Sone.
@@ -52,22 +52,23 @@ public class ViewSonePage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
                String soneId = request.getHttpRequest().getParam("sone");
                Sone sone = webInterface.getCore().getSone(soneId, false);
-               dataProvider.set("sone", sone);
+               templateContext.set("sone", sone);
        }
 
        /**
         * {@inheritDoc}
         */
        @Override
-       protected void postProcess(Request request, DataProvider dataProvider) {
-               Sone sone = (Sone) dataProvider.get("sone");
+       protected void postProcess(Request request, TemplateContext templateContext) {
+               Sone sone = (Sone) templateContext.get("sone");
                if (sone == null) {
                        return;
                }
+               webInterface.getCore().markSoneKnown(sone);
                List<Post> posts = sone.getPosts();
                for (Post post : posts) {
                        webInterface.getCore().markPostKnown(post);
index adcc17e..0df6d79 100644 (file)
@@ -50,12 +50,14 @@ import net.pterodactylus.sone.template.GetPagePlugin;
 import net.pterodactylus.sone.template.IdentityAccessor;
 import net.pterodactylus.sone.template.JavascriptFilter;
 import net.pterodactylus.sone.template.NotificationManagerAccessor;
+import net.pterodactylus.sone.template.ParserFilter;
 import net.pterodactylus.sone.template.PostAccessor;
 import net.pterodactylus.sone.template.ReplyAccessor;
 import net.pterodactylus.sone.template.RequestChangeFilter;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.sone.template.SubstringFilter;
 import net.pterodactylus.sone.template.TrustAccessor;
+import net.pterodactylus.sone.template.UnknownDateFilter;
 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
@@ -72,8 +74,7 @@ import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
 import net.pterodactylus.sone.web.ajax.GetTranslationPage;
 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
-import net.pterodactylus.sone.web.ajax.MarkPostAsKnownPage;
-import net.pterodactylus.sone.web.ajax.MarkReplyAsKnownPage;
+import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
 import net.pterodactylus.sone.web.ajax.TrustAjaxPage;
 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
@@ -83,19 +84,30 @@ import net.pterodactylus.sone.web.ajax.UntrustAjaxPage;
 import net.pterodactylus.sone.web.page.PageToadlet;
 import net.pterodactylus.sone.web.page.PageToadletFactory;
 import net.pterodactylus.sone.web.page.StaticPage;
+import net.pterodactylus.util.cache.Cache;
+import net.pterodactylus.util.cache.CacheException;
+import net.pterodactylus.util.cache.CacheItem;
+import net.pterodactylus.util.cache.DefaultCacheItem;
+import net.pterodactylus.util.cache.MemoryCache;
+import net.pterodactylus.util.cache.ValueRetriever;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.notify.NotificationManager;
 import net.pterodactylus.util.notify.TemplateNotification;
 import net.pterodactylus.util.template.DateFilter;
-import net.pterodactylus.util.template.DefaultTemplateFactory;
+import net.pterodactylus.util.template.FormatFilter;
+import net.pterodactylus.util.template.HtmlFilter;
 import net.pterodactylus.util.template.MatchFilter;
 import net.pterodactylus.util.template.PaginationPlugin;
+import net.pterodactylus.util.template.Provider;
 import net.pterodactylus.util.template.ReflectionAccessor;
+import net.pterodactylus.util.template.ReplaceFilter;
+import net.pterodactylus.util.template.StoreFilter;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.template.TemplateContextFactory;
 import net.pterodactylus.util.template.TemplateException;
-import net.pterodactylus.util.template.TemplateFactory;
-import net.pterodactylus.util.template.TemplateProvider;
+import net.pterodactylus.util.template.TemplateParser;
 import net.pterodactylus.util.template.XmlFilter;
 import net.pterodactylus.util.thread.Ticker;
 import net.pterodactylus.util.version.Version;
@@ -128,8 +140,8 @@ public class WebInterface implements CoreListener {
        /** The form password. */
        private final String formPassword;
 
-       /** The template factory. */
-       private DefaultTemplateFactory templateFactory;
+       /** The template context factory. */
+       private final TemplateContextFactory templateContextFactory;
 
        /** The “new Sone” notification. */
        private final ListNotification<Sone> newSoneNotification;
@@ -161,52 +173,60 @@ public class WebInterface implements CoreListener {
         * @param sonePlugin
         *            The Sone plugin
         */
+       @SuppressWarnings("synthetic-access")
        public WebInterface(SonePlugin sonePlugin) {
                this.sonePlugin = sonePlugin;
                formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
 
-               templateFactory = new DefaultTemplateFactory();
-               templateFactory.addAccessor(Object.class, new ReflectionAccessor());
-               templateFactory.addAccessor(Collection.class, new CollectionAccessor());
-               templateFactory.addAccessor(Sone.class, new SoneAccessor(getCore()));
-               templateFactory.addAccessor(Post.class, new PostAccessor(getCore(), templateFactory));
-               templateFactory.addAccessor(Reply.class, new ReplyAccessor(getCore(), templateFactory));
-               templateFactory.addAccessor(Identity.class, new IdentityAccessor(getCore()));
-               templateFactory.addAccessor(NotificationManager.class, new NotificationManagerAccessor());
-               templateFactory.addAccessor(Trust.class, new TrustAccessor());
-               templateFactory.addFilter("date", new DateFilter());
-               templateFactory.addFilter("l10n", new L10nFilter(getL10n()));
-               templateFactory.addFilter("substring", new SubstringFilter());
-               templateFactory.addFilter("xml", new XmlFilter());
-               templateFactory.addFilter("change", new RequestChangeFilter());
-               templateFactory.addFilter("match", new MatchFilter());
-               templateFactory.addFilter("css", new CssClassNameFilter());
-               templateFactory.addFilter("js", new JavascriptFilter());
-               templateFactory.addPlugin("getpage", new GetPagePlugin());
-               templateFactory.addPlugin("paginate", new PaginationPlugin());
-               templateFactory.setTemplateProvider(new ClassPathTemplateProvider(templateFactory));
-               templateFactory.addTemplateObject("formPassword", formPassword);
+               templateContextFactory = new TemplateContextFactory();
+               templateContextFactory.addAccessor(Object.class, new ReflectionAccessor());
+               templateContextFactory.addAccessor(Collection.class, new CollectionAccessor());
+               templateContextFactory.addAccessor(Sone.class, new SoneAccessor(getCore()));
+               templateContextFactory.addAccessor(Post.class, new PostAccessor(getCore()));
+               templateContextFactory.addAccessor(Reply.class, new ReplyAccessor(getCore()));
+               templateContextFactory.addAccessor(Identity.class, new IdentityAccessor(getCore()));
+               templateContextFactory.addAccessor(NotificationManager.class, new NotificationManagerAccessor());
+               templateContextFactory.addAccessor(Trust.class, new TrustAccessor());
+               templateContextFactory.addFilter("date", new DateFilter());
+               templateContextFactory.addFilter("html", new HtmlFilter());
+               templateContextFactory.addFilter("replace", new ReplaceFilter());
+               templateContextFactory.addFilter("store", new StoreFilter());
+               templateContextFactory.addFilter("l10n", new L10nFilter(getL10n()));
+               templateContextFactory.addFilter("substring", new SubstringFilter());
+               templateContextFactory.addFilter("xml", new XmlFilter());
+               templateContextFactory.addFilter("change", new RequestChangeFilter());
+               templateContextFactory.addFilter("match", new MatchFilter());
+               templateContextFactory.addFilter("css", new CssClassNameFilter());
+               templateContextFactory.addFilter("js", new JavascriptFilter());
+               templateContextFactory.addFilter("parse", new ParserFilter(templateContextFactory));
+               templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate"));
+               templateContextFactory.addFilter("format", new FormatFilter());
+               templateContextFactory.addPlugin("getpage", new GetPagePlugin());
+               templateContextFactory.addPlugin("paginate", new PaginationPlugin());
+               templateContextFactory.addProvider(Provider.TEMPLATE_CONTEXT_PROVIDER);
+               templateContextFactory.addProvider(new ClassPathTemplateProvider());
+               templateContextFactory.addTemplateObject("formPassword", formPassword);
 
                /* create notifications. */
-               Template newSoneNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/newSoneNotification.html"));
+               Template newSoneNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newSoneNotification.html"));
                newSoneNotification = new ListNotification<Sone>("new-sone-notification", "sones", newSoneNotificationTemplate);
 
-               Template newPostNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/newPostNotification.html"));
+               Template newPostNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newPostNotification.html"));
                newPostNotification = new ListNotification<Post>("new-post-notification", "posts", newPostNotificationTemplate);
 
-               Template newReplyNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/newReplyNotification.html"));
+               Template newReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html"));
                newReplyNotification = new ListNotification<Reply>("new-replies-notification", "replies", newReplyNotificationTemplate);
 
-               Template rescuingSonesTemplate = templateFactory.createTemplate(createReader("/templates/notify/rescuingSonesNotification.html"));
+               Template rescuingSonesTemplate = TemplateParser.parse(createReader("/templates/notify/rescuingSonesNotification.html"));
                rescuingSonesNotification = new ListNotification<Sone>("sones-being-rescued-notification", "sones", rescuingSonesTemplate);
 
-               Template sonesRescuedTemplate = templateFactory.createTemplate(createReader("/templates/notify/sonesRescuedNotification.html"));
+               Template sonesRescuedTemplate = TemplateParser.parse(createReader("/templates/notify/sonesRescuedNotification.html"));
                sonesRescuedNotification = new ListNotification<Sone>("sones-rescued-notification", "sones", sonesRescuedTemplate);
 
-               Template lockedSonesTemplate = templateFactory.createTemplate(createReader("/templates/notify/lockedSonesNotification.html"));
+               Template lockedSonesTemplate = TemplateParser.parse(createReader("/templates/notify/lockedSonesNotification.html"));
                lockedSonesNotification = new ListNotification<Sone>("sones-locked-notification", "sones", lockedSonesTemplate);
 
-               Template newVersionTemplate = templateFactory.createTemplate(createReader("/templates/notify/newVersionNotification.html"));
+               Template newVersionTemplate = TemplateParser.parse(createReader("/templates/notify/newVersionNotification.html"));
                newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
        }
 
@@ -224,6 +244,15 @@ public class WebInterface implements CoreListener {
        }
 
        /**
+        * Returns the template context factory of the web interface.
+        *
+        * @return The template context factory
+        */
+       public TemplateContextFactory getTemplateContextFactory() {
+               return templateContextFactory;
+       }
+
+       /**
         * Returns the current session, creating a new session if there is no
         * current session.
         *
@@ -280,6 +309,10 @@ public class WebInterface implements CoreListener {
         *         currently logged in
         */
        public Sone getCurrentSone(ToadletContext toadletContext, boolean create) {
+               Set<Sone> localSones = getCore().getLocalSones();
+               if (localSones.size() == 1) {
+                       return localSones.iterator().next();
+               }
                return getCurrentSone(getCurrentSession(toadletContext, create));
        }
 
@@ -385,7 +418,7 @@ public class WebInterface implements CoreListener {
         */
        public void setFirstStart(boolean firstStart) {
                if (firstStart) {
-                       Template firstStartNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/firstStartNotification.html"));
+                       Template firstStartNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/firstStartNotification.html"));
                        Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
                        notificationManager.addNotification(firstStartNotification);
                }
@@ -400,7 +433,7 @@ public class WebInterface implements CoreListener {
         */
        public void setNewConfig(boolean newConfig) {
                if (newConfig && !hasFirstStartNotification()) {
-                       Template configNotReadNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/configNotReadNotification.html"));
+                       Template configNotReadNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/configNotReadNotification.html"));
                        Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
                        notificationManager.addNotification(configNotReadNotification);
                }
@@ -431,7 +464,7 @@ public class WebInterface implements CoreListener {
                registerToadlets();
 
                /* notification templates. */
-               Template startupNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/startupNotification.html"));
+               Template startupNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/startupNotification.html"));
 
                final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
                notificationManager.addNotification(startupNotification);
@@ -444,7 +477,7 @@ public class WebInterface implements CoreListener {
                        }
                }, "Sone Startup Notification Remover");
 
-               Template wotMissingNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/wotMissingNotification.html"));
+               Template wotMissingNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/wotMissingNotification.html"));
                final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
                Ticker.getInstance().registerEvent(System.currentTimeMillis() + (15 * 1000), new Runnable() {
 
@@ -478,27 +511,27 @@ public class WebInterface implements CoreListener {
         * Register all toadlets.
         */
        private void registerToadlets() {
-               Template emptyTemplate = templateFactory.createTemplate(new StringReader(""));
-               Template loginTemplate = templateFactory.createTemplate(createReader("/templates/login.html"));
-               Template indexTemplate = templateFactory.createTemplate(createReader("/templates/index.html"));
-               Template knownSonesTemplate = templateFactory.createTemplate(createReader("/templates/knownSones.html"));
-               Template createSoneTemplate = templateFactory.createTemplate(createReader("/templates/createSone.html"));
-               Template createPostTemplate = templateFactory.createTemplate(createReader("/templates/createPost.html"));
-               Template createReplyTemplate = templateFactory.createTemplate(createReader("/templates/createReply.html"));
-               Template editProfileTemplate = templateFactory.createTemplate(createReader("/templates/editProfile.html"));
-               Template editProfileFieldTemplate = templateFactory.createTemplate(createReader("/templates/editProfileField.html"));
-               Template deleteProfileFieldTemplate = templateFactory.createTemplate(createReader("/templates/deleteProfileField.html"));
-               Template viewSoneTemplate = templateFactory.createTemplate(createReader("/templates/viewSone.html"));
-               Template viewPostTemplate = templateFactory.createTemplate(createReader("/templates/viewPost.html"));
-               Template deletePostTemplate = templateFactory.createTemplate(createReader("/templates/deletePost.html"));
-               Template deleteReplyTemplate = templateFactory.createTemplate(createReader("/templates/deleteReply.html"));
-               Template deleteSoneTemplate = templateFactory.createTemplate(createReader("/templates/deleteSone.html"));
-               Template noPermissionTemplate = templateFactory.createTemplate(createReader("/templates/noPermission.html"));
-               Template optionsTemplate = templateFactory.createTemplate(createReader("/templates/options.html"));
-               Template aboutTemplate = templateFactory.createTemplate(createReader("/templates/about.html"));
-               Template invalidTemplate = templateFactory.createTemplate(createReader("/templates/invalid.html"));
-               Template postTemplate = templateFactory.createTemplate(createReader("/templates/include/viewPost.html"));
-               Template replyTemplate = templateFactory.createTemplate(createReader("/templates/include/viewReply.html"));
+               Template emptyTemplate = TemplateParser.parse(new StringReader(""));
+               Template loginTemplate = TemplateParser.parse(createReader("/templates/login.html"));
+               Template indexTemplate = TemplateParser.parse(createReader("/templates/index.html"));
+               Template knownSonesTemplate = TemplateParser.parse(createReader("/templates/knownSones.html"));
+               Template createSoneTemplate = TemplateParser.parse(createReader("/templates/createSone.html"));
+               Template createPostTemplate = TemplateParser.parse(createReader("/templates/createPost.html"));
+               Template createReplyTemplate = TemplateParser.parse(createReader("/templates/createReply.html"));
+               Template editProfileTemplate = TemplateParser.parse(createReader("/templates/editProfile.html"));
+               Template editProfileFieldTemplate = TemplateParser.parse(createReader("/templates/editProfileField.html"));
+               Template deleteProfileFieldTemplate = TemplateParser.parse(createReader("/templates/deleteProfileField.html"));
+               Template viewSoneTemplate = TemplateParser.parse(createReader("/templates/viewSone.html"));
+               Template viewPostTemplate = TemplateParser.parse(createReader("/templates/viewPost.html"));
+               Template deletePostTemplate = TemplateParser.parse(createReader("/templates/deletePost.html"));
+               Template deleteReplyTemplate = TemplateParser.parse(createReader("/templates/deleteReply.html"));
+               Template deleteSoneTemplate = TemplateParser.parse(createReader("/templates/deleteSone.html"));
+               Template noPermissionTemplate = TemplateParser.parse(createReader("/templates/noPermission.html"));
+               Template optionsTemplate = TemplateParser.parse(createReader("/templates/options.html"));
+               Template aboutTemplate = TemplateParser.parse(createReader("/templates/about.html"));
+               Template invalidTemplate = TemplateParser.parse(createReader("/templates/invalid.html"));
+               Template postTemplate = TemplateParser.parse(createReader("/templates/include/viewPost.html"));
+               Template replyTemplate = TemplateParser.parse(createReader("/templates/include/viewReply.html"));
 
                PageToadletFactory pageToadletFactory = new PageToadletFactory(sonePlugin.pluginRespirator().getHLSimpleClient(), "/Sone/");
                pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this), "Index"));
@@ -522,6 +555,7 @@ public class WebInterface implements CoreListener {
                pageToadlets.add(pageToadletFactory.createPageToadlet(new TrustPage(emptyTemplate, this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustPage(emptyTemplate, this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustPage(emptyTemplate, this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkAsKnownPage(emptyTemplate, this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteSonePage(deleteSoneTemplate, this), "DeleteSone"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new LoginPage(loginTemplate, this), "Login"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new LogoutPage(emptyTemplate, this), "Logout"));
@@ -540,8 +574,7 @@ public class WebInterface implements CoreListener {
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new GetReplyAjaxPage(this, replyTemplate)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new GetPostAjaxPage(this, postTemplate)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkPostAsKnownPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkReplyAsKnownPage(this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkAsKnownAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new DeletePostAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteReplyAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new LockSoneAjaxPage(this)));
@@ -593,6 +626,7 @@ public class WebInterface implements CoreListener {
                try {
                        return new InputStreamReader(getClass().getResourceAsStream(resourceName), "UTF-8");
                } catch (UnsupportedEncodingException uee1) {
+                       System.out.println("  fail.");
                        return null;
                }
        }
@@ -732,8 +766,8 @@ public class WebInterface implements CoreListener {
         */
        @Override
        public void updateFound(Version version, long releaseTime) {
-               newVersionNotification.set("version", version);
-               newVersionNotification.set("releaseTime", releaseTime);
+               newVersionNotification.getTemplateContext().set("version", version);
+               newVersionNotification.getTemplateContext().set("releaseTime", releaseTime);
                notificationManager.addNotification(newVersionNotification);
        }
 
@@ -744,36 +778,53 @@ public class WebInterface implements CoreListener {
         *
         * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
-       private class ClassPathTemplateProvider implements TemplateProvider {
+       private class ClassPathTemplateProvider implements Provider {
 
-               /** The template factory. */
-               @SuppressWarnings("hiding")
-               private final TemplateFactory templateFactory;
+               /** Cache for templates. */
+               private final Cache<String, Template> templateCache = new MemoryCache<String, Template>(new ValueRetriever<String, Template>() {
+
+                       @Override
+                       @SuppressWarnings("synthetic-access")
+                       public CacheItem<Template> retrieve(String key) throws CacheException {
+                               Template template = findTemplate(key);
+                               if (template != null) {
+                                       return new DefaultCacheItem<Template>(template);
+                               }
+                               return null;
+                       }
+               });
 
                /**
-                * Creates a new template provider that locates templates on the
-                * classpath.
-                *
-                * @param templateFactory
-                *            The template factory to create the templates
+                * {@inheritDoc}
                 */
-               public ClassPathTemplateProvider(TemplateFactory templateFactory) {
-                       this.templateFactory = templateFactory;
+               @Override
+               @SuppressWarnings("synthetic-access")
+               public Template getTemplate(TemplateContext templateContext, String templateName) {
+                       try {
+                               return templateCache.get(templateName);
+                       } catch (CacheException ce1) {
+                               logger.log(Level.WARNING, "Could not get template for " + templateName + "!", ce1);
+                               return null;
+                       }
                }
 
                /**
-                * {@inheritDoc}
+                * Locates a template in the class path.
+                *
+                * @param templateName
+                *            The name of the template to load
+                * @return The loaded template, or {@code null} if no template could be
+                *         found
                 */
-               @Override
                @SuppressWarnings("synthetic-access")
-               public Template getTemplate(String templateName) {
+               private Template findTemplate(String templateName) {
                        Reader templateReader = createReader("/templates/" + templateName);
                        if (templateReader == null) {
                                return null;
                        }
-                       Template template = templateFactory.createTemplate(templateReader);
+                       Template template = null;
                        try {
-                               template.parse();
+                               template = TemplateParser.parse(templateReader);
                        } catch (TemplateException te1) {
                                logger.log(Level.WARNING, "Could not parse template “" + templateName + "” for inclusion!", te1);
                        }
index 1e45a60..eff7c41 100644 (file)
@@ -50,12 +50,17 @@ public class CreatePostAjaxPage extends JsonPage {
                }
                String recipientId = request.getHttpRequest().getParam("recipient");
                Sone recipient = webInterface.getCore().getSone(recipientId, false);
+               String senderId = request.getHttpRequest().getParam("sender");
+               Sone sender = webInterface.getCore().getLocalSone(senderId, false);
+               if (sender == null) {
+                       sender = sone;
+               }
                String text = request.getHttpRequest().getParam("text");
                if ((text == null) || (text.trim().length() == 0)) {
                        return createErrorJsonObject("text-required");
                }
-               Post newPost = webInterface.getCore().createPost(sone, recipient, text);
-               return createSuccessJsonObject().put("postId", newPost.getId());
+               Post newPost = webInterface.getCore().createPost(sender, recipient, text);
+               return createSuccessJsonObject().put("postId", newPost.getId()).put("sone", sender.getId()).put("recipient", (newPost.getRecipient() != null) ? newPost.getRecipient().getId() : null);
        }
 
 }
index 0181ff2..9ed960f 100644 (file)
@@ -51,16 +51,17 @@ public class CreateReplyAjaxPage extends JsonPage {
        protected JsonObject createJsonObject(Request request) {
                String postId = request.getHttpRequest().getParam("post");
                String text = request.getHttpRequest().getParam("text").trim();
-               Sone currentSone = getCurrentSone(request.getToadletContext());
-               if (currentSone == null) {
-                       return createErrorJsonObject("auth-required");
+               String senderId = request.getHttpRequest().getParam("sender");
+               Sone sender = webInterface.getCore().getLocalSone(senderId, false);
+               if (sender == null) {
+                       sender = getCurrentSone(request.getToadletContext());
                }
                Post post = webInterface.getCore().getPost(postId);
                if ((post == null) || (post.getSone() == null)) {
                        return createErrorJsonObject("invalid-post-id");
                }
-               Reply reply = webInterface.getCore().createReply(currentSone, post, text);
-               return createSuccessJsonObject().put("reply", reply.getId());
+               Reply reply = webInterface.getCore().createReply(sender, post, text);
+               return createSuccessJsonObject().put("reply", reply.getId()).put("sone", sender.getId());
        }
 
 }
index 479e71e..8d3b414 100644 (file)
@@ -18,7 +18,6 @@
 package net.pterodactylus.sone.web.ajax;
 
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.util.json.JsonObject;
 
@@ -50,14 +49,10 @@ public class DeletePostAjaxPage extends JsonPage {
        protected JsonObject createJsonObject(Request request) {
                String postId = request.getHttpRequest().getParam("post");
                Post post = webInterface.getCore().getPost(postId, false);
-               Sone currentSone = getCurrentSone(request.getToadletContext());
                if ((post == null) || (post.getSone() == null)) {
                        return createErrorJsonObject("invalid-post-id");
                }
-               if (currentSone == null) {
-                       return createErrorJsonObject("auth-required");
-               }
-               if (!post.getSone().equals(currentSone)) {
+               if (!webInterface.getCore().isLocalSone(post.getSone())) {
                        return createErrorJsonObject("not-authorized");
                }
                webInterface.getCore().deletePost(post);
index 7614de8..f34d202 100644 (file)
@@ -18,7 +18,6 @@
 package net.pterodactylus.sone.web.ajax;
 
 import net.pterodactylus.sone.data.Reply;
-import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.util.json.JsonObject;
 
@@ -50,14 +49,10 @@ public class DeleteReplyAjaxPage extends JsonPage {
        protected JsonObject createJsonObject(Request request) {
                String replyId = request.getHttpRequest().getParam("reply");
                Reply reply = webInterface.getCore().getReply(replyId);
-               Sone currentSone = getCurrentSone(request.getToadletContext());
                if (reply == null) {
                        return createErrorJsonObject("invalid-reply-id");
                }
-               if (currentSone == null) {
-                       return createErrorJsonObject("auth-required");
-               }
-               if (!reply.getSone().equals(currentSone)) {
+               if (!webInterface.getCore().isLocalSone(reply.getSone())) {
                        return createErrorJsonObject("not-authorized");
                }
                webInterface.getCore().deleteReply(reply);
index 92d5da6..127834b 100644 (file)
@@ -24,8 +24,8 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.json.JsonObject;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.template.TemplateException;
 
 /**
@@ -94,11 +94,11 @@ public class GetPostAjaxPage extends JsonPage {
                jsonPost.put("recipient", (post.getRecipient() == null) ? null : post.getRecipient().getId());
                jsonPost.put("time", post.getTime());
                StringWriter stringWriter = new StringWriter();
-               DataProvider dataProvider = postTemplate.createDataProvider();
-               dataProvider.setData("post", post);
-               dataProvider.setData("currentSone", currentSone);
+               TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext();
+               templateContext.set("post", post);
+               templateContext.set("currentSone", currentSone);
                try {
-                       postTemplate.render(dataProvider, stringWriter);
+                       postTemplate.render(templateContext, stringWriter);
                } catch (TemplateException te1) {
                        /* TODO - shouldn’t happen. */
                } finally {
index e589191..c19bcba 100644 (file)
@@ -24,8 +24,8 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.json.JsonObject;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.template.TemplateException;
 
 /**
@@ -96,11 +96,11 @@ public class GetReplyAjaxPage extends JsonPage {
                jsonReply.put("soneId", reply.getSone().getId());
                jsonReply.put("time", reply.getTime());
                StringWriter stringWriter = new StringWriter();
-               DataProvider dataProvider = replyTemplate.createDataProvider();
-               dataProvider.setData("reply", reply);
-               dataProvider.setData("currentSone", currentSone);
+               TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext();
+               templateContext.set("reply", reply);
+               templateContext.set("currentSone", currentSone);
                try {
-                       replyTemplate.render(dataProvider, stringWriter);
+                       replyTemplate.render(templateContext, stringWriter);
                } catch (TemplateException te1) {
                        /* TODO - shouldn’t happen. */
                } finally {
index 659c8d2..fd9ae76 100644 (file)
@@ -17,6 +17,8 @@
 
 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;
@@ -34,6 +36,8 @@ import net.pterodactylus.sone.web.WebInterface;
 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
@@ -147,6 +151,7 @@ public class GetStatusAjaxPage extends JsonPage {
                jsonSone.put("status", webInterface.getCore().getSoneStatus(sone).name());
                jsonSone.put("modified", webInterface.getCore().isModifiedSone(sone));
                jsonSone.put("locked", webInterface.getCore().isLocked(sone));
+               jsonSone.put("lastUpdatedUnknown", sone.getTime() == 0);
                synchronized (dateFormat) {
                        jsonSone.put("lastUpdated", dateFormat.format(new Date(sone.getTime())));
                }
@@ -161,10 +166,22 @@ public class GetStatusAjaxPage extends JsonPage {
         *            The notification to create a JSON object
         * @return The JSON object
         */
-       private static JsonObject createJsonNotification(Notification notification) {
+       private JsonObject createJsonNotification(Notification notification) {
                JsonObject jsonNotification = new JsonObject();
                jsonNotification.put("id", notification.getId());
-               jsonNotification.put("text", notification.toString());
+               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());
diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.java
new file mode 100644 (file)
index 0000000..b641ea0
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Sone - MarkAsKnownAjaxPage.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
+ * 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 net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.util.json.JsonObject;
+
+/**
+ * AJAX page that lets the user mark a number of {@link Sone}s, {@link Post}s,
+ * or {@link Reply}s as known.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class MarkAsKnownAjaxPage extends JsonPage {
+
+       /**
+        * Creates a new “mark as known” AJAX page.
+        *
+        * @param webInterface
+        *            The Sone web interface
+        */
+       public MarkAsKnownAjaxPage(WebInterface webInterface) {
+               super("markAsKnown.ajax", webInterface);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected JsonObject createJsonObject(Request request) {
+               String type = request.getHttpRequest().getParam("type");
+               if (!type.equals("sone") && !type.equals("post") && !type.equals("reply")) {
+                       return createErrorJsonObject("invalid-type");
+               }
+               String[] ids = request.getHttpRequest().getParam("id").split(" ");
+               Core core = webInterface.getCore();
+               for (String id : ids) {
+                       if (type.equals("post")) {
+                               Post post = core.getPost(id, false);
+                               if (post == null) {
+                                       continue;
+                               }
+                               core.markPostKnown(post);
+                       } else if (type.equals("reply")) {
+                               Reply reply = core.getReply(id, false);
+                               if (reply == null) {
+                                       continue;
+                               }
+                               core.markReplyKnown(reply);
+                       } else if (type.equals("sone")) {
+                               Sone sone = core.getSone(id, false);
+                               if (sone == null) {
+                                       continue;
+                               }
+                               core.markSoneKnown(sone);
+                       }
+               }
+               return createSuccessJsonObject();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected boolean requiresLogin() {
+               return false;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/MarkPostAsKnownPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/MarkPostAsKnownPage.java
deleted file mode 100644 (file)
index c2b0ca3..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Sone - MarkPostAsKnownPage.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 net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.web.WebInterface;
-import net.pterodactylus.util.json.JsonObject;
-
-/**
- * AJAX handler that marks a {@link Post} as known.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class MarkPostAsKnownPage extends JsonPage {
-
-       /**
-        * Creates a new “mark post as known” AJAX handler.
-        *
-        * @param webInterface
-        *            The Sone web interface
-        */
-       public MarkPostAsKnownPage(WebInterface webInterface) {
-               super("markPostAsKnown.ajax", webInterface);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       protected JsonObject createJsonObject(Request request) {
-               String postId = request.getHttpRequest().getParam("post");
-               Post post = webInterface.getCore().getPost(postId, false);
-               if (post == null) {
-                       return createErrorJsonObject("invalid-post-id");
-               }
-               webInterface.getCore().markPostKnown(post);
-               return createSuccessJsonObject();
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/MarkReplyAsKnownPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/MarkReplyAsKnownPage.java
deleted file mode 100644 (file)
index 6a4f72b..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Sone - MarkPostAsKnownPage.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 net.pterodactylus.sone.data.Reply;
-import net.pterodactylus.sone.web.WebInterface;
-import net.pterodactylus.util.json.JsonObject;
-
-/**
- * AJAX handler that marks a {@link Reply} as known.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class MarkReplyAsKnownPage extends JsonPage {
-
-       /**
-        * Creates a new “mark reply as known” AJAX handler.
-        *
-        * @param webInterface
-        *            The Sone web interface
-        */
-       public MarkReplyAsKnownPage(WebInterface webInterface) {
-               super("markReplyAsKnown.ajax", webInterface);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       protected JsonObject createJsonObject(Request request) {
-               String replyId = request.getHttpRequest().getParam("reply");
-               Reply reply = webInterface.getCore().getReply(replyId, false);
-               if (reply == null) {
-                       return createErrorJsonObject("invalid-reply-id");
-               }
-               webInterface.getCore().markReplyKnown(reply);
-               return createSuccessJsonObject();
-       }
-
-}
index e6ee539..9b40deb 100644 (file)
@@ -25,8 +25,9 @@ import java.util.logging.Logger;
 
 import net.pterodactylus.sone.web.page.Page.Request.Method;
 import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.template.TemplateContextFactory;
 import freenet.clients.http.LinkEnabledCallback;
 import freenet.clients.http.PageMaker;
 import freenet.clients.http.PageNode;
@@ -46,6 +47,9 @@ public class TemplatePage implements Page, LinkEnabledCallback {
        /** The path of the page. */
        private final String path;
 
+       /** The template context factory. */
+       private final TemplateContextFactory templateContextFactory;
+
        /** The template to render. */
        private final Template template;
 
@@ -63,6 +67,8 @@ public class TemplatePage implements Page, LinkEnabledCallback {
         *
         * @param path
         *            The path of the page
+        * @param templateContextFactory
+        *            The template context factory
         * @param template
         *            The template to render
         * @param l10n
@@ -73,8 +79,9 @@ public class TemplatePage implements Page, LinkEnabledCallback {
         *            The target to redirect to if a POST request does not contain
         *            the correct form password
         */
-       public TemplatePage(String path, Template template, BaseL10n l10n, String pageTitleKey, String invalidFormPasswordRedirectTarget) {
+       public TemplatePage(String path, TemplateContextFactory templateContextFactory, Template template, BaseL10n l10n, String pageTitleKey, String invalidFormPasswordRedirectTarget) {
                this.path = path;
+               this.templateContextFactory = templateContextFactory;
                this.template = template;
                this.l10n = l10n;
                this.pageTitleKey = pageTitleKey;
@@ -117,10 +124,11 @@ public class TemplatePage implements Page, LinkEnabledCallback {
                        pageNode.addForwardLink("icon", shortcutIcon);
                }
 
-               DataProvider dataProvider = template.createDataProvider();
+               TemplateContext templateContext = templateContextFactory.createTemplateContext();
+               templateContext.mergeContext(template.getInitialContext());
                try {
                        long start = System.nanoTime();
-                       processTemplate(request, dataProvider);
+                       processTemplate(request, templateContext);
                        long finish = System.nanoTime();
                        logger.log(Level.FINEST, "Template was rendered in " + ((finish - start) / 1000) / 1000.0 + "ms.");
                } catch (RedirectException re1) {
@@ -128,10 +136,10 @@ public class TemplatePage implements Page, LinkEnabledCallback {
                }
 
                StringWriter stringWriter = new StringWriter();
-               template.render(dataProvider, stringWriter);
+               template.render(templateContext, stringWriter);
                pageNode.content.addChild("%", stringWriter.toString());
 
-               postProcess(request, dataProvider);
+               postProcess(request, templateContext);
 
                return new Response(200, "OK", "text/html", pageNode.outer.generate());
        }
@@ -161,29 +169,29 @@ public class TemplatePage implements Page, LinkEnabledCallback {
         *
         * @param request
         *            The request that is rendered
-        * @param dataProvider
-        *            The data provider to set variables in
+        * @param templateContext
+        *            The template context to set variables in
         * @throws RedirectException
         *             if the processing page wants to redirect after processing
         */
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
+       protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
                /* do nothing. */
        }
 
        /**
         * This method will be called after
-        * {@link #processTemplate(net.pterodactylus.sone.web.page.Page.Request, DataProvider)}
+        * {@link #processTemplate(net.pterodactylus.sone.web.page.Page.Request, TemplateContext)}
         * has processed the template and the template was rendered. This method
         * will not be called if
-        * {@link #processTemplate(net.pterodactylus.sone.web.page.Page.Request, DataProvider)}
+        * {@link #processTemplate(net.pterodactylus.sone.web.page.Page.Request, TemplateContext)}
         * throws a {@link RedirectException}!
         *
         * @param request
         *            The request being processed
-        * @param dataProvider
-        *            The data provider that supplied the rendered data
+        * @param templateContext
+        *            The template context that supplied the rendered data
         */
-       protected void postProcess(Request request, DataProvider dataProvider) {
+       protected void postProcess(Request request, TemplateContext templateContext) {
                /* do nothing. */
        }
 
@@ -214,7 +222,7 @@ public class TemplatePage implements Page, LinkEnabledCallback {
        /**
         * Exception that can be thrown to signal that a subclassed {@link Page}
         * wants to redirect the user during the
-        * {@link TemplatePage#processTemplate(net.pterodactylus.sone.web.page.Page.Request, DataProvider)}
+        * {@link TemplatePage#processTemplate(net.pterodactylus.sone.web.page.Page.Request, TemplateContext)}
         * method call.
         *
         * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
index 76d8941..50dab27 100644 (file)
@@ -55,6 +55,7 @@ Page.DeleteSone.Button.No=No, do not delete.
 
 Page.Index.Title=Your Sone - Sone
 Page.Index.Label.Text=Post text:
+Page.Index.Label.Sender=Sender:
 Page.Index.Button.Post=Post!
 Page.Index.PostList.Title=Post Feed
 Page.Index.PostList.Text.NoPostYet=Nobody has written any posts yet. You should probably start it right now!
@@ -157,6 +158,8 @@ Page.Distrust.Title=Distrust Sone - Sone
 
 Page.Untrust.Title=Untrust Sone - Sone
 
+Page.MarkAsKnown.Title=Mark as Known - Sone
+
 Page.NoPermission.Title=Unauthorized Access - Sone
 Page.NoPermission.Page.Title=Unauthorized Access
 Page.NoPermission.Text.NoPermission=You tried to do something that you do not have sufficient authorization for. Please refrain from such actions in the future or we will be forced to take counter-measures!
@@ -180,6 +183,7 @@ View.CreateSone.Button.Create=Create Sone
 View.CreateSone.Text.Error.NoIdentity=You have not selected an identity.
 
 View.Sone.Label.LastUpdate=Last update:
+View.Sone.Text.UnknownDate=unknown
 View.Sone.Button.UnlockSone=unlock
 View.Sone.Button.UnlockSone.Tooltip=Allow this Sone to be inserted now
 View.Sone.Button.LockSone=lock
@@ -199,6 +203,8 @@ View.Post.Reply.DeleteLink=Delete
 View.Post.LikeLink=Like
 View.Post.UnlikeLink=Unlike
 
+View.UpdateStatus.Text.ChooseSenderIdentity=Choose the sender identity
+
 View.Trust.Tooltip.Trust=Trust this person
 View.Trust.Tooltip.Distrust=Assign negative trust to this person
 View.Trust.Tooltip.Untrust=Remove your trust assignment for this person
@@ -232,6 +238,7 @@ Notification.NewSone.ShortText=New Sones have been discovered:
 Notification.NewSone.Text=New Sones have been discovered:
 Notification.NewPost.ShortText=New posts have been discovered.
 Notification.NewPost.Text=New posts have been discovered by the following Sones:
+Notification.NewPost.Button.MarkRead=Mark as read
 Notification.NewReply.ShortText=New replies have been discovered.
 Notification.NewReply.Text=New replies have been discovered by the following Sones:
 Notification.SoneIsBeingRescued.Text=The following Sones are currently being rescued:
index f39cfef..c5d8e9a 100644 (file)
@@ -35,6 +35,11 @@ textarea {
        margin: 0px;
 }
 
+#sone select {
+       color: #444;
+       padding: 0.5ex 1.5ex;
+}
+
 /* now for the real stuff. */
 
 #sone {
@@ -100,6 +105,10 @@ textarea {
        min-height: 3.5ex;
 }
 
+#sone #notification-area .notification button {
+       margin-left: 1ex;
+}
+
 #sone #notification-area .notification .dismiss {
        float: right;
 }
@@ -108,6 +117,10 @@ textarea {
        margin-left: 1ex;
 }
 
+#sone #notification-area .notification .mark-as-read {
+       float: right;
+}
+
 #sone #plugin-warning {
        border: solid 0.5em red;
        padding: 0.5em;
@@ -147,6 +160,10 @@ textarea {
        padding-left: 1ex;
 }
 
+#sone #update-status {
+       margin-bottom: 1em;
+}
+
 #sone #update-status label {
        display: none;
 }
@@ -163,6 +180,15 @@ textarea {
        float: right;
 }
 
+#sone #update-status .select-sender, #sone .create-reply .select-sender {
+       display: none;
+}
+
+#sone #update-status .select-sender button {
+       display: inline;
+       float: left;
+}
+
 #sone .nice-name {
        font-weight: bold;
 }
@@ -353,12 +379,12 @@ textarea {
 
 #sone .post .create-reply input[type=text] {
        margin-left: 0.5ex;
-       width: 44em;
+       width: 42em;
 }
 
 #sone .post .create-reply textarea {
        margin-left: 0.5ex;
-       width: 44em;
+       width: 42em;
        height: 4em;
 }
 
@@ -366,6 +392,11 @@ textarea {
        float: right;
 }
 
+#sone .create-reply .select-sender button {
+       display: inline;
+       float: left;
+}
+
 #sone .sone {
        clear: both;
        background-color: #f0f0ff;
@@ -471,7 +502,7 @@ textarea {
        display: none;
 }
 
-#sone .profile-field button.confirm {
+#sone .profile-field button.confirm.edit {
        font-weight: bold;
        color: #080;
 }
@@ -492,6 +523,7 @@ textarea {
 #sone .profile-field .edit-field-name, #sone .profile-field .move-up-field, #sone .profile-field .move-down-field, #sone .profile-field .delete-field-name {
        float: right;
        margin-top: -1ex;
+       position: relative;
 }
 
 #sone #tail {
diff --git a/src/main/resources/static/javascript/jquery.url.js b/src/main/resources/static/javascript/jquery.url.js
new file mode 100644 (file)
index 0000000..20d8dfc
--- /dev/null
@@ -0,0 +1,174 @@
+// JQuery URL Parser
+// Written by Mark Perkins, mark@allmarkedup.com
+// License: http://unlicense.org/ (i.e. do what you want with it!)
+
+jQuery.url = function()
+{
+       var segments = {};
+       
+       var parsed = {};
+       
+       /**
+    * Options object. Only the URI and strictMode values can be changed via the setters below.
+    */
+       var options = {
+       
+               url : window.location, // default URI is the page in which the script is running
+               
+               strictMode: false, // 'loose' parsing by default
+       
+               key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], // keys available to query 
+               
+               q: {
+                       name: "queryKey",
+                       parser: /(?:^|&)([^&=]*)=?([^&]*)/g
+               },
+               
+               parser: {
+                       strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,  //less intuitive, more accurate to the specs
+                       loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs
+               }
+               
+       };
+       
+    /**
+     * Deals with the parsing of the URI according to the regex above.
+        * Written by Steven Levithan - see credits at top.
+     */                
+       var parseUri = function()
+       {
+               str = decodeURI( options.url );
+               
+               var m = options.parser[ options.strictMode ? "strict" : "loose" ].exec( str );
+               var uri = {};
+               var i = 14;
+
+               while ( i-- ) {
+                       uri[ options.key[i] ] = m[i] || "";
+               }
+
+               uri[ options.q.name ] = {};
+               uri[ options.key[12] ].replace( options.q.parser, function ( $0, $1, $2 ) {
+                       if ($1) {
+                               uri[options.q.name][$1] = $2;
+                       }
+               });
+
+               return uri;
+       };
+
+    /**
+     * Returns the value of the passed in key from the parsed URI.
+        * 
+        * @param string key The key whose value is required
+     */                
+       var key = function( key )
+       {
+               if ( jQuery.isEmptyObject(parsed) )
+               {
+                       setUp(); // if the URI has not been parsed yet then do this first...    
+               } 
+               if ( key == "base" )
+               {
+                       if ( parsed.port !== null && parsed.port !== "" )
+                       {
+                               return parsed.protocol+"://"+parsed.host+":"+parsed.port+"/";   
+                       }
+                       else
+                       {
+                               return parsed.protocol+"://"+parsed.host+"/";
+                       }
+               }
+       
+               return ( parsed[key] === "" ) ? null : parsed[key];
+       };
+       
+       /**
+     * Returns the value of the required query string parameter.
+        * 
+        * @param string item The parameter whose value is required
+     */                
+       var param = function( item )
+       {
+               if ( jQuery.isEmptyObject(parsed) )
+               {
+                       setUp(); // if the URI has not been parsed yet then do this first...    
+               }
+               return ( parsed.queryKey[item] === null ) ? null : parsed.queryKey[item];
+       };
+
+    /**
+     * 'Constructor' (not really!) function.
+     *  Called whenever the URI changes to kick off re-parsing of the URI and splitting it up into segments. 
+     */        
+       var setUp = function()
+       {
+               parsed = parseUri();
+               
+               getSegments();  
+       };
+       
+    /**
+     * Splits up the body of the URI into segments (i.e. sections delimited by '/')
+     */
+       var getSegments = function()
+       {
+               var p = parsed.path;
+               segments = []; // clear out segments array
+               segments = parsed.path.length == 1 ? {} : ( p.charAt( p.length - 1 ) == "/" ? p.substring( 1, p.length - 1 ) : path = p.substring( 1 ) ).split("/");
+       };
+       
+       return {
+               
+           /**
+            * Sets the parsing mode - either strict or loose. Set to loose by default.
+            *
+            * @param string mode The mode to set the parser to. Anything apart from a value of 'strict' will set it to loose!
+            */
+               setMode : function( mode )
+               {
+                       options.strictMode = mode == "strict" ? true : false;
+                       return this;
+               },
+               
+               /**
+            * Sets URI to parse if you don't want to to parse the current page's URI.
+                * Calling the function with no value for newUri resets it to the current page's URI.
+            *
+            * @param string newUri The URI to parse.
+            */         
+               setUrl : function( newUri )
+               {
+                       options.url = newUri === undefined ? window.location : newUri;
+                       setUp();
+                       return this;
+               },              
+               
+               /**
+            * Returns the value of the specified URI segment. Segments are numbered from 1 to the number of segments.
+                * For example the URI http://test.com/about/company/ segment(1) would return 'about'.
+                *
+                * If no integer is passed into the function it returns the number of segments in the URI.
+            *
+            * @param int pos The position of the segment to return. Can be empty.
+            */ 
+               segment : function( pos )
+               {
+                       if ( jQuery.isEmptyObject(parsed) )
+                       {
+                               setUp(); // if the URI has not been parsed yet then do this first...    
+                       } 
+                       if ( pos === undefined )
+                       {
+                               return segments.length;
+                       }
+                       return ( segments[pos] === "" || segments[pos] === undefined ) ? null : segments[pos];
+               },
+               
+               attr : key, // provides public access to private 'key' function - see above
+               
+               param : param // provides public access to private 'param' function - see above
+               
+       };
+       
+}();
\ No newline at end of file
index e0c8162..6ff4ceb 100644 (file)
@@ -65,7 +65,7 @@ function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, op
  *            The element to add a “comment” link to
  */
 function addCommentLink(postId, element, insertAfterThisElement) {
-       if ($(element).find(".show-reply-form").length > 0) {
+       if (($(element).find(".show-reply-form").length > 0) || (getPostElement(element).find(".create-reply").length == 0)) {
                return;
        }
        commentElement = (function(postId) {
@@ -153,7 +153,13 @@ function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated) {
                toggleClass("modified", modified);
        $("#sone .sone." + filterSoneId(soneId) + " .lock").toggleClass("hidden", locked);
        $("#sone .sone." + filterSoneId(soneId) + " .unlock").toggleClass("hidden", !locked);
-       $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(lastUpdated);
+       if (lastUpdated != null) {
+               $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(lastUpdated);
+       } else {
+               getTranslation("View.Sone.Text.UnknownDate", function(unknown) {
+                       $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(unknown);
+               });
+       }
        $("#sone .sone." + filterSoneId(soneId) + " .profile-link a").text(name);
 }
 
@@ -457,14 +463,14 @@ function updateTrustControls(soneId, trustValue) {
        $("#sone .post").each(function() {
                if (getPostAuthor(this) == soneId) {
                        getPostElement(this).find(".post-trust").toggleClass("hidden", trustValue != null);
-                       getPostElement(this).find(".post-distrust").toggleClass("hidden", (trustValue != null) && (trustValue < 0));
+                       getPostElement(this).find(".post-distrust").toggleClass("hidden", trustValue != null);
                        getPostElement(this).find(".post-untrust").toggleClass("hidden", trustValue == null);
                }
        });
        $("#sone .reply").each(function() {
                if (getReplyAuthor(this) == soneId) {
                        getReplyElement(this).find(".reply-trust").toggleClass("hidden", trustValue != null);
-                       getReplyElement(this).find(".reply-distrust").toggleClass("hidden", (trustValue != null) && (trustValue < 0));
+                       getReplyElement(this).find(".reply-distrust").toggleClass("hidden", trustValue != null);
                        getReplyElement(this).find(".reply-untrust").toggleClass("hidden", trustValue == null);
                }
        });
@@ -485,6 +491,8 @@ function updateReplyLikes(replyId) {
 /**
  * Posts a reply and calls the given callback when the request finishes.
  *
+ * @param sender
+ *            The ID of the sender
  * @param postId
  *            The ID of the post the reply refers to
  * @param text
@@ -493,14 +501,14 @@ function updateReplyLikes(replyId) {
  *            The callback function to call when the request finishes (takes 3
  *            parameters: success, error, replyId)
  */
-function postReply(postId, text, callbackFunction) {
-       $.getJSON("createReply.ajax", { "formPassword" : getFormPassword(), "post" : postId, "text": text }, function(data, textStatus) {
+function postReply(sender, postId, text, callbackFunction) {
+       $.getJSON("createReply.ajax", { "formPassword" : getFormPassword(), "sender": sender, "post" : postId, "text": text }, function(data, textStatus) {
                if (data == null) {
                        /* TODO - show error */
                        return;
                }
                if (data.success) {
-                       callbackFunction(true, null, data.reply);
+                       callbackFunction(true, null, data.reply, data.sone);
                } else {
                        callbackFunction(false, data.error);
                }
@@ -529,6 +537,56 @@ function getReply(replyId, callbackFunction) {
 }
 
 /**
+ * Ajaxifies the given Sone by enhancing all eligible elements with AJAX.
+ *
+ * @param soneElement
+ *            The Sone to ajaxify
+ */
+function ajaxifySone(soneElement) {
+       /*
+        * convert all “follow”, “unfollow”, “lock”, and “unlock” links to something
+        * nicer.
+        */
+       $(".follow", soneElement).submit(function() {
+               var followElement = this;
+               $.getJSON("followSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
+                       $(followElement).addClass("hidden");
+                       $(followElement).parent().find(".unfollow").removeClass("hidden");
+               });
+               return false;
+       });
+       $(".unfollow", soneElement).submit(function() {
+               var unfollowElement = this;
+               $.getJSON("unfollowSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
+                       $(unfollowElement).addClass("hidden");
+                       $(unfollowElement).parent().find(".follow").removeClass("hidden");
+               });
+               return false;
+       });
+       $(".lock", soneElement).submit(function() {
+               var lockElement = this;
+               $.getJSON("lockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
+                       $(lockElement).addClass("hidden");
+                       $(lockElement).parent().find(".unlock").removeClass("hidden");
+               });
+               return false;
+       });
+       $(".unlock", soneElement).submit(function() {
+               var unlockElement = this;
+               $.getJSON("unlockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
+                       $(unlockElement).addClass("hidden");
+                       $(unlockElement).parent().find(".lock").removeClass("hidden");
+               });
+               return false;
+       });
+
+       /* mark Sone as known when clicking it. */
+       $(soneElement).click(function() {
+               markSoneAsKnown(soneElement);
+       });
+}
+
+/**
  * Ajaxifies the given post by enhancing all eligible elements with AJAX.
  *
  * @param postElement
@@ -539,21 +597,25 @@ function ajaxifyPost(postElement) {
                return false;
        });
        $(postElement).find(".create-reply button:submit").click(function() {
-               inputField = $(this.form).find(":input:enabled").get(0);
+               sender = $(this.form).find(":input[name=sender]").val();
+               inputField = $(this.form).find(":input[name=text]:enabled").get(0);
                postId = getPostId(this);
                text = $(inputField).val();
-               (function(postId, text, inputField) {
-                       postReply(postId, text, function(success, error, replyId) {
+               (function(sender, postId, text, inputField) {
+                       postReply(sender, postId, text, function(success, error, replyId, soneId) {
                                if (success) {
                                        $(inputField).val("");
-                                       loadNewReply(replyId, getCurrentSoneId(), postId);
+                                       loadNewReply(replyId, soneId, postId);
                                        markPostAsKnown(getPostElement(inputField));
                                        $("#sone .post#" + postId + " .create-reply").addClass("hidden");
+                                       $("#sone .post#" + postId + " .create-reply .sender").hide();
+                                       $("#sone .post#" + postId + " .create-reply .select-sender").show();
+                                       $("#sone .post#" + postId + " .create-reply :input[name=sender]").val(getCurrentSoneId());
                                } else {
                                        alert(error);
                                }
                        });
-               })(postId, text, inputField);
+               })(sender, postId, text, inputField);
                return false;
        });
 
@@ -606,6 +668,15 @@ function ajaxifyPost(postElement) {
                });
        });
 
+       /* process sender selection. */
+       $(".select-sender", postElement).css("display", "inline");
+       $(".sender", postElement).hide();
+       $(".select-sender button", postElement).click(function() {
+               $(".sender", postElement).show();
+               $(".select-sender", postElement).hide();
+               return false;
+       });
+
        /* mark everything as known on click. */
        $(postElement).click(function(event) {
                if ($(event.target).hasClass("click-to-show")) {
@@ -671,9 +742,17 @@ function ajaxifyReply(replyElement) {
  *            jQuery object representing the notification.
  */
 function ajaxifyNotification(notification) {
-       notification.find("form.dismiss").submit(function() {
+       notification.find("form").submit(function() {
                return false;
        });
+       notification.find("input[name=returnPage]").val($.url.attr("relative"));
+       if (notification.find(".short-text").length > 0) {
+               notification.find(".short-text").removeClass("hidden");
+               notification.find(".text").addClass("hidden");
+       }
+       notification.find("form.mark-as-read button").click(function() {
+               $.getJSON("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": $(":input[name=type]", this.form).val(), "id": $(":input[name=id]", this.form).val()});
+       });
        notification.find("form.dismiss button").click(function() {
                $.getJSON("dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) {
                        /* dismiss in case of error, too. */
@@ -690,13 +769,18 @@ 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.lastUpdated);
+                               updateSoneStatus(value.id, value.name, value.status, value.modified, value.locked, value.lastUpdatedUnknown ? null : value.lastUpdated);
                        });
                        /* process notifications. */
                        $.each(data.notifications, function(index, value) {
                                oldNotification = $("#sone #notification-area .notification#" + value.id);
                                notification = ajaxifyNotification(createNotification(value.id, value.text, value.dismissable)).hide();
                                if (oldNotification.length != 0) {
+                                       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);
+                                       }
                                        oldNotification.replaceWith(notification.show());
                                } else {
                                        $("#sone #notification-area").append(notification);
@@ -910,12 +994,26 @@ function loadNewReply(replyId, soneId, postId, postSoneId) {
        });
 }
 
+/**
+ * Marks the given Sone as known if it is still new.
+ *
+ * @param soneElement
+ *            The Sone to mark as known
+ */
+function markSoneAsKnown(soneElement) {
+       if ($(".new", soneElement).length > 0) {
+               $.getJSON("maskAsKnown.ajax", {"formPassword": getFormPassword(), "type": "sone", "id": getSoneId(soneElement)}, function(data, textStatus) {
+                       $(soneElement).removeClass("new");
+               });
+       }
+}
+
 function markPostAsKnown(postElements) {
        $(postElements).each(function() {
                postElement = this;
                if ($(postElement).hasClass("new")) {
                        (function(postElement) {
-                               $.getJSON("markPostAsKnown.ajax", {"formPassword": getFormPassword(), "post": getPostId(postElement)}, function(data, textStatus) {
+                               $.getJSON("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "post", "id": getPostId(postElement)}, function(data, textStatus) {
                                        $(postElement).removeClass("new");
                                        $(".click-to-show", postElement).removeClass("new");
                                });
@@ -930,7 +1028,7 @@ function markReplyAsKnown(replyElements) {
                replyElement = this;
                if ($(replyElement).hasClass("new")) {
                        (function(replyElement) {
-                               $.getJSON("markReplyAsKnown.ajax", {"formPassword": getFormPassword(), "reply": getReplyId(replyElement)}, function(data, textStatus) {
+                               $.getJSON("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "reply", "id": getReplyId(replyElement)}, function(data, textStatus) {
                                        $(replyElement).removeClass("new");
                                });
                        })(replyElement);
@@ -983,8 +1081,8 @@ function createNotification(id, text, dismissable) {
  *            The ID of the notification
  */
 function showNotificationDetails(notificationId) {
-       $("#sone .notification#" + notificationId + " .text").show();
-       $("#sone .notification#" + notificationId + " .short-text").hide();
+       $("#sone .notification#" + notificationId + " .text").removeClass("hidden");
+       $("#sone .notification#" + notificationId + " .short-text").addClass("hidden");
 }
 
 /**
@@ -1072,17 +1170,28 @@ $(document).ready(function() {
        /* this initializes the status update input field. */
        getTranslation("WebInterface.DefaultText.StatusUpdate", function(defaultText) {
                registerInputTextareaSwap("#sone #update-status .status-input", defaultText, "text", false, false);
+               $("#sone #update-status .select-sender").css("display", "inline");
+               $("#sone #update-status .sender").hide();
+               $("#sone #update-status .select-sender button").click(function() {
+                       $("#sone #update-status .sender").show();
+                       $("#sone #update-status .select-sender").hide();
+                       return false;
+               });
                $("#sone #update-status").submit(function() {
                        if ($(this).find(":input.default:enabled").length > 0) {
                                return false;
                        }
-                       text = $(this).find(":input:enabled").val();
-                       $.getJSON("createPost.ajax", { "formPassword": getFormPassword(), "text": text }, function(data, textStatus) {
+                       sender = $(this).find(":input[name=sender]").val();
+                       text = $(this).find(":input[name=text]:enabled").val();
+                       $.getJSON("createPost.ajax", { "formPassword": getFormPassword(), "sender": sender, "text": text }, function(data, textStatus) {
                                if ((data != null) && data.success) {
-                                       loadNewPost(data.postId, getCurrentSoneId());
+                                       loadNewPost(data.postId, data.sone, data.recipient);
                                }
                        });
-                       $(this).find(":input:enabled").val("").blur();
+                       $(this).find(":input[name=sender]").val(getCurrentSoneId());
+                       $(this).find(":input[name=text]:enabled").val("").blur();
+                       $(this).find(".sender").hide();
+                       $(this).find(".select-sender").show();
                        return false;
                });
        });
@@ -1142,41 +1251,8 @@ $(document).ready(function() {
                });
        }
 
-       /*
-        * convert all “follow”, “unfollow”, “lock”, and “unlock” links to something
-        * nicer.
-        */
-       $("#sone .follow").submit(function() {
-               var followElement = this;
-               $.getJSON("followSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
-                       $(followElement).addClass("hidden");
-                       $(followElement).parent().find(".unfollow").removeClass("hidden");
-               });
-               return false;
-       });
-       $("#sone .unfollow").submit(function() {
-               var unfollowElement = this;
-               $.getJSON("unfollowSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
-                       $(unfollowElement).addClass("hidden");
-                       $(unfollowElement).parent().find(".follow").removeClass("hidden");
-               });
-               return false;
-       });
-       $("#sone .lock").submit(function() {
-               var lockElement = this;
-               $.getJSON("lockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
-                       $(lockElement).addClass("hidden");
-                       $(lockElement).parent().find(".unlock").removeClass("hidden");
-               });
-               return false;
-       });
-       $("#sone .unlock").submit(function() {
-               var unlockElement = this;
-               $.getJSON("unlockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
-                       $(unlockElement).addClass("hidden");
-                       $(unlockElement).parent().find(".lock").removeClass("hidden");
-               });
-               return false;
+       $("#sone .sone").each(function() {
+               ajaxifySone($(this));
        });
 
        /* process all existing notifications, ajaxify dismiss buttons. */
index 8a1b7d7..ecde2f7 100644 (file)
                <%foreach fields field fieldLoop>
                        <div class="profile-field" id="<% field.id|html>">
                                <div class="name"><% field.name|html></div>
-                               <input class="short hidden" type="text"><button class="confirm hidden" type="button">✔</button><button class="cancel hidden" type="button">✘</button>
+                               <input class="short hidden" type="text"><button class="edit confirm hidden" type="button">✔</button><button class="cancel hidden" type="button">✘</button>
                                <div class="edit-field-name"><button type="submit" name="edit-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.Edit|l10n|html></button></div>
                                <div class="delete-field-name"><button type="submit" name="delete-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.Delete|l10n|html></button></div>
                                <div class="<%if fieldLoop.last>hidden <%/if>move-down-field"><button type="submit" name="move-down-field-<% field.id|html>" value="true"><%= Page.EditProfile.Fields.Button.MoveDown|l10n|html></button></div>
index 2c2cf3f..a9fbb49 100644 (file)
@@ -4,6 +4,7 @@
        <div id="currentSoneId" class="hidden"><% currentSone.id|html></div>
 
        <script src="javascript/jquery-1.4.2.js" language="javascript"></script>
+       <script src="javascript/jquery.url.js" language="javascript"></script>
        <script src="javascript/sone.js" language="javascript"></script>
 
        <div id="main">
@@ -27,7 +28,7 @@
                                                        <button type="submit"><%= Notification.Button.Dismiss|l10n|html></button>
                                                </form>
                                        <%/if>
-                                       <% notification>
+                                       <%include notification>
                                </div>
                        <%/foreach>
                </div>
@@ -44,7 +45,6 @@
                                <div id="home-sone">
                                        <% currentSone|store key=sone>
                                        <%include include/viewSone.html>
-                                       <%include include/updateStatus.html>
                                </div>
                        <%/if>
                </div>
index fba951d..43d4d70 100644 (file)
@@ -1,7 +1,15 @@
 <form id="update-status" action="createPost.html" method="post">
        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
        <input type="hidden" name="returnPage" value="<% request.uri|html>" />
-       <label for="text"><%= Page.Index.Label.Text|l10n|html></label>
+       <label for="sender"><%= Page.Index.Label.Sender|l10n|html></label>
+       <div class="sender">
+               <select name="sender" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">
+                       <%foreach localSones localSone>
+                               <option value="<% localSone.id|html>"<%if localSone.current> selected="selected"<%/if>><% localSone.niceName|html></option>
+                       <%/foreach>
+               </select>
+       </div>
+       <div class="select-sender"><button type="button" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">+</button></div><label for="text"><%= Page.Index.Label.Text|l10n|html></label>
        <input type="text" class="status-input" name="text" value="" />
        <button type="submit"><%= Page.Index.Button.Post|l10n|html></button>
 </form>
index a19ee73..74e3450 100644 (file)
                                        <div class="recipient profile-link"><a href="viewSone.html?sone=<% post.recipient.id|html>"><% post.recipient.niceName|html></a></div>
                                <%/if>
                        <%/if>
-                       <div class="text"><% post.text></div>
+                       <div class="text"><% post.text|parse sone=post.sone></div>
                </div>
                <div class="post-status-line status-line">
                        <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="likes<%if post.likes.size|match value=0> hidden<%/if>"><span title="<% post.likes.soneNames|html>">↑<span class="like-count"><% post.likes.size></span></span></div>
+                       <div class="likes<%if post.likes.size|match value=0> hidden<%/if>">
+                               <span class='separator'>·</span>
+                               <span title="<% post.likes.soneNames|html>">↑<span class="like-count"><% post.likes.size></span></span>
+                       </div>
                        <%ifnull ! currentSone>
+                               <span class='separator'>·</span>
                                <form class="like like-post<%if post.liked> hidden<%/if>" action="like.html" method="post">
                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                        <input type="hidden" name="returnPage" value="<% request.uri|html>" />
                                        <input type="hidden" name="post" value="<% post.id|html>" />
                                        <button type="submit" value="1"><%= View.Post.UnlikeLink|l10n|html></button>
                                </form>
+                               <%if !post.sone.current>
+                                       <span class='separator'>·</span>
+                                       <form class="trust post-trust<%if post.sone.trust.assigned> hidden<%/if>" action="trust.html" method="post">
+                                               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+                                               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+                                               <input type="hidden" name="sone" value="<% post.sone.id|html>" />
+                                               <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>">✓</button>
+                                       </form>
+                                       <form class="distrust post-distrust<%if post.sone.trust.assigned> hidden<%/if>" action="distrust.html" method="post">
+                                               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+                                               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+                                               <input type="hidden" name="sone" value="<% post.sone.id|html>" />
+                                               <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>">✗</button>
+                                       </form>
+                                       <form class="untrust post-untrust<%if !post.sone.trust.assigned> hidden<%/if>" action="untrust.html" method="post">
+                                               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+                                               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+                                               <input type="hidden" name="sone" value="<% post.sone.id|html>" />
+                                               <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">↶</button>
+                                       </form>
+                               <%/if>
                        <%/if>
-                       <%if !post.sone.current>
-                               <span class='separator'>·</span>
-                               <form class="trust post-trust<%if post.sone.trust.assigned> hidden<%/if>" action="trust.html" method="post">
-                                       <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-                                       <input type="hidden" name="returnPage" value="<% request.uri|html>" />
-                                       <input type="hidden" name="sone" value="<% post.sone.id|html>" />
-                                       <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>">✓</button>
-                               </form>
-                               <form class="distrust post-distrust" action="distrust.html" method="post">
-                                       <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-                                       <input type="hidden" name="returnPage" value="<% request.uri|html>" />
-                                       <input type="hidden" name="sone" value="<% post.sone.id|html>" />
-                                       <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>">✗</button>
-                               </form>
-                               <form class="untrust post-untrust<%if !post.sone.trust.assigned> hidden<%/if>" action="untrust.html" method="post">
-                                       <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-                                       <input type="hidden" name="returnPage" value="<% request.uri|html>" />
-                                       <input type="hidden" name="sone" value="<% post.sone.id|html>" />
-                                       <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">↶</button>
-                               </form>
-                       <%/if>
-                       <%if post.sone.current>
+                       <%if post.sone.local>
                                <span class='separator'>·</span>
                                <form class="delete delete-post" action="deletePost.html" method="post">
                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                                <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                                <input type="hidden" name="returnPage" value="<% request.uri|html>" />
                                                <input type="hidden" name="post" value="<% post.id|html>" />
+                                               <div class="sender">
+                                                       <select name="sender" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">
+                                                               <%foreach localSones localSone>
+                                                                       <option value="<% localSone.id|html>"<%if localSone.current> selected="selected"<%/if>><% localSone.niceName|html></option>
+                                                               <%/foreach>
+                                                       </select>
+                                               </div>
+                                               <div class="select-sender"><button type="button" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">+</button></div>
                                                <input type="text" class="reply-input" name="text" value="" />
                                                <button type="submit"><%= View.Post.SendReply|l10n|html></button>
                                        </form>
index 8c6884b..6b13776 100644 (file)
@@ -8,13 +8,16 @@
        <div class="inner-part">
                <div>
                        <div class="author profile-link"><a href="viewSone.html?sone=<% reply.sone.id|html>"><% reply.sone.niceName|html></a></div>
-                       <div class="text"><% reply.text></div>
+                       <div class="text"><% reply.text|parse sone=reply.sone></div>
                </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="likes<%if reply.likes.size|match value=0> hidden<%/if>"><span title="<% reply.likes.soneNames|html>">↑<span class="like-count"><% reply.likes.size></span></span></div>
+                       <div class="likes<%if reply.likes.size|match value=0> hidden<%/if>">
+                               <span class='separator'>·</span>
+                               <span title="<% reply.likes.soneNames|html>">↑<span class="like-count"><% reply.likes.size></span></span>
+                       </div>
                        <%ifnull ! currentSone>
+                               <span class='separator'>·</span>
                                <form class="like like-reply<%if reply.liked> hidden<%/if>" action="like.html" method="post">
                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                        <input type="hidden" name="returnPage" value="<% request.uri|html>" />
                                        <input type="hidden" name="reply" value="<% reply.id|html>" />
                                        <button type="submit" value="1"><%= View.Post.UnlikeLink|l10n|html></button>
                                </form>
+                               <%if !reply.sone.current>
+                                       <span class='separator'>·</span>
+                                       <form class="trust reply-trust<%if reply.sone.trust.assigned> hidden<%/if>" action="trust.html" method="post">
+                                               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+                                               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+                                               <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
+                                               <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>">✓</button>
+                                       </form>
+                                       <form class="distrust reply-distrust<%if reply.sone.trust.assigned> hidden<%/if>" action="distrust.html" method="post">
+                                               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+                                               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+                                               <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
+                                               <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>">✗</button>
+                                       </form>
+                                       <form class="untrust reply-untrust<%if !reply.sone.trust.assigned> hidden<%/if>" action="untrust.html" method="post">
+                                               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+                                               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+                                               <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
+                                               <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">↶</button>
+                                       </form>
+                               <%/if>
                        <%/if>
-                       <%if !reply.sone.current>
-                               <span class='separator'>·</span>
-                               <form class="trust reply-trust<%if reply.sone.trust.assigned> hidden<%/if>" action="trust.html" method="post">
-                                       <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-                                       <input type="hidden" name="returnPage" value="<% request.uri|html>" />
-                                       <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
-                                       <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>">✓</button>
-                               </form>
-                               <form class="distrust reply-distrust" action="distrust.html" method="post">
-                                       <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-                                       <input type="hidden" name="returnPage" value="<% request.uri|html>" />
-                                       <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
-                                       <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>">✗</button>
-                               </form>
-                               <form class="untrust reply-untrust<%if !reply.sone.trust.assigned> hidden<%/if>" action="untrust.html" method="post">
-                                       <input type="hidden" name="formPassword" value="<% formPassword|html>" />
-                                       <input type="hidden" name="returnPage" value="<% request.uri|html>" />
-                                       <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
-                                       <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">↶</button>
-                               </form>
-                       <%/if>
-                       <%if reply.sone.current>
+                       <%if reply.sone.local>
                                <span class='separator'>·</span>
                                <form class="delete delete-reply" action="deleteReply.html" method="post">
                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
index 8b62fa4..c403b0d 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|date format="MMM d, yyyy, HH:mm:ss"></span></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="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 345f376..59bfee7 100644 (file)
@@ -4,6 +4,8 @@
 
        <h1><%= Page.Index.PostList.Title|l10n|html></h1>
 
+       <%include include/updateStatus.html>
+
        <div id="posts">
                <%= page|store key=pageParameter>
                <%include include/pagination.html>
index f5907a0..33f0e27 100644 (file)
@@ -5,8 +5,6 @@
        <h1><%= Page.KnownSones.Page.Title|l10n|html></h1>
 
        <div id="known-sones">
-               <%getpage parameter=page>
-               <%paginate list=knownSones pagesize=25>
                <%= page|store key=pageParameter>
                <%include include/pagination.html>
                <%foreach pagination.items sone>
index 94f7a1f..e5d7792 100644 (file)
@@ -1,8 +1,15 @@
-<div class="short-text">
+<div class="short-text hidden">
        <%= Notification.NewPost.ShortText|l10n|html>
        <a class="link" onclick="showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
 </div>
-<div class="text hidden">
+<div class="text">
+       <form class="mark-as-read" action="markAsKnown.html" method="post">
+               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+               <input type="hidden" name="type" value="post" />
+               <input type="hidden" name="id" value="<%foreach posts post><% post.id|html><%notlast> <%/notlast><%/foreach>" />
+               <button type="submit" name="mark-read" value="true"><%= Notification.NewPost.Button.MarkRead|l10n|html></button>
+       </form>
        <%= Notification.NewPost.Text|l10n|html>
        <%foreach posts post>
                <a href="viewPost.html?post=<% post.id|html>"><% post.sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
index d6d0670..2a194f2 100644 (file)
@@ -1,8 +1,15 @@
-<div class="short-text">
+<div class="short-text hidden">
        <%= Notification.NewReply.ShortText|l10n|html>
        <a class="link" onclick="showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
 </div>
-<div class="text hidden">
+<div class="text">
+       <form class="mark-as-read" action="markAsKnown.html" method="post">
+               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+               <input type="hidden" name="type" value="reply" />
+               <input type="hidden" name="id" value="<%foreach replies reply><% reply.id|html><%notlast> <%/notlast><%/foreach>" />
+               <button type="submit" name="mark-read" value="true"><%= Notification.NewPost.Button.MarkRead|l10n|html></button>
+       </form>
        <%= Notification.NewReply.Text|l10n|html>
        <%foreach replies reply>
                <a href="viewPost.html?post=<% reply.post.id|html>"><% reply.sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
index 8389f7a..45357b1 100644 (file)
@@ -1,8 +1,15 @@
-<div class="short-text">
+<div class="short-text hidden">
        <%= Notification.NewSone.ShortText|l10n|html>
        <a class="link" onclick="showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
 </div>
-<div class="text hidden">
+<div class="text">
+       <form class="mark-as-read" action="markAsKnown.html" method="post">
+               <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+               <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+               <input type="hidden" name="type" value="sone" />
+               <input type="hidden" name="id" value="<%foreach sones sone><% sone.id|html><%notlast> <%/notlast><%/foreach>" />
+               <button type="submit" name="mark-read" value="true"><%= Notification.NewPost.Button.MarkRead|l10n|html></button>
+       </form>
        <%= Notification.NewSone.Text|l10n|html>
        <%foreach sones sone>
                <a href="viewSone.html?sone=<% sone.id|html>" title="<% sone.requestUri|html>"><% sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
index a61f858..836bf62 100644 (file)
@@ -8,7 +8,7 @@
 
                <p><%= Page.ViewPost.Text.UnknownPost|l10n|html></p>
        <%else>
-               <h1><%= Page.ViewPost.Page.Title|l10n|insert needle="{sone}" key=post.sone.niceName|html></h1>
+               <h1><%= Page.ViewPost.Page.Title|l10n|replace needle="{sone}" replacementKey=post.sone.niceName|html></h1>
 
                <%include include/viewPost.html>
        <%/if>
index 98551b3..fac920b 100644 (file)
@@ -7,7 +7,7 @@
 
                <h1><%= Page.ViewSone.Page.TitleWithoutSone|l10n|html></h1>
 
-               <p><%= Page.ViewSone.NoSone.Description|l10n|insert needle="{sone}" key=sone.id|html></p>
+               <p><%= Page.ViewSone.NoSone.Description|l10n|replace needle="{sone}" replacementKey=sone.id|html></p>
 
        <%elseifnull sone.name>
 
@@ -31,7 +31,7 @@
                        <%foreach sone.profile.fields field>
                                <div class="profile-field">
                                        <div class="name"><% field.name|html></div>
-                                       <div class="value"><% field.value|html></div>
+                                       <div class="value"><% field.value|parse></div>
                                </div>
                        <%/foreach>
 
                        </form>
                <%/if>
 
-               <h1><%= Page.ViewSone.PostList.Title|l10n|insert needle="{sone}" key=sone.niceName|html></h1>
+               <h1><%= Page.ViewSone.PostList.Title|l10n|replace needle="{sone}" replacementKey=sone.niceName|html></h1>
 
                <div id="posts">
-                       <%getpage parameter=postPage>
-                       <%paginate list=sone.posts pagesize=25>
+                       <%:getpage parameter=postPage>
+                       <%:paginate list=sone.posts pagesize=25>
                        <%= postPage|store key=pageParameter>
                        <%include include/pagination.html>
                        <%foreach pagination.items post>