Merge branch 'next' into image-management
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Tue, 22 Mar 2011 18:46:01 +0000 (19:46 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Tue, 22 Mar 2011 18:46:01 +0000 (19:46 +0100)
Conflicts:
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/resources/i18n/sone.en.properties

1  2 
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/template/AlbumAccessor.java
src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java
src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/resources/i18n/sone.en.properties
src/main/resources/static/javascript/sone.js
src/main/resources/templates/insert/sone.xml

@@@ -31,11 -31,10 +31,12 @@@ import java.util.logging.Logger
  import net.pterodactylus.sone.core.Options.DefaultOption;
  import net.pterodactylus.sone.core.Options.Option;
  import net.pterodactylus.sone.core.Options.OptionWatcher;
 +import net.pterodactylus.sone.data.Album;
  import net.pterodactylus.sone.data.Client;
 +import net.pterodactylus.sone.data.Image;
  import net.pterodactylus.sone.data.Post;
  import net.pterodactylus.sone.data.Profile;
+ import net.pterodactylus.sone.data.Profile.Field;
  import net.pterodactylus.sone.data.Reply;
  import net.pterodactylus.sone.data.Sone;
  import net.pterodactylus.sone.freenet.wot.Identity;
@@@ -153,12 -158,13 +160,19 @@@ public class Core implements IdentityLi
        /** All known replies. */
        private Set<String> knownReplies = new HashSet<String>();
  
+       /** All bookmarked posts. */
+       /* synchronize access on itself. */
+       private Set<String> bookmarkedPosts = new HashSet<String>();
+       /** Trusted identities, sorted by own identities. */
+       private Map<OwnIdentity, Set<Identity>> trustedIdentities = Collections.synchronizedMap(new HashMap<OwnIdentity, Set<Identity>>());
 +      /** All known albums. */
 +      private Map<String, Album> albums = new HashMap<String, Album>();
 +
 +      /** All known images. */
 +      private Map<String, Image> images = new HashMap<String, Image>();
 +
        /**
         * Creates a new core.
         *
        }
  
        /**
+        * Returns whether the given post is bookmarked.
+        *
+        * @param post
+        *            The post to check
+        * @return {@code true} if the given post is bookmarked, {@code false}
+        *         otherwise
+        */
+       public boolean isBookmarked(Post post) {
+               return isPostBookmarked(post.getId());
+       }
+       /**
+        * Returns whether the post with the given ID is bookmarked.
+        *
+        * @param id
+        *            The ID of the post to check
+        * @return {@code true} if the post with the given ID is bookmarked,
+        *         {@code false} otherwise
+        */
+       public boolean isPostBookmarked(String id) {
+               synchronized (bookmarkedPosts) {
+                       return bookmarkedPosts.contains(id);
+               }
+       }
+       /**
+        * Returns all currently known bookmarked posts.
+        *
+        * @return All bookmarked posts
+        */
+       public Set<Post> getBookmarkedPosts() {
+               Set<Post> posts = new HashSet<Post>();
+               synchronized (bookmarkedPosts) {
+                       for (String bookmarkedPostId : bookmarkedPosts) {
+                               Post post = getPost(bookmarkedPostId, false);
+                               if (post != null) {
+                                       posts.add(post);
+                               }
+                       }
+               }
+               return posts;
+       }
++      /**
++       *      
 +       * Returns the album with the given ID, creating a new album if no album
 +       * with the given ID can be found.
 +       *
 +       * @param albumId
 +       *            The ID of the album
 +       * @return The album with the given ID
 +       */
 +      public Album getAlbum(String albumId) {
 +              return getAlbum(albumId, true);
 +      }
 +
 +      /**
 +       * Returns the album with the given ID, optionally creating a new album if
 +       * an album with the given ID can not be found.
 +       *
 +       * @param albumId
 +       *            The ID of the album
 +       * @param create
 +       *            {@code true} to create a new album if none exists for the
 +       *            given ID
 +       * @return The album with the given ID, or {@code null} if no album with the
 +       *         given ID exists and {@code create} is {@code false}
 +       */
 +      public Album getAlbum(String albumId, boolean create) {
 +              synchronized (albums) {
 +                      Album album = albums.get(albumId);
 +                      if (create && (album == null)) {
 +                              album = new Album(albumId);
 +                              albums.put(albumId, album);
 +                      }
 +                      return album;
 +              }
 +      }
 +
 +      /**
 +       * Returns the image with the given ID, creating it if necessary.
 +       *
 +       * @param imageId
 +       *            The ID of the image
 +       * @return The image with the given ID
 +       */
 +      public Image getImage(String imageId) {
 +              return getImage(imageId, true);
 +      }
 +
 +      /**
 +       * Returns the image with the given ID, optionally creating it if it does
 +       * not exist.
 +       *
 +       * @param imageId
 +       *            The ID of the image
 +       * @param create
 +       *            {@code true} to create an image if none exists with the given
 +       *            ID
 +       * @return The image with the given ID, or {@code null} if none exists and
 +       *         none was created
 +       */
 +      public Image getImage(String imageId, boolean create) {
 +              synchronized (images) {
 +                      Image image = images.get(imageId);
 +                      if (create && (image == null)) {
 +                              image = new Image(imageId);
 +                              images.put(imageId, image);
 +                      }
 +                      return image;
 +              }
 +      }
 +
        //
        // ACTIONS
        //
index e2c6f16,0000000..5042995
mode 100644,000000..100644
--- /dev/null
@@@ -1,78 -1,0 +1,78 @@@
- import net.pterodactylus.util.template.DataProvider;
 +/*
 + * Sone - AlbumAccessor.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.ArrayList;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +
 +import net.pterodactylus.sone.data.Album;
 +import net.pterodactylus.util.template.Accessor;
-       public Object get(DataProvider dataProvider, Object object, String member) {
 +import net.pterodactylus.util.template.ReflectionAccessor;
++import net.pterodactylus.util.template.TemplateContext;
 +
 +/**
 + * {@link Accessor} implementation for {@link Album}s. A property named
 + * “backlinks” is added, it returns links to all parents and the owner Sone of
 + * an album.
 + *
 + * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
 + */
 +public class AlbumAccessor extends ReflectionAccessor {
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
-               return super.get(dataProvider, object, member);
++      public Object get(TemplateContext templateContext, Object object, String member) {
 +              Album album = (Album) object;
 +              if ("backlinks".equals(member)) {
 +                      List<Map<String, String>> backlinks = new ArrayList<Map<String, String>>();
 +                      Album currentAlbum = album;
 +                      while (currentAlbum != null) {
 +                              backlinks.add(0, createLink("imageBrowser.html?album=" + album.getId(), album.getName()));
 +                              currentAlbum = currentAlbum.getParent();
 +                      }
 +                      backlinks.add(0, createLink("viewSone.html?sone=" + album.getSone().getId(), SoneAccessor.getNiceName(album.getSone())));
 +                      return backlinks;
 +              }
++              return super.get(templateContext, object, member);
 +      }
 +
 +      //
 +      // PRIVATE METHODS
 +      //
 +
 +      /**
 +       * Creates a map containing mappings for “target” and “link.”
 +       *
 +       * @param target
 +       *            The target to link to
 +       * @param name
 +       *            The name of the link
 +       * @return The created map containing the mappings
 +       */
 +      private Map<String, String> createLink(String target, String name) {
 +              Map<String, String> link = new HashMap<String, String>();
 +              link.put("target", target);
 +              link.put("name", name);
 +              return link;
 +      }
 +
 +}
index 7e1062d,0000000..7b51fb5
mode 100644,000000..100644
--- /dev/null
@@@ -1,70 -1,0 +1,70 @@@
- import net.pterodactylus.util.template.DataProvider;
 +/*
 + * Sone - CreateAlbumPage.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 net.pterodactylus.sone.data.Album;
 +import net.pterodactylus.sone.data.Sone;
 +import net.pterodactylus.sone.web.page.Page.Request.Method;
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
 +import net.pterodactylus.util.template.Template;
++import net.pterodactylus.util.template.TemplateContext;
 +
 +/**
 + * Page that lets the user create a new album.
 + *
 + * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
 + */
 +public class CreateAlbumPage extends SoneTemplatePage {
 +
 +      /**
 +       * Creates a new “create album” page.
 +       *
 +       * @param template
 +       *            The template to render
 +       * @param webInterface
 +       *            The Sone web interface
 +       */
 +      public CreateAlbumPage(Template template, WebInterface webInterface) {
 +              super("createAlbum.html", template, "Page.CreateAlbum.Title", webInterface, true);
 +      }
 +
 +      //
 +      // SONETEMPLATEPAGE METHODS
 +      //
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
-                               dataProvider.set("nameMissing", true);
++      protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
++              super.processTemplate(request, templateContext);
 +              if (request.getMethod() == Method.POST) {
 +                      String name = request.getHttpRequest().getPartAsStringFailsafe("name", 64).trim();
 +                      if (name.length() == 0) {
++                              templateContext.set("nameMissing", true);
 +                              return;
 +                      }
 +                      Sone currentSone = getCurrentSone(request.getToadletContext());
 +                      String parentId = request.getHttpRequest().getPartAsStringFailsafe("parent", 36);
 +                      Album parent = webInterface.getCore().getAlbum(parentId, false);
 +                      Album album = webInterface.getCore().createAlbum(currentSone, parent);
 +                      album.setName(name);
 +                      throw new RedirectException("imageBrowser.html?album=" + album.getId());
 +              }
 +      }
 +
 +}
