Update French translation.
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 27 Sep 2013 04:29:41 +0000 (06:29 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 27 Sep 2013 04:29:41 +0000 (06:29 +0200)
73 files changed:
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.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/core/UpdateChecker.java
src/main/java/net/pterodactylus/sone/core/WebOfTrustUpdater.java
src/main/java/net/pterodactylus/sone/data/Album.java
src/main/java/net/pterodactylus/sone/data/Identified.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/Image.java
src/main/java/net/pterodactylus/sone/data/Post.java
src/main/java/net/pterodactylus/sone/data/PostReply.java
src/main/java/net/pterodactylus/sone/data/Reply.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java
src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/template/AlbumAccessor.java
src/main/java/net/pterodactylus/sone/template/ReplyGroupFilter.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/web/CreateAlbumPage.java
src/main/java/net/pterodactylus/sone/web/CreateSonePage.java
src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java
src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java
src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java
src/main/java/net/pterodactylus/sone/web/SearchPage.java
src/main/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.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/DeleteProfileFieldAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationsAjaxPage.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/GetTimesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetTranslationPage.java
src/main/java/net/pterodactylus/sone/web/ajax/JsonErrorReturnObject.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java
src/main/java/net/pterodactylus/sone/web/ajax/JsonReturnObject.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/web/ajax/LikeAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/TrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnlikeAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java
src/main/resources/i18n/sone.de.properties
src/main/resources/i18n/sone.en.properties
src/main/resources/i18n/sone.fr.properties
src/main/resources/i18n/sone.ja.properties
src/main/resources/i18n/sone.no.properties
src/main/resources/i18n/sone.pl.properties
src/main/resources/i18n/sone.ru.properties
src/main/resources/static/javascript/sone.js
src/main/resources/templates/deleteAlbum.html
src/main/resources/templates/imageBrowser.html
src/main/resources/templates/include/updateStatus.html
src/main/resources/templates/insert/sone.xml
src/main/resources/templates/notify/soneInsertNotification.html
src/test/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPageTest.java [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index d2f00a4..f506a24 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.8.6</version>
+       <version>0.8.7</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
                        <artifactId>utils</artifactId>
-                       <version>0.12.3</version>
+                       <version>${version.utils}</version>
                </dependency>
                <dependency>
                        <groupId>junit</groupId>
                        <scope>provided</scope>
                </dependency>
                <dependency>
-                       <groupId>net.pterodactylus</groupId>
-                       <artifactId>utils.json</artifactId>
-                       <version>0.1</version>
-               </dependency>
-               <dependency>
                        <groupId>com.google.inject</groupId>
                        <artifactId>guice</artifactId>
                        <version>3.0</version>
                        <artifactId>commons-lang</artifactId>
                        <version>2.6</version>
                </dependency>
+               <dependency>
+                       <groupId>com.fasterxml.jackson.core</groupId>
+                       <artifactId>jackson-databind</artifactId>
+                       <version>2.1.2</version>
+               </dependency>
+               <dependency>
+                       <groupId>com.google.code.findbugs</groupId>
+                       <artifactId>jsr305</artifactId>
+                       <version>2.0.1</version>
+               </dependency>
        </dependencies>
        <repositories>
                <repository>
        </repositories>
        <properties>
                <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+               <version.utils>0.12.4</version.utils>
+               <findbugs.timeout>600000</findbugs.timeout>
        </properties>
        <build>
                <plugins>
                        <plugin>
+                               <groupId>org.codehaus.mojo</groupId>
+                               <artifactId>findbugs-maven-plugin</artifactId>
+                               <version>2.5.2</version>
+                               <configuration>
+                                       <timeout>${findbugs.timeout}</timeout>
+                               </configuration>
+                       </plugin>
+                       <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-compiler-plugin</artifactId>
                                <version>2.0.2</version>
index 7b4bc4b..5103b7d 100644 (file)
@@ -1035,13 +1035,13 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        database.storePostReplies(sone, sone.getReplies());
                        synchronized (albums) {
                                synchronized (images) {
-                                       for (Album album : storedSone.get().getAlbums()) {
+                                       for (Album album : storedSone.get().getRootAlbum().getAlbums()) {
                                                albums.remove(album.getId());
                                                for (Image image : album.getImages()) {
                                                        images.remove(image.getId());
                                                }
                                        }
-                                       for (Album album : sone.getAlbums()) {
+                                       for (Album album : sone.getRootAlbum().getAlbums()) {
                                                albums.put(album.getId(), album);
                                                for (Image image : album.getImages()) {
                                                        images.put(image.getId(), image);
@@ -1052,6 +1052,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        synchronized (sones) {
                                sone.setOptions(storedSone.get().getOptions());
                                sone.setKnown(storedSone.get().isKnown());
+                               sone.setStatus((sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
+                               if (sone.isLocal()) {
+                                       soneInserters.get(storedSone.get()).setSone(sone);
+                                       touchConfiguration();
+                               }
                                sones.put(sone.getId(), sone);
                        }
                }
@@ -1314,7 +1319,12 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        for (String friendId : friends) {
                                followSone(sone, friendId);
                        }
-                       sone.setAlbums(topLevelAlbums);
+                       for (Album album : sone.getRootAlbum().getAlbums()) {
+                               sone.getRootAlbum().removeAlbum(album);
+                       }
+                       for (Album album : topLevelAlbums) {
+                               sone.getRootAlbum().addAlbum(album);
+                       }
                        soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint);
                }
                synchronized (knownSones) {
@@ -1579,7 +1589,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The new album
         */
        public Album createAlbum(Sone sone) {
-               return createAlbum(sone, null);
+               return createAlbum(sone, sone.getRootAlbum());
        }
 
        /**
@@ -1598,11 +1608,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        albums.put(album.getId(), album);
                }
                album.setSone(sone);
-               if (parent != null) {
-                       parent.addAlbum(album);
-               } else {
-                       sone.addAlbum(album);
-               }
+               parent.addAlbum(album);
                return album;
        }
 
@@ -1619,11 +1625,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                if (!album.isEmpty()) {
                        return;
                }
-               if (album.getParent() == null) {
-                       album.getSone().removeAlbum(album);
-               } else {
-                       album.getParent().removeAlbum(album);
-               }
+               album.getParent().removeAlbum(album);
                synchronized (albums) {
                        albums.remove(album.getId());
                }
@@ -1878,7 +1880,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null);
 
                        /* save albums. first, collect in a flat structure, top-level first. */
-                       List<Album> albums = FluentIterable.from(sone.getAlbums()).transformAndConcat(Album.FLATTENER).toList();
+                       List<Album> albums = FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).toList();
 
                        int albumCounter = 0;
                        for (Album album : albums) {
@@ -1886,7 +1888,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                configuration.getStringValue(albumPrefix + "/ID").setValue(album.getId());
                                configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle());
                                configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription());
-                               configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent() == null ? null : album.getParent().getId());
+                               configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent().equals(sone.getRootAlbum()) ? null : album.getParent().getId());
                                configuration.getStringValue(albumPrefix + "/AlbumImage").setValue(album.getAlbumImage() == null ? null : album.getAlbumImage().getId());
                        }
                        configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null);
index f493fdc..9af6328 100644 (file)
@@ -239,7 +239,7 @@ public class SoneDownloader extends AbstractService {
                        return null;
                }
 
-               Sone sone = new Sone(originalSone.getId(), false).setIdentity(originalSone.getIdentity());
+               Sone sone = new Sone(originalSone.getId(), originalSone.isLocal()).setIdentity(originalSone.getIdentity());
 
                SimpleXML soneXml;
                try {
@@ -518,7 +518,9 @@ public class SoneDownloader extends AbstractService {
                        sone.setReplies(replies);
                        sone.setLikePostIds(likedPostIds);
                        sone.setLikeReplyIds(likedReplyIds);
-                       sone.setAlbums(topLevelAlbums);
+                       for (Album album : topLevelAlbums) {
+                               sone.getRootAlbum().addAlbum(album);
+                       }
                }
 
                return sone;
index 8efcc73..a1bb903 100644 (file)
@@ -17,6 +17,9 @@
 
 package net.pterodactylus.sone.core;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static net.pterodactylus.sone.data.Album.NOT_EMPTY;
+
 import java.io.InputStreamReader;
 import java.io.StringWriter;
 import java.nio.charset.Charset;
@@ -90,7 +93,7 @@ public class SoneInserter extends AbstractService {
        private final FreenetInterface freenetInterface;
 
        /** The Sone to insert. */
-       private final Sone sone;
+       private volatile Sone sone;
 
        /** Whether a modification has been detected. */
        private volatile boolean modified = false;
@@ -123,8 +126,21 @@ public class SoneInserter extends AbstractService {
        //
 
        /**
-        * Changes the insertion delay, i.e. the time the Sone inserter waits after
-        * it has noticed a Sone modification before it starts the insert.
+        * Sets the Sone to insert.
+        *
+        * @param sone
+        *              The Sone to insert
+        * @return This Sone inserter
+        */
+       public SoneInserter setSone(Sone sone) {
+               checkArgument((this.sone == null) || sone.equals(this.sone), "Sone to insert can not be set to a different Sone");
+               this.sone = sone;
+               return this;
+       }
+
+       /**
+        * Changes the insertion delay, i.e. the time the Sone inserter waits after it
+        * has noticed a Sone modification before it starts the insert.
         *
         * @param insertionDelay
         *            The insertion delay (in seconds)
@@ -175,12 +191,14 @@ public class SoneInserter extends AbstractService {
                long lastModificationTime = 0;
                String lastInsertedFingerprint = lastInsertFingerprint;
                String lastFingerprint = "";
+               Sone sone;
                while (!shouldStop()) {
                        try {
                                /* check every seconds. */
                                sleep(1000);
 
                                /* don’t insert locked Sones. */
+                               sone = this.sone;
                                if (core.isLocked(sone)) {
                                        /* trigger redetection when the Sone is unlocked. */
                                        synchronized (sone) {
@@ -291,7 +309,7 @@ public class SoneInserter extends AbstractService {
                        soneProperties.put("replies", Ordering.from(Reply.TIME_COMPARATOR).reverse().sortedCopy(sone.getReplies()));
                        soneProperties.put("likedPostIds", new HashSet<String>(sone.getLikedPostIds()));
                        soneProperties.put("likedReplyIds", new HashSet<String>(sone.getLikedReplyIds()));
-                       soneProperties.put("albums", FluentIterable.from(sone.getAlbums()).transformAndConcat(Album.FLATTENER).toList());
+                       soneProperties.put("albums", FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).filter(NOT_EMPTY).toList());
                }
 
                //
index bb7016d..e18c6d4 100644 (file)
@@ -53,7 +53,7 @@ public class UpdateChecker {
        private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
 
        /** The current latest known edition. */
-       private static final int LATEST_EDITION = 58;
+       private static final int LATEST_EDITION = 60;
 
        /** The event bus. */
        private final EventBus eventBus;
index d449835..dd3de58 100644 (file)
@@ -61,7 +61,7 @@ public class WebOfTrustUpdater extends AbstractService {
         * Creates a new trust updater.
         *
         * @param webOfTrustConnector
-        *            The web of trust connector
+        *              The web of trust connector
         */
        @Inject
        public WebOfTrustUpdater(WebOfTrustConnector webOfTrustConnector) {
@@ -74,19 +74,18 @@ public class WebOfTrustUpdater extends AbstractService {
        //
 
        /**
-        * Updates the trust relation between the truster and the trustee. This
-        * method will return immediately and perform a trust update in the
-        * background.
+        * Updates the trust relation between the truster and the trustee. This method
+        * will return immediately and perform a trust update in the background.
         *
         * @param truster
-        *            The identity giving the trust
+        *              The identity giving the trust
         * @param trustee
-        *            The identity receiving the trust
+        *              The identity receiving the trust
         * @param score
-        *            The new level of trust (from -100 to 100, may be {@code null}
-        *            to remove the trust completely)
+        *              The new level of trust (from -100 to 100, may be {@code null} to remove
+        *              the trust completely)
         * @param comment
-        *            The comment of the trust relation
+        *              The comment of the trust relation
         */
        public void setTrust(OwnIdentity truster, Identity trustee, Integer score, String comment) {
                SetTrustJob setTrustJob = new SetTrustJob(truster, trustee, score, comment);
@@ -105,22 +104,22 @@ public class WebOfTrustUpdater extends AbstractService {
         * Adds the given context to the given own identity.
         *
         * @param ownIdentity
-        *            The own identity to add the context to
+        *              The own identity to add the context to
         * @param context
-        *            The context to add
+        *              The context to add
         */
        public void addContext(OwnIdentity ownIdentity, String context) {
                addContextWait(ownIdentity, context, false);
        }
 
        /**
-        * Adds the given context to the given own identity, waiting for completion
-        * of the operation.
+        * Adds the given context to the given own identity, waiting for completion of
+        * the operation.
         *
         * @param ownIdentity
-        *            The own identity to add the context to
+        *              The own identity to add the context to
         * @param context
-        *            The context to add
+        *              The context to add
         * @return {@code true} if the context was added successfully, {@code false}
         *         otherwise
         */
@@ -129,19 +128,19 @@ public class WebOfTrustUpdater extends AbstractService {
        }
 
        /**
-        * Adds the given context to the given own identity, waiting for completion
-        * of the operation.
+        * Adds the given context to the given own identity, waiting for completion of
+        * the operation.
         *
         * @param ownIdentity
-        *            The own identity to add the context to
+        *              The own identity to add the context to
         * @param context
-        *            The context to add
+        *              The context to add
         * @param wait
-        *            {@code true} to wait for the end of the operation,
-        *            {@code false} to return immediately
-        * @return {@code true} if the context was added successfully, {@code false}
-        *         if the context was not added successfully, or if the job should
-        *         not wait for completion
+        *              {@code true} to wait for the end of the operation, {@code false} to return
+        *              immediately
+        * @return {@code true} if the context was added successfully, {@code false} if
+        *         the context was not added successfully, or if the job should not
+        *         wait for completion
         */
        private boolean addContextWait(OwnIdentity ownIdentity, String context, boolean wait) {
                AddContextJob addContextJob = new AddContextJob(ownIdentity, context);
@@ -169,9 +168,9 @@ public class WebOfTrustUpdater extends AbstractService {
         * Removes the given context from the given own identity.
         *
         * @param ownIdentity
-        *            The own identity to remove the context from
+        *              The own identity to remove the context from
         * @param context
-        *            The context to remove
+        *              The context to remove
         */
        public void removeContext(OwnIdentity ownIdentity, String context) {
                RemoveContextJob removeContextJob = new RemoveContextJob(ownIdentity, context);
@@ -189,11 +188,11 @@ public class WebOfTrustUpdater extends AbstractService {
         * Sets a property on the given own identity.
         *
         * @param ownIdentity
-        *            The own identity to set the property on
+        *              The own identity to set the property on
         * @param propertyName
-        *            The name of the property to set
+        *              The name of the property to set
         * @param propertyValue
-        *            The value of the property to set
+        *              The value of the property to set
         */
        public void setProperty(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
                SetPropertyJob setPropertyJob = new SetPropertyJob(ownIdentity, propertyName, propertyValue);
@@ -212,9 +211,9 @@ public class WebOfTrustUpdater extends AbstractService {
         * Removes a property from the given own identity.
         *
         * @param ownIdentity
-        *            The own identity to remove the property from
+        *              The own identity to remove the property from
         * @param propertyName
-        *            The name of the property to remove
+        *              The name of the property to remove
         */
        public void removeProperty(OwnIdentity ownIdentity, String propertyName) {
                setProperty(ownIdentity, propertyName, null);
@@ -224,9 +223,7 @@ public class WebOfTrustUpdater extends AbstractService {
        // SERVICE METHODS
        //
 
-       /**
-        * {@inheritDoc}
-        */
+       /** {@inheritDoc} */
        @Override
        protected void serviceRun() {
                while (!shouldStop()) {
@@ -246,9 +243,7 @@ public class WebOfTrustUpdater extends AbstractService {
                }
        }
 
-       /**
-        * {@inheritDoc}
-        */
+       /** {@inheritDoc} */
        @Override
        protected void serviceStop() {
                try {
@@ -281,7 +276,7 @@ public class WebOfTrustUpdater extends AbstractService {
 
                /**
                 * Performs the actual update operation.
-                * <p>
+                * <p/>
                 * The implementation of this class does nothing.
                 */
                public void run() {
@@ -289,8 +284,7 @@ public class WebOfTrustUpdater extends AbstractService {
                }
 
                /**
-                * Waits for completion of this job or stopping of the WebOfTrust
-                * updater.
+                * Waits for completion of this job or stopping of the WebOfTrust updater.
                 *
                 * @return {@code true} if this job finished successfully, {@code false}
                 *         otherwise
@@ -318,8 +312,7 @@ public class WebOfTrustUpdater extends AbstractService {
                 * Signals that this job has finished.
                 *
                 * @param success
-                *            {@code true} if this job finished successfully,
-                *            {@code false} otherwise
+                *              {@code true} if this job finished successfully, {@code false} otherwise
                 */
                protected void finish(boolean success) {
                        synchronized (syncObject) {
@@ -332,72 +325,17 @@ public class WebOfTrustUpdater extends AbstractService {
        }
 
        /**
-        * Base class for WebOfTrust trust update jobs.
+        * Update job that sets the trust relation between two identities.
         *
         * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
-       private class WebOfTrustTrustUpdateJob extends WebOfTrustUpdateJob {
+       private class SetTrustJob extends WebOfTrustUpdateJob {
 
                /** The identity giving the trust. */
-               protected final OwnIdentity truster;
+               private final OwnIdentity truster;
 
                /** The identity receiving the trust. */
-               protected final Identity trustee;
-
-               /**
-                * Creates a new trust update job.
-                *
-                * @param truster
-                *            The identity giving the trust
-                * @param trustee
-                *            The identity receiving the trust
-                */
-               @SuppressWarnings("synthetic-access")
-               public WebOfTrustTrustUpdateJob(OwnIdentity truster, Identity trustee) {
-                       this.truster = truster;
-                       this.trustee = trustee;
-               }
-
-               //
-               // OBJECT METHODS
-               //
-
-               /**
-                * {@inheritDoc}
-                */
-               @Override
-               public boolean equals(Object object) {
-                       if ((object == null) || !object.getClass().equals(getClass())) {
-                               return false;
-                       }
-                       WebOfTrustTrustUpdateJob updateJob = (WebOfTrustTrustUpdateJob) object;
-                       return ((truster == null) ? (updateJob.truster == null) : updateJob.truster.equals(truster)) && ((trustee == null) ? (updateJob.trustee == null) : updateJob.trustee.equals(trustee));
-               }
-
-               /**
-                * {@inheritDoc}
-                */
-               @Override
-               public int hashCode() {
-                       return getClass().hashCode() ^ ((truster == null) ? 0 : truster.hashCode()) ^ ((trustee == null) ? 0 : trustee.hashCode());
-               }
-
-               /**
-                * {@inheritDoc}
-                */
-               @Override
-               public String toString() {
-                       return String.format("%s[truster=%s,trustee=%s]", getClass().getSimpleName(), (truster == null) ? null : truster.getId(), (trustee == null) ? null : trustee.getId());
-               }
-
-       }
-
-       /**
-        * Update job that sets the trust relation between two identities.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
-        */
-       private class SetTrustJob extends WebOfTrustTrustUpdateJob {
+               private final Identity trustee;
 
                /** The score of the relation. */
                private final Integer score;
@@ -409,24 +347,23 @@ public class WebOfTrustUpdater extends AbstractService {
                 * Creates a new set trust job.
                 *
                 * @param truster
-                *            The identity giving the trust
+                *              The identity giving the trust
                 * @param trustee
-                *            The identity receiving the trust
+                *              The identity receiving the trust
                 * @param score
-                *            The score of the trust (from -100 to 100, may be
-                *            {@code null} to remote the trust relation completely)
+                *              The score of the trust (from -100 to 100, may be {@code null} to remote
+                *              the trust relation completely)
                 * @param comment
-                *            The comment of the trust relation
+                *              The comment of the trust relation
                 */
                public SetTrustJob(OwnIdentity truster, Identity trustee, Integer score, String comment) {
-                       super(truster, trustee);
+                       this.truster = truster;
+                       this.trustee = trustee;
                        this.score = score;
                        this.comment = comment;
                }
 
-               /**
-                * {@inheritDoc}
-                */
+               /** {@inheritDoc} */
                @Override
                @SuppressWarnings("synthetic-access")
                public void run() {
@@ -449,6 +386,32 @@ public class WebOfTrustUpdater extends AbstractService {
                        }
                }
 
+               //
+               // OBJECT METHODS
+               //
+
+               /** {@inheritDoc} */
+               @Override
+               public boolean equals(Object object) {
+                       if ((object == null) || !object.getClass().equals(getClass())) {
+                               return false;
+                       }
+                       SetTrustJob updateJob = (SetTrustJob) object;
+                       return ((truster == null) ? (updateJob.truster == null) : updateJob.truster.equals(truster)) && ((trustee == null) ? (updateJob.trustee == null) : updateJob.trustee.equals(trustee));
+               }
+
+               /** {@inheritDoc} */
+               @Override
+               public int hashCode() {
+                       return getClass().hashCode() ^ ((truster == null) ? 0 : truster.hashCode()) ^ ((trustee == null) ? 0 : trustee.hashCode());
+               }
+
+               /** {@inheritDoc} */
+               @Override
+               public String toString() {
+                       return String.format("%s[truster=%s,trustee=%s]", getClass().getSimpleName(), (truster == null) ? null : truster.getId(), (trustee == null) ? null : trustee.getId());
+               }
+
        }
 
        /**
@@ -468,9 +431,9 @@ public class WebOfTrustUpdater extends AbstractService {
                 * Creates a new context update job.
                 *
                 * @param ownIdentity
-                *            The own identity to update
+                *              The own identity to update
                 * @param context
-                *            The context to update
+                *              The context to update
                 */
                @SuppressWarnings("synthetic-access")
                public WebOfTrustContextUpdateJob(OwnIdentity ownIdentity, String context) {
@@ -482,9 +445,7 @@ public class WebOfTrustUpdater extends AbstractService {
                // OBJECT METHODS
                //
 
-               /**
-                * {@inheritDoc}
-                */
+               /** {@inheritDoc} */
                @Override
                public boolean equals(Object object) {
                        if ((object == null) || !object.getClass().equals(getClass())) {
@@ -494,17 +455,13 @@ public class WebOfTrustUpdater extends AbstractService {
                        return updateJob.ownIdentity.equals(ownIdentity) && updateJob.context.equals(context);
                }
 
-               /**
-                * {@inheritDoc}
-                */
+               /** {@inheritDoc} */
                @Override
                public int hashCode() {
                        return getClass().hashCode() ^ ownIdentity.hashCode() ^ context.hashCode();
                }
 
-               /**
-                * {@inheritDoc}
-                */
+               /** {@inheritDoc} */
                @Override
                public String toString() {
                        return String.format("%s[ownIdentity=%s,context=%s]", getClass().getSimpleName(), ownIdentity, context);
@@ -523,17 +480,15 @@ public class WebOfTrustUpdater extends AbstractService {
                 * Creates a new add-context job.
                 *
                 * @param ownIdentity
-                *            The own identity whose contexts to manage
+                *              The own identity whose contexts to manage
                 * @param context
-                *            The context to add
+                *              The context to add
                 */
                public AddContextJob(OwnIdentity ownIdentity, String context) {
                        super(ownIdentity, context);
                }
 
-               /**
-                * {@inheritDoc}
-                */
+               /** {@inheritDoc} */
                @Override
                @SuppressWarnings("synthetic-access")
                public void run() {
@@ -560,17 +515,15 @@ public class WebOfTrustUpdater extends AbstractService {
                 * Creates a new remove-context job.
                 *
                 * @param ownIdentity
-                *            The own identity whose contexts to manage
+                *              The own identity whose contexts to manage
                 * @param context
-                *            The context to remove
+                *              The context to remove
                 */
                public RemoveContextJob(OwnIdentity ownIdentity, String context) {
                        super(ownIdentity, context);
                }
 
-               /**
-                * {@inheritDoc}
-                */
+               /** {@inheritDoc} */
                @Override
                @SuppressWarnings("synthetic-access")
                public void run() {
@@ -587,72 +540,17 @@ public class WebOfTrustUpdater extends AbstractService {
        }
 
        /**
-        * Base class for update jobs that deal with properties.
+        * WebOfTrust update job that sets a property on an {@link OwnIdentity}.
         *
         * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
-       private class WebOfTrustPropertyUpdateJob extends WebOfTrustUpdateJob {
+       private class SetPropertyJob extends WebOfTrustUpdateJob {
 
                /** The own identity to update properties on. */
-               protected final OwnIdentity ownIdentity;
+               private final OwnIdentity ownIdentity;
 
                /** The name of the property to update. */
-               protected final String propertyName;
-
-               /**
-                * Creates a new property update job.
-                *
-                * @param ownIdentity
-                *            The own identity to update the property on
-                * @param propertyName
-                *            The name of the property to update
-                */
-               @SuppressWarnings("synthetic-access")
-               public WebOfTrustPropertyUpdateJob(OwnIdentity ownIdentity, String propertyName) {
-                       this.ownIdentity = ownIdentity;
-                       this.propertyName = propertyName;
-               }
-
-               //
-               // OBJECT METHODS
-               //
-
-               /**
-                * {@inheritDoc}
-                */
-               @Override
-               public boolean equals(Object object) {
-                       if ((object == null) || !object.getClass().equals(getClass())) {
-                               return false;
-                       }
-                       WebOfTrustPropertyUpdateJob updateJob = (WebOfTrustPropertyUpdateJob) object;
-                       return updateJob.ownIdentity.equals(ownIdentity) && updateJob.propertyName.equals(propertyName);
-               }
-
-               /**
-                * {@inheritDoc}
-                */
-               @Override
-               public int hashCode() {
-                       return getClass().hashCode() ^ ownIdentity.hashCode() ^ propertyName.hashCode();
-               }
-
-               /**
-                * {@inheritDoc}
-                */
-               @Override
-               public String toString() {
-                       return String.format("%s[ownIdentity=%s,propertyName=%s]", getClass().getSimpleName(), ownIdentity, propertyName);
-               }
-
-       }
-
-       /**
-        * WebOfTrust update job that sets a property on an {@link OwnIdentity}.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
-        */
-       private class SetPropertyJob extends WebOfTrustPropertyUpdateJob {
+               private final String propertyName;
 
                /** The value of the property to set. */
                private final String propertyValue;
@@ -661,20 +559,19 @@ public class WebOfTrustUpdater extends AbstractService {
                 * Creates a new set-property job.
                 *
                 * @param ownIdentity
-                *            The own identity to set the property on
+                *              The own identity to set the property on
                 * @param propertyName
-                *            The name of the property to set
+                *              The name of the property to set
                 * @param propertyValue
-                *            The value of the property to set
+                *              The value of the property to set
                 */
                public SetPropertyJob(OwnIdentity ownIdentity, String propertyName, String propertyValue) {
-                       super(ownIdentity, propertyName);
+                       this.ownIdentity = ownIdentity;
+                       this.propertyName = propertyName;
                        this.propertyValue = propertyValue;
                }
 
-               /**
-                * {@inheritDoc}
-                */
+               /** {@inheritDoc} */
                @Override
                @SuppressWarnings("synthetic-access")
                public void run() {
@@ -693,6 +590,32 @@ public class WebOfTrustUpdater extends AbstractService {
                        }
                }
 
+               //
+               // OBJECT METHODS
+               //
+
+               /** {@inheritDoc} */
+               @Override
+               public boolean equals(Object object) {
+                       if ((object == null) || !object.getClass().equals(getClass())) {
+                               return false;
+                       }
+                       SetPropertyJob updateJob = (SetPropertyJob) object;
+                       return updateJob.ownIdentity.equals(ownIdentity) && updateJob.propertyName.equals(propertyName);
+               }
+
+               /** {@inheritDoc} */
+               @Override
+               public int hashCode() {
+                       return getClass().hashCode() ^ ownIdentity.hashCode() ^ propertyName.hashCode();
+               }
+
+               /** {@inheritDoc} */
+               @Override
+               public String toString() {
+                       return String.format("%s[ownIdentity=%s,propertyName=%s]", getClass().getSimpleName(), ownIdentity, propertyName);
+               }
+
        }
 
 }
index cb7b4f3..70478f3 100644 (file)
@@ -21,13 +21,16 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import javax.annotation.Nonnull;
 
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
@@ -44,7 +47,7 @@ import com.google.common.hash.Hashing;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Album implements Fingerprintable {
+public class Album implements Identified, Fingerprintable {
 
        /** Compares two {@link Album}s by {@link #getTitle()}. */
        public static final Comparator<Album> TITLE_COMPARATOR = new Comparator<Album>() {
@@ -59,7 +62,11 @@ public class Album implements Fingerprintable {
        public static final Function<Album, List<Album>> FLATTENER = new Function<Album, List<Album>>() {
 
                @Override
+               @Nonnull
                public List<Album> apply(Album album) {
+                       if (album == null) {
+                               return emptyList();
+                       }
                        List<Album> albums = new ArrayList<Album>();
                        albums.add(album);
                        for (Album subAlbum : album.getAlbums()) {
@@ -69,6 +76,16 @@ public class Album implements Fingerprintable {
                }
        };
 
+       /** Function that transforms an album into the images it contains. */
+       public static final Function<Album, List<Image>> IMAGES = new Function<Album, List<Image>>() {
+
+               @Override
+               @Nonnull
+               public List<Image> apply(Album album) {
+                       return (album != null) ? album.getImages() : Collections.<Image>emptyList();
+               }
+       };
+
        /**
         * Filter that removes all albums that do not have any images in any album
         * below it.
@@ -77,11 +94,19 @@ public class Album implements Fingerprintable {
 
                @Override
                public boolean apply(Album album) {
+                       /* so, we flatten all albums below the given one and check whether at least one album… */
                        return FluentIterable.from(asList(album)).transformAndConcat(FLATTENER).anyMatch(new Predicate<Album>() {
 
                                @Override
                                public boolean apply(Album album) {
-                                       return !album.getImages().isEmpty();
+                                       /* …contains any inserted images. */
+                                       return !album.getImages().isEmpty() && FluentIterable.from(album.getImages()).allMatch(new Predicate<Image>() {
+
+                                               @Override
+                                               public boolean apply(Image input) {
+                                                       return input.isInserted();
+                                               }
+                                       });
                                }
                        });
                }
@@ -186,7 +211,6 @@ public class Album implements Fingerprintable {
        public void addAlbum(Album album) {
                checkNotNull(album, "album must not be null");
                checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
-               checkState((this.parent == null) || (this.parent.equals(album.parent)), "album must not already be set to some other Sone");
                album.setParent(this);
                if (!albums.contains(album)) {
                        albums.add(album);
@@ -392,6 +416,16 @@ public class Album implements Fingerprintable {
        }
 
        /**
+        * Returns whether this album is an identitiy’s root album.
+        *
+        * @return {@code true} if this album is an identity’s root album, {@code
+        *         false} otherwise
+        */
+       public boolean isRoot() {
+               return parent == null;
+       }
+
+       /**
         * Returns the parent album of this album.
         *
         * @return The parent album of this album, or {@code null} if this album
diff --git a/src/main/java/net/pterodactylus/sone/data/Identified.java b/src/main/java/net/pterodactylus/sone/data/Identified.java
new file mode 100644 (file)
index 0000000..4892479
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Sone - Identified.java - Copyright © 2013 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.data;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+
+/**
+ * Interface for all objects that expose an ID.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface Identified {
+
+       /** Function to extract the ID from an optional. */
+       public static final Function<Optional<? extends Identified>, Optional<String>> GET_ID = new Function<Optional<? extends Identified>, Optional<String>>() {
+
+               @Override
+               @Nonnull
+               public Optional<String> apply(Optional<? extends Identified> identified) {
+                       return (identified == null) ? Optional.<String>absent() : (identified.isPresent() ? Optional.of(identified.get().getId()) : Optional.<String>absent());
+               }
+       };
+
+       /**
+        * Returns the ID of this element.
+        *
+        * @return The ID of this element
+        */
+       public String getId();
+
+}
index 952d540..d8a8ab1 100644 (file)
@@ -31,7 +31,7 @@ import com.google.common.hash.Hashing;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Image implements Fingerprintable {
+public class Image implements Identified, Fingerprintable {
 
        /** The ID of the image. */
        private final String id;
index 6523e9b..759a8ba 100644 (file)
@@ -28,7 +28,7 @@ import com.google.common.base.Predicate;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public interface Post {
+public interface Post extends Identified {
 
        /** Comparator for posts, sorts descending by time. */
        public static final Comparator<Post> TIME_COMPARATOR = new Comparator<Post>() {
@@ -45,7 +45,7 @@ public interface Post {
 
                @Override
                public boolean apply(Post post) {
-                       return post.getTime() <= System.currentTimeMillis();
+                       return (post == null) ? false : post.getTime() <= System.currentTimeMillis();
                }
 
        };
@@ -77,10 +77,7 @@ public interface Post {
        public Optional<String> getRecipientId();
 
        /**
-        * Returns the recipient of this post, if any. As this method can return
-        * {@link Optional#absent()} if the post has a recipient which has not yet
-        * been loaded, it is recommended to use {@link #hasRecipient()} to check
-        * for the presence of a recipient.
+        * Returns the recipient of this post, if any.
         *
         * @return The recipient of this post, or {@link Optional#absent()} if there
         *         is no recipient
index 1e870b9..4a1fbad 100644 (file)
@@ -36,7 +36,7 @@ public interface PostReply extends Reply<PostReply> {
 
                @Override
                public boolean apply(PostReply postReply) {
-                       return postReply.getPost().isPresent();
+                       return (postReply == null) ? false : postReply.getPost().isPresent();
                }
        };
 
index a686023..c229d04 100644 (file)
@@ -28,7 +28,7 @@ import com.google.common.base.Predicate;
  *            The type of the reply
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public interface Reply<T extends Reply<T>> {
+public interface Reply<T extends Reply<T>> extends Identified {
 
        /** Comparator that sorts replies ascending by time. */
        public static final Comparator<? super Reply<?>> TIME_COMPARATOR = new Comparator<Reply<?>>() {
@@ -51,7 +51,7 @@ public interface Reply<T extends Reply<T>> {
                 */
                @Override
                public boolean apply(Reply<?> reply) {
-                       return reply.getTime() <= System.currentTimeMillis();
+                       return (reply == null) ? false : reply.getTime() <= System.currentTimeMillis();
                }
 
        };
index 4f4fd98..31153ff 100644 (file)
 
 package net.pterodactylus.sone.data;
 
-import static com.google.common.base.Preconditions.*;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.FluentIterable.from;
+import static java.util.Arrays.asList;
+import static net.pterodactylus.sone.data.Album.FLATTENER;
+import static net.pterodactylus.sone.data.Album.IMAGES;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -25,7 +29,6 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -39,9 +42,9 @@ import net.pterodactylus.util.logging.Logging;
 import freenet.keys.FreenetURI;
 
 import com.google.common.base.Predicate;
-import com.google.common.collect.FluentIterable;
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
+import com.google.common.primitives.Ints;
 
 /**
  * A Sone defines everything about a user: her profile, her status updates, her
@@ -51,7 +54,7 @@ import com.google.common.hash.Hashing;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Sone implements Fingerprintable, Comparable<Sone> {
+public class Sone implements Identified, Fingerprintable, Comparable<Sone> {
 
        /**
         * Enumeration for the possible states of a {@link Sone}.
@@ -140,7 +143,10 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                 */
                @Override
                public int compare(Sone leftSone, Sone rightSone) {
-                       return rightSone.getAllImages().size() - leftSone.getAllImages().size();
+                       int rightSoneImageCount = from(asList(rightSone.getRootAlbum())).transformAndConcat(FLATTENER).transformAndConcat(IMAGES).size();
+                       int leftSoneImageCount = from(asList(leftSone.getRootAlbum())).transformAndConcat(FLATTENER).transformAndConcat(IMAGES).size();
+                       /* sort descending. */
+                       return Ints.compare(rightSoneImageCount, leftSoneImageCount);
                }
        };
 
@@ -149,7 +155,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
 
                @Override
                public boolean apply(Sone sone) {
-                       return sone.getTime() != 0;
+                       return (sone == null) ? false : sone.getTime() != 0;
                }
        };
 
@@ -158,7 +164,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
 
                @Override
                public boolean apply(Sone sone) {
-                       return sone.getIdentity() instanceof OwnIdentity;
+                       return (sone == null) ? false : sone.getIdentity() instanceof OwnIdentity;
                }
 
        };
@@ -168,7 +174,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
 
                @Override
                public boolean apply(Sone sone) {
-                       return !sone.getAlbums().isEmpty();
+                       return (sone == null) ? false : !sone.getRootAlbum().getAlbums().isEmpty();
                }
        };
 
@@ -224,8 +230,8 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        /** The IDs of all liked replies. */
        private final Set<String> likedReplyIds = new CopyOnWriteArraySet<String>();
 
-       /** The albums of this Sone. */
-       private final List<Album> albums = new CopyOnWriteArrayList<Album>();
+       /** The root album containing all albums. */
+       private final Album rootAlbum = new Album().setSone(this);
 
        /** Sone-specific options. */
        private Options options = new Options();
@@ -757,110 +763,12 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        /**
-        * Returns the albums of this Sone.
+        * Returns the root album that contains all visible albums of this Sone.
         *
-        * @return The albums of this Sone
+        * @return The root album of this Sone
         */
-       public List<Album> getAlbums() {
-               return Collections.unmodifiableList(albums);
-       }
-
-       /**
-        * Returns all images of a Sone. Images of a album are inserted into this list
-        * before images of all child albums.
-        *
-        * @return The list of all images
-        */
-       public List<Image> getAllImages() {
-               List<Image> allImages = new ArrayList<Image>();
-               for (Album album : FluentIterable.from(getAlbums()).transformAndConcat(Album.FLATTENER).toList()) {
-                       allImages.addAll(album.getImages());
-               }
-               return allImages;
-       }
-
-       /**
-        * Adds an album to this Sone.
-        *
-        * @param album
-        *              The album to add
-        */
-       public void addAlbum(Album album) {
-               checkNotNull(album, "album must not be null");
-               checkArgument(album.getSone().equals(this), "album must belong to this Sone");
-               if (!albums.contains(album)) {
-                       albums.add(album);
-               }
-       }
-
-       /**
-        * Sets the albums of this Sone.
-        *
-        * @param albums
-        *              The albums of this Sone
-        */
-       public void setAlbums(Collection<? extends Album> albums) {
-               checkNotNull(albums, "albums must not be null");
-               this.albums.clear();
-               for (Album album : albums) {
-                       addAlbum(album);
-               }
-       }
-
-       /**
-        * Removes an album from this Sone.
-        *
-        * @param album
-        *              The album to remove
-        */
-       public void removeAlbum(Album album) {
-               checkNotNull(album, "album must not be null");
-               checkArgument(album.getSone().equals(this), "album must belong to this Sone");
-               albums.remove(album);
-       }
-
-       /**
-        * Moves the given album up in this album’s albums. If the album is already the
-        * first album, nothing happens.
-        *
-        * @param album
-        *              The album to move up
-        * @return The album that the given album swapped the place with, or
-        *         <code>null</code> if the album did not change its place
-        */
-       public Album moveAlbumUp(Album album) {
-               checkNotNull(album, "album must not be null");
-               checkArgument(album.getSone().equals(this), "album must belong to this Sone");
-               checkArgument(album.getParent() == null, "album must not have a parent");
-               int oldIndex = albums.indexOf(album);
-               if (oldIndex <= 0) {
-                       return null;
-               }
-               albums.remove(oldIndex);
-               albums.add(oldIndex - 1, album);
-               return albums.get(oldIndex);
-       }
-
-       /**
-        * Moves the given album down in this album’s albums. If the album is already
-        * the last album, nothing happens.
-        *
-        * @param album
-        *              The album to move down
-        * @return The album that the given album swapped the place with, or
-        *         <code>null</code> if the album did not change its place
-        */
-       public Album moveAlbumDown(Album album) {
-               checkNotNull(album, "album must not be null");
-               checkArgument(album.getSone().equals(this), "album must belong to this Sone");
-               checkArgument(album.getParent() == null, "album must not have a parent");
-               int oldIndex = albums.indexOf(album);
-               if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
-                       return null;
-               }
-               albums.remove(oldIndex);
-               albums.add(oldIndex + 1, album);
-               return albums.get(oldIndex);
+       public Album getRootAlbum() {
+               return rootAlbum;
        }
 
        /**
@@ -924,7 +832,10 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                hash.putString(")");
 
                hash.putString("Albums(");
-               for (Album album : albums) {
+               for (Album album : rootAlbum.getAlbums()) {
+                       if (!Album.NOT_EMPTY.apply(album)) {
+                               continue;
+                       }
                        hash.putString(album.getFingerprint());
                }
                hash.putString(")");
@@ -964,7 +875,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        /** {@inheritDoc} */
        @Override
        public String toString() {
-               return getClass().getName() + "[identity=" + identity + ",requestUri=" + requestUri + ",insertUri(" + String.valueOf(insertUri).length() + "),friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + ")]";
+               return getClass().getName() + "[identity=" + identity + ",requestUri=" + requestUri + ",insertUri(" + String.valueOf(insertUri).length() + "),friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + "),albums(" + getRootAlbum().getAlbums().size() + ")]";
        }
 
 }
index 36ac8cd..c8075c5 100644 (file)
@@ -65,4 +65,5 @@ public class L10nFilter implements Filter {
                }
                return new MessageFormat(webInterface.getL10n().getString(String.valueOf(data)), new Locale(webInterface.getL10n().getSelectedLanguage().shortCode)).format(parameterValues.toArray());
        }
+
 }
index 4f87b97..348cd8c 100644 (file)
@@ -70,4 +70,18 @@ public class DefaultOwnIdentity extends DefaultIdentity implements OwnIdentity {
                return insertUri;
        }
 
+       //
+       // OBJECT METHODS
+       //
+
+       @Override
+       public int hashCode() {
+               return super.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object object) {
+               return super.equals(object);
+       }
+
 }
index 6f2a908..e31bbf5 100644 (file)
@@ -104,7 +104,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 8, 6);
+       public static final Version VERSION = new Version(0, 8, 7);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
index 96891e5..13b4d65 100644 (file)
@@ -18,9 +18,7 @@
 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;
@@ -43,36 +41,66 @@ public class AlbumAccessor extends ReflectionAccessor {
        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>>();
+                       List<Link> backlinks = new ArrayList<Link>();
                        Album currentAlbum = album;
-                       while (currentAlbum != null) {
-                               backlinks.add(0, createLink("imageBrowser.html?album=" + currentAlbum.getId(), currentAlbum.getTitle()));
+                       while (!currentAlbum.equals(album.getSone().getRootAlbum())) {
+                               backlinks.add(0, new Link("imageBrowser.html?album=" + currentAlbum.getId(), currentAlbum.getTitle()));
                                currentAlbum = currentAlbum.getParent();
                        }
-                       backlinks.add(0, createLink("imageBrowser.html?sone=" + album.getSone().getId(), SoneAccessor.getNiceName(album.getSone())));
+                       backlinks.add(0, new Link("imageBrowser.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.”
+        * Container for links.
         *
-        * @param target
-        *            The target to link to
-        * @param name
-        *            The name of the link
-        * @return The created map containing the mappings
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
-       private static 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;
+       private static class Link {
+
+               /** The target of the link. */
+               private final String target;
+
+               /** The name of the link. */
+               private final String name;
+
+               /**
+                * Creates a new link.
+                *
+                * @param target
+                *              The target of the link
+                * @param name
+                *              The name of the link
+                */
+               private Link(String target, String name) {
+                       this.target = target;
+                       this.name = name;
+               }
+
+               //
+               // ACCESSORS
+               //
+
+               /**
+                * Returns the target of the link.
+                *
+                * @return The target of the link
+                */
+               public String getTarget() {
+                       return target;
+               }
+
+               /**
+                * Returns the name of the link.
+                *
+                * @return The name of the link
+                */
+               public String getName() {
+                       return name;
+               }
+
        }
 
 }
index bcf73d8..3d5fda1 100644 (file)
@@ -21,6 +21,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 import net.pterodactylus.sone.data.Post;
@@ -69,14 +70,14 @@ public class ReplyGroupFilter implements Filter {
                        replies.add(reply);
                }
                Map<Post, Map<String, Set<?>>> result = new HashMap<Post, Map<String, Set<?>>>();
-               for (Post post : postSones.keySet()) {
-                       if (result.containsKey(post)) {
+               for (Entry<Post, Set<Sone>> postEntry : postSones.entrySet()) {
+                       if (result.containsKey(postEntry.getKey())) {
                                continue;
                        }
                        Map<String, Set<?>> postResult = new HashMap<String, Set<?>>();
-                       postResult.put("sones", postSones.get(post));
-                       postResult.put("replies", postReplies.get(post));
-                       result.put(post, postResult);
+                       postResult.put("sones", postEntry.getValue());
+                       postResult.put("replies", postReplies.get(postEntry.getKey()));
+                       result.put(postEntry.getKey(), postResult);
                }
                return result;
        }
index c54fc0d..ab872d9 100644 (file)
 
 package net.pterodactylus.sone.template;
 
+import static com.google.common.collect.FluentIterable.from;
+import static java.util.Arrays.asList;
+import static net.pterodactylus.sone.data.Album.FLATTENER;
+import static net.pterodactylus.sone.data.Album.IMAGES;
+
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -111,6 +116,8 @@ public class SoneAccessor extends ReflectionAccessor {
                                return new Trust(null, null, null);
                        }
                        return trust;
+               } else if (member.equals("allImages")) {
+                       return from(asList(sone.getRootAlbum())).transformAndConcat(FLATTENER).transformAndConcat(IMAGES);
                }
                return super.get(templateContext, object, member);
        }
index 913e058..6efc5ed 100644 (file)
@@ -48,12 +48,6 @@ public class SubstringFilter implements Filter {
                }
                String dataString = String.valueOf(data);
                int dataLength = dataString.length();
-               if (lengthString == null) {
-                       if (start < 0) {
-                               return dataString.substring(dataLength + start);
-                       }
-                       return dataString.substring(start);
-               }
                int length = Integer.MAX_VALUE;
                try {
                        length = Integer.parseInt(lengthString);
index 95ca0da..9e9cd9e 100644 (file)
@@ -64,6 +64,9 @@ public class CreateAlbumPage extends SoneTemplatePage {
                        Sone currentSone = getCurrentSone(request.getToadletContext());
                        String parentId = request.getHttpRequest().getPartAsStringFailsafe("parent", 36);
                        Album parent = webInterface.getCore().getAlbum(parentId, false);
+                       if (parentId.equals("")) {
+                               parent = currentSone.getRootAlbum();
+                       }
                        Album album = webInterface.getCore().createAlbum(currentSone, parent);
                        album.setTitle(name).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description));
                        webInterface.getCore().touchConfiguration();
index aea76c4..fe8c32e 100644 (file)
@@ -97,6 +97,9 @@ public class CreateSonePage extends SoneTemplatePage {
        @Override
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
+               List<Sone> localSones = new ArrayList<Sone>(webInterface.getCore().getLocalSones());
+               Collections.sort(localSones, Sone.NICE_NAME_COMPARATOR);
+               templateContext.set("sones", localSones);
                List<OwnIdentity> ownIdentitiesWithoutSone = getOwnIdentitiesWithoutSone(webInterface.getCore());
                templateContext.set("identitiesWithoutSone", ownIdentitiesWithoutSone);
                if (request.getMethod() == Method.POST) {
index 3dfb8b4..391e9ff 100644 (file)
@@ -62,7 +62,7 @@ public class DeleteAlbumPage extends SoneTemplatePage {
                        }
                        Album parentAlbum = album.getParent();
                        webInterface.getCore().deleteAlbum(album);
-                       if (parentAlbum == null) {
+                       if (parentAlbum.equals(album.getSone().getRootAlbum())) {
                                throw new RedirectException("imageBrowser.html?sone=" + album.getSone().getId());
                        }
                        throw new RedirectException("imageBrowser.html?album=" + parentAlbum.getId());
index 8ef03e3..3db14d5 100644 (file)
@@ -51,7 +51,6 @@ public class EditAlbumPage extends SoneTemplatePage {
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.POST) {
-                       Sone currentSone = getCurrentSone(request.getToadletContext());
                        String albumId = request.getHttpRequest().getPartAsStringFailsafe("album", 36);
                        Album album = webInterface.getCore().getAlbum(albumId, false);
                        if (album == null) {
@@ -61,20 +60,10 @@ public class EditAlbumPage extends SoneTemplatePage {
                                throw new RedirectException("noPermission.html");
                        }
                        if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveLeft", 4))) {
-                               if (album.getParent() == null) {
-                                       currentSone.moveAlbumUp(album);
-                                       webInterface.getCore().touchConfiguration();
-                                       throw new RedirectException("imageBrowser.html?sone=" + currentSone.getId());
-                               }
                                album.getParent().moveAlbumUp(album);
                                webInterface.getCore().touchConfiguration();
                                throw new RedirectException("imageBrowser.html?album=" + album.getParent().getId());
                        } else if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveRight", 4))) {
-                               if (album.getParent() == null) {
-                                       currentSone.moveAlbumDown(album);
-                                       webInterface.getCore().touchConfiguration();
-                                       throw new RedirectException("imageBrowser.html?sone=" + currentSone.getId());
-                               }
                                album.getParent().moveAlbumDown(album);
                                webInterface.getCore().touchConfiguration();
                                throw new RedirectException("imageBrowser.html?album=" + album.getParent().getId());
index 9dec1dc..766f018 100644 (file)
@@ -17,6 +17,7 @@
 
 package net.pterodactylus.sone.web;
 
+import static com.google.common.collect.FluentIterable.from;
 import static net.pterodactylus.sone.data.Album.FLATTENER;
 import static net.pterodactylus.sone.data.Album.NOT_EMPTY;
 import static net.pterodactylus.sone.data.Album.TITLE_COMPARATOR;
@@ -27,7 +28,6 @@ import java.util.Collections;
 import java.util.List;
 
 import com.google.common.base.Optional;
-import com.google.common.collect.FluentIterable;
 
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Image;
@@ -94,7 +94,7 @@ public class ImageBrowserPage extends SoneTemplatePage {
                        templateContext.set("galleryRequested", true);
                        List<Album> albums = new ArrayList<Album>();
                        for (Sone sone : webInterface.getCore().getSones()) {
-                               albums.addAll(FluentIterable.from(sone.getAlbums()).transformAndConcat(FLATTENER).filter(NOT_EMPTY).toList());
+                               albums.addAll(from(sone.getRootAlbum().getAlbums()).transformAndConcat(FLATTENER).filter(NOT_EMPTY).toList());
                        }
                        Collections.sort(albums, TITLE_COMPARATOR);
                        Pagination<Album> albumPagination = new Pagination<Album>(albums, 12).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
index 6c9e15c..e9241a1 100644 (file)
@@ -581,7 +581,7 @@ public class SearchPage extends SoneTemplatePage {
 
                        @Override
                        public boolean apply(Hit<?> hit) {
-                               return hit.getScore() > 0;
+                               return (hit == null) ? false : hit.getScore() > 0;
                        }
 
                };
index 1005381..acee365 100644 (file)
@@ -19,7 +19,6 @@ package net.pterodactylus.sone.web.ajax;
 
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets the user bookmark a post.
@@ -46,7 +45,7 @@ public class BookmarkAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String id = request.getHttpRequest().getParam("post", null);
                if ((id == null) || (id.length() == 0)) {
                        return createErrorJsonObject("invalid-post-id");
index 4c278c6..423d2df 100644 (file)
@@ -22,7 +22,6 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.text.TextFilter;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 import com.google.common.base.Optional;
 
@@ -47,7 +46,7 @@ public class CreatePostAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                Sone sone = getCurrentSone(request.getToadletContext());
                if (sone == null) {
                        return createErrorJsonObject("auth-required");
index 8a35185..ea2a2c2 100644 (file)
@@ -25,7 +25,6 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.text.TextFilter;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * This AJAX page create a reply.
@@ -52,7 +51,7 @@ public class CreateReplyAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String postId = request.getHttpRequest().getParam("post");
                String text = request.getHttpRequest().getParam("text").trim();
                String senderId = request.getHttpRequest().getParam("sender");
index 71b0016..acf5f03 100644 (file)
@@ -22,7 +22,6 @@ import com.google.common.base.Optional;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * This AJAX page deletes a post.
@@ -49,7 +48,7 @@ public class DeletePostAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String postId = request.getHttpRequest().getParam("post");
                Optional<Post> post = webInterface.getCore().getPost(postId);
                if (!post.isPresent()) {
index 7874f83..cb33fb9 100644 (file)
 
 package net.pterodactylus.sone.web.ajax;
 
+import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance;
+
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
 
 /**
  * AJAX page that lets the user delete a profile field.
@@ -45,7 +49,7 @@ public class DeleteProfileFieldAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String fieldId = request.getHttpRequest().getParam("field");
                Sone currentSone = getCurrentSone(request.getToadletContext());
                Profile profile = currentSone.getProfile();
@@ -56,7 +60,7 @@ public class DeleteProfileFieldAjaxPage extends JsonPage {
                profile.removeField(field);
                currentSone.setProfile(profile);
                webInterface.getCore().touchConfiguration();
-               return createSuccessJsonObject().put("field", new JsonObject().put("id", field.getId()));
+               return createSuccessJsonObject().put("field", new ObjectNode(instance).put("id", new TextNode(field.getId())));
        }
 
 }
index 2aa217d..3ea9f7f 100644 (file)
@@ -22,7 +22,6 @@ import com.google.common.base.Optional;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * This AJAX page deletes a reply.
@@ -49,7 +48,7 @@ public class DeleteReplyAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String replyId = request.getHttpRequest().getParam("reply");
                Optional<PostReply> reply = webInterface.getCore().getPostReply(replyId);
                if (!reply.isPresent()) {
index 595696f..aed9cad 100644 (file)
@@ -19,7 +19,6 @@ package net.pterodactylus.sone.web.ajax;
 
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.notify.Notification;
 
 /**
@@ -43,7 +42,7 @@ public class DismissNotificationAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String notificationId = request.getHttpRequest().getParam("notification");
                Notification notification = webInterface.getNotifications().getNotification(notificationId);
                if (notification == null) {
index 843b088..4206e8f 100644 (file)
@@ -23,7 +23,6 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets the user distrust a Sone.
@@ -47,7 +46,7 @@ public class DistrustAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                Sone currentSone = getCurrentSone(request.getToadletContext(), false);
                if (currentSone == null) {
                        return createErrorJsonObject("auth-required");
index 8d436ea..5d587db 100644 (file)
@@ -21,7 +21,6 @@ import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.text.TextFilter;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * Page that stores a user’s album modifications.
@@ -48,7 +47,7 @@ public class EditAlbumAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String albumId = request.getHttpRequest().getParam("album");
                Album album = webInterface.getCore().getAlbum(albumId, false);
                if (album == null) {
@@ -58,12 +57,12 @@ public class EditAlbumAjaxPage extends JsonPage {
                        return createErrorJsonObject("not-authorized");
                }
                if ("true".equals(request.getHttpRequest().getParam("moveLeft"))) {
-                       Album swappedAlbum = (album.getParent() != null) ? album.getParent().moveAlbumUp(album) : album.getSone().moveAlbumUp(album);
+                       Album swappedAlbum = album.getParent().moveAlbumUp(album);
                        webInterface.getCore().touchConfiguration();
                        return createSuccessJsonObject().put("sourceAlbumId", album.getId()).put("destinationAlbumId", swappedAlbum.getId());
                }
                if ("true".equals(request.getHttpRequest().getParam("moveRight"))) {
-                       Album swappedAlbum = (album.getParent() != null) ? album.getParent().moveAlbumDown(album) : album.getSone().moveAlbumDown(album);
+                       Album swappedAlbum = album.getParent().moveAlbumDown(album);
                        webInterface.getCore().touchConfiguration();
                        return createSuccessJsonObject().put("sourceAlbumId", album.getId()).put("destinationAlbumId", swappedAlbum.getId());
                }
index 378b03b..08452ef 100644 (file)
@@ -22,7 +22,6 @@ import net.pterodactylus.sone.template.ParserFilter;
 import net.pterodactylus.sone.text.TextFilter;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.template.TemplateContext;
 
 import com.google.common.collect.ImmutableMap;
@@ -58,7 +57,7 @@ public class EditImageAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String imageId = request.getHttpRequest().getParam("image");
                Image image = webInterface.getCore().getImage(imageId, false);
                if (image == null) {
@@ -81,7 +80,7 @@ public class EditImageAjaxPage extends JsonPage {
                String description = request.getHttpRequest().getParam("description").trim();
                image.setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description));
                webInterface.getCore().touchConfiguration();
-               return createSuccessJsonObject().put("imageId", image.getId()).put("title", image.getTitle()).put("description", image.getDescription()).put("parsedDescription", (String) parserFilter.format(new TemplateContext(), image.getDescription(), ImmutableMap.<String, Object> builder().put("sone", image.getSone()).build()));
+               return createSuccessJsonObject().put("imageId", image.getId()).put("title", image.getTitle()).put("description", image.getDescription()).put("parsedDescription", (String) parserFilter.format(new TemplateContext(), image.getDescription(), ImmutableMap.<String, Object>builder().put("sone", image.getSone()).build()));
        }
 
 }
index 8a47ffe..b4596ad 100644 (file)
@@ -22,7 +22,6 @@ import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets the user rename a profile field.
@@ -49,7 +48,7 @@ public class EditProfileFieldAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String fieldId = request.getHttpRequest().getParam("field");
                Sone currentSone = getCurrentSone(request.getToadletContext());
                Profile profile = currentSone.getProfile();
index 071f8e9..64c402a 100644 (file)
@@ -22,7 +22,6 @@ import com.google.common.base.Optional;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets a Sone follow another Sone.
@@ -45,7 +44,7 @@ public class FollowSoneAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String soneId = request.getHttpRequest().getParam("sone");
                Optional<Sone> sone = webInterface.getCore().getSone(soneId);
                if (!sone.isPresent()) {
index 1a5286f..ccc51dc 100644 (file)
@@ -17,9 +17,9 @@
 
 package net.pterodactylus.sone.web.ajax;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance;
+import static net.pterodactylus.sone.data.Sone.NICE_NAME_COMPARATOR;
+
 import java.util.Set;
 
 import net.pterodactylus.sone.data.Post;
@@ -28,10 +28,12 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonArray;
-import net.pterodactylus.util.json.JsonObject;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
 
 /**
  * AJAX page that retrieves the number of “likes” a {@link Post} has.
@@ -58,7 +60,7 @@ public class GetLikesAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String type = request.getHttpRequest().getParam("type", null);
                String id = request.getHttpRequest().getParam(type, null);
                if ((id == null) || (id.length() == 0)) {
@@ -102,12 +104,10 @@ public class GetLikesAjaxPage extends JsonPage {
         *            The Sones to convert to an array
         * @return The Sones, sorted by name
         */
-       private static JsonArray getSones(Set<Sone> sones) {
-               JsonArray soneArray = new JsonArray();
-               List<Sone> sortedSones = new ArrayList<Sone>(sones);
-               Collections.sort(sortedSones, Sone.NICE_NAME_COMPARATOR);
-               for (Sone sone : sortedSones) {
-                       soneArray.add(new JsonObject().put("id", sone.getId()).put("name", SoneAccessor.getNiceName(sone)));
+       private static JsonNode getSones(Set<Sone> sones) {
+               ArrayNode soneArray = new ArrayNode(instance);
+               for (Sone sone : FluentIterable.from(sones).toSortedList(NICE_NAME_COMPARATOR)) {
+                       soneArray.add(new ObjectNode(instance).put("id", sone.getId()).put("name", SoneAccessor.getNiceName(sone)));
                }
                return soneArray;
        }
index 6573dda..e8107ef 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance;
+
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.Collection;
@@ -28,12 +30,14 @@ import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.sone.notify.ListNotificationFilters;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-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;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
 /**
  * AJAX handler to return all current notifications.
  *
@@ -75,12 +79,12 @@ public class GetNotificationsAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                Sone currentSone = getCurrentSone(request.getToadletContext(), false);
                Collection<Notification> notifications = webInterface.getNotifications().getNotifications();
                List<Notification> filteredNotifications = ListNotificationFilters.filterNotifications(notifications, currentSone);
                Collections.sort(filteredNotifications, Notification.CREATED_TIME_SORTER);
-               JsonArray jsonNotifications = new JsonArray();
+               ArrayNode jsonNotifications = new ArrayNode(instance);
                for (Notification notification : filteredNotifications) {
                        jsonNotifications.add(createJsonNotification(request, notification));
                }
@@ -100,8 +104,8 @@ public class GetNotificationsAjaxPage extends JsonPage {
         *            The notification to create a JSON object
         * @return The JSON object
         */
-       private JsonObject createJsonNotification(FreenetRequest request, Notification notification) {
-               JsonObject jsonNotification = new JsonObject();
+       private JsonNode createJsonNotification(FreenetRequest request, Notification notification) {
+               ObjectNode jsonNotification = new ObjectNode(instance);
                jsonNotification.put("id", notification.getId());
                StringWriter notificationWriter = new StringWriter();
                try {
@@ -140,8 +144,8 @@ public class GetNotificationsAjaxPage extends JsonPage {
         *            The current Sone (may be {@code null})
         * @return The current options
         */
-       private static JsonObject createJsonOptions(Sone currentSone) {
-               JsonObject options = new JsonObject();
+       private static JsonNode createJsonOptions(Sone currentSone) {
+               ObjectNode options = new ObjectNode(instance);
                if (currentSone != null) {
                        options.put("ShowNotification/NewSones", currentSone.getOptions().getBooleanOption("ShowNotification/NewSones").get());
                        options.put("ShowNotification/NewPosts", currentSone.getOptions().getBooleanOption("ShowNotification/NewPosts").get());
index 8eeedc9..96002b3 100644 (file)
@@ -18,6 +18,7 @@
 package net.pterodactylus.sone.web.ajax;
 
 import java.io.StringWriter;
+import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance;
 
 import com.google.common.base.Optional;
 
@@ -26,11 +27,13 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.io.Closer;
-import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.template.TemplateException;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
 /**
  * This AJAX handler retrieves information and rendered representation of a
  * {@link Post}.
@@ -59,7 +62,7 @@ public class GetPostAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String postId = request.getHttpRequest().getParam("post");
                Optional<Post> post = webInterface.getCore().getPost(postId);
                if (!post.isPresent()) {
@@ -92,8 +95,8 @@ public class GetPostAjaxPage extends JsonPage {
         *            The currently logged in Sone (to store in the template)
         * @return The JSON representation of the post
         */
-       private JsonObject createJsonPost(FreenetRequest request, Post post, Sone currentSone) {
-               JsonObject jsonPost = new JsonObject();
+       private JsonNode createJsonPost(FreenetRequest request, Post post, Sone currentSone) {
+               ObjectNode jsonPost = new ObjectNode(instance);
                jsonPost.put("id", post.getId());
                jsonPost.put("sone", post.getSone().getId());
                jsonPost.put("recipient", post.getRecipientId().orNull());
index b617608..251c784 100644 (file)
@@ -18,6 +18,7 @@
 package net.pterodactylus.sone.web.ajax;
 
 import java.io.StringWriter;
+import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance;
 
 import com.google.common.base.Optional;
 
@@ -26,11 +27,13 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.io.Closer;
-import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.template.TemplateException;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
 /**
  * This AJAX page returns the details of a reply.
  *
@@ -62,7 +65,7 @@ public class GetReplyAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String replyId = request.getHttpRequest().getParam("reply");
                Optional<PostReply> reply = webInterface.getCore().getPostReply(replyId);
                if (!reply.isPresent()) {
@@ -94,8 +97,8 @@ public class GetReplyAjaxPage extends JsonPage {
         *            The currently logged in Sone (to store in the template)
         * @return The JSON representation of the reply
         */
-       private JsonObject createJsonReply(FreenetRequest request, PostReply reply, Sone currentSone) {
-               JsonObject jsonReply = new JsonObject();
+       private JsonNode createJsonReply(FreenetRequest request, PostReply reply, Sone currentSone) {
+               ObjectNode jsonReply = new ObjectNode(instance);
                jsonReply.put("id", reply.getId());
                jsonReply.put("postId", reply.getPostId());
                jsonReply.put("soneId", reply.getSone().getId());
index 48b25d6..e43910c 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance;
+
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Collection;
@@ -33,10 +35,11 @@ import net.pterodactylus.sone.notify.ListNotificationFilters;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonArray;
-import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.notify.Notification;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 
@@ -65,7 +68,7 @@ public class GetStatusAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                final Sone currentSone = getCurrentSone(request.getToadletContext(), false);
                /* load Sones. always return the status of the current Sone. */
                Set<Sone> sones = new HashSet<Sone>(Collections.singleton(getCurrentSone(request.getToadletContext(), false)));
@@ -77,13 +80,12 @@ public class GetStatusAjaxPage extends JsonPage {
                                sones.add(webInterface.getCore().getSone(soneId).orNull());
                        }
                }
-               JsonArray jsonSones = new JsonArray();
+               ArrayNode jsonSones = new ArrayNode(instance);
                for (Sone sone : sones) {
                        if (sone == null) {
                                continue;
                        }
-                       JsonObject jsonSone = createJsonSone(sone);
-                       jsonSones.add(jsonSone);
+                       jsonSones.add(createJsonSone(sone));
                }
                /* load notifications. */
                List<Notification> notifications = ListNotificationFilters.filterNotifications(webInterface.getNotifications().getNotifications(), currentSone);
@@ -100,9 +102,9 @@ public class GetStatusAjaxPage extends JsonPage {
 
                        });
                }
-               JsonArray jsonPosts = new JsonArray();
+               ArrayNode jsonPosts = new ArrayNode(instance);
                for (Post post : newPosts) {
-                       JsonObject jsonPost = new JsonObject();
+                       ObjectNode jsonPost = new ObjectNode(instance);
                        jsonPost.put("id", post.getId());
                        jsonPost.put("sone", post.getSone().getId());
                        jsonPost.put("recipient", post.getRecipientId().orNull());
@@ -123,9 +125,9 @@ public class GetStatusAjaxPage extends JsonPage {
                }
                /* remove replies to unknown posts. */
                newReplies = Collections2.filter(newReplies, PostReply.HAS_POST_FILTER);
-               JsonArray jsonReplies = new JsonArray();
+               ArrayNode jsonReplies = new ArrayNode(instance);
                for (PostReply reply : newReplies) {
-                       JsonObject jsonReply = new JsonObject();
+                       ObjectNode jsonReply = new ObjectNode(instance);
                        jsonReply.put("id", reply.getId());
                        jsonReply.put("sone", reply.getSone().getId());
                        jsonReply.put("post", reply.getPostId());
@@ -162,8 +164,8 @@ public class GetStatusAjaxPage extends JsonPage {
         *            The Sone to convert to a JSON object
         * @return The JSON representation of the given Sone
         */
-       private JsonObject createJsonSone(Sone sone) {
-               JsonObject jsonSone = new JsonObject();
+       private JsonNode createJsonSone(Sone sone) {
+               ObjectNode jsonSone = new ObjectNode(instance);
                jsonSone.put("id", sone.getId());
                jsonSone.put("name", SoneAccessor.getNiceName(sone));
                jsonSone.put("local", sone.getInsertUri() != null);
@@ -187,8 +189,8 @@ public class GetStatusAjaxPage extends JsonPage {
         *            The current Sone (may be {@code null})
         * @return The current options
         */
-       private static JsonObject createJsonOptions(Sone currentSone) {
-               JsonObject options = new JsonObject();
+       private static JsonNode createJsonOptions(Sone currentSone) {
+               ObjectNode options = new ObjectNode(instance);
                if (currentSone != null) {
                        options.put("ShowNotification/NewSones", currentSone.getOptions().getBooleanOption("ShowNotification/NewSones").get());
                        options.put("ShowNotification/NewPosts", currentSone.getOptions().getBooleanOption("ShowNotification/NewPosts").get());
index b0d9dac..0c78bb6 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance;
+
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -26,8 +28,8 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.base.Optional;
 
 /**
@@ -54,9 +56,9 @@ public class GetTimesAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String allIds = request.getHttpRequest().getParam("posts");
-               JsonObject postTimes = new JsonObject();
+               ObjectNode postTimes = new ObjectNode(instance);
                if (allIds.length() > 0) {
                        String[] ids = allIds.split(",");
                        for (String id : ids) {
@@ -64,7 +66,7 @@ public class GetTimesAjaxPage extends JsonPage {
                                if (!post.isPresent()) {
                                        continue;
                                }
-                               JsonObject postTime = new JsonObject();
+                               ObjectNode postTime = new ObjectNode(instance);
                                Time time = getTime(post.get().getTime());
                                postTime.put("timeText", time.getText());
                                postTime.put("refreshTime", TimeUnit.MILLISECONDS.toSeconds(time.getRefresh()));
@@ -74,7 +76,7 @@ public class GetTimesAjaxPage extends JsonPage {
                                postTimes.put(id, postTime);
                        }
                }
-               JsonObject replyTimes = new JsonObject();
+               ObjectNode replyTimes = new ObjectNode(instance);
                allIds = request.getHttpRequest().getParam("replies");
                if (allIds.length() > 0) {
                        String[] ids = allIds.split(",");
@@ -83,7 +85,7 @@ public class GetTimesAjaxPage extends JsonPage {
                                if (!reply.isPresent()) {
                                        continue;
                                }
-                               JsonObject replyTime = new JsonObject();
+                               ObjectNode replyTime = new ObjectNode(instance);
                                Time time = getTime(reply.get().getTime());
                                replyTime.put("timeText", time.getText());
                                replyTime.put("refreshTime", TimeUnit.MILLISECONDS.toSeconds(time.getRefresh()));
index 0ecfaea..5c81d95 100644 (file)
@@ -19,7 +19,6 @@ package net.pterodactylus.sone.web.ajax;
 
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * Returns the translation for a given key as JSON object.
@@ -46,7 +45,7 @@ public class GetTranslationPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String key = request.getHttpRequest().getParam("key");
                String translation = webInterface.getL10n().getString(key);
                return createSuccessJsonObject().put("value", translation);
diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/JsonErrorReturnObject.java b/src/main/java/net/pterodactylus/sone/web/ajax/JsonErrorReturnObject.java
new file mode 100644 (file)
index 0000000..4612f0c
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * © 2013 xplosion interactive
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * {@link JsonReturnObject} that signals an error has occured.
+ *
+ * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+ */
+public class JsonErrorReturnObject extends JsonReturnObject {
+
+       /** The error that has occured. */
+       @JsonProperty
+       private final String error;
+
+       /**
+        * Creates a new error JSON return object.
+        *
+        * @param error
+        *              The error that occured
+        */
+       public JsonErrorReturnObject(String error) {
+               super(false);
+               this.error = error;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the error that occured.
+        *
+        * @return The error that occured
+        */
+       @VisibleForTesting
+       public String getError() {
+               return error;
+       }
+
+}
index 994f90c..3c7f587 100644 (file)
@@ -30,11 +30,11 @@ import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetPage;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.io.Closer;
-import net.pterodactylus.util.json.JsonObject;
-import net.pterodactylus.util.json.JsonUtils;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.web.Page;
 import net.pterodactylus.util.web.Response;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
 import freenet.clients.http.SessionManager.Session;
 import freenet.clients.http.ToadletContext;
 
@@ -49,6 +49,9 @@ public abstract class JsonPage implements FreenetPage {
        /** The logger. */
        private static final Logger logger = Logging.getLogger(JsonPage.class);
 
+       /** The JSON serializer. */
+       private static final ObjectMapper objectMapper = new ObjectMapper();
+
        /** The path of the page. */
        private final String path;
 
@@ -140,7 +143,7 @@ public abstract class JsonPage implements FreenetPage {
         *            The request to handle
         * @return The created JSON object
         */
-       protected abstract JsonObject createJsonObject(FreenetRequest request);
+       protected abstract JsonReturnObject createJsonObject(FreenetRequest request);
 
        /**
         * Returns whether this command needs the form password for authentication
@@ -174,8 +177,8 @@ public abstract class JsonPage implements FreenetPage {
         *
         * @return A reply signaling success
         */
-       protected static JsonObject createSuccessJsonObject() {
-               return new JsonObject().put("success", true);
+       protected static JsonReturnObject createSuccessJsonObject() {
+               return new JsonReturnObject(true);
        }
 
        /**
@@ -185,8 +188,8 @@ public abstract class JsonPage implements FreenetPage {
         *            The error that has occured
         * @return The JSON object, signalling failure and the error code
         */
-       protected static JsonObject createErrorJsonObject(String error) {
-               return new JsonObject().put("success", false).put("error", error);
+       protected static JsonReturnObject createErrorJsonObject(String error) {
+               return new JsonErrorReturnObject(error);
        }
 
        //
@@ -215,22 +218,22 @@ public abstract class JsonPage implements FreenetPage {
        @Override
        public Response handleRequest(FreenetRequest request, Response response) throws IOException {
                if (webInterface.getCore().getPreferences().isRequireFullAccess() && !request.getToadletContext().isAllowedFullAccess()) {
-                       return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
+                       return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(objectMapper.writeValueAsString(new JsonErrorReturnObject("auth-required")));
                }
                if (needsFormPassword()) {
                        String formPassword = request.getHttpRequest().getParam("formPassword");
                        if (!webInterface.getFormPassword().equals(formPassword)) {
-                               return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
+                               return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(objectMapper.writeValueAsString(new JsonErrorReturnObject("auth-required")));
                        }
                }
                if (requiresLogin()) {
                        if (getCurrentSone(request.getToadletContext(), false) == null) {
-                               return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
+                               return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(objectMapper.writeValueAsString(new JsonErrorReturnObject("auth-required")));
                        }
                }
                try {
-                       JsonObject jsonObject = createJsonObject(request);
-                       return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(JsonUtils.format(jsonObject));
+                       JsonReturnObject jsonObject = createJsonObject(request);
+                       return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(objectMapper.writeValueAsString(jsonObject));
                } catch (Exception e1) {
                        logger.log(Level.WARNING, "Error executing JSON page!", e1);
                        return response.setStatusCode(500).setStatusText(e1.getMessage()).setContentType("text/plain").write(dumpStackTrace(e1));
diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/JsonReturnObject.java b/src/main/java/net/pterodactylus/sone/web/ajax/JsonReturnObject.java
new file mode 100644 (file)
index 0000000..ca29f3c
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * © 2013 xplosion interactive
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.IntNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Maps;
+
+/**
+ * JSON return object for AJAX requests.
+ *
+ * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+ */
+public class JsonReturnObject {
+
+       /** Whether the request was successful. */
+       @JsonProperty
+       private final boolean success;
+
+       /** The returned values. */
+       private final Map<String, JsonNode> content = Maps.newHashMap();
+
+       /**
+        * Creates a new JSON return object.
+        *
+        * @param success
+        *              {@code true} if the request was successful, {@code false} otherwise
+        */
+       public JsonReturnObject(boolean success) {
+               this.success = success;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns whether the request was successful.
+        *
+        * @return {@code true} if the request was successful, {@code false} otherwise
+        */
+       @VisibleForTesting
+       public boolean isSuccess() {
+               return success;
+       }
+
+       /**
+        * Returns the value stored under the given key.
+        *
+        * @param key
+        *              The key of the value to retrieve
+        * @return The value of the key, or {@code null} if there is no value for the
+        *         given key
+        */
+       @VisibleForTesting
+       public JsonNode get(String key) {
+               return content.get(key);
+       }
+
+       /**
+        * Returns the content of this object for serialization.
+        *
+        * @return The content of this object
+        */
+       @JsonAnyGetter
+       public Map<String, JsonNode> getContent() {
+               return content;
+       }
+
+       //
+       // ACTIONS
+       //
+
+       /**
+        * Stores the given value under the given key.
+        *
+        * @param key
+        *              The key under which to store the value
+        * @param value
+        *              The value to store
+        * @return This JSON return object
+        */
+       public JsonReturnObject put(String key, boolean value) {
+               return put(key, BooleanNode.valueOf(value));
+       }
+
+       /**
+        * Stores the given value under the given key.
+        *
+        * @param key
+        *              The key under which to store the value
+        * @param value
+        *              The value to store
+        * @return This JSON return object
+        */
+       public JsonReturnObject put(String key, int value) {
+               return put(key, new IntNode(value));
+       }
+
+       /**
+        * Stores the given value under the given key.
+        *
+        * @param key
+        *              The key under which to store the value
+        * @param value
+        *              The value to store
+        * @return This JSON return object
+        */
+       public JsonReturnObject put(String key, String value) {
+               return put(key, new TextNode(value));
+       }
+
+       /**
+        * Stores the given value under the given key.
+        *
+        * @param key
+        *              The key under which to store the value
+        * @param value
+        *              The value to store
+        * @return This JSON return object
+        */
+       public JsonReturnObject put(String key, JsonNode value) {
+               content.put(key, value);
+               return this;
+       }
+
+}
index 6787711..55a947a 100644 (file)
@@ -21,7 +21,6 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets the user like a {@link Post}.
@@ -44,7 +43,7 @@ public class LikeAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String type = request.getHttpRequest().getParam("type", null);
                String id = request.getHttpRequest().getParam(type, null);
                if ((id == null) || (id.length() == 0)) {
index af74426..68668b7 100644 (file)
@@ -21,7 +21,6 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * Lets the user {@link Core#lockSone(Sone) lock} a {@link Sone}.
@@ -44,7 +43,7 @@ public class LockSoneAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String soneId = request.getHttpRequest().getParam("sone");
                Sone sone = webInterface.getCore().getLocalSone(soneId, false);
                if (sone == null) {
index 6610971..cd026ef 100644 (file)
@@ -24,7 +24,6 @@ import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 import com.google.common.base.Optional;
 
@@ -50,7 +49,7 @@ public class MarkAsKnownAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String type = request.getHttpRequest().getParam("type");
                if (!type.equals("sone") && !type.equals("post") && !type.equals("reply")) {
                        return createErrorJsonObject("invalid-type");
index 7721aa3..d8781ba 100644 (file)
@@ -22,7 +22,6 @@ import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets the user move a profile field up or down.
@@ -51,7 +50,7 @@ public class MoveProfileFieldAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                Sone currentSone = getCurrentSone(request.getToadletContext());
                Profile profile = currentSone.getProfile();
                String fieldId = request.getHttpRequest().getParam("field");
index d03e030..3661cb1 100644 (file)
@@ -23,7 +23,6 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets the user trust a Sone.
@@ -47,7 +46,7 @@ public class TrustAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                Sone currentSone = getCurrentSone(request.getToadletContext(), false);
                if (currentSone == null) {
                        return createErrorJsonObject("auth-required");
index 8d209af..65bb14d 100644 (file)
@@ -19,7 +19,6 @@ package net.pterodactylus.sone.web.ajax;
 
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets the user unbookmark a post.
@@ -46,7 +45,7 @@ public class UnbookmarkAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String id = request.getHttpRequest().getParam("post", null);
                if ((id == null) || (id.length() == 0)) {
                        return createErrorJsonObject("invalid-post-id");
index 3d984b9..a285d20 100644 (file)
@@ -20,7 +20,6 @@ package net.pterodactylus.sone.web.ajax;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets a Sone unfollow another Sone.
@@ -43,7 +42,7 @@ public class UnfollowSoneAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String soneId = request.getHttpRequest().getParam("sone");
                if (!webInterface.getCore().getSone(soneId).isPresent()) {
                        return createErrorJsonObject("invalid-sone-id");
index 0633fda..17be966 100644 (file)
@@ -21,7 +21,6 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets the user unlike a {@link Post}.
@@ -44,7 +43,7 @@ public class UnlikeAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String type = request.getHttpRequest().getParam("type", null);
                String id = request.getHttpRequest().getParam(type, null);
                if ((id == null) || (id.length() == 0)) {
index 32bb93c..f92132d 100644 (file)
@@ -21,7 +21,6 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * Lets the user {@link Core#unlockSone(Sone) unlock} a {@link Sone}.
@@ -44,7 +43,7 @@ public class UnlockSoneAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String soneId = request.getHttpRequest().getParam("sone");
                Sone sone = webInterface.getCore().getLocalSone(soneId, false);
                if (sone == null) {
index 65d45c9..229f219 100644 (file)
@@ -23,7 +23,6 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.json.JsonObject;
 
 /**
  * AJAX page that lets the user untrust a Sone.
@@ -47,7 +46,7 @@ public class UntrustAjaxPage extends JsonPage {
         * {@inheritDoc}
         */
        @Override
-       protected JsonObject createJsonObject(FreenetRequest request) {
+       protected JsonReturnObject createJsonObject(FreenetRequest request) {
                Sone currentSone = getCurrentSone(request.getToadletContext(), false);
                if (currentSone == null) {
                        return createErrorJsonObject("auth-required");
index 2c6706f..a40a543 100644 (file)
 package net.pterodactylus.sone.web.page;
 
 import java.io.IOException;
+import java.io.OutputStream;
 import java.net.URI;
 
 import net.pterodactylus.util.web.Header;
 import net.pterodactylus.util.web.Method;
 import net.pterodactylus.util.web.Page;
 import net.pterodactylus.util.web.Response;
+
 import freenet.client.HighLevelSimpleClient;
 import freenet.clients.http.LinkEnabledCallback;
 import freenet.clients.http.LinkFilterExceptedToadlet;
@@ -144,21 +146,28 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFil
         */
        private void handleRequest(FreenetRequest pageRequest) throws IOException, ToadletContextClosedException {
                Bucket pageBucket = null;
+               OutputStream pageBucketOutputStream = null;
+               Response pageResponse;
                try {
                        pageBucket = pageRequest.getToadletContext().getBucketFactory().makeBucket(-1);
-                       Response pageResponse = new Response(pageBucket.getOutputStream());
-                       pageResponse = page.handleRequest(pageRequest, pageResponse);
-                       MultiValueTable<String, String> headers = new MultiValueTable<String, String>();
-                       if (pageResponse.getHeaders() != null) {
-                               for (Header header : pageResponse.getHeaders()) {
-                                       for (String value : header) {
-                                               headers.put(header.getName(), value);
-                                       }
+                       pageBucketOutputStream = pageBucket.getOutputStream();
+                       pageResponse = page.handleRequest(pageRequest, new Response(pageBucketOutputStream));
+               } catch (IOException ioe1) {
+                       Closer.close(pageBucket);
+                       throw ioe1;
+               } finally {
+                       Closer.close(pageBucketOutputStream);
+               }
+               MultiValueTable<String, String> headers = new MultiValueTable<String, String>();
+               if (pageResponse.getHeaders() != null) {
+                       for (Header header : pageResponse.getHeaders()) {
+                               for (String value : header) {
+                                       headers.put(header.getName(), value);
                                }
                        }
+               }
+               try {
                        writeReply(pageRequest.getToadletContext(), pageResponse.getStatusCode(), pageResponse.getContentType(), pageResponse.getStatusText(), headers, pageBucket);
-               } catch (Throwable t1) {
-                       writeInternalError(t1, pageRequest.getToadletContext());
                } finally {
                        Closer.close(pageBucket);
                }
index d0ec575..d7a4b9d 100644 (file)
@@ -451,4 +451,6 @@ Notification.InsertedImages.Text=Diese Bilder wurden nach Freenet hoch geladen:
 Notification.ImageInsertFailed.Text=Diese Bilder konnten nicht nach Freenet hoch geladen werden:
 Notification.Mention.ShortText=Sie wurden erwähnt.
 Notification.Mention.Text=Sie wurden in diesen Nachrichten erwähnt:
-Notification.SoneInsert.Duration={0,number} {0,choice,0#Sekunden|1#Sekunde|1<Sekunden}
+Notification.SoneIsInserting.Text=Ihre Sone sone://{0} wird jetzt hoch geladen.
+Notification.SoneIsInserted.Text=Ihre Sone sone://{0} wurde in {1,number} {1,choice,0#Sekunden|1#Sekunde|1<Sekunden} hoch geladen.
+Notification.SoneInsertAborted.Text=Ihre Sone sone://{0} konnte nicht hoch geladen werden.
index 6bab23e..9ecd0de 100644 (file)
@@ -451,4 +451,6 @@ Notification.InsertedImages.Text=The following images have been inserted:
 Notification.ImageInsertFailed.Text=The following images could not be inserted:
 Notification.Mention.ShortText=You have been mentioned.
 Notification.Mention.Text=You have been mentioned in the following posts:
-Notification.SoneInsert.Duration={0,number} {0,choice,0#seconds|1#second|1<seconds}
+Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
+Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
+Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
index b5a5e1b..b4df80f 100644 (file)
@@ -451,4 +451,7 @@ Notification.InsertedImages.Text=Les images suivantes ont été insérées:
 Notification.ImageInsertFailed.Text=Les images suivantes ne peuvent être insérées:
 Notification.Mention.ShortText=Vous avez été mentionné.
 Notification.Mention.Text=Vous avez été mentionné dans les messages suivants:
-Notification.SoneInsert.Duration={0,number} {0,choice,0#seconds|1#second|1<seconds}
+Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
+Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
+Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
+# 454-456
index cf6e026..2c57315 100644 (file)
@@ -58,7 +58,7 @@ Page.Options.Option.PostsPerPage.Description=ページ送りのボタンが表
 Page.Options.Option.ImagesPerPage.Description=ページ送りのボタンが表示されるまでに表示する画像の数。
 Page.Options.Option.CharactersPerPost.Description=投稿を切って全文を見るリンクが表示されるまでの文字数。(-1で無効になります。)文字数は以下の設定により判定されます。
 Page.Options.Option.PostCutOffLength.Description=投稿が長い場合に表示される文字数。(上記の設定も参照してください。)
-Page.Options.Option.RequireFullAccess.Description=完全なアクセスが設定されていないホストに対してSoneへのアクセスを拒否するか否かの設定。
+Page.Options.Option.RequireFullAccess.Description=完全なアクセスが設定されていないホストに対してSoneへのアクセスを拒否する
 Page.Options.Section.TrustOptions.Title=信用設定
 Page.Options.Option.PositiveTrust.Description=返信のリンクの下に表示されるチェックメークのリンクをクリックした際にSoneに設定されるポジティブな信用値。
 Page.Options.Option.NegativeTrust.Description=返信のリンクの下に表示される赤い☓のリンクをクリックした際にSoneに設定されるネガティブな信用値。
@@ -67,7 +67,7 @@ Page.Options.Section.FcpOptions.Title=FCPインターフェースの設定
 Page.Options.Option.FcpInterfaceActive.Description=FCPインターフェースを有効にし、Soneプラグインに他のプラグインやリモートクライアントからアクセスできるようにする。
 Page.Options.Option.FcpFullAccessRequired.Description=許可されたホストのみFCP接続を許可する。({link}ノード設定の「FCP」内の設定{/link}も確認してください。)
 Page.Options.Option.FcpFullAccessRequired.Value.No=いいえ
-Page.Options.Option.FcpFullAccessRequired.Value.Writing=書き込みのアクセス
+Page.Options.Option.FcpFullAccessRequired.Value.Writing=書き込みのアクセスの場合
 Page.Options.Option.FcpFullAccessRequired.Value.Always=常に
 Page.Options.Section.Cleaning.Title=クリーンアップ
 Page.Options.Option.ClearOnNextRestart.Description=次回のプラグインの再起動時に設定を初期化する。注意:{strong}これを行うとあなたの全てのSoneが破棄されます{/strong}。実行の前に必要なものは全てバックアップされていることを確認してください。また、次項の設定も同時に有効にする必要があります。
@@ -117,8 +117,8 @@ Page.KnownSones.Filter.Followed=フォローしているSoneのみ表示
 Page.KnownSones.Filter.NotFollowed=フォローしているSoneを隠す
 Page.KnownSones.Filter.New=新しいSoneのみ表示
 Page.KnownSones.Filter.NotNew=新しいSoneを隠す
-Page.KnownSones.Filter.Own=Show only local Sones
-Page.KnownSones.Filter.NotOwn=Show only remote Sones
+Page.KnownSones.Filter.Own=ローカルのSoneのみ表示
+Page.KnownSones.Filter.NotOwn=リモートのSoneのみ表示
 Page.KnownSones.Button.Apply=実行
 Page.KnownSones.Button.FollowAllSones=このページ内の全てのSoneをフォロー
 Page.KnownSones.Button.UnfollowAllSones=このページ内の全てのフォローを解除
@@ -167,14 +167,14 @@ Page.DeleteProfileField.Button.No=いいえ、消去しません
 Page.CreatePost.Title=投稿文を作成 - Sone
 Page.CreatePost.Page.Title=投稿文を作成
 Page.CreatePost.Label.Text=投稿文のテキスト:
-Page.CreatePost.Button.Post=投稿する
-Page.CreatePost.Error.EmptyText=投稿文が空っぽです。何か書いてください
+Page.CreatePost.Button.Post=投稿する
+Page.CreatePost.Error.EmptyText=投稿文が空っぽです。何か書いてください
 
 Page.CreateReply.Title=返信を作成 - Sone
 Page.CreateReply.Page.Title=返信を作成
-Page.CreateReply.Error.EmptyText=返信が空っぽです。何か書いてください
+Page.CreateReply.Error.EmptyText=返信が空っぽです。何か書いてください
 Page.CreateReply.Label.Text=返信テキスト:
-Page.CreateReply.Button.Post=返信する
+Page.CreateReply.Button.Post=返信する
 
 Page.ViewSone.Title=Soneを表示 - Sone
 Page.ViewSone.Page.TitleWithoutSone=未知のSoneを表示する
@@ -378,7 +378,7 @@ View.Time.InTheFuture=今より未来
 View.Time.AFewSecondsAgo=およそ数秒前
 View.Time.HalfAMinuteAgo=およそ30秒前
 View.Time.AMinuteAgo=およそ1分前
-View.Time.XMinutesAgo=${min}分前o
+View.Time.XMinutesAgo=${min}分前
 View.Time.HalfAnHourAgo=30分前
 View.Time.AnHourAgo=およそ1時間前
 View.Time.XHoursAgo=${hour}時間前
@@ -451,5 +451,6 @@ Notification.InsertedImages.Text=次の画像のインサートされました:
 Notification.ImageInsertFailed.Text=次の画像のインサートに失敗しました:
 Notification.Mention.ShortText=誰かにメンションされました。
 Notification.Mention.Text=次の投稿でメンションされています:
-Notification.SoneInsert.Duration={0,number} {0,choice,0#秒|1#秒|1<秒}
-# 120-121
+Notification.SoneIsInserting.Text=あなたのSone sone://{0}は現在インサート中です。
+Notification.SoneIsInserted.Text=あなたのSone sone://{0}は{1,number}{1,choice,0#秒|1#秒|1<秒}でインサートされました。
+Notification.SoneInsertAborted.Text=あなたのSone sone://{0}のインサートに失敗しました。
index c61d7a5..92ac37b 100644 (file)
@@ -451,5 +451,7 @@ Notification.InsertedImages.Text=De følgende bildene har blitt lastet opp:
 Notification.ImageInsertFailed.Text=De følgende bildene kunne ikke bli innsatt:
 Notification.Mention.ShortText=Du har blitt nevnt:
 Notification.Mention.Text=Du har blitt nevnt i følgende innlegg:
-Notification.SoneInsert.Duration={0,number} {0,choice,0#sekund|1#sekund|1<sekunder}
-# 120-121
+Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
+Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
+Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
+# 120-121, 454-456
index caac397..6edc20f 100644 (file)
@@ -117,8 +117,8 @@ Page.KnownSones.Filter.Followed=Pokazuj tylko śledzone Sone
 Page.KnownSones.Filter.NotFollowed=Ukryj śledzone Sone
 Page.KnownSones.Filter.New=Pokazuj tylko nowe Sone
 Page.KnownSones.Filter.NotNew=Ukryj nowe Sone
-Page.KnownSones.Filter.Own=Show only local Sones
-Page.KnownSones.Filter.NotOwn=Show only remote Sones
+Page.KnownSones.Filter.Own=Pokazuj tylko moje Sone
+Page.KnownSones.Filter.NotOwn=Pokazuj tylko zdalne Sone
 Page.KnownSones.Button.Apply=Zastosuj
 Page.KnownSones.Button.FollowAllSones=Śledź wszystkie Sone na tej stronie
 Page.KnownSones.Button.UnfollowAllSones=Przestań śledzić wszystkie Sone na tej stronie
@@ -451,5 +451,6 @@ Notification.InsertedImages.Text=Załadowano nastepujące obrazy:
 Notification.ImageInsertFailed.Text=Nie można załadowac następujących obrazów:
 Notification.Mention.ShortText=Zostałeś oznaczony.
 Notification.Mention.Text=Zostałeś oznaczony w następujących postach:
-Notification.SoneInsert.Duration={0,number} {0,choice,0#seconds|1#second|1<seconds}
-# 120-121
+Notification.SoneIsInserting.Text=Twoje Sone sone://{0} jest w tej chili wysyłane.
+Notification.SoneIsInserted.Text=Twoje sone://{0} zostało wysłane w {1,number} {1,choice,0#seconds|1#second|1<seconds}.
+Notification.SoneInsertAborted.Text=Twoje Sone sone://{0} nie mogło zostać wysłane.
index b34c905..09efa9f 100644 (file)
@@ -451,5 +451,7 @@ Notification.InsertedImages.Text=Следующие изображения бы
 Notification.ImageInsertFailed.Text=Следующие изображения не могут быть выгружены:
 Notification.Mention.ShortText=Вас упомянули.
 Notification.Mention.Text=Вас упомянули в следующих сообщениях:
-Notification.SoneInsert.Duration={0,number} {0,choice,0#секунд|1#секунда|2#секунды|4<секунд}
-# 120-121
+Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
+Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
+Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
+# 120-121, 454-456
index 3339d18..07c5b3d 100644 (file)
@@ -2,7 +2,7 @@
 
 function ajaxGet(url, data, successCallback, errorCallback) {
        (function(url, data, successCallback, errorCallback) {
-               $.ajax({"type": "GET", "url": url, "data": data, "dataType": "json", "success": function(data, textStatus, xmlHttpRequest) {
+               $.ajax({"cache": false, "type": "GET", "url": url, "data": data, "dataType": "json", "success": function(data, textStatus, xmlHttpRequest) {
                        ajaxSuccess();
                        if (typeof successCallback != "undefined") {
                                successCallback(data, textStatus);
@@ -22,10 +22,10 @@ function ajaxGet(url, data, successCallback, errorCallback) {
 
 function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, optional, dontUseTextarea) {
        $(inputElement).each(function() {
-               textarea = $(dontUseTextarea ? "<input type=\"text\" name=\"" + inputFieldName + "\">" : "<textarea name=\"" + inputFieldName + "\"></textarea>").blur(function() {
+               var textarea = $(dontUseTextarea ? "<input type=\"text\" name=\"" + inputFieldName + "\">" : "<textarea name=\"" + inputFieldName + "\"></textarea>").blur(function() {
                        if ($(this).val() == "") {
                                $(this).hide();
-                               inputField = $(this).data("inputField");
+                               var inputField = $(this).data("inputField");
                                inputField.show().removeAttr("disabled").addClass("default");
                                inputField.val(defaultText);
                        }
@@ -68,14 +68,14 @@ function addCommentLink(postId, author, element, insertAfterThisElement) {
                return;
        }
        (function(postId, author, insertAfterThisElement) {
-               separator = $("<span> · </span>").addClass("separator");
+               var separator = $("<span> · </span>").addClass("separator");
                getTranslation("WebInterface.Button.Comment", function(text) {
-                       commentElement = $("<div><span>" + text + "</span></div>").addClass("show-reply-form").click(function() {
-                               replyElement = $("#sone .post#post-" + postId + " .create-reply");
+                       var commentElement = $("<div><span>" + text + "</span></div>").addClass("show-reply-form").click(function() {
+                               var replyElement = sone.find(".post#post-" + postId + " .create-reply");
                                replyElement.removeClass("hidden");
                                replyElement.removeClass("light");
                                (function(replyElement) {
-                                       replyElement.find("input.reply-input").blur(function() {
+                                       replyElement.find(":input.reply-input").blur(function() {
                                                if ($(this).hasClass("default")) {
                                                        replyElement.addClass("light");
                                                }
@@ -83,7 +83,7 @@ function addCommentLink(postId, author, element, insertAfterThisElement) {
                                                replyElement.removeClass("light");
                                        });
                                })(replyElement);
-                               textArea = replyElement.find("input.reply-input").focus().data("textarea");
+                               var textArea = replyElement.find(":input.reply-input").focus().data("textarea");
                                if (author != getCurrentSoneId()) {
                                        textArea.val(textArea.val() + "@sone://" + author + " ");
                                }
@@ -145,22 +145,22 @@ function filterSoneId(soneId) {
  *            The date and time of the last update (formatted for display)
  */
 function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated, lastUpdatedText) {
-       $("#sone .sone." + filterSoneId(soneId)).
-               toggleClass("unknown", status == "unknown").
+    var updateSone = sone.find(".sone." + filterSoneId(soneId));
+       updateSone.toggleClass("unknown", status == "unknown").
                toggleClass("idle", status == "idle").
                toggleClass("inserting", status == "inserting").
                toggleClass("downloading", status == "downloading").
                toggleClass("modified", modified);
-       $("#sone .sone." + filterSoneId(soneId) + " .lock").toggleClass("hidden", locked);
-       $("#sone .sone." + filterSoneId(soneId) + " .unlock").toggleClass("hidden", !locked);
+       updateSone.find(".lock").toggleClass("hidden", locked);
+       updateSone.find(".unlock").toggleClass("hidden", !locked);
        if (lastUpdated != null) {
-               $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").attr("title", lastUpdated).text(lastUpdatedText);
+               updateSone.find(".last-update span.time").attr("title", lastUpdated).text(lastUpdatedText);
        } else {
                getTranslation("View.Sone.Text.UnknownDate", function(unknown) {
-                       $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(unknown);
+                       updateSone.find(".last-update span.time").text(unknown);
                });
        }
-       $("#sone .sone." + filterSoneId(soneId) + " .profile-link a").text(name);
+       updateSone.find(".profile-link a").text(name);
 }
 
 /**
@@ -175,7 +175,7 @@ function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated, l
  */
 function enhanceDeleteButton(button, text, deleteCallback) {
        (function(button) {
-               newButton = $("<button></button>").addClass("confirm").hide().text(text).click(function() {
+               var newButton = $("<button></button>").addClass("confirm").hide().text(text).click(function() {
                        $(this).fadeOut("slow");
                        deleteCallback();
                        return false;
@@ -215,7 +215,7 @@ function enhanceDeletePostButton(button, postId, text) {
                                return;
                        }
                        if (data.success) {
-                               $("#sone .post#post-" + postId).slideUp();
+                               sone.find(".post#post-" + postId).slideUp();
                        } else if (data.error == "invalid-post-id") {
                                /* pretend the post is already gone. */
                                getPost(postId).slideUp();
@@ -242,12 +242,12 @@ function enhanceDeletePostButton(button, postId, text) {
  */
 function enhanceDeleteReplyButton(button, replyId, text) {
        enhanceDeleteButton(button, text, function() {
-               ajaxGet("deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
+               ajaxGet("deleteReply.ajax", { "reply": replyId, "formPassword": sone.find("#formPassword").text() }, function(data, textStatus) {
                        if (data == null) {
                                return;
                        }
                        if (data.success) {
-                               $("#sone .reply#reply-" + replyId).slideUp();
+                               sone.find(".reply#reply-" + replyId).slideUp();
                        } else if (data.error == "invalid-reply-id") {
                                /* pretend the reply is already gone. */
                                getReply(replyId).slideUp();
@@ -263,7 +263,7 @@ function enhanceDeleteReplyButton(button, replyId, text) {
 }
 
 function getFormPassword() {
-       return $("#sone #formPassword").text();
+       return sone.find("#formPassword").text();
 }
 
 /**
@@ -274,7 +274,7 @@ function getFormPassword() {
  * @returns All Sone elements with the given ID
  */
 function getSone(soneId) {
-       return $("#sone .sone").filter(function(index) {
+       return sone.find(".sone").filter(function(index) {
                return $(".id", this).text() == soneId;
        });
 }
@@ -333,7 +333,7 @@ function getSoneId(element) {
  * @returns The element of the post
  */
 function getPost(postId) {
-       return $("#sone .post#post-" + postId);
+       return sone.find(".post#post-" + postId);
 }
 
 function getPostElement(element) {
@@ -367,7 +367,7 @@ function getPostAuthor(element) {
  * @returns The element of the reply
  */
 function getReply(replyId) {
-       return $("#sone .reply#reply-" + replyId);
+       return sone.find(".reply#reply-" + replyId);
 }
 
 function getReplyElement(element) {
@@ -401,7 +401,7 @@ function getReplyAuthor(element) {
  * @returns The notification element
  */
 function getNotification(notificationId) {
-       return $("#sone #notification-area .notification#" + notificationId);
+       return sone.find("#notification-area .notification#" + notificationId);
 }
 
 /**
@@ -442,8 +442,8 @@ function likePost(postId) {
                if ((data == null) || !data.success) {
                        return;
                }
-               $("#sone .post#post-" + postId + " > .inner-part > .status-line .like").addClass("hidden");
-               $("#sone .post#post-" + postId + " > .inner-part > .status-line .unlike").removeClass("hidden");
+               sone.find(".post#post-" + postId + " > .inner-part > .status-line .like").addClass("hidden");
+               sone.find(".post#post-" + postId + " > .inner-part > .status-line .unlike").removeClass("hidden");
                updatePostLikes(postId);
        }, function(xmlHttpRequest, textStatus, error) {
                /* ignore error. */
@@ -455,8 +455,8 @@ function unlikePost(postId) {
                if ((data == null) || !data.success) {
                        return;
                }
-               $("#sone .post#post-" + postId + " > .inner-part > .status-line .unlike").addClass("hidden");
-               $("#sone .post#post-" + postId + " > .inner-part > .status-line .like").removeClass("hidden");
+               sone.find(".post#post-" + postId + " > .inner-part > .status-line .unlike").addClass("hidden");
+               sone.find(".post#post-" + postId + " > .inner-part > .status-line .like").removeClass("hidden");
                updatePostLikes(postId);
        }, function(xmlHttpRequest, textStatus, error) {
                /* ignore error. */
@@ -466,9 +466,9 @@ function unlikePost(postId) {
 function updatePostLikes(postId) {
        ajaxGet("getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) {
                if ((data != null) && data.success) {
-                       $("#sone .post#post-" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0);
-                       $("#sone .post#post-" + postId + " > .inner-part > .status-line .likes span.like-count").text(data.likes);
-                       $("#sone .post#post-" + postId + " > .inner-part > .status-line .likes > span").attr("title", generateSoneList(data.sones));
+                       sone.find(".post#post-" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0);
+                       sone.find(".post#post-" + postId + " > .inner-part > .status-line .likes span.like-count").text(data.likes);
+                       sone.find(".post#post-" + postId + " > .inner-part > .status-line .likes > span").attr("title", generateSoneList(data.sones));
                }
        }, function(xmlHttpRequest, textStatus, error) {
                /* ignore error. */
@@ -480,8 +480,8 @@ function likeReply(replyId) {
                if ((data == null) || !data.success) {
                        return;
                }
-               $("#sone .reply#reply-" + replyId + " .status-line .like").addClass("hidden");
-               $("#sone .reply#reply-" + replyId + " .status-line .unlike").removeClass("hidden");
+               sone.find(".reply#reply-" + replyId + " .status-line .like").addClass("hidden");
+               sone.find(".reply#reply-" + replyId + " .status-line .unlike").removeClass("hidden");
                updateReplyLikes(replyId);
        }, function(xmlHttpRequest, textStatus, error) {
                /* ignore error. */
@@ -493,8 +493,8 @@ function unlikeReply(replyId) {
                if ((data == null) || !data.success) {
                        return;
                }
-               $("#sone .reply#reply-" + replyId + " .status-line .unlike").addClass("hidden");
-               $("#sone .reply#reply-" + replyId + " .status-line .like").removeClass("hidden");
+               sone.find(".reply#reply-" + replyId + " .status-line .unlike").addClass("hidden");
+               sone.find(".reply#reply-" + replyId + " .status-line .like").removeClass("hidden");
                updateReplyLikes(replyId);
        }, function(xmlHttpRequest, textStatus, error) {
                /* ignore error. */
@@ -553,14 +553,14 @@ function untrustSone(soneId) {
  *            The trust value for the Sone
  */
 function updateTrustControls(soneId, trustValue) {
-       $("#sone .post").each(function() {
+       sone.find(".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);
                        getPostElement(this).find(".post-untrust").toggleClass("hidden", trustValue == null);
                }
        });
-       $("#sone .reply").each(function() {
+       sone.find(".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);
@@ -604,9 +604,9 @@ function unbookmarkPost(postId) {
 function updateReplyLikes(replyId) {
        ajaxGet("getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) {
                if ((data != null) && data.success) {
-                       $("#sone .reply#reply-" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0);
-                       $("#sone .reply#reply-" + replyId + " .status-line .likes span.like-count").text(data.likes);
-                       $("#sone .reply#reply-" + replyId + " .status-line .likes > span").attr("title", generateSoneList(data.sones));
+                       sone.find(".reply#reply-" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0);
+                       sone.find(".reply#reply-" + replyId + " .status-line .likes span.like-count").text(data.likes);
+                       sone.find(".reply#reply-" + replyId + " .status-line .likes > span").attr("title", generateSoneList(data.sones));
                }
        }, function(xmlHttpRequest, textStatus, error) {
                /* ignore error. */
@@ -703,21 +703,21 @@ function ajaxifyPost(postElement) {
                return false;
        });
        $(postElement).find(".create-reply button:submit").click(function() {
-               button = $(this);
+               var button = $(this);
                button.attr("disabled", "disabled");
-               sender = $(this.form).find(":input[name=sender]").val();
-               inputField = $(this.form).find(":input[name=text]:enabled").get(0);
-               postId = getPostId(this);
-               text = $(inputField).val();
+               var sender = $(this.form).find(":input[name=sender]").val();
+               var inputField = $(this.form).find(":input[name=text]:enabled").get(0);
+               var postId = getPostId(this);
+               var text = $(inputField).val();
                (function(sender, postId, text, inputField) {
                        postReply(sender, postId, text, function(success, error, replyId, soneId) {
                                if (success) {
                                        $(inputField).val("");
                                        loadNewReply(replyId, soneId, postId);
-                                       $("#sone .post#post-" + postId + " .create-reply").addClass("hidden");
-                                       $("#sone .post#post-" + postId + " .create-reply .sender").hide();
-                                       $("#sone .post#post-" + postId + " .create-reply .select-sender").show();
-                                       $("#sone .post#post-" + postId + " .create-reply :input[name=sender]").val(getCurrentSoneId());
+                                       sone.find(".post#post-" + postId + " .create-reply").addClass("hidden");
+                                       sone.find(".post#post-" + postId + " .create-reply .sender").hide();
+                                       sone.find(".post#post-" + postId + " .create-reply .select-sender").show();
+                                       sone.find(".post#post-" + postId + " .create-reply :input[name=sender]").val(getCurrentSoneId());
                                } else {
                                        alert(error);
                                }
@@ -730,7 +730,7 @@ function ajaxifyPost(postElement) {
        /* replace all “delete” buttons with javascript. */
        (function(postElement) {
                getTranslation("WebInterface.Confirmation.DeletePostButton", function(deletePostText) {
-                       postId = getPostId(postElement);
+                       var postId = getPostId(postElement);
                        enhanceDeletePostButton($(postElement).find(".delete-post button"), postId, deletePostText);
                });
        })(postElement);
@@ -772,8 +772,8 @@ function ajaxifyPost(postElement) {
        /* convert “show source” link into javascript function. */
        $(postElement).find(".show-source").each(function() {
                $("a", this).click(function() {
-                       post = getPostElement(this);
-                       rawPostText = $(".post-text.raw-text", post);
+                       var post = getPostElement(this);
+                       var rawPostText = $(".post-text.raw-text", post);
                        rawPostText.toggleClass("hidden");
                        if (rawPostText.hasClass("hidden")) {
                                $(".post-text.short-text", post).removeClass("hidden");
@@ -813,7 +813,7 @@ function ajaxifyPost(postElement) {
        /* ajaxify author/post links */
        $(".post-status-line .permalink a", postElement).click(function() {
                if (!$(".create-reply", postElement).hasClass("hidden")) {
-                       textArea = $("input.reply-input", postElement).focus().data("textarea");
+                       var textArea = $(":input.reply-input", postElement).focus().data("textarea");
                        $(textArea).replaceSelection($(this).attr("href"));
                }
                return false;
@@ -823,7 +823,7 @@ function ajaxifyPost(postElement) {
        addCommentLink(getPostId(postElement), getPostAuthor(postElement), postElement, $(postElement).find(".post-status-line .permalink-author"));
 
        /* process all replies. */
-       replyIds = [];
+       var replyIds = [];
        $(postElement).find(".reply").each(function() {
                replyIds.push(getReplyId(this));
                ajaxifyReply(this);
@@ -832,7 +832,7 @@ function ajaxifyPost(postElement) {
 
        /* process reply input fields. */
        getTranslation("WebInterface.DefaultText.Reply", function(text) {
-               $(postElement).find("input.reply-input").each(function() {
+               $(postElement).find(":input.reply-input").each(function() {
                        registerInputTextareaSwap(this, text, "text", false, false);
                });
        });
@@ -872,7 +872,7 @@ function ajaxifyPost(postElement) {
                        }).fadeIn();
                }, 1000);
        }).mouseleave(function() {
-               if (currentSoneMenuId = getPostId(this)) {
+               if (currentSoneMenuId == getPostId(this)) {
                        clearTimeout(currentSoneMenuTimeoutHandler);
                }
        });
@@ -883,7 +883,7 @@ function ajaxifyPost(postElement) {
                        ajaxGet("followSone.ajax", { "sone": soneId, "formPassword": getFormPassword() }, function() {
                                $(followElement).addClass("hidden");
                                $(followElement).parent().find(".unfollow").removeClass("hidden");
-                               $("#sone .sone-menu").each(function() {
+                               sone.find(".sone-menu").each(function() {
                                        if (getMenuSone(this) == soneId) {
                                                $(".follow", this).toggleClass("hidden", true);
                                                $(".unfollow", this).toggleClass("hidden", false);
@@ -897,7 +897,7 @@ function ajaxifyPost(postElement) {
                        ajaxGet("unfollowSone.ajax", { "sone": soneId, "formPassword": getFormPassword() }, function() {
                                $(unfollowElement).addClass("hidden");
                                $(unfollowElement).parent().find(".follow").removeClass("hidden");
-                               $("#sone .sone-menu").each(function() {
+                               sone.find(".sone-menu").each(function() {
                                        if (getMenuSone(this) == soneId) {
                                                $(".follow", this).toggleClass("hidden", false);
                                                $(".unfollow", this).toggleClass("hidden", true);
@@ -935,7 +935,7 @@ function ajaxifyReply(replyElement) {
        /* ajaxify author links */
        $(".reply-status-line .permalink a", replyElement).click(function() {
                if (!$(".create-reply", getPostElement(replyElement)).hasClass("hidden")) {
-                       textArea = $("input.reply-input", getPostElement(replyElement)).focus().data("textarea");
+                       var textArea = $(":input.reply-input", getPostElement(replyElement)).focus().data("textarea");
                        $(textArea).replaceSelection($(this).attr("href"));
                }
                return false;
@@ -946,8 +946,8 @@ function ajaxifyReply(replyElement) {
        /* convert “show source” link into javascript function. */
        $(replyElement).find(".show-reply-source").each(function() {
                $("a", this).click(function() {
-                       reply = getReplyElement(this);
-                       rawReplyText = $(".reply-text.raw-text", reply);
+                       var reply = getReplyElement(this);
+                       var rawReplyText = $(".reply-text.raw-text", reply);
                        rawReplyText.toggleClass("hidden");
                        if (rawReplyText.hasClass("hidden")) {
                                $(".reply-text.short-text", reply).removeClass("hidden");
@@ -1011,7 +1011,7 @@ function ajaxifyReply(replyElement) {
                        }).fadeIn();
                }, 1000);
        }).mouseleave(function() {
-               if (currentSoneMenuId = getPostId(this) + "-" + getReplyId(this)) {
+               if (currentSoneMenuId == getPostId(this) + "-" + getReplyId(this)) {
                        clearTimeout(currentSoneMenuTimeoutHandler);
                }
        });
@@ -1022,7 +1022,7 @@ function ajaxifyReply(replyElement) {
                        ajaxGet("followSone.ajax", { "sone": soneId, "formPassword": getFormPassword() }, function() {
                                $(followElement).addClass("hidden");
                                $(followElement).parent().find(".unfollow").removeClass("hidden");
-                               $("#sone .sone-menu").each(function() {
+                               sone.find(".sone-menu").each(function() {
                                        if (getMenuSone(this) == soneId) {
                                                $(".follow", this).toggleClass("hidden", true);
                                                $(".unfollow", this).toggleClass("hidden", false);
@@ -1036,7 +1036,7 @@ function ajaxifyReply(replyElement) {
                        ajaxGet("unfollowSone.ajax", { "sone": soneId, "formPassword": getFormPassword() }, function() {
                                $(unfollowElement).addClass("hidden");
                                $(unfollowElement).parent().find(".follow").removeClass("hidden");
-                               $("#sone .sone-menu").each(function() {
+                               sone.find(".sone-menu").each(function() {
                                        if (getMenuSone(this) == soneId) {
                                                $(".follow", this).toggleClass("hidden", false);
                                                $(".unfollow", this).toggleClass("hidden", true);
@@ -1064,16 +1064,16 @@ function ajaxifyNotification(notification) {
                notification.find(".text").addClass("hidden");
        }
        notification.find("form.mark-as-read button").click(function() {
-               allIds = $(":input[name=id]", this.form).val().split(" ");
+               var allIds = $(":input[name=id]", this.form).val().split(" ");
                for (var index = 0; index < allIds.length; index += 16) {
-                       ids = allIds.slice(index, index + 16).join(" ");
+                       var ids = allIds.slice(index, index + 16).join(" ");
                        ajaxGet("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": $(":input[name=type]", this.form).val(), "id": ids});
                }
        });
        notification.find("a[class^='link-']").each(function() {
-               linkElement = $(this);
+               var linkElement = $(this);
                if (linkElement.is("[href^='viewPost']")) {
-                       id = linkElement.attr("class").substr(5);
+                       var id = linkElement.attr("class").substr(5);
                        if (hasPost(id)) {
                                linkElement.attr("href", "#post-" + id).addClass("in-page-link");
                        }
@@ -1095,7 +1095,7 @@ function ajaxifyNotification(notification) {
  * determine whether the notifications changed and need to be reloaded.
  */
 function getNotificationHash() {
-       return $("#sone #notification-area #notification-hash").text();
+       return sone.find("#notification-area #notification-hash").text();
 }
 
 /**
@@ -1105,7 +1105,7 @@ function getNotificationHash() {
  *            The new notification hash
  */
 function setNotificationHash(notificationHash) {
-       $("#sone #notification-area #notification-hash").text(notificationHash);
+       sone.find("#notification-area #notification-hash").text(notificationHash);
 }
 
 /**
@@ -1118,7 +1118,7 @@ function setNotificationHash(notificationHash) {
  * @returns All extracted IDs
  */
 function getElementIds(notification, selector) {
-       elementIds = [];
+       var elementIds = [];
        $(selector, notification).each(function() {
                elementIds.push($(this).text());
        });
@@ -1138,8 +1138,8 @@ function checkForRemovedSones(oldNotification, newNotification) {
        if (getNotificationId(oldNotification) != "new-sone-notification") {
                return;
        }
-       oldIds = getElementIds(oldNotification, ".new-sone-id");
-       newIds = getElementIds(newNotification, ".new-sone-id");
+       var oldIds = getElementIds(oldNotification, ".new-sone-id");
+       var newIds = getElementIds(newNotification, ".new-sone-id");
        $.each(oldIds, function(index, value) {
                if ($.inArray(value, newIds) == -1) {
                        markSoneAsKnown(getSone(value), true);
@@ -1160,8 +1160,8 @@ function checkForRemovedPosts(oldNotification, newNotification) {
        if (getNotificationId(oldNotification) != "new-post-notification") {
                return;
        }
-       oldIds = getElementIds(oldNotification, ".post-id");
-       newIds = getElementIds(newNotification, ".post-id");
+       var oldIds = getElementIds(oldNotification, ".post-id");
+       var newIds = getElementIds(newNotification, ".post-id");
        $.each(oldIds, function(index, value) {
                if ($.inArray(value, newIds) == -1) {
                        markPostAsKnown(getPost(value), true);
@@ -1183,8 +1183,8 @@ function checkForRemovedReplies(oldNotification, newNotification) {
        if (getNotificationId(oldNotification) != "new-reply-notification") {
                return;
        }
-       oldIds = getElementIds(oldNotification, ".reply-id");
-       newIds = getElementIds(newNotification, ".reply-id");
+       var oldIds = getElementIds(oldNotification, ".reply-id");
+       var newIds = getElementIds(newNotification, ".reply-id");
        $.each(oldIds, function(index, value) {
                if ($.inArray(value, newIds) == -1) {
                        markReplyAsKnown(getReply(value), true);
@@ -1231,9 +1231,9 @@ function requestNotifications() {
        ajaxGet("getNotifications.ajax", {}, function(data, textStatus) {
                if (data && data.success) {
                        /* search for removed notifications. */
-                       $("#sone #notification-area .notification").each(function() {
-                               notificationId = $(this).attr("id");
-                               foundNotification = false;
+                       sone.find("#notification-area .notification").each(function() {
+                               var notificationId = $(this).attr("id");
+                               var foundNotification = false;
                                $.each(data.notifications, function(index, value) {
                                        if (value.id == notificationId) {
                                                foundNotification = true;
@@ -1243,24 +1243,24 @@ function requestNotifications() {
                                if (!foundNotification) {
                                        if (notificationId == "new-sone-notification" && (data.options["ShowNotification/NewSones"] == true)) {
                                                $(".new-sone-id", this).each(function(index, element) {
-                                                       soneId = $(this).text();
+                                                       var soneId = $(this).text();
                                                        markSoneAsKnown(getSone(soneId), true);
                                                });
                                        } else if (notificationId == "new-post-notification" && (data.options["ShowNotification/NewPosts"] == true)) {
                                                $(".post-id", this).each(function(index, element) {
-                                                       postId = $(this).text();
+                                                       var postId = $(this).text();
                                                        markPostAsKnown(getPost(postId), true);
                                                });
                                        } else if (notificationId == "new-reply-notification" && (data.options["ShowNotification/NewReplies"] == true)) {
                                                $(".reply-id", this).each(function(index, element) {
-                                                       replyId = $(this).text();
+                                                       var replyId = $(this).text();
                                                        markReplyAsKnown(getReply(replyId), true);
                                                });
                                        }
                                        $(this).slideUp("normal", function() {
                                                $(this).remove();
                                                /* remove activity when no notifications are visible. */
-                                               if ($("#sone #notification-area .notification").length == 0) {
+                                               if (sone.find("#notification-area .notification").length == 0) {
                                                        resetActivity();
                                                }
                                        });
@@ -1268,11 +1268,11 @@ function requestNotifications() {
                        });
                        /* process notifications. */
                        $.each(data.notifications, function(index, value) {
-                               oldNotification = getNotification(value.id);
-                               notification = ajaxifyNotification(createNotification(value.id, value.lastUpdatedTime, value.text, value.dismissable)).hide();
+                               var oldNotification = getNotification(value.id);
+                               var notification = ajaxifyNotification(createNotification(value.id, value.lastUpdatedTime, value.text, value.dismissable)).hide();
                                if (oldNotification.length != 0) {
                                        if ((oldNotification.find(".short-text").length > 0) && (notification.find(".short-text").length > 0)) {
-                                               opened = oldNotification.is(":visible") && oldNotification.find(".short-text").hasClass("hidden");
+                                               var opened = oldNotification.is(":visible") && oldNotification.find(".short-text").hasClass("hidden");
                                                notification.find(".short-text").toggleClass("hidden", opened);
                                                notification.find(".text").toggleClass("hidden", !opened);
                                        }
@@ -1281,7 +1281,7 @@ function requestNotifications() {
                                        checkForRemovedReplies(oldNotification, notification);
                                        oldNotification.replaceWith(notification.show());
                                } else {
-                                       $("#sone #notification-area").append(notification);
+                                       sone.find("#notification-area").append(notification);
                                        if (value.id.substring(0, 5) != "local") {
                                                notification.slideDown();
                                                setActivity();
@@ -1309,7 +1309,7 @@ function getCurrentSoneId() {
  * @returns The page ID
  */
 function getPageId() {
-       return $("#sone .page-id").text();
+       return sone.find(".page-id").text();
 }
 
 /**
@@ -1331,7 +1331,7 @@ function isIndexPage() {
  * @returns The current page of the pagination
  */
 function getPage(paginationSelector) {
-       pagination = $(paginationSelector);
+       var pagination = $(paginationSelector);
        if (pagination.length > 0) {
                return $(".current-page", paginationSelector).text();
        }
@@ -1355,7 +1355,7 @@ function isViewSonePage() {
  * @returns The ID of the currently shown Sone
  */
 function getShownSoneId() {
-       return $("#sone .sone-id").first().text();
+       return sone.find(".sone-id").first().text();
 }
 
 /**
@@ -1365,8 +1365,8 @@ function getShownSoneId() {
  * @returns The ID of the currently shown Sones
  */
 function getShownSoneIds() {
-       var soneIds = new Array();
-       $("#sone #known-sones .sone .id").each(function() {
+       var soneIds = [];
+       sone.find("#known-sones .sone .id").each(function() {
                soneIds.push($(this).text());
        });
        return soneIds.join(",");
@@ -1389,7 +1389,7 @@ function isViewPostPage() {
  * @returns The ID of the currently shown post
  */
 function getShownPostId() {
-       return $("#sone .post-id").text();
+       return sone.find(".post-id").text();
 }
 
 /**
@@ -1423,7 +1423,7 @@ function hasPost(postId) {
  *          exists on the page, <code>false</code> otherwise
  */
 function hasReply(replyId) {
-       return $("#sone .reply#reply-" + replyId).length > 0;
+       return sone.find(".reply#reply-" + replyId).length > 0;
 }
 
 function loadNewPost(postId, soneId, recipientId, time) {
@@ -1437,7 +1437,7 @@ function loadNewPost(postId, soneId, recipientId, time) {
                        }
                }
        }
-       if (getPostTime($("#sone .post").last()) > time) {
+       if (getPostTime(sone.find(".post").last()) > time) {
                return;
        }
        ajaxGet("getPost.ajax", { "post" : postId }, function(data, textStatus) {
@@ -1449,13 +1449,13 @@ function loadNewPost(postId, soneId, recipientId, time) {
                                return;
                        }
                        var firstOlderPost = null;
-                       $("#sone .post").each(function() {
+                       sone.find(".post").each(function() {
                                if (getPostTime(this) < data.post.time) {
                                        firstOlderPost = $(this);
                                        return false;
                                }
                        });
-                       newPost = $(data.post.html).addClass("hidden");
+                       var newPost = $(data.post.html).addClass("hidden");
                        if ($(".post-author-local", newPost).text() == "true") {
                                newPost.removeClass("new");
                        }
@@ -1483,7 +1483,7 @@ function loadNewReply(replyId, soneId, postId, postSoneId) {
                        if (hasReply(data.reply.id)) {
                                return;
                        }
-                       $("#sone .post#post-" + data.reply.postId).each(function() {
+                       sone.find(".post#post-" + data.reply.postId).each(function() {
                                var firstNewerReply = null;
                                $(this).find(".replies .reply").each(function() {
                                        if (getReplyTime(this) > data.reply.time) {
@@ -1491,7 +1491,7 @@ function loadNewReply(replyId, soneId, postId, postSoneId) {
                                                return false;
                                        }
                                });
-                               newReply = $(data.reply.html).addClass("hidden");
+                               var newReply = $(data.reply.html).addClass("hidden");
                                if ($(".reply-author-local", newReply).text() == "true") {
                                        newReply.removeClass("new");
                                        (function(newReply) {
@@ -1540,7 +1540,7 @@ function markSoneAsKnown(soneElement, skipRequest) {
 
 function markPostAsKnown(postElements, skipRequest) {
        $(postElements).each(function() {
-               postElement = this;
+               var postElement = this;
                if ($(postElement).hasClass("new") || ((typeof skipRequest != "undefined"))) {
                        (function(postElement) {
                                $(postElement).removeClass("new");
@@ -1557,7 +1557,7 @@ function markPostAsKnown(postElements, skipRequest) {
 
 function markReplyAsKnown(replyElements, skipRequest) {
        $(replyElements).each(function() {
-               replyElement = this;
+               var replyElement = this;
                if ($(replyElement).hasClass("new") || ((typeof skipRequest != "undefined"))) {
                        (function(replyElement) {
                                $(replyElement).removeClass("new");
@@ -1648,7 +1648,7 @@ function updateReplyTimes(replyIds) {
 }
 
 function resetActivity() {
-       title = document.title;
+       var title = document.title;
        if (title.indexOf('(') == 0) {
                setTitle(title.substr(title.indexOf(' ') + 1));
        }
@@ -1657,7 +1657,7 @@ function resetActivity() {
 
 function setActivity() {
        if (!focus) {
-               title = document.title;
+               var title = document.title;
                if (title.indexOf('(') != 0) {
                        setTitle("(!) " + title);
                }
@@ -1728,9 +1728,9 @@ function changeIcon(iconUrl) {
  *            user
  */
 function createNotification(id, lastUpdatedTime, text, dismissable) {
-       notification = $("<div></div>").addClass("notification").attr("id", id).attr("lastUpdatedTime", lastUpdatedTime);
+       var notification = $("<div></div>").addClass("notification").attr("id", id).attr("lastUpdatedTime", lastUpdatedTime);
        if (dismissable) {
-               dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id");
+               var dismissForm = sone.find("#notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id");
                dismissForm.find("input[name=notification]").val(id);
                notification.append(dismissForm);
        }
@@ -1745,8 +1745,8 @@ function createNotification(id, lastUpdatedTime, text, dismissable) {
  *            The ID of the notification
  */
 function showNotificationDetails(notificationId) {
-       $("#sone .notification#" + notificationId + " .text").removeClass("hidden");
-       $("#sone .notification#" + notificationId + " .short-text").addClass("hidden");
+       sone.find(".notification#" + notificationId + " .text").removeClass("hidden");
+       sone.find(".notification#" + notificationId + " .short-text").addClass("hidden");
 }
 
 /**
@@ -1758,7 +1758,7 @@ function showNotificationDetails(notificationId) {
 function deleteProfileField(fieldId) {
        ajaxGet("deleteProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId}, function(data, textStatus) {
                if (data && data.success) {
-                       $("#sone .profile-field#" + data.field.id).slideUp();
+                       sone.find(".profile-field#" + data.field.id).slideUp();
                }
        });
 }
@@ -1854,11 +1854,11 @@ function ajaxSuccess() {
  */
 function showOfflineMarker(visible) {
        /* jQuery documentation says toggle() works the other way around?! */
-       $("#sone #offline-marker").toggle(visible);
+       sone.find("#offline-marker").toggle(visible);
        if (visible) {
-               $("#sone #main").addClass("offline");
+               sone.find("#main").addClass("offline");
        } else {
-               $("#sone #main").removeClass("offline");
+               sone.find("#main").removeClass("offline");
        }
 }
 
@@ -1866,9 +1866,10 @@ function showOfflineMarker(visible) {
 // EVERYTHING BELOW HERE IS EXECUTED AFTER LOADING THE PAGE
 //
 
+var sone = $("#sone");
 var focus = true;
 var online = true;
-var initiallyLoggedIn = $("#sone #loggedIn").text() == "true";
+var initiallyLoggedIn = sone.find("#loggedIn").text() == "true";
 var notLoggedIn = !initiallyLoggedIn;
 
 /** ID of the next-to-show Sone context menu. */
@@ -1880,9 +1881,9 @@ var currentSoneMenuTimeoutHandler;
 $(document).ready(function() {
 
        /* rip out the status update textarea. */
-       $("#sone .rip-out").each(function() {
+       sone.find(".rip-out").each(function() {
                var oldElement = $(this);
-               newElement = $("<input type='text'/>");
+               var newElement = $("<input type='text'/>");
                newElement.attr("class", oldElement.attr("class")).attr("name", oldElement.attr("name"));
                oldElement.before(newElement).remove();
        });
@@ -1890,21 +1891,21 @@ $(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();
+               sone.find("#update-status .select-sender").css("display", "inline");
+               sone.find("#update-status .sender").hide();
+               sone.find("#update-status .select-sender button").click(function() {
+                       sone.find("#update-status .sender").show();
+                       sone.find("#update-status .select-sender").hide();
                        return false;
                });
-               $("#sone #update-status").submit(function() {
-                       button = $("button:submit", this);
+               sone.find("#update-status").submit(function() {
+                       var button = $("button:submit", this);
                        button.attr("disabled", "disabled");
                        if ($(this).find(":input.default:enabled").length > 0) {
                                return false;
                        }
-                       sender = $(this).find(":input[name=sender]").val();
-                       text = $(this).find(":input[name=text]:enabled").val();
+                       var sender = $(this).find(":input[name=sender]").val();
+                       var text = $(this).find(":input[name=text]:enabled").val();
                        ajaxGet("createPost.ajax", { "formPassword": getFormPassword(), "sender": sender, "text": text }, function(data, textStatus) {
                                button.removeAttr("disabled");
                        });
@@ -1924,16 +1925,16 @@ $(document).ready(function() {
        /* ajaxify input field on “view Sone” page. */
        getTranslation("WebInterface.DefaultText.Message", function(defaultText) {
                registerInputTextareaSwap("#sone #post-message input[name=text]", defaultText, "text", false, false);
-               $("#sone #post-message .select-sender").css("display", "inline");
-               $("#sone #post-message .sender").hide();
-               $("#sone #post-message .select-sender button").click(function() {
-                       $("#sone #post-message .sender").show();
-                       $("#sone #post-message .select-sender").hide();
+               sone.find("#post-message .select-sender").css("display", "inline");
+               sone.find("#post-message .sender").hide();
+               sone.find("#post-message .select-sender button").click(function() {
+                       sone.find("#post-message .sender").show();
+                       sone.find("#post-message .select-sender").hide();
                        return false;
                });
-               $("#sone #post-message").submit(function() {
-                       sender = $(this).find(":input[name=sender]").val();
-                       text = $(this).find(":input[name=text]:enabled").val();
+               sone.find("#post-message").submit(function() {
+                       var sender = $(this).find(":input[name=sender]").val();
+                       var text = $(this).find(":input[name=text]:enabled").val();
                        ajaxGet("createPost.ajax", { "formPassword": getFormPassword(), "recipient": getShownSoneId(), "sender": sender, "text": text });
                        $(this).find(":input[name=sender]").val(getCurrentSoneId());
                        $(this).find(":input[name=text]:enabled").val("").blur();
@@ -1945,11 +1946,11 @@ $(document).ready(function() {
 
        /* Ajaxifies all posts. */
        /* calling getTranslation here will cache the necessary values. */
-       getTranslation("WebInterface.Confirmation.DeletePostButton", function(text) {
-               getTranslation("WebInterface.Confirmation.DeleteReplyButton", function(text) {
-                       getTranslation("WebInterface.DefaultText.Reply", function(text) {
-                               getTranslation("WebInterface.Button.Comment", function(text) {
-                                       $("#sone .post").each(function() {
+       getTranslation("WebInterface.Confirmation.DeletePostButton", function() {
+               getTranslation("WebInterface.Confirmation.DeleteReplyButton", function() {
+                       getTranslation("WebInterface.DefaultText.Reply", function() {
+                getTranslation("WebInterface.Button.Comment", function () {
+                    sone.find(".post").each(function() {
                                                ajaxifyPost(this);
                                        });
                                });
@@ -1958,8 +1959,8 @@ $(document).ready(function() {
        });
 
        /* update post times. */
-       postIds = [];
-       $("#sone .post").each(function() {
+       var postIds = [];
+       sone.find(".post").each(function() {
                postIds.push(getPostId(this));
        });
        updatePostTimes(postIds.join(","));
@@ -1967,15 +1968,15 @@ $(document).ready(function() {
        /* hides all replies but the latest two. */
        if (!isViewPostPage()) {
                getTranslation("WebInterface.ClickToShow.Replies", function(text) {
-                       $("#sone .post .replies").each(function() {
-                               allReplies = $(this).find(".reply");
+                       sone.find(".post .replies").each(function() {
+                               var allReplies = $(this).find(".reply");
                                if (allReplies.length > 2) {
-                                       newHidden = false;
-                                       for (replyIndex = 0; replyIndex < (allReplies.length - 2); ++replyIndex) {
+                                       var newHidden = false;
+                                       for (var replyIndex = 0; replyIndex < (allReplies.length - 2); ++replyIndex) {
                                                $(allReplies[replyIndex]).addClass("hidden");
                                                newHidden |= $(allReplies[replyIndex]).hasClass("new");
                                        }
-                                       clickToShowElement = $("<div></div>").addClass("click-to-show");
+                                       var clickToShowElement = $("<div></div>").addClass("click-to-show");
                                        if (newHidden) {
                                                clickToShowElement.addClass("new");
                                        }
@@ -1992,12 +1993,12 @@ $(document).ready(function() {
                });
        }
 
-       $("#sone .sone").each(function() {
+       sone.find(".sone").each(function() {
                ajaxifySone($(this));
        });
 
        /* process all existing notifications, ajaxify dismiss buttons. */
-       $("#sone #notification-area .notification").each(function() {
+       sone.find("#notification-area .notification").each(function() {
                ajaxifyNotification($(this));
        });
 
index ea583fc..9b31548 100644 (file)
@@ -2,7 +2,7 @@
 
        <h1><%= Page.DeleteAlbum.Page.Title|l10n|html></h1>
 
-       <p><%= Page.DeleteAlbum.Text.AlbumWillBeGone|l10n|replace needle=="{title}" replacementKey==album.title|html></p>
+       <p><%= Page.DeleteAlbum.Text.AlbumWillBeGone|l10n|replace needle=="{title}" replacement=album.title|html></p>
 
        <form method="post">
                <input type="hidden" name="formPassword" value="<% formPassword|html>" />
index a80680f..da8d032 100644 (file)
                                <div class="backlink"><a href="imageBrowser.html?sone=<%sone.id|html>"><%sone.niceName|html></a></div>
                        </div>
 
-                       <%include include/browseAlbums.html albums=sone.albums>
+                       <%include include/browseAlbums.html albums=sone.rootAlbum.albums>
 
                        <%if sone.local>
                                <div class="show-create-album hidden toggle-link"><a class="small-link">» <%= View.CreateAlbum.Title|l10n|html></a></div>
index dde0486..aa87ce8 100644 (file)
@@ -1,15 +1,17 @@
-<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="sender"><%= Page.Index.Label.Sender|l10n|html></label>
-       <div class="sender">
-               <select name="sender" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">
-                       <%foreach localSones localSone|sort>
-                               <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>
-       <textarea class="rip-out status-input" name="text"></textarea>
-       <button type="submit"><%= Page.Index.Button.Post|l10n|html></button>
-</form>
+<%ifnull !currentSone>
+    <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="sender"><%= Page.Index.Label.Sender|l10n|html></label>
+        <div class="sender">
+            <select name="sender" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">
+                <%foreach localSones localSone|sort>
+                    <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>
+        <textarea class="rip-out status-input" name="text"></textarea>
+        <button type="submit"><%= Page.Index.Button.Post|l10n|html></button>
+    </form>
+<%/if>
index 511cbf2..3d456d4 100644 (file)
@@ -67,7 +67,7 @@
                <%/first>
                <album>
                        <id><%album.id|xml></id>
-                       <%ifnull !album.parent>
+                       <%if !album.parent.root>
                        <parent><%album.parent.id|xml></parent>
                        <%/if>
                        <title><%album.title|xml></title>
index d760191..c15864f 100644 (file)
@@ -1,7 +1,7 @@
 <%if soneStatus|match value=="inserting">
-       Your Sone <a href="viewSone.html?sone=<%insertSone.id|html>"><%insertSone.niceName|html></a> is now being inserted.
+       <%= Notification.SoneIsInserting.Text|l10n 0=insertSone.id|parse>
 <%elseif soneStatus|match value=="inserted">
-       Your Sone <a href="viewSone.html?sone=<%insertSone.id|html>"><%insertSone.niceName|html></a> has been inserted in <%= Notification.SoneInsert.Duration|l10n 0=insertDuration>.
+       <%= Notification.SoneIsInserted.Text|l10n 0=insertSone.id 1=insertDuration|parse>
 <%elseif soneStatus|match value=="insert-aborted">
-       Inserting your Sone <a href="viewSone.html?sone=<%insertSone.id|html>"><%insertSone.niceName|html></a> has failed.
-<%/if>
\ No newline at end of file
+       <%= Notification.SoneInsertAborted.Text|l10n 0=insertSone.id|parse>
+<%/if>
diff --git a/src/test/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPageTest.java b/src/test/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPageTest.java
new file mode 100644 (file)
index 0000000..1db89d0
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * © 2013 xplosion interactive
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.sone.web.page.FreenetRequest;
+
+import freenet.clients.http.HTTPRequestImpl;
+import freenet.support.api.HTTPRequest;
+import org.junit.Test;
+
+/**
+ * Tests for {@link BookmarkAjaxPage}.
+ *
+ * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+ */
+public class BookmarkAjaxPageTest {
+
+       @Test
+       public void testBookmarkingExistingPost() throws URISyntaxException {
+               /* create mocks. */
+               Core core = mock(Core.class);
+               WebInterface webInterface = mock(WebInterface.class);
+               when(webInterface.getCore()).thenReturn(core);
+               HTTPRequest httpRequest = new HTTPRequestImpl(new URI("/ajax/bookmark.ajax?post=abc"), "GET");
+               FreenetRequest request = mock(FreenetRequest.class);
+               when(request.getHttpRequest()).thenReturn(httpRequest);
+
+               /* create JSON page. */
+               BookmarkAjaxPage bookmarkAjaxPage = new BookmarkAjaxPage(webInterface);
+               JsonReturnObject jsonReturnObject = bookmarkAjaxPage.createJsonObject(request);
+
+               /* verify response. */
+               assertThat(jsonReturnObject, notNullValue());
+               assertThat(jsonReturnObject.isSuccess(), is(true));
+
+               /* verify behaviour. */
+               verify(core, times(1)).bookmarkPost(anyString());
+               verify(core).bookmarkPost("abc");
+       }
+
+       @Test
+       public void testBookmarkingMissingPost() throws URISyntaxException {
+               /* create mocks. */
+               Core core = mock(Core.class);
+               WebInterface webInterface = mock(WebInterface.class);
+               when(webInterface.getCore()).thenReturn(core);
+               HTTPRequest httpRequest = new HTTPRequestImpl(new URI("/ajax/bookmark.ajax"), "GET");
+               FreenetRequest request = mock(FreenetRequest.class);
+               when(request.getHttpRequest()).thenReturn(httpRequest);
+
+               /* create JSON page. */
+               BookmarkAjaxPage bookmarkAjaxPage = new BookmarkAjaxPage(webInterface);
+               JsonReturnObject jsonReturnObject = bookmarkAjaxPage.createJsonObject(request);
+
+               /* verify response. */
+               assertThat(jsonReturnObject, notNullValue());
+               assertThat(jsonReturnObject.isSuccess(), is(false));
+               assertThat(((JsonErrorReturnObject) jsonReturnObject).getError(), is("invalid-post-id"));
+
+               /* verify behaviour. */
+               verify(core, never()).bookmarkPost(anyString());
+       }
+
+}