index c204128,0000000..6da7cda
mode 100644,000000..100644
--- /dev/null
@@@ -1,68 -1,0 +1,68 @@@
- import net.pterodactylus.util.template.DataProvider;
 +/*
 + * Sone - ImageBrowserPage.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 net.pterodactylus.sone.data.Album;
 +import net.pterodactylus.sone.data.Image;
-       protected void processTemplate(Request request, DataProvider dataProvider) throws RedirectException {
-               super.processTemplate(request, dataProvider);
 +import net.pterodactylus.util.template.Template;
++import net.pterodactylus.util.template.TemplateContext;
 +
 +/**
 + * The image browser page is the entry page for the image management.
 + *
 + * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
 + */
 +public class ImageBrowserPage extends SoneTemplatePage {
 +
 +      /**
 +       * Creates a new image browser page.
 +       *
 +       * @param template
 +       *            The template to render
 +       * @param webInterface
 +       *            The Sone web interface
 +       */
 +      public ImageBrowserPage(Template template, WebInterface webInterface) {
 +              super("imageBrowser.html", template, "Page.ImageBrowser.Title", webInterface, true);
 +      }
 +
 +      //
 +      // SONETEMPLATEPAGE METHODS
 +      //
 +
 +      /**
 +       * {@inheritDoc}
 +       */
 +      @Override
-                       dataProvider.set("albumRequested", true);
-                       dataProvider.set("album", album);
++      protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
++              super.processTemplate(request, templateContext);
 +              String albumId = request.getHttpRequest().getParam("album", null);
 +              if (albumId != null) {
 +                      Album album = webInterface.getCore().getAlbum(albumId, false);
-                       dataProvider.set("imageRequested", true);
-                       dataProvider.set("image", image);
++                      templateContext.set("albumRequested", true);
++                      templateContext.set("album", album);
 +                      return;
 +              }
 +              String imageId = request.getHttpRequest().getParam("image", null);
 +              if (imageId != null) {
 +                      Image image = webInterface.getCore().getImage(imageId, false);
++                      templateContext.set("imageRequested", true);
++                      templateContext.set("image", image);
 +              }
 +      }
 +}
@@@ -42,9 -41,9 +42,10 @@@ import net.pterodactylus.sone.data.Repl
  import net.pterodactylus.sone.data.Sone;
  import net.pterodactylus.sone.freenet.L10nFilter;
  import net.pterodactylus.sone.freenet.wot.Identity;
+ import net.pterodactylus.sone.freenet.wot.Trust;
  import net.pterodactylus.sone.main.SonePlugin;
  import net.pterodactylus.sone.notify.ListNotification;
 +import net.pterodactylus.sone.template.AlbumAccessor;
  import net.pterodactylus.sone.template.CollectionAccessor;
  import net.pterodactylus.sone.template.CssClassNameFilter;
  import net.pterodactylus.sone.template.GetPagePlugin;
@@@ -158,47 -181,56 +183,57 @@@ public class WebInterface implements Co
                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(Album.class, new AlbumAccessor());
-               templateFactory.addAccessor(Identity.class, new IdentityAccessor(getCore()));
-               templateFactory.addAccessor(NotificationManager.class, new NotificationManagerAccessor());
-               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.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(Album.class, new AlbumAccessor());
+               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(getCore(), templateContextFactory));
+               templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate"));
+               templateContextFactory.addFilter("format", new FormatFilter());
+               templateContextFactory.addFilter("sort", new CollectionSortFilter());
+               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);
        }
  
         * 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 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 imageBrowserTemplate = templateFactory.createTemplate(createReader("/templates/imageBrowser.html"));
-               Template createAlbumTemplate = templateFactory.createTemplate(createReader("/templates/createAlbum.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 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 bookmarksTemplate = TemplateParser.parse(createReader("/templates/bookmarks.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 imageBrowserTemplate = TemplateParser.parse(createReader("/templates/imageBrowser.html"));
++              Template createAlbumTemplate = TemplateParser.parse(createReader("/templates/createAlbum.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"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlockSonePage(emptyTemplate, this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSonePage(emptyTemplate, this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSonePage(emptyTemplate, this)));
 +              pageToadlets.add(pageToadletFactory.createPageToadlet(new ImageBrowserPage(imageBrowserTemplate, this), "ImageBrowser"));
 +              pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateAlbumPage(createAlbumTemplate, this)));
+               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 BookmarkPage(emptyTemplate, this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new UnbookmarkPage(emptyTemplate, this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new BookmarksPage(bookmarksTemplate, this), "Bookmarks"));
                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"));
@@@ -8,10 -8,10 +8,12 @@@ Navigation.Menu.Item.CreateSone.Name=Cr
  Navigation.Menu.Item.CreateSone.Tooltip=Create a new Sone
  Navigation.Menu.Item.KnownSones.Name=Known Sones
  Navigation.Menu.Item.KnownSones.Tooltip=Shows all known Sones
+ Navigation.Menu.Item.Bookmarks.Name=Bookmarks
+ Navigation.Menu.Item.Bookmarks.Tooltip=Show bookmarked posts
  Navigation.Menu.Item.EditProfile.Name=Edit Profile
  Navigation.Menu.Item.EditProfile.Tooltip=Edit the Profile of your Sone
 +Navigation.Menu.Item.ImageBrowser.Name=Images
 +Navigation.Menu.Item.ImageBrowser.Tooltip=Manages your Images
  Navigation.Menu.Item.DeleteSone.Name=Delete Sone
  Navigation.Menu.Item.DeleteSone.Tooltip=Deletes the current Sone
  Navigation.Menu.Item.Logout.Name=Logout
@@@ -123,15 -154,21 +156,30 @@@ Page.FollowSone.Title=Follow Sone - Son
  
  Page.UnfollowSone.Title=Unfollow Sone - Sone
  
 +Page.ImageBrowser.Title=Image Browser - Sone
 +Page.ImageBrowser.Page.Title=Image Browser
 +Page.ImageBrowser.Album.Error.NotFound.Text=The requested album could not be found. It is possible that it has not yet been downloaded, or that it has been deleted.
 +Page.ImageBrowser.CreateAlbum.Button.CreateAlbum=Create Album
 +
 +Page.CreateAlbum.Title=Create Album - Sone
 +Page.CreateAlbum.Page.Title=Create Album
 +Page.CreateAlbum.Error.NameMissing=You seem to have forgotten to enter a name for your new album.
 +
+ Page.Trust.Title=Trust Sone - Sone
+ Page.Distrust.Title=Distrust Sone - Sone
+ Page.Untrust.Title=Untrust Sone - Sone
+ Page.MarkAsKnown.Title=Mark as Known - Sone
+ Page.Bookmark.Title=Bookmark - Sone
+ Page.Unbookmark.Title=Remove Bookmark - Sone
+ Page.Bookmarks.Title=Bookmarks - Sone
+ Page.Bookmarks.Page.Title=Bookmarks
+ Page.Bookmarks.Text.NoBookmarks=You don’t have any bookmarks defined right now. You can bookmark posts by clicking the star below the post.
+ Page.Bookmarks.Text.PostsNotLoaded=Some of your bookmarked posts have not been shown because they could not be loaded. This can happen if you restarted Sone recently or if the originating Sone has deleted the post. If you are reasonable sure that these posts do not exist anymore, you can {link}unbookmark them{/link}.
  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!
@@@ -169,10 -213,14 +224,17 @@@ View.Post.SendReply=Post Reply
  View.Post.Reply.DeleteLink=Delete
  View.Post.LikeLink=Like
  View.Post.UnlikeLink=Unlike
+ View.Post.ShowSource=Toggle Parser
+ 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
  
 +View.CreateAlbum.Title=Create Album
 +View.CreateAlbum.Label.Name=Name:
 +
  WebInterface.DefaultText.StatusUpdate=What’s on your mind?
  WebInterface.DefaultText.Message=Write a Message…
  WebInterface.DefaultText.Reply=Write a Reply…