From: David ‘Bombe’ Roden Date: Wed, 11 May 2011 04:12:43 +0000 (+0200) Subject: Merge branch 'fcp-interface' into next X-Git-Tag: 0.6.5^2~39 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=aa94dcb712392b69cb431d1637e4948688d15791;hp=2fef2b7dae1fb428e1b060b740b253189dc22bf7 Merge branch 'fcp-interface' into next This fixes #21. --- diff --git a/pom.xml b/pom.xml index 21f47ee..f88c9c7 100644 --- a/pom.xml +++ b/pom.xml @@ -2,12 +2,12 @@ 4.0.0 net.pterodactylus sone - 0.6 + 0.6.4 net.pterodactylus utils - 0.9.3-SNAPSHOT + 0.9.6-SNAPSHOT junit diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index cfe53ea..765b672 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -25,6 +25,9 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; @@ -50,6 +53,7 @@ import net.pterodactylus.util.config.Configuration; import net.pterodactylus.util.config.ConfigurationException; import net.pterodactylus.util.logging.Logging; import net.pterodactylus.util.number.Numbers; +import net.pterodactylus.util.validation.IntegerRangeValidator; import net.pterodactylus.util.validation.Validation; import net.pterodactylus.util.version.Version; import freenet.keys.FreenetURI; @@ -108,6 +112,9 @@ public class Core implements IdentityListener, UpdateListener { /** The Sone downloader. */ private final SoneDownloader soneDownloader; + /** Sone downloader thread-pool. */ + private final ExecutorService soneDownloaders = Executors.newFixedThreadPool(10); + /** The update checker. */ private final UpdateChecker updateChecker; @@ -891,6 +898,9 @@ public class Core implements IdentityListener, UpdateListener { return null; } Sone sone = addLocalSone(ownIdentity); + sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption(false)); + sone.addFriend("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"); + saveSone(sone); return sone; } @@ -930,15 +940,15 @@ public class Core implements IdentityListener, UpdateListener { remoteSones.put(identity.getId(), sone); soneDownloader.addSone(sone); setSoneStatus(sone, SoneStatus.unknown); - new Thread(new Runnable() { + soneDownloaders.execute(new Runnable() { @Override @SuppressWarnings("synthetic-access") public void run() { - soneDownloader.fetchSone(sone); + soneDownloader.fetchSone(sone, sone.getRequestUri()); } - }, "Sone Downloader").start(); + }); return sone; } } @@ -1184,6 +1194,9 @@ public class Core implements IdentityListener, UpdateListener { return; } + /* initialize options. */ + sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption(false)); + /* load Sone. */ String sonePrefix = "Sone/" + sone.getId(); Long soneTime = configuration.getLongValue(sonePrefix + "/Time").getValue(null); @@ -1284,7 +1297,6 @@ public class Core implements IdentityListener, UpdateListener { } /* load options. */ - sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption(false)); sone.getOptions().getBooleanOption("AutoFollow").set(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null)); /* if we’re still here, Sone was loaded successfully. */ @@ -1508,6 +1520,7 @@ public class Core implements IdentityListener, UpdateListener { synchronized (posts) { posts.remove(post.getId()); } + coreListenerManager.firePostRemoved(post); synchronized (newPosts) { markPostKnown(post); knownPosts.remove(post.getId()); @@ -1704,6 +1717,7 @@ public class Core implements IdentityListener, UpdateListener { configuration.getIntValue("Option/ConfigurationVersion").setValue(0); configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal()); configuration.getIntValue("Option/PostsPerPage").setValue(options.getIntegerOption("PostsPerPage").getReal()); + configuration.getBooleanValue("Option/RequireFullAccess").setValue(options.getBooleanOption("RequireFullAccess").getReal()); configuration.getIntValue("Option/PositiveTrust").setValue(options.getIntegerOption("PositiveTrust").getReal()); configuration.getIntValue("Option/NegativeTrust").setValue(options.getIntegerOption("NegativeTrust").getReal()); configuration.getStringValue("Option/TrustComment").setValue(options.getStringOption("TrustComment").getReal()); @@ -1771,7 +1785,7 @@ public class Core implements IdentityListener, UpdateListener { @SuppressWarnings("unchecked") private void loadConfiguration() { /* create options. */ - options.addIntegerOption("InsertionDelay", new DefaultOption(60, new OptionWatcher() { + options.addIntegerOption("InsertionDelay", new DefaultOption(60, new IntegerRangeValidator(0, Integer.MAX_VALUE), new OptionWatcher() { @Override public void optionChanged(Option option, Integer oldValue, Integer newValue) { @@ -1779,9 +1793,10 @@ public class Core implements IdentityListener, UpdateListener { } })); - options.addIntegerOption("PostsPerPage", new DefaultOption(25)); - options.addIntegerOption("PositiveTrust", new DefaultOption(75)); - options.addIntegerOption("NegativeTrust", new DefaultOption(-25)); + options.addIntegerOption("PostsPerPage", new DefaultOption(10, new IntegerRangeValidator(1, Integer.MAX_VALUE))); + options.addBooleanOption("RequireFullAccess", new DefaultOption(false)); + options.addIntegerOption("PositiveTrust", new DefaultOption(75, new IntegerRangeValidator(0, 100))); + options.addIntegerOption("NegativeTrust", new DefaultOption(-25, new IntegerRangeValidator(-100, 100))); options.addStringOption("TrustComment", new DefaultOption("Set from Sone Web Interface")); options.addBooleanOption("ActivateFcpInterface", new DefaultOption(false, new OptionWatcher() { @@ -1815,10 +1830,11 @@ public class Core implements IdentityListener, UpdateListener { return; } - options.getIntegerOption("InsertionDelay").set(configuration.getIntValue("Option/InsertionDelay").getValue(null)); - options.getIntegerOption("PostsPerPage").set(configuration.getIntValue("Option/PostsPerPage").getValue(null)); - options.getIntegerOption("PositiveTrust").set(configuration.getIntValue("Option/PositiveTrust").getValue(null)); - options.getIntegerOption("NegativeTrust").set(configuration.getIntValue("Option/NegativeTrust").getValue(null)); + loadConfigurationValue("InsertionDelay"); + loadConfigurationValue("PostsPerPage"); + options.getBooleanOption("RequireFullAccess").set(configuration.getBooleanValue("Option/RequireFullAccess").getValue(null)); + loadConfigurationValue("PositiveTrust"); + loadConfigurationValue("NegativeTrust"); options.getStringOption("TrustComment").set(configuration.getStringValue("Option/TrustComment").getValue(null)); options.getBooleanOption("ActivateFcpInterface").set(configuration.getBooleanValue("Option/ActivateFcpInterface").getValue(null)); options.getIntegerOption("FcpFullAccessRequired").set(configuration.getIntValue("Option/FcpFullAccessRequired").getValue(null)); @@ -1875,6 +1891,21 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Loads an {@link Integer} configuration value for the option with the + * given name, logging validation failures. + * + * @param optionName + * The name of the option to load + */ + private void loadConfigurationValue(String optionName) { + try { + options.getIntegerOption(optionName).set(configuration.getIntValue("Option/" + optionName).getValue(null)); + } catch (IllegalArgumentException iae1) { + logger.log(Level.WARNING, "Invalid value for " + optionName + " in configuration, using default."); + } + } + + /** * Generate a Sone URI from the given URI and latest edition. * * @param uriString @@ -1938,6 +1969,7 @@ public class Core implements IdentityListener, UpdateListener { public void run() { Sone sone = getRemoteSone(identity.getId()); sone.setIdentity(identity); + sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), sone.getLatestEdition())); soneDownloader.addSone(sone); soneDownloader.fetchSone(sone); } @@ -1950,6 +1982,48 @@ public class Core implements IdentityListener, UpdateListener { @Override public void identityRemoved(OwnIdentity ownIdentity, Identity identity) { trustedIdentities.get(ownIdentity).remove(identity); + boolean foundIdentity = false; + for (Entry> trustedIdentity : trustedIdentities.entrySet()) { + if (trustedIdentity.getKey().equals(ownIdentity)) { + continue; + } + if (trustedIdentity.getValue().contains(identity)) { + foundIdentity = true; + } + } + if (foundIdentity) { + /* some local identity still trusts this identity, don’t remove. */ + return; + } + Sone sone = getSone(identity.getId(), false); + if (sone == null) { + /* TODO - we don’t have the Sone anymore. should this happen? */ + return; + } + synchronized (posts) { + synchronized (newPosts) { + for (Post post : sone.getPosts()) { + posts.remove(post.getId()); + newPosts.remove(post.getId()); + coreListenerManager.firePostRemoved(post); + } + } + } + synchronized (replies) { + synchronized (newReplies) { + for (Reply reply : sone.getReplies()) { + replies.remove(reply.getId()); + newReplies.remove(reply.getId()); + coreListenerManager.fireReplyRemoved(reply); + } + } + } + synchronized (remoteSones) { + remoteSones.remove(identity.getId()); + } + synchronized (newSones) { + newSones.remove(identity.getId()); + } } // @@ -1995,6 +2069,18 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Validates the given insertion delay. + * + * @param insertionDelay + * The insertion delay to validate + * @return {@code true} if the given insertion delay was valid, {@code + * false} otherwise + */ + public boolean validateInsertionDelay(Integer insertionDelay) { + return options.getIntegerOption("InsertionDelay").validate(insertionDelay); + } + + /** * Sets the insertion delay * * @param insertionDelay @@ -2017,6 +2103,18 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Validates the number of posts per page. + * + * @param postsPerPage + * The number of posts per page + * @return {@code true} if the number of posts per page was valid, + * {@code false} otherwise + */ + public boolean validatePostsPerPage(Integer postsPerPage) { + return options.getIntegerOption("PostsPerPage").validate(postsPerPage); + } + + /** * Sets the number of posts to show per page. * * @param postsPerPage @@ -2029,6 +2127,27 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Returns whether Sone requires full access to be even visible. + * + * @return {@code true} if Sone requires full access, {@code false} + * otherwise + */ + public boolean isRequireFullAccess() { + return options.getBooleanOption("RequireFullAccess").get(); + } + + /** + * Sets whether Sone requires full access to be even visible. + * + * @param requireFullAccess + * {@code true} if Sone requires full access, {@code false} + * otherwise + */ + public void setRequireFullAccess(Boolean requireFullAccess) { + options.getBooleanOption("RequireFullAccess").set(requireFullAccess); + } + + /** * Returns the positive trust. * * @return The positive trust @@ -2038,6 +2157,18 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Validates the positive trust. + * + * @param positiveTrust + * The positive trust to validate + * @return {@code true} if the positive trust was valid, {@code false} + * otherwise + */ + public boolean validatePositiveTrust(Integer positiveTrust) { + return options.getIntegerOption("PositiveTrust").validate(positiveTrust); + } + + /** * Sets the positive trust. * * @param positiveTrust @@ -2060,6 +2191,18 @@ public class Core implements IdentityListener, UpdateListener { } /** + * Validates the negative trust. + * + * @param negativeTrust + * The negative trust to validate + * @return {@code true} if the negative trust was valid, {@code false} + * otherwise + */ + public boolean validateNegativeTrust(Integer negativeTrust) { + return options.getIntegerOption("NegativeTrust").validate(negativeTrust); + } + + /** * Sets the negative trust. * * @param negativeTrust diff --git a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java index e22f407..20c8da7 100644 --- a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java +++ b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java @@ -1,5 +1,5 @@ /* - * FreenetSone - FreenetInterface.java - Copyright © 2010 David Roden + * Sone - FreenetInterface.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/core/Options.java b/src/main/java/net/pterodactylus/sone/core/Options.java index b7ece81..7392da2 100644 --- a/src/main/java/net/pterodactylus/sone/core/Options.java +++ b/src/main/java/net/pterodactylus/sone/core/Options.java @@ -1,3 +1,20 @@ +/* + * Sone - Options.java - Copyright © 2010 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package net.pterodactylus.sone.core; import java.util.ArrayList; @@ -7,6 +24,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import net.pterodactylus.util.validation.Validator; + /** * Stores various options that influence Sone’s behaviour. * @@ -47,12 +66,26 @@ public class Options { public T getReal(); /** + * Validates the given value. Note that {@code null} is always a valid + * value! + * + * @param value + * The value to validate + * @return {@code true} if this option does not have a {@link Validator} + * , or the {@link Validator} validates this object, {@code + * false} otherwise + */ + public boolean validate(T value); + + /** * Sets the current value of the option. * * @param value * The new value of the option + * @throws IllegalArgumentException + * if the value is not valid for this option */ - public void set(T value); + public void set(T value) throws IllegalArgumentException; } @@ -96,6 +129,9 @@ public class Options { /** The current value. */ private volatile T value; + /** The validator. */ + private Validator validator; + /** The option watcher. */ private final List> optionWatchers = new ArrayList>(); @@ -108,7 +144,22 @@ public class Options { * The option watchers */ public DefaultOption(T defaultValue, OptionWatcher... optionWatchers) { + this(defaultValue, null, optionWatchers); + } + + /** + * Creates a new default option. + * + * @param defaultValue + * The default value of the option + * @param validator + * The validator for value validation + * @param optionWatchers + * The option watchers + */ + public DefaultOption(T defaultValue, Validator validator, OptionWatcher... optionWatchers) { this.defaultValue = defaultValue; + this.validator = validator; this.optionWatchers.addAll(Arrays.asList(optionWatchers)); } @@ -142,8 +193,18 @@ public class Options { /** * {@inheritDoc} */ + public boolean validate(T value) { + return (validator == null) || (value == null) || validator.validate(value); + } + + /** + * {@inheritDoc} + */ @Override public void set(T value) { + if ((value != null) && (validator != null) && (!validator.validate(value))) { + throw new IllegalArgumentException("New Value (" + value + ") could not be validated."); + } T oldValue = this.value; this.value = value; if (!get().equals(oldValue)) { diff --git a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java index 7022f5e..56c1e4a 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java @@ -119,7 +119,7 @@ public class SoneDownloader extends AbstractService { * The Sone to fetch */ public void fetchSone(Sone sone) { - fetchSone(sone, sone.getRequestUri()); + fetchSone(sone, sone.getRequestUri().sskForUSK()); } /** diff --git a/src/main/java/net/pterodactylus/sone/core/SoneException.java b/src/main/java/net/pterodactylus/sone/core/SoneException.java index 74f257d..c786661 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneException.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneException.java @@ -1,5 +1,5 @@ /* - * FreenetSone - SoneException.java - Copyright © 2010 David Roden + * Sone - SoneException.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/core/SoneInserter.java b/src/main/java/net/pterodactylus/sone/core/SoneInserter.java index 15e054f..0abebc3 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneInserter.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneInserter.java @@ -1,5 +1,5 @@ /* - * FreenetSone - SoneInserter.java - Copyright © 2010 David Roden + * Sone - SoneInserter.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -229,7 +229,6 @@ public class SoneInserter extends AbstractService { synchronized (sone) { if (lastInsertFingerprint.equals(sone.getFingerprint())) { logger.log(Level.FINE, "Sone “%s” was not modified further, resetting counter…", new Object[] { sone }); - core.saveSone(sone); lastModificationTime = 0; modified = false; } @@ -248,7 +247,7 @@ public class SoneInserter extends AbstractService { * * @author David ‘Bombe’ Roden */ - private static class InsertInformation { + private class InsertInformation { /** All properties of the Sone, copied for thread safety. */ private final Map soneProperties = new HashMap(); @@ -347,6 +346,7 @@ public class SoneInserter extends AbstractService { TemplateContext templateContext = templateContextFactory.createTemplateContext(); templateContext.set("currentSone", soneProperties); + templateContext.set("currentEdition", core.getUpdateChecker().getLatestEdition()); templateContext.set("version", SonePlugin.VERSION); StringWriter writer = new StringWriter(); StringBucket bucket = null; diff --git a/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java b/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java index e07e160..9c50f37 100644 --- a/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java +++ b/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java @@ -49,7 +49,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 = 25; + private static final int LATEST_EDITION = 36; /** The Freenet interface. */ private final FreenetInterface freenetInterface; diff --git a/src/main/java/net/pterodactylus/sone/data/Post.java b/src/main/java/net/pterodactylus/sone/data/Post.java index bb431d0..a28bf9f 100644 --- a/src/main/java/net/pterodactylus/sone/data/Post.java +++ b/src/main/java/net/pterodactylus/sone/data/Post.java @@ -1,5 +1,5 @@ /* - * FreenetSone - StatusUpdate.java - Copyright © 2010 David Roden + * Sone - Post.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/data/Profile.java b/src/main/java/net/pterodactylus/sone/data/Profile.java index 8d4306d..6b44d10 100644 --- a/src/main/java/net/pterodactylus/sone/data/Profile.java +++ b/src/main/java/net/pterodactylus/sone/data/Profile.java @@ -1,5 +1,5 @@ /* - * FreenetSone - Profile.java - Copyright © 2010 David Roden + * Sone - Profile.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index 2dd4bc1..715d95b 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -1,5 +1,5 @@ /* - * FreenetSone - Sone.java - Copyright © 2010 David Roden + * Sone - Sone.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java b/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java index ef1a183..fa7385c 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java +++ b/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java @@ -1,5 +1,5 @@ /* - * FreenetSone - L10nFilter.java - Copyright © 2010 David Roden + * Sone - L10nFilter.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/freenet/PluginStoreConfigurationBackend.java b/src/main/java/net/pterodactylus/sone/freenet/PluginStoreConfigurationBackend.java index 5e30de1..5aa68cf 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/PluginStoreConfigurationBackend.java +++ b/src/main/java/net/pterodactylus/sone/freenet/PluginStoreConfigurationBackend.java @@ -1,5 +1,5 @@ /* - * FreenetSone - PluginStoreConfigurationBackend.java - Copyright © 2010 David Roden + * Sone - PluginStoreConfigurationBackend.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/freenet/StringBucket.java b/src/main/java/net/pterodactylus/sone/freenet/StringBucket.java index ebe036a..2993b2f 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/StringBucket.java +++ b/src/main/java/net/pterodactylus/sone/freenet/StringBucket.java @@ -1,5 +1,5 @@ /* - * FreenetSone - TemplateBucket.java - Copyright © 2010 David Roden + * Sone - StringBucket.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java index 71710e4..3da08f3 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java +++ b/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java @@ -19,7 +19,6 @@ package net.pterodactylus.sone.freenet.plugin; import java.util.EventListener; - import freenet.support.SimpleFieldSet; import freenet.support.api.Bucket; diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java index 3c04823..46f81d6 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java @@ -64,6 +64,7 @@ public class DefaultIdentity implements Identity { private final Map properties = Collections.synchronizedMap(new HashMap()); /** Cached trust. */ + /* synchronize on itself. */ private final WritableCache trustCache = new MemoryCache(new ValueRetriever() { @Override @@ -249,7 +250,9 @@ public class DefaultIdentity implements Identity { @Override public Trust getTrust(OwnIdentity ownIdentity) { try { - return trustCache.get(ownIdentity); + synchronized (trustCache) { + return trustCache.get(ownIdentity); + } } catch (CacheException ce1) { logger.log(Level.WARNING, "Could not get trust for OwnIdentity: " + ownIdentity, ce1); return null; @@ -265,7 +268,9 @@ public class DefaultIdentity implements Identity { * The trust received for this identity */ void setTrustPrivate(OwnIdentity ownIdentity, Trust trust) { - trustCache.put(ownIdentity, trust); + synchronized (trustCache) { + trustCache.put(ownIdentity, trust); + } } // diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java index 98ba2c7..3c97e55 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java @@ -180,21 +180,18 @@ public class IdentityManager extends AbstractService { Map> currentIdentities = new HashMap>(); Map currentOwnIdentities = new HashMap(); + Set ownIdentities = null; + boolean identitiesLoaded = false; try { /* get all identities with the wanted context from WoT. */ - Set ownIdentities = webOfTrustConnector.loadAllOwnIdentities(); + ownIdentities = webOfTrustConnector.loadAllOwnIdentities(); - /* check for changes. */ - for (OwnIdentity ownIdentity : ownIdentities) { - currentOwnIdentities.put(ownIdentity.getId(), ownIdentity); - } - checkOwnIdentities(currentOwnIdentities); - - /* now filter for context and get all identities. */ + /* load trusted identities. */ for (OwnIdentity ownIdentity : ownIdentities) { if ((context != null) && !ownIdentity.hasContext(context)) { continue; } + currentOwnIdentities.put(ownIdentity.getId(), ownIdentity); Set trustedIdentities = webOfTrustConnector.loadTrustedIdentities(ownIdentity, context); Map identities = new HashMap(); @@ -202,6 +199,19 @@ public class IdentityManager extends AbstractService { for (Identity identity : trustedIdentities) { identities.put(identity.getId(), identity); } + } + identitiesLoaded = true; + } catch (WebOfTrustException wote1) { + logger.log(Level.WARNING, "WoT has disappeared!", wote1); + } + + if (identitiesLoaded) { + + /* check for changes. */ + checkOwnIdentities(currentOwnIdentities); + + /* now check for changes in remote identities. */ + for (OwnIdentity ownIdentity : currentOwnIdentities.values()) { /* find new identities. */ for (Identity currentIdentity : currentIdentities.get(ownIdentity).values()) { @@ -258,13 +268,10 @@ public class IdentityManager extends AbstractService { } } } - - /* remember the current set of identities. */ - oldIdentities = currentIdentities; } - } catch (WebOfTrustException wote1) { - logger.log(Level.WARNING, "WoT has disappeared!", wote1); + /* remember the current set of identities. */ + oldIdentities = currentIdentities; } /* wait a minute before checking again. */ diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java index 47e6d33..6259fd3 100644 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java +++ b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java @@ -1,5 +1,5 @@ /* - * FreenetSone - SonePlugin.java - Copyright © 2010 David Roden + * Sone - SonePlugin.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -83,7 +83,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr } /** The version. */ - public static final Version VERSION = new Version(0, 6); + public static final Version VERSION = new Version(0, 6, 4); /** The logger. */ private static final Logger logger = Logging.getLogger(SonePlugin.class); diff --git a/src/main/java/net/pterodactylus/sone/notify/ListNotification.java b/src/main/java/net/pterodactylus/sone/notify/ListNotification.java index c66d376..3a79c93 100644 --- a/src/main/java/net/pterodactylus/sone/notify/ListNotification.java +++ b/src/main/java/net/pterodactylus/sone/notify/ListNotification.java @@ -101,7 +101,8 @@ public class ListNotification extends TemplateNotification { } /** - * Sets the elements to show in this notification. + * Sets the elements to show in this notification. This method will not call + * {@link #touch()}. * * @param elements * The elements to show @@ -109,7 +110,6 @@ public class ListNotification extends TemplateNotification { public void setElements(Collection elements) { this.elements.clear(); this.elements.addAll(elements); - touch(); } /** diff --git a/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java b/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java index 2a98ec8..2362d6a 100644 --- a/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java +++ b/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java @@ -24,7 +24,10 @@ import java.util.List; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.freenet.wot.OwnIdentity; +import net.pterodactylus.sone.freenet.wot.Trust; import net.pterodactylus.util.notify.Notification; +import net.pterodactylus.util.validation.Validation; /** * Filter for {@link ListNotification}s. @@ -47,28 +50,25 @@ public class ListNotificationFilters { * The current Sone, or {@code null} if not logged in * @return The filtered notifications */ - public static List filterNotifications(List notifications, Sone currentSone) { - ListNotification newPostNotification = getNotification(notifications, "new-post-notification", Post.class); - if (newPostNotification != null) { - ListNotification filteredNotification = filterNewPostNotification(newPostNotification, currentSone); - int notificationIndex = notifications.indexOf(newPostNotification); - if (filteredNotification == null) { - notifications.remove(notificationIndex); - } else { - notifications.set(notificationIndex, filteredNotification); - } - } - ListNotification newReplyNotification = getNotification(notifications, "new-replies-notification", Reply.class); - if (newReplyNotification != null) { - ListNotification filteredNotification = filterNewReplyNotification(newReplyNotification, currentSone); - int notificationIndex = notifications.indexOf(newReplyNotification); - if (filteredNotification == null) { - notifications.remove(notificationIndex); + @SuppressWarnings("unchecked") + public static List filterNotifications(Collection notifications, Sone currentSone) { + List filteredNotifications = new ArrayList(); + for (Notification notification : notifications) { + if (notification.getId().equals("new-post-notification")) { + ListNotification filteredNotification = filterNewPostNotification((ListNotification) notification, currentSone); + if (filteredNotification != null) { + filteredNotifications.add(filteredNotification); + } + } else if (notification.getId().equals("new-replies-notification")) { + ListNotification filteredNotification = filterNewReplyNotification((ListNotification) notification, currentSone); + if (filteredNotification != null) { + filteredNotifications.add(filteredNotification); + } } else { - notifications.set(notificationIndex, filteredNotification); + filteredNotifications.add(notification); } } - return notifications; + return filteredNotifications; } /** @@ -84,13 +84,13 @@ public class ListNotificationFilters { * @return The filtered new-post notification, or {@code null} if the * notification should be removed */ - private static ListNotification filterNewPostNotification(ListNotification newPostNotification, Sone currentSone) { + public static ListNotification filterNewPostNotification(ListNotification newPostNotification, Sone currentSone) { if (currentSone == null) { return null; } List newPosts = new ArrayList(); for (Post post : newPostNotification.getElements()) { - if (currentSone.hasFriend(post.getSone().getId()) || currentSone.equals(post.getSone()) || currentSone.equals(post.getRecipient())) { + if (isPostVisible(currentSone, post)) { newPosts.add(post); } } @@ -119,13 +119,13 @@ public class ListNotificationFilters { * @return The filtered new-reply notification, or {@code null} if the * notification should be removed */ - private static ListNotification filterNewReplyNotification(ListNotification newReplyNotification, Sone currentSone) { + public static ListNotification filterNewReplyNotification(ListNotification newReplyNotification, Sone currentSone) { if (currentSone == null) { return null; } List newReplies = new ArrayList(); for (Reply reply : newReplyNotification.getElements()) { - if (currentSone.hasFriend(reply.getPost().getSone().getId()) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient())) { + if (isReplyVisible(currentSone, reply)) { newReplies.add(reply); } } @@ -141,29 +141,94 @@ public class ListNotificationFilters { } /** - * Finds the notification with the given ID in the list of notifications and - * returns it. + * Checks whether a post is visible to the given Sone. A post is not + * considered visible if one of the following statements is true: + *
    + *
  • The post does not have a Sone.
  • + *
  • The Sone of the post is not the given Sone, the given Sone does not + * follow the post’s Sone, and the given Sone is not the recipient of the + * post.
  • + *
  • The trust relationship between the two Sones can not be retrieved.
  • + *
  • The given Sone has explicitely assigned negative trust to the post’s + * Sone.
  • + *
  • The given Sone has not explicitely assigned negative trust to the + * post’s Sone but the implicit trust is negative.
  • + *
  • The post’s {@link Post#getTime() time} is in the future.
  • + *
+ * If none of these statements is true the post is considered visible. * - * @param - * The type of the item in the notification - * @param notifications - * The notification to search - * @param notificationId - * The ID of the requested notification - * @param notificationElementClass - * The class of the notification item - * @return The requested notification, or {@code null} if no notification - * with the given ID could be found + * @param sone + * The Sone that checks for a post’s visibility + * @param post + * The post to check for visibility + * @return {@code true} if the post is considered visible, {@code false} + * otherwise */ - @SuppressWarnings("unchecked") - private static ListNotification getNotification(Collection notifications, String notificationId, Class notificationElementClass) { - for (Notification notification : notifications) { - if (!notificationId.equals(notification.getId())) { - continue; + public static boolean isPostVisible(Sone sone, Post post) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Post", post).check().isNotNull("Sone’s Identity", sone.getIdentity()).check().isInstanceOf("Sone’s Identity", sone.getIdentity(), OwnIdentity.class).check(); + Sone postSone = post.getSone(); + if (postSone == null) { + return false; + } + Trust trust = postSone.getIdentity().getTrust((OwnIdentity) sone.getIdentity()); + if (trust != null) { + if ((trust.getExplicit() != null) && (trust.getExplicit() < 0)) { + return false; + } + if ((trust.getExplicit() == null) && (trust.getImplicit() != null) && (trust.getImplicit() < 0)) { + return false; } - return (ListNotification) notification; + } else { + return false; + } + if ((!postSone.equals(sone)) && !sone.hasFriend(postSone.getId()) && !sone.equals(post.getRecipient())) { + return false; + } + if (post.getTime() > System.currentTimeMillis()) { + return false; + } + return true; + } + + /** + * Checks whether a reply is visible to the given Sone. A reply is not + * considered visible if one of the following statements is true: + *
    + *
  • The reply does not have a post.
  • + *
  • The reply’s post does not have a Sone.
  • + *
  • The Sone of the reply’s post is not the given Sone, the given Sone + * does not follow the reply’s post’s Sone, and the given Sone is not the + * recipient of the reply’s post.
  • + *
  • The trust relationship between the two Sones can not be retrieved.
  • + *
  • The given Sone has explicitely assigned negative trust to the post’s + * Sone.
  • + *
  • The given Sone has not explicitely assigned negative trust to the + * reply’s post’s Sone but the implicit trust is negative.
  • + *
  • The reply’s post’s {@link Post#getTime() time} is in the future.
  • + *
  • The reply’s {@link Reply#getTime() time} is in the future.
  • + *
+ * If none of these statements is true the reply is considered visible. + * + * @param sone + * The Sone that checks for a post’s visibility + * @param reply + * The reply to check for visibility + * @return {@code true} if the reply is considered visible, {@code false} + * otherwise + */ + public static boolean isReplyVisible(Sone sone, Reply reply) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Reply", reply).check().isNotNull("Sone’s Identity", sone.getIdentity()).check().isInstanceOf("Sone’s Identity", sone.getIdentity(), OwnIdentity.class).check(); + Post post = reply.getPost(); + if (post == null) { + return false; + } + if (!isPostVisible(sone, post)) { + return false; + } + if (reply.getTime() > System.currentTimeMillis()) { + return false; } - return null; + return true; } } diff --git a/src/main/java/net/pterodactylus/sone/template/NotificationManagerAccessor.java b/src/main/java/net/pterodactylus/sone/template/NotificationManagerAccessor.java deleted file mode 100644 index c4468ea..0000000 --- a/src/main/java/net/pterodactylus/sone/template/NotificationManagerAccessor.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Sone - NotificationManagerAccessor.java - Copyright © 2010 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.template; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.notify.ListNotificationFilters; -import net.pterodactylus.util.notify.Notification; -import net.pterodactylus.util.notify.NotificationManager; -import net.pterodactylus.util.template.ReflectionAccessor; -import net.pterodactylus.util.template.TemplateContext; - -/** - * Adds additional properties to a {@link NotificationManager}. - *
- *
all
- *
Returns all notifications, sorted by creation time, oldest first.
- *
new
- *
Returns all changed notifications, sorted by last updated time, newest - * first.
- *
- * - * @author David ‘Bombe’ Roden - */ -public class NotificationManagerAccessor extends ReflectionAccessor { - - /** - * {@inheritDoc} - */ - @Override - public Object get(TemplateContext templateContext, Object object, String member) { - NotificationManager notificationManager = (NotificationManager) object; - if ("all".equals(member)) { - List notifications = ListNotificationFilters.filterNotifications(new ArrayList(notificationManager.getNotifications()), (Sone) templateContext.get("currentSone")); - Collections.sort(notifications, Notification.CREATED_TIME_SORTER); - return notifications; - } - return super.get(templateContext, object, member); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/template/ParserFilter.java b/src/main/java/net/pterodactylus/sone/template/ParserFilter.java index 477e972..db945b5 100644 --- a/src/main/java/net/pterodactylus/sone/template/ParserFilter.java +++ b/src/main/java/net/pterodactylus/sone/template/ParserFilter.java @@ -25,6 +25,7 @@ import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.text.FreenetLinkParser; import net.pterodactylus.sone.text.FreenetLinkParserContext; +import net.pterodactylus.sone.web.page.Page.Request; import net.pterodactylus.util.template.Filter; import net.pterodactylus.util.template.TemplateContext; import net.pterodactylus.util.template.TemplateContextFactory; @@ -70,7 +71,7 @@ public class ParserFilter implements Filter { if (sone == null) { sone = core.getSone(soneKey, false); } - FreenetLinkParserContext context = new FreenetLinkParserContext(sone); + FreenetLinkParserContext context = new FreenetLinkParserContext((Request) templateContext.get("request"), sone); try { return linkParser.parse(context, new StringReader(text)); } catch (IOException ioe1) { diff --git a/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java b/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java index 23b2227..76fdcc1 100644 --- a/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java @@ -25,6 +25,8 @@ import net.pterodactylus.sone.core.Core.SoneStatus; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.freenet.wot.Trust; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage; import net.pterodactylus.util.logging.Logging; import net.pterodactylus.util.template.Accessor; import net.pterodactylus.util.template.ReflectionAccessor; @@ -97,6 +99,8 @@ public class SoneAccessor extends ReflectionAccessor { return core.isNewSone(sone.getId()); } else if (member.equals("locked")) { return core.isLocked(sone); + } else if (member.equals("lastUpdatedText")) { + return GetTimesAjaxPage.getTime((WebInterface) templateContext.get("webInterface"), System.currentTimeMillis() - sone.getTime()); } else if (member.equals("trust")) { Sone currentSone = (Sone) templateContext.get("currentSone"); if (currentSone == null) { diff --git a/src/main/java/net/pterodactylus/sone/text/FreenetLinkParser.java b/src/main/java/net/pterodactylus/sone/text/FreenetLinkParser.java index cea4452..7cb44ce 100644 --- a/src/main/java/net/pterodactylus/sone/text/FreenetLinkParser.java +++ b/src/main/java/net/pterodactylus/sone/text/FreenetLinkParser.java @@ -253,7 +253,7 @@ public class FreenetLinkParser implements Parser { } else if (linkType == LinkType.POST) { String postId = link.substring(7); Post post = core.getPost(postId, false); - if (post != null) { + if ((post != null) && (post.getSone() != null)) { String postText = post.getText(); postText = postText.substring(0, Math.min(postText.length(), 20)) + "…"; Sone postSone = post.getSone(); diff --git a/src/main/java/net/pterodactylus/sone/text/FreenetLinkParserContext.java b/src/main/java/net/pterodactylus/sone/text/FreenetLinkParserContext.java index e524d91..0712fc0 100644 --- a/src/main/java/net/pterodactylus/sone/text/FreenetLinkParserContext.java +++ b/src/main/java/net/pterodactylus/sone/text/FreenetLinkParserContext.java @@ -18,6 +18,7 @@ package net.pterodactylus.sone.text; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.page.Page.Request; /** * {@link ParserContext} implementation for the {@link FreenetLinkParser}. It @@ -28,20 +29,35 @@ import net.pterodactylus.sone.data.Sone; */ public class FreenetLinkParserContext implements ParserContext { + /** The request being processed. */ + private final Request request; + /** The posting Sone. */ private final Sone postingSone; /** * Creates a new link parser context. * + * @param request + * The request being processed * @param postingSone * The posting Sone */ - public FreenetLinkParserContext(Sone postingSone) { + public FreenetLinkParserContext(Request request, Sone postingSone) { + this.request = request; this.postingSone = postingSone; } /** + * Returns the request that is currently being processed. + * + * @return The request being processed + */ + public Request getRequest() { + return request; + } + + /** * Returns the Sone that provided the text that is being parsed. * * @return The posting Sone diff --git a/src/main/java/net/pterodactylus/sone/text/ParserContext.java b/src/main/java/net/pterodactylus/sone/text/ParserContext.java index 7f38ad8..ee61b6f 100644 --- a/src/main/java/net/pterodactylus/sone/text/ParserContext.java +++ b/src/main/java/net/pterodactylus/sone/text/ParserContext.java @@ -1,3 +1,19 @@ +/* + * Sone - ParserContext.java - Copyright © 2010 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package net.pterodactylus.sone.text; diff --git a/src/main/java/net/pterodactylus/sone/text/TextFilter.java b/src/main/java/net/pterodactylus/sone/text/TextFilter.java new file mode 100644 index 0000000..5744368 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/text/TextFilter.java @@ -0,0 +1,65 @@ +/* + * Sone - TextFilter.java - Copyright © 2011 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.text; + +import java.util.logging.Logger; + +import net.pterodactylus.util.logging.Logging; + +/** + * Filter for newly inserted text. This filter strips HTTP links to the local + * node of identifying marks, e.g. a link to “http://localhost:8888/KSK@gpl.txt” + * will be converted to “KSK@gpl.txt”. This will only work for links that point + * to the same address Sone is accessed by, so if you access Sone using + * localhost:8888, links to 127.0.0.1:8888 will not be removed. + * + * @author David ‘Bombe’ Roden + */ +public class TextFilter { + + /** The logger. */ + private static final Logger logger = Logging.getLogger(TextFilter.class); + + /** + * Filters the given text, stripping the host header part for links to the + * local node. + * + * @param hostHeader + * The host header from the request + * @param text + * The text to filter + * @return The filtered text + */ + public static String filter(String hostHeader, String text) { + + /* filter http(s) links to own node. */ + if (hostHeader != null) { + String line = text; + for (String toRemove : new String[] { "http://" + hostHeader + "/", "https://" + hostHeader + "/", "http://" + hostHeader, "https://" + hostHeader }) { + while (line.indexOf(toRemove) != -1) { + line = line.replace(toRemove, ""); + } + } + return line; + } + + /* not modified. */ + return text; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/CreatePostPage.java b/src/main/java/net/pterodactylus/sone/web/CreatePostPage.java index 57eb18a..147e4ae 100644 --- a/src/main/java/net/pterodactylus/sone/web/CreatePostPage.java +++ b/src/main/java/net/pterodactylus/sone/web/CreatePostPage.java @@ -19,6 +19,7 @@ package net.pterodactylus.sone.web; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.text.TextFilter; import net.pterodactylus.sone.web.page.Page.Request.Method; import net.pterodactylus.util.template.Template; import net.pterodactylus.util.template.TemplateContext; @@ -64,6 +65,7 @@ public class CreatePostPage extends SoneTemplatePage { sender = currentSone; } Sone recipient = webInterface.getCore().getSone(recipientId, false); + text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text); webInterface.getCore().createPost(sender, recipient, System.currentTimeMillis(), text); throw new RedirectException(returnPage); } diff --git a/src/main/java/net/pterodactylus/sone/web/CreateSonePage.java b/src/main/java/net/pterodactylus/sone/web/CreateSonePage.java index cc40320..3f940a3 100644 --- a/src/main/java/net/pterodactylus/sone/web/CreateSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/CreateSonePage.java @@ -1,5 +1,5 @@ /* - * FreenetSone - CreateSonePage.java - Copyright © 2010 David Roden + * Sone - CreateSonePage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -129,6 +129,9 @@ public class CreateSonePage extends SoneTemplatePage { */ @Override public boolean isEnabled(ToadletContext toadletContext) { + if (webInterface.getCore().getPreferences().isRequireFullAccess() && !toadletContext.isAllowedFullAccess()) { + return false; + } return (getCurrentSone(toadletContext, false) == null) || (webInterface.getCore().getLocalSones().size() == 1); } diff --git a/src/main/java/net/pterodactylus/sone/web/DeleteSonePage.java b/src/main/java/net/pterodactylus/sone/web/DeleteSonePage.java index 54d77b8..21979b8 100644 --- a/src/main/java/net/pterodactylus/sone/web/DeleteSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/DeleteSonePage.java @@ -1,5 +1,5 @@ /* - * FreenetSone - DeleteSonePage.java - Copyright © 2010 David Roden + * Sone - DeleteSonePage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/DistrustPage.java b/src/main/java/net/pterodactylus/sone/web/DistrustPage.java index c055545..37a792c 100644 --- a/src/main/java/net/pterodactylus/sone/web/DistrustPage.java +++ b/src/main/java/net/pterodactylus/sone/web/DistrustPage.java @@ -1,5 +1,5 @@ /* - * Sone - TrustPage.java - Copyright © 2011 David Roden + * Sone - DistrustPage.java - Copyright © 2011 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/IndexPage.java b/src/main/java/net/pterodactylus/sone/web/IndexPage.java index e675311..35b88a9 100644 --- a/src/main/java/net/pterodactylus/sone/web/IndexPage.java +++ b/src/main/java/net/pterodactylus/sone/web/IndexPage.java @@ -1,5 +1,5 @@ /* - * FreenetSone - IndexPage.java - Copyright © 2010 David Roden + * Sone - IndexPage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,7 +23,9 @@ import java.util.List; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.notify.ListNotificationFilters; import net.pterodactylus.util.collection.Pagination; +import net.pterodactylus.util.filter.Filter; import net.pterodactylus.util.filter.Filters; import net.pterodactylus.util.number.Numbers; import net.pterodactylus.util.template.Template; @@ -57,7 +59,7 @@ public class IndexPage extends SoneTemplatePage { @Override protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { super.processTemplate(request, templateContext); - Sone currentSone = getCurrentSone(request.getToadletContext()); + final Sone currentSone = getCurrentSone(request.getToadletContext()); List allPosts = new ArrayList(); allPosts.addAll(currentSone.getPosts()); for (String friendSoneId : currentSone.getFriends()) { @@ -73,6 +75,13 @@ public class IndexPage extends SoneTemplatePage { } } } + allPosts = Filters.filteredList(allPosts, new Filter() { + + @Override + public boolean filterObject(Post post) { + return ListNotificationFilters.isPostVisible(currentSone, post); + } + }); allPosts = Filters.filteredList(allPosts, Post.FUTURE_POSTS_FILTER); Collections.sort(allPosts, Post.TIME_COMPARATOR); Pagination pagination = new Pagination(allPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0)); diff --git a/src/main/java/net/pterodactylus/sone/web/LikePage.java b/src/main/java/net/pterodactylus/sone/web/LikePage.java index 73fdd3c..56f55ef 100644 --- a/src/main/java/net/pterodactylus/sone/web/LikePage.java +++ b/src/main/java/net/pterodactylus/sone/web/LikePage.java @@ -53,7 +53,7 @@ public class LikePage extends SoneTemplatePage { protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { super.processTemplate(request, templateContext); if (request.getMethod() == Method.POST) { - String type=request.getHttpRequest().getPartAsStringFailsafe("type", 16); + String type = request.getHttpRequest().getPartAsStringFailsafe("type", 16); String id = request.getHttpRequest().getPartAsStringFailsafe(type, 36); String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); Sone currentSone = getCurrentSone(request.getToadletContext()); diff --git a/src/main/java/net/pterodactylus/sone/web/LoginPage.java b/src/main/java/net/pterodactylus/sone/web/LoginPage.java index eaa8351..8e612ea 100644 --- a/src/main/java/net/pterodactylus/sone/web/LoginPage.java +++ b/src/main/java/net/pterodactylus/sone/web/LoginPage.java @@ -1,5 +1,5 @@ /* - * FreenetSone - LoginPage.java - Copyright © 2010 David Roden + * Sone - LoginPage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -103,6 +103,9 @@ public class LoginPage extends SoneTemplatePage { */ @Override public boolean isEnabled(ToadletContext toadletContext) { + if (webInterface.getCore().getPreferences().isRequireFullAccess() && !toadletContext.isAllowedFullAccess()) { + return false; + } return getCurrentSone(toadletContext, false) == null; } diff --git a/src/main/java/net/pterodactylus/sone/web/LogoutPage.java b/src/main/java/net/pterodactylus/sone/web/LogoutPage.java index f388368..7cd0587 100644 --- a/src/main/java/net/pterodactylus/sone/web/LogoutPage.java +++ b/src/main/java/net/pterodactylus/sone/web/LogoutPage.java @@ -1,5 +1,5 @@ /* - * FreenetSone - LogoutPage.java - Copyright © 2010 David Roden + * Sone - LogoutPage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -57,6 +57,9 @@ public class LogoutPage extends SoneTemplatePage { */ @Override public boolean isEnabled(ToadletContext toadletContext) { + if (webInterface.getCore().getPreferences().isRequireFullAccess() && !toadletContext.isAllowedFullAccess()) { + return false; + } return (getCurrentSone(toadletContext, false) != null) && (webInterface.getCore().getLocalSones().size() != 1); } diff --git a/src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java b/src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java index 4ecf6e0..9f91c84 100644 --- a/src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java +++ b/src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java @@ -1,5 +1,5 @@ /* - * Sone - MarkReadPage.java - Copyright © 2011 David Roden + * Sone - MarkAsKnownPage.java - Copyright © 2011 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java index 161de32..6785e81 100644 --- a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java +++ b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java @@ -17,6 +17,9 @@ package net.pterodactylus.sone.web; +import java.util.ArrayList; +import java.util.List; + import net.pterodactylus.sone.core.Core.Preferences; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired; @@ -57,19 +60,38 @@ public class OptionsPage extends SoneTemplatePage { Preferences preferences = webInterface.getCore().getPreferences(); Sone currentSone = webInterface.getCurrentSone(request.getToadletContext(), false); if (request.getMethod() == Method.POST) { + List fieldErrors = new ArrayList(); if (currentSone != null) { boolean autoFollow = request.getHttpRequest().isPartSet("auto-follow"); currentSone.getOptions().getBooleanOption("AutoFollow").set(autoFollow); webInterface.getCore().saveSone(currentSone); } Integer insertionDelay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16)); - preferences.setInsertionDelay(insertionDelay); + if (!preferences.validateInsertionDelay(insertionDelay)) { + fieldErrors.add("insertion-delay"); + } else { + preferences.setInsertionDelay(insertionDelay); + } Integer postsPerPage = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("posts-per-page", 4), null); - preferences.setPostsPerPage(postsPerPage); + if (!preferences.validatePostsPerPage(postsPerPage)) { + fieldErrors.add("posts-per-page"); + } else { + preferences.setPostsPerPage(postsPerPage); + } + boolean requireFullAccess = request.getHttpRequest().isPartSet("require-full-access"); + preferences.setRequireFullAccess(requireFullAccess); Integer positiveTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("positive-trust", 3)); - preferences.setPositiveTrust(positiveTrust); + if (!preferences.validatePositiveTrust(positiveTrust)) { + fieldErrors.add("positive-trust"); + } else { + preferences.setPositiveTrust(positiveTrust); + } Integer negativeTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("negative-trust", 4)); - preferences.setNegativeTrust(negativeTrust); + if (!preferences.validateNegativeTrust(negativeTrust)) { + fieldErrors.add("negative-trust"); + } else { + preferences.setNegativeTrust(negativeTrust); + } String trustComment = request.getHttpRequest().getPartAsStringFailsafe("trust-comment", 256); if (trustComment.trim().length() == 0) { trustComment = null; @@ -87,13 +109,17 @@ public class OptionsPage extends SoneTemplatePage { boolean reallyClearOnNextRestart = Boolean.parseBoolean(request.getHttpRequest().getPartAsStringFailsafe("really-clear-on-next-restart", 5)); preferences.setReallyClearOnNextRestart(reallyClearOnNextRestart); webInterface.getCore().saveConfiguration(); - throw new RedirectException(getPath()); + if (fieldErrors.isEmpty()) { + throw new RedirectException(getPath()); + } + templateContext.set("fieldErrors", fieldErrors); } if (currentSone != null) { templateContext.set("auto-follow", currentSone.getOptions().getBooleanOption("AutoFollow").get()); } templateContext.set("insertion-delay", preferences.getInsertionDelay()); templateContext.set("posts-per-page", preferences.getPostsPerPage()); + templateContext.set("require-full-access", preferences.isRequireFullAccess()); templateContext.set("positive-trust", preferences.getPositiveTrust()); templateContext.set("negative-trust", preferences.getNegativeTrust()); templateContext.set("trust-comment", preferences.getTrustComment()); diff --git a/src/main/java/net/pterodactylus/sone/web/SearchPage.java b/src/main/java/net/pterodactylus/sone/web/SearchPage.java index 1d1aa36..6f5e001 100644 --- a/src/main/java/net/pterodactylus/sone/web/SearchPage.java +++ b/src/main/java/net/pterodactylus/sone/web/SearchPage.java @@ -1,5 +1,5 @@ /* - * Sone - OptionsPage.java - Copyright © 2010 David Roden + * Sone - SearchPage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,17 +24,20 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.util.collection.Converter; -import net.pterodactylus.util.collection.Converters; +import net.pterodactylus.util.collection.Mapper; +import net.pterodactylus.util.collection.Mappers; import net.pterodactylus.util.collection.Pagination; import net.pterodactylus.util.filter.Filter; import net.pterodactylus.util.filter.Filters; +import net.pterodactylus.util.logging.Logging; import net.pterodactylus.util.number.Numbers; import net.pterodactylus.util.template.Template; import net.pterodactylus.util.template.TemplateContext; @@ -49,6 +52,9 @@ import net.pterodactylus.util.text.TextException; */ public class SearchPage extends SoneTemplatePage { + /** The logger. */ + private static final Logger logger = Logging.getLogger(SearchPage.class); + /** * Creates a new search page. * @@ -99,8 +105,8 @@ public class SearchPage extends SoneTemplatePage { Collections.sort(sortedPostHits, Hit.DESCENDING_COMPARATOR); /* extract Sones and posts. */ - List resultSones = Converters.convertList(sortedSoneHits, new HitConverter()); - List resultPosts = Converters.convertList(sortedPostHits, new HitConverter()); + List resultSones = Mappers.mappedList(sortedSoneHits, new HitMapper()); + List resultPosts = Mappers.mappedList(sortedPostHits, new HitMapper()); /* pagination. */ Pagination sonePagination = new Pagination(resultSones, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("sonePage"), 0)); @@ -137,7 +143,7 @@ public class SearchPage extends SoneTemplatePage { Set> hits = new HashSet>(); for (T object : objects) { String objectString = stringGenerator.generateString(object); - int score = calculateScore(phrases, objectString); + double score = calculateScore(phrases, objectString); hits.add(new Hit(object, score)); } return hits; @@ -185,9 +191,10 @@ public class SearchPage extends SoneTemplatePage { * The expression to search * @return The score of the expression */ - private int calculateScore(List phrases, String expression) { - int optionalHits = 0; - int requiredHits = 0; + private double calculateScore(List phrases, String expression) { + logger.log(Level.FINEST, "Calculating Score for “%s”…", expression); + double optionalHits = 0; + double requiredHits = 0; int forbiddenHits = 0; int requiredPhrases = 0; for (Phrase phrase : phrases) { @@ -197,22 +204,26 @@ public class SearchPage extends SoneTemplatePage { } int matches = 0; int index = 0; + double score = 0; while (index < expression.length()) { int position = expression.toLowerCase().indexOf(phraseString, index); if (position == -1) { break; } + score += Math.pow(1 - position / (double) expression.length(), 2); index = position + phraseString.length(); + logger.log(Level.FINEST, "Got hit at position %d.", position); ++matches; } + logger.log(Level.FINEST, "Score: %f", score); if (matches == 0) { continue; } if (phrase.getOptionality() == Phrase.Optionality.REQUIRED) { - requiredHits += matches; + requiredHits += score; } if (phrase.getOptionality() == Phrase.Optionality.OPTIONAL) { - optionalHits += matches; + optionalHits += score; } if (phrase.getOptionality() == Phrase.Optionality.FORBIDDEN) { forbiddenHits += matches; @@ -418,7 +429,7 @@ public class SearchPage extends SoneTemplatePage { @Override public int compare(Hit leftHit, Hit rightHit) { - return rightHit.getScore() - leftHit.getScore(); + return (rightHit.getScore() < leftHit.getScore()) ? -1 : ((rightHit.getScore() > leftHit.getScore()) ? 1 : 0); } }; @@ -427,7 +438,7 @@ public class SearchPage extends SoneTemplatePage { private final T object; /** The score of the object. */ - private final int score; + private final double score; /** * Creates a new hit. @@ -437,7 +448,7 @@ public class SearchPage extends SoneTemplatePage { * @param score * The score of the object */ - public Hit(T object, int score) { + public Hit(T object, double score) { this.object = object; this.score = score; } @@ -456,7 +467,7 @@ public class SearchPage extends SoneTemplatePage { * * @return The score of the object */ - public int getScore() { + public double getScore() { return score; } @@ -469,13 +480,13 @@ public class SearchPage extends SoneTemplatePage { * The type of the object to extract * @author David ‘Bombe’ Roden */ - public static class HitConverter implements Converter, T> { + public static class HitMapper implements Mapper, T> { /** * {@inheritDoc} */ @Override - public T convert(Hit input) { + public T map(Hit input) { return input.getObject(); } diff --git a/src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java b/src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java index d6c41c1..42c0129 100644 --- a/src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java +++ b/src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java @@ -1,5 +1,5 @@ /* - * Freetalk - FreetalkTemplatePage.java - Copyright © 2010 David Roden + * Sone - SoneTemplatePage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,8 +26,9 @@ import java.util.Map; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.main.SonePlugin; -import net.pterodactylus.sone.web.page.Page; +import net.pterodactylus.sone.notify.ListNotificationFilters; import net.pterodactylus.sone.web.page.FreenetTemplatePage; +import net.pterodactylus.sone.web.page.Page; import net.pterodactylus.util.collection.ListBuilder; import net.pterodactylus.util.collection.MapBuilder; import net.pterodactylus.util.template.Template; @@ -37,7 +38,7 @@ import freenet.clients.http.ToadletContext; import freenet.support.api.HTTPRequest; /** - * Base page for the Freetalk web interface. + * Base page for the Sone web interface. * * @author David ‘Bombe’ Roden */ @@ -53,8 +54,8 @@ public class SoneTemplatePage extends FreenetTemplatePage { private final boolean requireLogin; /** - * Creates a new template page for Freetalk that does not require the user - * to be logged in. + * Creates a new template page for Sone that does not require the user to be + * logged in. * * @param path * The path of the page @@ -68,8 +69,8 @@ public class SoneTemplatePage extends FreenetTemplatePage { } /** - * Creates a new template page for Freetalk that does not require the user - * to be logged in. + * Creates a new template page for Sone that does not require the user to be + * logged in. * * @param path * The path of the page @@ -85,7 +86,7 @@ public class SoneTemplatePage extends FreenetTemplatePage { } /** - * Creates a new template page for Freetalk. + * Creates a new template page for Sone. * * @param path * The path of the page @@ -101,7 +102,7 @@ public class SoneTemplatePage extends FreenetTemplatePage { } /** - * Creates a new template page for Freetalk. + * Creates a new template page for Sone. * * @param path * The path of the page @@ -249,7 +250,8 @@ public class SoneTemplatePage extends FreenetTemplatePage { @Override protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException { super.processTemplate(request, templateContext); - templateContext.set("currentSone", getCurrentSone(request.getToadletContext(), false)); + Sone currentSone = getCurrentSone(request.getToadletContext(), false); + templateContext.set("currentSone", currentSone); templateContext.set("localSones", webInterface.getCore().getLocalSones()); templateContext.set("request", request); templateContext.set("currentVersion", SonePlugin.VERSION); @@ -257,6 +259,7 @@ public class SoneTemplatePage extends FreenetTemplatePage { templateContext.set("latestEdition", webInterface.getCore().getUpdateChecker().getLatestEdition()); templateContext.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion()); templateContext.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate()); + templateContext.set("notifications", ListNotificationFilters.filterNotifications(webInterface.getNotifications().getNotifications(), currentSone)); } /** @@ -293,7 +296,18 @@ public class SoneTemplatePage extends FreenetTemplatePage { * {@inheritDoc} */ @Override + protected boolean isFullAccessOnly() { + return webInterface.getCore().getPreferences().isRequireFullAccess(); + } + + /** + * {@inheritDoc} + */ + @Override public boolean isEnabled(ToadletContext toadletContext) { + if (webInterface.getCore().getPreferences().isRequireFullAccess() && !toadletContext.isAllowedFullAccess()) { + return false; + } if (requiresLogin()) { return getCurrentSone(toadletContext, false) != null; } diff --git a/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java b/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java index 85f46f0..60165b2 100644 --- a/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java +++ b/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java @@ -1,5 +1,5 @@ /* - * Sone - BookmarkPage.java - Copyright © 2011 David Roden + * Sone - UnbookmarkPage.java - Copyright © 2011 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java b/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java index e026a9a..a7836f5 100644 --- a/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java @@ -1,5 +1,5 @@ /* - * Sone - FollowSonePage.java - Copyright © 2010 David Roden + * Sone - UnfollowSonePage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java b/src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java index 5408f20..ccb5959 100644 --- a/src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java @@ -1,5 +1,5 @@ /* - * Sone - LockSonePage.java - Copyright © 2010 David Roden + * Sone - UnlockSonePage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/UntrustPage.java b/src/main/java/net/pterodactylus/sone/web/UntrustPage.java index 0e7e198..c5e7dec 100644 --- a/src/main/java/net/pterodactylus/sone/web/UntrustPage.java +++ b/src/main/java/net/pterodactylus/sone/web/UntrustPage.java @@ -1,5 +1,5 @@ /* - * Sone - TrustPage.java - Copyright © 2011 David Roden + * Sone - UntrustPage.java - Copyright © 2011 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java b/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java index 14c4f94..889c74c 100644 --- a/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java @@ -80,6 +80,10 @@ public class ViewSonePage extends SoneTemplatePage { String soneId = request.getHttpRequest().getParam("sone"); Sone sone = webInterface.getCore().getSone(soneId, false); templateContext.set("sone", sone); + templateContext.set("soneId", soneId); + if (sone == null) { + return; + } List sonePosts = sone.getPosts(); sonePosts.addAll(webInterface.getCore().getDirectedPosts(sone)); Collections.sort(sonePosts, Post.TIME_COMPARATOR); diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 46592d8..75caba2 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -1,5 +1,5 @@ /* - * FreenetSone - WebInterface.java - Copyright © 2010 David Roden + * Sone - WebInterface.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,7 +49,6 @@ import net.pterodactylus.sone.template.CssClassNameFilter; import net.pterodactylus.sone.template.HttpRequestAccessor; import net.pterodactylus.sone.template.IdentityAccessor; import net.pterodactylus.sone.template.JavascriptFilter; -import net.pterodactylus.sone.template.NotificationManagerAccessor; import net.pterodactylus.sone.template.ParserFilter; import net.pterodactylus.sone.template.PostAccessor; import net.pterodactylus.sone.template.ReplyAccessor; @@ -70,6 +69,7 @@ import net.pterodactylus.sone.web.ajax.DistrustAjaxPage; import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage; import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage; import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage; +import net.pterodactylus.sone.web.ajax.GetNotificationAjaxPage; import net.pterodactylus.sone.web.ajax.GetPostAjaxPage; import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage; import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage; @@ -87,6 +87,7 @@ import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage; import net.pterodactylus.sone.web.ajax.UntrustAjaxPage; import net.pterodactylus.sone.web.page.PageToadlet; import net.pterodactylus.sone.web.page.PageToadletFactory; +import net.pterodactylus.sone.web.page.RedirectPage; import net.pterodactylus.sone.web.page.StaticPage; import net.pterodactylus.sone.web.page.TemplatePage; import net.pterodactylus.util.cache.Cache; @@ -100,6 +101,7 @@ import net.pterodactylus.util.notify.Notification; import net.pterodactylus.util.notify.NotificationManager; import net.pterodactylus.util.notify.TemplateNotification; import net.pterodactylus.util.template.CollectionSortFilter; +import net.pterodactylus.util.template.ContainsFilter; import net.pterodactylus.util.template.DateFilter; import net.pterodactylus.util.template.FormatFilter; import net.pterodactylus.util.template.HtmlFilter; @@ -191,7 +193,6 @@ public class WebInterface implements CoreListener { templateContextFactory.addAccessor(Post.class, new PostAccessor(getCore())); templateContextFactory.addAccessor(Reply.class, new ReplyAccessor(getCore())); templateContextFactory.addAccessor(Identity.class, new IdentityAccessor(getCore())); - templateContextFactory.addAccessor(NotificationManager.class, new NotificationManagerAccessor()); templateContextFactory.addAccessor(Trust.class, new TrustAccessor()); templateContextFactory.addAccessor(HTTPRequest.class, new HttpRequestAccessor()); templateContextFactory.addFilter("date", new DateFilter()); @@ -210,6 +211,7 @@ public class WebInterface implements CoreListener { templateContextFactory.addFilter("format", new FormatFilter()); templateContextFactory.addFilter("sort", new CollectionSortFilter()); templateContextFactory.addFilter("replyGroup", new ReplyGroupFilter()); + templateContextFactory.addFilter("in", new ContainsFilter()); templateContextFactory.addProvider(Provider.TEMPLATE_CONTEXT_PROVIDER); templateContextFactory.addProvider(new ClassPathTemplateProvider()); templateContextFactory.addTemplateObject("formPassword", formPassword); @@ -544,6 +546,7 @@ public class WebInterface implements CoreListener { Template openSearchTemplate = TemplateParser.parse(createReader("/templates/xml/OpenSearch.xml")); PageToadletFactory pageToadletFactory = new PageToadletFactory(sonePlugin.pluginRespirator().getHLSimpleClient(), "/Sone/"); + pageToadlets.add(pageToadletFactory.createPageToadlet(new RedirectPage("", "index.html"))); pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this), "Index")); pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateSonePage(createSoneTemplate, this), "CreateSone")); pageToadlets.add(pageToadletFactory.createPageToadlet(new KnownSonesPage(knownSonesTemplate, this), "KnownSones")); @@ -584,6 +587,7 @@ public class WebInterface implements CoreListener { pageToadlets.add(pageToadletFactory.createPageToadlet(new TemplatePage("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate))); pageToadlets.add(pageToadletFactory.createPageToadlet(new GetTranslationPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new GetStatusAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new GetNotificationAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyAjaxPage(this))); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java index eff7c41..0fc1236 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java @@ -19,6 +19,7 @@ package net.pterodactylus.sone.web.ajax; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.text.TextFilter; import net.pterodactylus.sone.web.WebInterface; import net.pterodactylus.util.json.JsonObject; @@ -59,6 +60,7 @@ public class CreatePostAjaxPage extends JsonPage { if ((text == null) || (text.trim().length() == 0)) { return createErrorJsonObject("text-required"); } + text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text); Post newPost = webInterface.getCore().createPost(sender, recipient, text); return createSuccessJsonObject().put("postId", newPost.getId()).put("sone", sender.getId()).put("recipient", (newPost.getRecipient() != null) ? newPost.getRecipient().getId() : null); } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java index f34d202..e12b2cf 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java @@ -1,5 +1,5 @@ /* - * Sone - DeleteReplysAjaxPage.java - Copyright © 2010 David Roden + * Sone - DeleteReplyAjaxPage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java index 1a6d28f..ca770e4 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java @@ -1,5 +1,5 @@ /* - * Sone - TrustAjaxPage.java - Copyright © 2011 David Roden + * Sone - DistrustAjaxPage.java - Copyright © 2011 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java new file mode 100644 index 0000000..650a9ae --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java @@ -0,0 +1,143 @@ +/* + * Sone - GetNotificationAjaxPage.java - Copyright © 2010 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.ajax; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.Reply; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.main.SonePlugin; +import net.pterodactylus.sone.notify.ListNotification; +import net.pterodactylus.sone.notify.ListNotificationFilters; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.util.json.JsonObject; +import net.pterodactylus.util.notify.Notification; +import net.pterodactylus.util.notify.TemplateNotification; +import net.pterodactylus.util.template.TemplateContext; + +/** + * The “get notification” AJAX handler returns a number of rendered + * notifications. + * + * @author David ‘Bombe’ Roden + */ +public class GetNotificationAjaxPage extends JsonPage { + + /** + * Creates a new “get notification” AJAX page. + * + * @param webInterface + * The Sone web interface + */ + public GetNotificationAjaxPage(WebInterface webInterface) { + super("getNotification.ajax", webInterface); + } + + // + // JSONPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected boolean needsFormPassword() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean requiresLogin() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + protected JsonObject createJsonObject(Request request) { + String[] notificationIds = request.getHttpRequest().getParam("notifications").split(","); + JsonObject jsonNotifications = new JsonObject(); + Sone currentSone = webInterface.getCurrentSone(request.getToadletContext(), false); + for (String notificationId : notificationIds) { + Notification notification = webInterface.getNotifications().getNotification(notificationId); + ListNotificationFilters.filterNotifications(new ArrayList(), currentSone); + if ("new-post-notification".equals(notificationId)) { + notification = ListNotificationFilters.filterNewPostNotification((ListNotification) notification, currentSone); + } else if ("new-reply-notification".equals(notificationId)) { + notification = ListNotificationFilters.filterNewReplyNotification((ListNotification) notification, currentSone); + } + if (notification == null) { + // TODO - show error + continue; + } + jsonNotifications.put(notificationId, createJsonNotification(request, notification)); + } + return createSuccessJsonObject().put("notifications", jsonNotifications); + } + + // + // PRIVATE METHODS + // + + /** + * Creates a JSON object from the given notification. + * + * @param request + * The request to load the session from + * @param notification + * The notification to create a JSON object + * @return The JSON object + */ + private JsonObject createJsonNotification(Request request, Notification notification) { + JsonObject jsonNotification = new JsonObject(); + jsonNotification.put("id", notification.getId()); + StringWriter notificationWriter = new StringWriter(); + try { + if (notification instanceof TemplateNotification) { + TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext().mergeContext(((TemplateNotification) notification).getTemplateContext()); + templateContext.set("currentSone", webInterface.getCurrentSone(request.getToadletContext(), false)); + templateContext.set("localSones", webInterface.getCore().getLocalSones()); + templateContext.set("request", request); + templateContext.set("currentVersion", SonePlugin.VERSION); + templateContext.set("hasLatestVersion", webInterface.getCore().getUpdateChecker().hasLatestVersion()); + templateContext.set("latestEdition", webInterface.getCore().getUpdateChecker().getLatestEdition()); + templateContext.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion()); + templateContext.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate()); + templateContext.set("notification", notification); + ((TemplateNotification) notification).render(templateContext, notificationWriter); + } else { + notification.render(notificationWriter); + } + } catch (IOException ioe1) { + /* StringWriter never throws, ignore. */ + } + jsonNotification.put("text", notificationWriter.toString()); + jsonNotification.put("createdTime", notification.getCreatedTime()); + jsonNotification.put("lastUpdatedTime", notification.getLastUpdatedTime()); + jsonNotification.put("dismissable", notification.isDismissable()); + return jsonNotification; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java index 4e5f1b8..f46ec6b 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java @@ -62,7 +62,7 @@ public class GetPostAjaxPage extends JsonPage { if (post == null) { return createErrorJsonObject("invalid-post-id"); } - return createSuccessJsonObject().put("post", createJsonPost(post, getCurrentSone(request.getToadletContext()))); + return createSuccessJsonObject().put("post", createJsonPost(request, post, getCurrentSone(request.getToadletContext()))); } /** @@ -81,13 +81,15 @@ public class GetPostAjaxPage extends JsonPage { * Creates a JSON object from the given post. The JSON object will only * contain the ID of the post, its time, and its rendered HTML code. * + * @param request + * The request being processed * @param post * The post to create a JSON object from * @param currentSone * The currently logged in Sone (to store in the template) * @return The JSON representation of the post */ - private JsonObject createJsonPost(Post post, Sone currentSone) { + private JsonObject createJsonPost(Request request, Post post, Sone currentSone) { JsonObject jsonPost = new JsonObject(); jsonPost.put("id", post.getId()); jsonPost.put("sone", post.getSone().getId()); @@ -95,6 +97,7 @@ public class GetPostAjaxPage extends JsonPage { jsonPost.put("time", post.getTime()); StringWriter stringWriter = new StringWriter(); TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext(); + templateContext.set("request", request); templateContext.set("post", post); templateContext.set("currentSone", currentSone); templateContext.set("localSones", webInterface.getCore().getLocalSones()); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java index c19bcba..24bbbe2 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java @@ -65,7 +65,7 @@ public class GetReplyAjaxPage extends JsonPage { if ((reply == null) || (reply.getSone() == null)) { return createErrorJsonObject("invalid-reply-id"); } - return createSuccessJsonObject().put("reply", createJsonReply(reply, getCurrentSone(request.getToadletContext()))); + return createSuccessJsonObject().put("reply", createJsonReply(request, reply, getCurrentSone(request.getToadletContext()))); } /** @@ -83,13 +83,15 @@ public class GetReplyAjaxPage extends JsonPage { /** * Creates a JSON representation of the given reply. * + * @param request + * The request being processed * @param reply * The reply to convert * @param currentSone * The currently logged in Sone (to store in the template) * @return The JSON representation of the reply */ - private JsonObject createJsonReply(Reply reply, Sone currentSone) { + private JsonObject createJsonReply(Request request, Reply reply, Sone currentSone) { JsonObject jsonReply = new JsonObject(); jsonReply.put("id", reply.getId()); jsonReply.put("postId", reply.getPost().getId()); @@ -97,6 +99,7 @@ public class GetReplyAjaxPage extends JsonPage { jsonReply.put("time", reply.getTime()); StringWriter stringWriter = new StringWriter(); TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext(); + templateContext.set("request", request); templateContext.set("reply", reply); templateContext.set("currentSone", currentSone); try { diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java index 426c08f..4e2049e 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java @@ -17,8 +17,6 @@ package net.pterodactylus.sone.web.ajax; -import java.io.IOException; -import java.io.StringWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -39,8 +37,6 @@ import net.pterodactylus.util.filter.Filters; import net.pterodactylus.util.json.JsonArray; import net.pterodactylus.util.json.JsonObject; import net.pterodactylus.util.notify.Notification; -import net.pterodactylus.util.notify.TemplateNotification; -import net.pterodactylus.util.template.TemplateContext; /** * The “get status” AJAX handler returns all information that is necessary to @@ -70,10 +66,19 @@ public class GetStatusAjaxPage extends JsonPage { protected JsonObject createJsonObject(Request request) { final Sone currentSone = getCurrentSone(request.getToadletContext(), false); /* load Sones. */ - boolean loadAllSones = Boolean.parseBoolean(request.getHttpRequest().getParam("loadAllSones", "true")); + boolean loadAllSones = Boolean.parseBoolean(request.getHttpRequest().getParam("loadAllSones", "false")); Set sones = new HashSet(Collections.singleton(getCurrentSone(request.getToadletContext(), false))); if (loadAllSones) { sones.addAll(webInterface.getCore().getSones()); + } else { + String loadSoneIds = request.getHttpRequest().getParam("soneIds"); + if (loadSoneIds.length() > 0) { + String[] soneIds = loadSoneIds.split(","); + for (String soneId : soneIds) { + /* just add it, we skip null further down. */ + sones.add(webInterface.getCore().getSone(soneId, false)); + } + } } JsonArray jsonSones = new JsonArray(); for (Sone sone : sones) { @@ -86,9 +91,9 @@ public class GetStatusAjaxPage extends JsonPage { /* load notifications. */ List notifications = ListNotificationFilters.filterNotifications(new ArrayList(webInterface.getNotifications().getNotifications()), currentSone); Collections.sort(notifications, Notification.LAST_UPDATED_TIME_SORTER); - JsonArray jsonNotifications = new JsonArray(); + JsonArray jsonNotificationInformations = new JsonArray(); for (Notification notification : notifications) { - jsonNotifications.add(createJsonNotification(notification)); + jsonNotificationInformations.add(createJsonNotificationInformation(notification)); } /* load new posts. */ Set newPosts = webInterface.getNewPosts(); @@ -97,7 +102,7 @@ public class GetStatusAjaxPage extends JsonPage { @Override public boolean filterObject(Post post) { - return currentSone.hasFriend(post.getSone().getId()) || currentSone.equals(post.getSone()) || currentSone.equals(post.getRecipient()); + return ListNotificationFilters.isPostVisible(currentSone, post); } }); @@ -118,7 +123,7 @@ public class GetStatusAjaxPage extends JsonPage { @Override public boolean filterObject(Reply reply) { - return currentSone.hasFriend(reply.getPost().getSone().getId()) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient()); + return ListNotificationFilters.isReplyVisible(currentSone, reply); } }); @@ -132,7 +137,7 @@ public class GetStatusAjaxPage extends JsonPage { jsonReply.put("postSone", reply.getPost().getSone().getId()); jsonReplies.add(jsonReply); } - return createSuccessJsonObject().put("sones", jsonSones).put("notifications", jsonNotifications).put("newPosts", jsonPosts).put("newReplies", jsonReplies); + return createSuccessJsonObject().put("sones", jsonSones).put("notifications", jsonNotificationInformations).put("newPosts", jsonPosts).put("newReplies", jsonReplies); } /** @@ -174,36 +179,25 @@ public class GetStatusAjaxPage extends JsonPage { synchronized (dateFormat) { jsonSone.put("lastUpdated", dateFormat.format(new Date(sone.getTime()))); } - jsonSone.put("age", (System.currentTimeMillis() - sone.getTime()) / 1000); + jsonSone.put("lastUpdatedText", GetTimesAjaxPage.getTime(webInterface, System.currentTimeMillis() - sone.getTime()).getText()); return jsonSone; } /** - * Creates a JSON object from the given notification. + * Creates a JSON object that only contains the ID and the last-updated time + * of the given notification. * + * @see Notification#getId() + * @see Notification#getLastUpdatedTime() * @param notification - * The notification to create a JSON object - * @return The JSON object + * The notification + * @return A JSON object containing the notification ID and last-updated + * time */ - private JsonObject createJsonNotification(Notification notification) { + private JsonObject createJsonNotificationInformation(Notification notification) { JsonObject jsonNotification = new JsonObject(); jsonNotification.put("id", notification.getId()); - StringWriter notificationWriter = new StringWriter(); - try { - if (notification instanceof TemplateNotification) { - TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext().mergeContext(((TemplateNotification) notification).getTemplateContext()); - templateContext.set("notification", notification); - ((TemplateNotification) notification).render(templateContext, notificationWriter); - } else { - notification.render(notificationWriter); - } - } catch (IOException ioe1) { - /* StringWriter never throws, ignore. */ - } - jsonNotification.put("text", notificationWriter.toString()); - jsonNotification.put("createdTime", notification.getCreatedTime()); jsonNotification.put("lastUpdatedTime", notification.getLastUpdatedTime()); - jsonNotification.put("dismissable", notification.isDismissable()); return jsonNotification; } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java index b17e4a2..0be0c46 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java @@ -120,6 +120,23 @@ public class GetTimesAjaxPage extends JsonPage { * @return The formatted age */ private Time getTime(long age) { + return getTime(webInterface, age); + } + + // + // STATIC METHODS + // + + /** + * Returns the formatted relative time for a given age. + * + * @param webInterface + * The Sone web interface (for l10n access) + * @param age + * The age to format (in milliseconds) + * @return The formatted age + */ + public static Time getTime(WebInterface webInterface, long age) { String text; long refresh; if (age < 0) { @@ -135,7 +152,7 @@ public class GetTimesAjaxPage extends JsonPage { text = webInterface.getL10n().getString("View.Time.AMinuteAgo"); refresh = Time.MINUTE; } else if (age < 30 * Time.MINUTE) { - text = webInterface.getL10n().getString("View.Time.XMinutesAgo", "min", String.valueOf((int) Digits.round(age / Time.MINUTE, 1))); + text = webInterface.getL10n().getString("View.Time.XMinutesAgo", "min", String.valueOf((int) (Digits.round(age, Time.MINUTE) / Time.MINUTE))); refresh = 1 * Time.MINUTE; } else if (age < 45 * Time.MINUTE) { text = webInterface.getL10n().getString("View.Time.HalfAnHourAgo"); @@ -144,31 +161,31 @@ public class GetTimesAjaxPage extends JsonPage { text = webInterface.getL10n().getString("View.Time.AnHourAgo"); refresh = Time.HOUR; } else if (age < 21 * Time.HOUR) { - text = webInterface.getL10n().getString("View.Time.XHoursAgo", "hour", String.valueOf((int) Digits.round(age / Time.HOUR, 1))); + text = webInterface.getL10n().getString("View.Time.XHoursAgo", "hour", String.valueOf((int) (Digits.round(age, Time.HOUR) / Time.HOUR))); refresh = Time.HOUR; } else if (age < 42 * Time.HOUR) { text = webInterface.getL10n().getString("View.Time.ADayAgo"); refresh = Time.DAY; } else if (age < 6 * Time.DAY) { - text = webInterface.getL10n().getString("View.Time.XDaysAgo", "day", String.valueOf((int) Digits.round(age / Time.DAY, 1))); + text = webInterface.getL10n().getString("View.Time.XDaysAgo", "day", String.valueOf((int) (Digits.round(age, Time.DAY) / Time.DAY))); refresh = Time.DAY; } else if (age < 11 * Time.DAY) { text = webInterface.getL10n().getString("View.Time.AWeekAgo"); refresh = Time.DAY; } else if (age < 4 * Time.WEEK) { - text = webInterface.getL10n().getString("View.Time.XWeeksAgo", "week", String.valueOf((int) Digits.round(age / Time.WEEK, 1))); + text = webInterface.getL10n().getString("View.Time.XWeeksAgo", "week", String.valueOf((int) (Digits.round(age, Time.WEEK) / Time.WEEK))); refresh = Time.DAY; } else if (age < 6 * Time.WEEK) { text = webInterface.getL10n().getString("View.Time.AMonthAgo"); refresh = Time.DAY; } else if (age < 11 * Time.MONTH) { - text = webInterface.getL10n().getString("View.Time.XMonthsAgo", "month", String.valueOf((int) Digits.round(age / Time.MONTH, 1))); + text = webInterface.getL10n().getString("View.Time.XMonthsAgo", "month", String.valueOf((int) (Digits.round(age, Time.MONTH) / Time.MONTH))); refresh = Time.DAY; } else if (age < 18 * Time.MONTH) { text = webInterface.getL10n().getString("View.Time.AYearAgo"); refresh = Time.WEEK; } else { - text = webInterface.getL10n().getString("View.Time.XYearsAgo", "year", String.valueOf((int) Digits.round(age / Time.YEAR, 1))); + text = webInterface.getL10n().getString("View.Time.XYearsAgo", "year", String.valueOf((int) (Digits.round(age, Time.YEAR) / Time.YEAR))); refresh = Time.WEEK; } return new Time(text, refresh); @@ -179,7 +196,7 @@ public class GetTimesAjaxPage extends JsonPage { * * @author David ‘Bombe’ Roden */ - private static class Time { + public static class Time { /** Number of milliseconds in a second. */ private static final long SECOND = 1000; @@ -239,6 +256,14 @@ public class GetTimesAjaxPage extends JsonPage { return refresh; } + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return text; + } + } } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java index 8d48bce..b027ab8 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java @@ -188,15 +188,18 @@ public abstract class JsonPage implements Page { */ @Override public Response handleRequest(Request request) { + if (webInterface.getCore().getPreferences().isRequireFullAccess() && !request.getToadletContext().isAllowedFullAccess()) { + return new Response(403, "Forbidden", "application/json", JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required"))); + } if (needsFormPassword()) { String formPassword = request.getHttpRequest().getParam("formPassword"); if (!webInterface.getFormPassword().equals(formPassword)) { - return new Response(401, "Not authorized", "application/json", JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required"))); + return new Response(403, "Forbidden", "application/json", JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required"))); } } if (requiresLogin()) { if (getCurrentSone(request.getToadletContext(), false) == null) { - return new Response(401, "Not authorized", "application/json", JsonUtils.format(createErrorJsonObject("auth-required"))); + return new Response(403, "Forbidden", "application/json", JsonUtils.format(createErrorJsonObject("auth-required"))); } } JsonObject jsonObject = createJsonObject(request); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java index ad1689e..f07dcb0 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java @@ -1,5 +1,5 @@ /* - * Sone - BookmarkAjaxPage.java - Copyright © 2011 David Roden + * Sone - UnbookmarkAjaxPage.java - Copyright © 2011 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java index e1c408c..3160db0 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java @@ -1,5 +1,5 @@ /* - * Sone - LockSoneAjaxPage.java - Copyright © 2010 David Roden + * Sone - UnlockSoneAjaxPage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java index 32c6229..ad613ff 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java @@ -1,5 +1,5 @@ /* - * Sone - TrustAjaxPage.java - Copyright © 2011 David Roden + * Sone - UntrustAjaxPage.java - Copyright © 2011 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java b/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java index 0668154..5831a1b 100644 --- a/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java +++ b/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java @@ -1,5 +1,5 @@ /* - * shortener - TemplatePage.java - Copyright © 2010 David Roden + * Sone - FreenetTemplatePage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -109,6 +109,9 @@ public class FreenetTemplatePage implements Page, LinkEnabledCallback { return new RedirectResponse(redirectTarget); } + if (isFullAccessOnly() && !request.getToadletContext().isAllowedFullAccess()) { + return new Response(401, "Not authorized", "text/html", "Not authorized"); + } ToadletContext toadletContext = request.getToadletContext(); if (request.getMethod() == Method.POST) { /* require form password. */ @@ -227,6 +230,17 @@ public class FreenetTemplatePage implements Page, LinkEnabledCallback { return Collections.emptyList(); } + /** + * Returns whether this page should only be allowed for requests from hosts + * with full access. + * + * @return {@code true} if this page should only be allowed for hosts with + * full access, {@code false} to allow this page for any host + */ + protected boolean isFullAccessOnly() { + return false; + } + // // INTERFACE LinkEnabledCallback // @@ -236,7 +250,7 @@ public class FreenetTemplatePage implements Page, LinkEnabledCallback { */ @Override public boolean isEnabled(ToadletContext toadletContext) { - return true; + return !isFullAccessOnly(); } /** diff --git a/src/main/java/net/pterodactylus/sone/web/page/Page.java b/src/main/java/net/pterodactylus/sone/web/page/Page.java index dd54f41..297081e 100644 --- a/src/main/java/net/pterodactylus/sone/web/page/Page.java +++ b/src/main/java/net/pterodactylus/sone/web/page/Page.java @@ -1,5 +1,5 @@ /* - * shortener - Page.java - Copyright © 2010 David Roden + * Sone - Page.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java b/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java index f7f59a6..f594e58 100644 --- a/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java +++ b/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java @@ -1,5 +1,5 @@ /* - * shortener - PageToadlet.java - Copyright © 2010 David Roden + * Sone - PageToadlet.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java b/src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java index af87470..bd0adb9 100644 --- a/src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java +++ b/src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java @@ -1,5 +1,5 @@ /* - * shortener - PageToadletFactory.java - Copyright © 2010 David Roden + * Sone - PageToadletFactory.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/java/net/pterodactylus/sone/web/page/RedirectPage.java b/src/main/java/net/pterodactylus/sone/web/page/RedirectPage.java new file mode 100644 index 0000000..2ce34f9 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/page/RedirectPage.java @@ -0,0 +1,62 @@ +/* + * Sone - RedirectPage.java - Copyright © 2011 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.page; + +/** + * Page implementation that redirects the user to another URL. + * + * @author David ‘Bombe’ Roden + */ +public class RedirectPage implements Page { + + /** The original path. */ + private String originalPath; + + /** The path to redirect the browser to. */ + private String newPath; + + /** + * Creates a new redirect page. + * + * @param originalPath + * The original path + * @param newPath + * The path to redirect the browser to + */ + public RedirectPage(String originalPath, String newPath) { + this.originalPath = originalPath; + this.newPath = newPath; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPath() { + return originalPath; + } + + /** + * {@inheritDoc} + */ + @Override + public Response handleRequest(Request request) { + return new RedirectResponse(newPath); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/page/StaticPage.java b/src/main/java/net/pterodactylus/sone/web/page/StaticPage.java index a840958..3d24fdd 100644 --- a/src/main/java/net/pterodactylus/sone/web/page/StaticPage.java +++ b/src/main/java/net/pterodactylus/sone/web/page/StaticPage.java @@ -1,5 +1,5 @@ /* - * shortener - CSSPage.java - Copyright © 2010 David Roden + * Sone - StaticPage.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index ba38c06..5032867 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -33,10 +33,12 @@ Page.Options.Page.Title=Options Page.Options.Page.Description=These options influence the runtime behaviour of the Sone plugin. Page.Options.Section.SoneSpecificOptions.Title=Sone-specific Options Page.Options.Section.SoneSpecificOptions.NotLoggedIn=These options are only available if you are {link}logged in{/link}. -Page.Options.Option.AutoFollow.Description=If a new Sone is discovered, follow it automatically. +Page.Options.Section.SoneSpecificOptions.LoggedIn=These options are only available while you are logged in and they are only valid for the Sone you are logged in as. +Page.Options.Option.AutoFollow.Description=If a new Sone is discovered, follow it automatically. Note that this will only follow Sones that are discovered after you activate this option! Page.Options.Section.RuntimeOptions.Title=Runtime Behaviour Page.Options.Option.InsertionDelay.Description=The number of seconds the Sone inserter waits after a modification of a Sone before it is being inserted. Page.Options.Option.PostsPerPage.Description=The number of posts to display on a page before pagination controls are being shown. +Page.Options.Option.RequireFullAccess.Description=Whether to deny access to Sone to any host that has not been granted full access. Page.Options.Section.TrustOptions.Title=Trust Settings Page.Options.Option.PositiveTrust.Description=The amount of positive trust you want to assign to other Sones by clicking the checkmark below a post or reply. Page.Options.Option.NegativeTrust.Description=The amount of trust you want to assign to other Sones by clicking the red X below a post or reply. This value should be negative. @@ -48,10 +50,13 @@ Page.Options.Option.FcpFullAccessRequired.Value.No=No Page.Options.Option.FcpFullAccessRequired.Value.Writing=For Write Access Page.Options.Option.FcpFullAccessRequired.Value.Always=Always Page.Options.Section.RescueOptions.Title=Rescue Settings -Page.Options.Option.SoneRescueMode.Description=Try to rescue your Sones at the next start of the Sone plugin. This will read your all your old Sones from Freenet and ignore any disappearing postings and replies. You have to unlock your local Sones after they have been restored and you have to manually disable the rescue mode once you are satisfied with what has been restored! +Page.Options.Option.SoneRescueMode.Description1=Try to rescue your Sones at the next start of the Sone plugin. The Rescue Mode will start at the latest known edition and will try to download all editions sequentially backwards, merging all discovered posts and replies together, until it is stopped or it has reached the first edition. +Page.Options.Option.SoneRescueMode.Description2=When using the Rescue Mode because Sone lost its configuration it usually suffices to let the Rescue Mode only run for a short time; use a second tab to control how many posts of your Sone are visible again. As soon as the last valid edition is loaded you can then deactivate the Rescue Mode. +Page.Options.Option.SoneRescueMode.Description3=Note that when you use the Rescue Mode posts that you have deleted after they have been inserted will have to be deleted again. Unfortunately this is an unavoidable side effect of the Rescue Mode. Page.Options.Section.Cleaning.Title=Clean Up Page.Options.Option.ClearOnNextRestart.Description=Resets the configuration of the Sone plugin at the next restart. Warning! {strong}This will destroy all of your Sones{/strong} so make sure you have backed up everyhing you still need! Also, you need to set the next option to true to actually do it. Page.Options.Option.ReallyClearOnNextRestart.Description=This option needs to be set to “yes” if you really, {strong}really{/strong} want to clear the plugin configuration on the next restart. +Page.Options.Warnings.ValueNotChanged=This option was not changed because value you specified was not valid. Page.Options.Button.Save=Save Page.Login.Title=Login - Sone @@ -139,7 +144,8 @@ Page.ViewSone.PostList.Title=Posts by {sone} Page.ViewSone.PostList.Text.NoPostYet=This Sone has not yet posted anything. Page.ViewSone.Profile.Title=Profile Page.ViewSone.Profile.Label.Name=Name -Page.ViewSone.Replies.Title=Replies to Posts +Page.ViewSone.Profile.Name.WoTLink=Web of trust profile +Page.ViewSone.Replies.Title=Posts {sone} has replied to Page.ViewPost.Title=View Post - Sone Page.ViewPost.Page.Title=View Post by {sone} @@ -229,6 +235,8 @@ View.Sone.Status.Downloading=This Sone is currently being downloaded. View.Sone.Status.Inserting=This Sone is currently being inserted. View.Post.UnknownAuthor=(unknown) +View.Post.Permalink=link post +View.Post.PermalinkAuthor=link author View.Post.Bookmarks.PostIsBookmarked=Post is bookmarked, click to remove from bookmarks View.Post.Bookmarks.PostIsNotBookmarked=Post is not bookmarked, click to bookmark View.Post.DeleteLink=Delete @@ -256,7 +264,7 @@ View.Time.XHoursAgo=${hour} hours ago View.Time.ADayAgo=about a day ago View.Time.XDaysAgo=${day} days ago View.Time.AWeekAgo=about a week ago -View.Time.XWeeksAgo=${week} week ago +View.Time.XWeeksAgo=${week} weeks ago View.Time.AMonthAgo=about a month ago View.Time.XMonthsAgo=${month} months ago View.Time.AYearAgo=about a year ago diff --git a/src/main/resources/static/css/sone.css b/src/main/resources/static/css/sone.css index f6bb62d..c0a7b6d 100644 --- a/src/main/resources/static/css/sone.css +++ b/src/main/resources/static/css/sone.css @@ -89,10 +89,28 @@ textarea { content: '★ '; } +#sone a.in-page-link:before { + content: '↓ '; +} + #sone a img { border: none; } +#sone #main.offline { + opacity: 0.5; +} + +#sone #offline-marker { + display: none; + position: fixed; + top: 2em; + right: 2em; + width: 128px; + height: 128px; + background-image: url("../images/sone-offline.png"); +} + #sone #notification-area { margin-top: 1em; } @@ -266,6 +284,10 @@ textarea { display: inline; } +#sone .permalink { + display: inline; +} + #sone .post .bookmarks { display: inline; color: rgb(28, 131, 191); @@ -648,3 +670,8 @@ textarea { font-weight: bold; color: red; } + +#sone .warning { + color: red; + font-style: italic; +} diff --git a/src/main/resources/static/images/sone-offline.png b/src/main/resources/static/images/sone-offline.png new file mode 100644 index 0000000..18430bf Binary files /dev/null and b/src/main/resources/static/images/sone-offline.png differ diff --git a/src/main/resources/static/javascript/sone.js b/src/main/resources/static/javascript/sone.js index d60ee23..9c082f1 100644 --- a/src/main/resources/static/javascript/sone.js +++ b/src/main/resources/static/javascript/sone.js @@ -1,26 +1,20 @@ /* Sone JavaScript functions. */ -/* jQuery overrides. */ -oldGetJson = jQuery.prototype.getJSON; -jQuery.prototype.getJSON = function(url, data, successCallback, errorCallback) { - if (typeof errorCallback == "undefined") { - return oldGetJson(url, data, successCallback); - } - if (jQuery.isFunction(data)) { - errorCallback = successCallback; - successCallback = data; - data = null; - } - return jQuery.ajax({ - data: data, - error: errorCallback, - success: successCallback, - url: url - }); -} - -function isOnline() { - return $("#sone").hasClass("online"); +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) { + ajaxSuccess(); + if (typeof successCallback != "undefined") { + successCallback(data, textStatus); + } + }, "error": function(xmlHttpRequest, textStatus, errorThrown) { + if (typeof errorCallback != "undefined") { + errorCallback(); + } else { + ajaxError(); + } + }}); + })(url, data, successCallback, errorCallback); } function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, optional, dontUseTextarea) { @@ -33,7 +27,7 @@ function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, op inputField.val(defaultText); } }).hide().data("inputField", $(this)).val($(this).val()); - $(this).after(textarea); + $(this).data("textarea", textarea).after(textarea); (function(inputField, textarea) { inputField.focus(function() { $(this).hide().attr("disabled", "disabled"); @@ -66,11 +60,11 @@ function registerInputTextareaSwap(inputElement, defaultText, inputFieldName, op * @param element * The element to add a “comment” link to */ -function addCommentLink(postId, element, insertAfterThisElement) { +function addCommentLink(postId, author, element, insertAfterThisElement) { if (($(element).find(".show-reply-form").length > 0) || (getPostElement(element).find(".create-reply").length == 0)) { return; } - commentElement = (function(postId) { + commentElement = (function(postId, author) { separator = $(" · ").addClass("separator"); var commentElement = $("
Comment
").addClass("show-reply-form").click(function() { replyElement = $("#sone .post#" + postId + " .create-reply"); @@ -85,10 +79,11 @@ function addCommentLink(postId, element, insertAfterThisElement) { replyElement.removeClass("light"); }); })(replyElement); - replyElement.find("input.reply-input").focus(); + textArea = replyElement.find("input.reply-input").focus().data("textarea"); + textArea.val(textArea.val() + "@sone://" + author + " "); }); return commentElement; - })(postId); + })(postId, author); $(insertAfterThisElement).after(commentElement.clone(true)); $(insertAfterThisElement).after(separator); } @@ -109,13 +104,11 @@ function getTranslation(key, callback) { callback(translations[key]); return; } - $.getJSON("getTranslation.ajax", {"key": key}, function(data, textStatus) { + ajaxGet("getTranslation.ajax", {"key": key}, function(data, textStatus) { if ((data != null) && data.success) { translations[key] = data.value; callback(data.value); } - }, function(xmlHttpRequest, textStatus, error) { - /* ignore error. */ }); } @@ -145,7 +138,7 @@ function filterSoneId(soneId) { * @param lastUpdated * The date and time of the last update (formatted for display) */ -function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated) { +function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated, lastUpdatedText) { $("#sone .sone." + filterSoneId(soneId)). toggleClass("unknown", status == "unknown"). toggleClass("idle", status == "idle"). @@ -155,7 +148,7 @@ function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated) { $("#sone .sone." + filterSoneId(soneId) + " .lock").toggleClass("hidden", locked); $("#sone .sone." + filterSoneId(soneId) + " .unlock").toggleClass("hidden", !locked); if (lastUpdated != null) { - $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(lastUpdated); + $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").attr("title", lastUpdated).text(lastUpdatedText); } else { getTranslation("View.Sone.Text.UnknownDate", function(unknown) { $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(unknown); @@ -211,7 +204,7 @@ function enhanceDeleteButton(button, text, deleteCallback) { */ function enhanceDeletePostButton(button, postId, text) { enhanceDeleteButton(button, text, function() { - $.getJSON("deletePost.ajax", { "post": postId, "formPassword": getFormPassword() }, function(data, textStatus) { + ajaxGet("deletePost.ajax", { "post": postId, "formPassword": getFormPassword() }, function(data, textStatus) { if (data == null) { return; } @@ -243,7 +236,7 @@ function enhanceDeletePostButton(button, postId, text) { */ function enhanceDeleteReplyButton(button, replyId, text) { enhanceDeleteButton(button, text, function() { - $.getJSON("deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) { + ajaxGet("deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) { if (data == null) { return; } @@ -276,7 +269,7 @@ function getFormPassword() { */ function getSone(soneId) { return $("#sone .sone").filter(function(index) { - return $(".id").text() == soneId; + return $(".id", this).text() == soneId; }); } @@ -415,8 +408,19 @@ function getNotificationId(notificationElement) { return $(notificationElement).attr("id"); } +/** + * Returns the time the notification was last updated. + * + * @param notificationElement + * The notification element + * @returns The last update time of the notification + */ +function getNotificationLastUpdatedTime(notificationElement) { + return $(notificationElement).attr("lastUpdatedTime"); +} + function likePost(postId) { - $.getJSON("like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) { + ajaxGet("like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) { if ((data == null) || !data.success) { return; } @@ -429,7 +433,7 @@ function likePost(postId) { } function unlikePost(postId) { - $.getJSON("unlike.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) { + ajaxGet("unlike.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) { if ((data == null) || !data.success) { return; } @@ -442,9 +446,9 @@ function unlikePost(postId) { } function updatePostLikes(postId) { - $.getJSON("getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) { + ajaxGet("getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) { if ((data != null) && data.success) { - $("#sone .post#" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0) + $("#sone .post#" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0); $("#sone .post#" + postId + " > .inner-part > .status-line .likes span.like-count").text(data.likes); $("#sone .post#" + postId + " > .inner-part > .status-line .likes > span").attr("title", generateSoneList(data.sones)); } @@ -454,7 +458,7 @@ function updatePostLikes(postId) { } function likeReply(replyId) { - $.getJSON("like.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) { + ajaxGet("like.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) { if ((data == null) || !data.success) { return; } @@ -467,7 +471,7 @@ function likeReply(replyId) { } function unlikeReply(replyId) { - $.getJSON("unlike.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) { + ajaxGet("unlike.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) { if ((data == null) || !data.success) { return; } @@ -486,7 +490,7 @@ function unlikeReply(replyId) { * The ID of the Sone to trust */ function trustSone(soneId) { - $.getJSON("trustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) { + ajaxGet("trustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) { if ((data != null) && data.success) { updateTrustControls(soneId, data.trustValue); } @@ -500,7 +504,7 @@ function trustSone(soneId) { * The ID of the Sone to distrust */ function distrustSone(soneId) { - $.getJSON("distrustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) { + ajaxGet("distrustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) { if ((data != null) && data.success) { updateTrustControls(soneId, data.trustValue); } @@ -514,7 +518,7 @@ function distrustSone(soneId) { * The ID of the Sone to untrust */ function untrustSone(soneId) { - $.getJSON("untrustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) { + ajaxGet("untrustSone.ajax", { "formPassword" : getFormPassword(), "sone" : soneId }, function(data, textStatus) { if ((data != null) && data.success) { updateTrustControls(soneId, data.trustValue); } @@ -555,7 +559,7 @@ function updateTrustControls(soneId, trustValue) { */ function bookmarkPost(postId) { (function(postId) { - $.getJSON("bookmark.ajax", {"formPassword": getFormPassword(), "type": "post", "post": postId}, function(data, textStatus) { + ajaxGet("bookmark.ajax", {"formPassword": getFormPassword(), "type": "post", "post": postId}, function(data, textStatus) { if ((data != null) && data.success) { getPost(postId).find(".bookmark").toggleClass("hidden", true); getPost(postId).find(".unbookmark").toggleClass("hidden", false); @@ -571,7 +575,7 @@ function bookmarkPost(postId) { * The ID of the post to unbookmark */ function unbookmarkPost(postId) { - $.getJSON("unbookmark.ajax", {"formPassword": getFormPassword(), "type": "post", "post": postId}, function(data, textStatus) { + ajaxGet("unbookmark.ajax", {"formPassword": getFormPassword(), "type": "post", "post": postId}, function(data, textStatus) { if ((data != null) && data.success) { getPost(postId).find(".bookmark").toggleClass("hidden", false); getPost(postId).find(".unbookmark").toggleClass("hidden", true); @@ -580,9 +584,9 @@ function unbookmarkPost(postId) { } function updateReplyLikes(replyId) { - $.getJSON("getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) { + ajaxGet("getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) { if ((data != null) && data.success) { - $("#sone .reply#" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0) + $("#sone .reply#" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0); $("#sone .reply#" + replyId + " .status-line .likes span.like-count").text(data.likes); $("#sone .reply#" + replyId + " .status-line .likes > span").attr("title", generateSoneList(data.sones)); } @@ -605,7 +609,7 @@ function updateReplyLikes(replyId) { * parameters: success, error, replyId) */ function postReply(sender, postId, text, callbackFunction) { - $.getJSON("createReply.ajax", { "formPassword" : getFormPassword(), "sender": sender, "post" : postId, "text": text }, function(data, textStatus) { + ajaxGet("createReply.ajax", { "formPassword" : getFormPassword(), "sender": sender, "post" : postId, "text": text }, function(data, textStatus) { if (data == null) { /* TODO - show error */ return; @@ -633,7 +637,7 @@ function ajaxifySone(soneElement) { */ $(".follow", soneElement).submit(function() { var followElement = this; - $.getJSON("followSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() { + ajaxGet("followSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() { $(followElement).addClass("hidden"); $(followElement).parent().find(".unfollow").removeClass("hidden"); }); @@ -641,7 +645,7 @@ function ajaxifySone(soneElement) { }); $(".unfollow", soneElement).submit(function() { var unfollowElement = this; - $.getJSON("unfollowSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() { + ajaxGet("unfollowSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() { $(unfollowElement).addClass("hidden"); $(unfollowElement).parent().find(".follow").removeClass("hidden"); }); @@ -649,7 +653,7 @@ function ajaxifySone(soneElement) { }); $(".lock", soneElement).submit(function() { var lockElement = this; - $.getJSON("lockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() { + ajaxGet("lockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() { $(lockElement).addClass("hidden"); $(lockElement).parent().find(".unlock").removeClass("hidden"); }); @@ -657,7 +661,7 @@ function ajaxifySone(soneElement) { }); $(".unlock", soneElement).submit(function() { var unlockElement = this; - $.getJSON("unlockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() { + ajaxGet("unlockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() { $(unlockElement).addClass("hidden"); $(unlockElement).parent().find(".lock").removeClass("hidden"); }); @@ -666,7 +670,7 @@ function ajaxifySone(soneElement) { /* mark Sone as known when clicking it. */ $(soneElement).click(function() { - markSoneAsKnown(soneElement); + markSoneAsKnown(this); }); } @@ -757,7 +761,7 @@ function ajaxifyPost(postElement) { }); /* add “comment” link. */ - addCommentLink(getPostId(postElement), postElement, $(postElement).find(".post-status-line .time")); + addCommentLink(getPostId(postElement), getPostAuthor(postElement), postElement, $(postElement).find(".post-status-line .permalink-author")); /* process all replies. */ replyIds = []; @@ -817,7 +821,7 @@ function ajaxifyReply(replyElement) { }); }); })(replyElement); - addCommentLink(getPostId(replyElement), replyElement, $(replyElement).find(".reply-status-line .time")); + addCommentLink(getPostId(replyElement), getReplyAuthor(replyElement), replyElement, $(replyElement).find(".reply-status-line .permalink-author")); /* convert “show source” link into javascript function. */ $(replyElement).find(".show-reply-source").each(function() { @@ -859,19 +863,19 @@ function ajaxifyNotification(notification) { notification.find(".text").addClass("hidden"); } notification.find("form.mark-as-read button").click(function() { - $.getJSON("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": $(":input[name=type]", this.form).val(), "id": $(":input[name=id]", this.form).val()}); + ajaxGet("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": $(":input[name=type]", this.form).val(), "id": $(":input[name=id]", this.form).val()}); }); notification.find("a[class^='link-']").each(function() { linkElement = $(this); if (linkElement.is("[href^='viewPost']")) { id = linkElement.attr("class").substr(5); if (hasPost(id)) { - linkElement.attr("href", "#post-" + id); + linkElement.attr("href", "#post-" + id).addClass("in-page-link"); } } }); notification.find("form.dismiss button").click(function() { - $.getJSON("dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) { + ajaxGet("dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) { /* dismiss in case of error, too. */ notification.slideUp(); }, function(xmlHttpRequest, textStatus, error) { @@ -911,8 +915,8 @@ function checkForRemovedSones(oldNotification, newNotification) { if (getNotificationId(oldNotification) != "new-sone-notification") { return; } - oldIds = getElementIds(oldNotification, ".sone-id"); - newIds = getElementIds(newNotification, ".sone-id"); + oldIds = getElementIds(oldNotification, ".new-sone-id"); + newIds = getElementIds(newNotification, ".new-sone-id"); $.each(oldIds, function(index, value) { if ($.inArray(value, newIds) == -1) { markSoneAsKnown(getSone(value), true); @@ -966,11 +970,11 @@ function checkForRemovedReplies(oldNotification, newNotification) { } function getStatus() { - $.getJSON("getStatus.ajax", {"loadAllSones": isKnownSonesPage()}, function(data, textStatus) { + ajaxGet("getStatus.ajax", isViewSonePage() ? {"soneIds": getShownSoneId() } : {"loadAllSones": isKnownSonesPage()}, function(data, textStatus) { if ((data != null) && data.success) { /* process Sone information. */ $.each(data.sones, function(index, value) { - updateSoneStatus(value.id, value.name, value.status, value.modified, value.locked, value.lastUpdatedUnknown ? null : value.lastUpdated); + updateSoneStatus(value.id, value.name, value.status, value.modified, value.locked, value.lastUpdatedUnknown ? null : value.lastUpdated, value.lastUpdatedText); }); /* search for removed notifications. */ $("#sone #notification-area .notification").each(function() { @@ -984,7 +988,7 @@ function getStatus() { }); if (!foundNotification) { if (notificationId == "new-sone-notification") { - $(".sone-id", this).each(function(index, element) { + $(".new-sone-id", this).each(function(index, element) { soneId = $(this).text(); markSoneAsKnown(getSone(soneId), true); }); @@ -1009,25 +1013,16 @@ function getStatus() { } }); /* process notifications. */ + notificationIds = []; $.each(data.notifications, function(index, value) { oldNotification = getNotification(value.id); - notification = ajaxifyNotification(createNotification(value.id, value.text, value.dismissable)).hide(); - if (oldNotification.length != 0) { - if ((oldNotification.find(".short-text").length > 0) && (notification.find(".short-text").length > 0)) { - opened = oldNotification.is(":visible") && oldNotification.find(".short-text").hasClass("hidden"); - notification.find(".short-text").toggleClass("hidden", opened); - notification.find(".text").toggleClass("hidden", !opened); - } - checkForRemovedSones(oldNotification, notification); - checkForRemovedPosts(oldNotification, notification); - checkForRemovedReplies(oldNotification, notification); - oldNotification.replaceWith(notification.show()); - } else { - $("#sone #notification-area").append(notification); - notification.slideDown(); - setActivity(); + if ((oldNotification.length == 0) || (value.lastUpdatedTime > getNotificationLastUpdatedTime(oldNotification))) { + notificationIds.push(value.id); } }); + if (notificationIds.length > 0) { + loadNotifications(notificationIds); + } /* process new posts. */ $.each(data.newPosts, function(index, value) { loadNewPost(value.id, value.sone, value.recipient, value.time); @@ -1042,10 +1037,44 @@ function getStatus() { /* data.success was false, wait 30 seconds. */ setTimeout(getStatus, 30000); } - }, function(xmlHttpRequest, textStatus, error) { - /* something really bad happend, wait a minute. */ - setTimeout(getStatus, 60000); - }) + }, function() { + statusRequestQueued = false; + ajaxError(); + }); +} + +/** + * Requests multiple notifications from Sone and displays them. + * + * @param notificationIds + * Array of IDs of the notifications to load + */ +function loadNotifications(notificationIds) { + ajaxGet("getNotification.ajax", {"notifications": notificationIds.join(",")}, function(data, textStatus) { + if (!data || !data.success) { + // TODO - show error + return; + } + $.each(data.notifications, function(index, value) { + oldNotification = getNotification(value.id); + notification = ajaxifyNotification(createNotification(value.id, value.lastUpdatedTime, value.text, value.dismissable)).hide(); + if (oldNotification.length != 0) { + if ((oldNotification.find(".short-text").length > 0) && (notification.find(".short-text").length > 0)) { + opened = oldNotification.is(":visible") && oldNotification.find(".short-text").hasClass("hidden"); + notification.find(".short-text").toggleClass("hidden", opened); + notification.find(".text").toggleClass("hidden", !opened); + } + checkForRemovedSones(oldNotification, notification); + checkForRemovedPosts(oldNotification, notification); + checkForRemovedReplies(oldNotification, notification); + oldNotification.replaceWith(notification.show()); + } else { + $("#sone #notification-area").append(notification); + notification.slideDown(); + setActivity(); + } + }); + }); } /** @@ -1078,6 +1107,22 @@ function isIndexPage() { } /** + * Returns the current page of the selected pagination. If no pagination can be + * found with the given selector, {@code 1} is returned. + * + * @param paginationSelector + * The pagination selector + * @returns The current page of the pagination + */ +function getPage(paginationSelector) { + pagination = $(paginationSelector); + if (pagination.length > 0) { + return $(".current-page", paginationSelector).text(); + } + return 1; +} + +/** * Returns whether the current page is a “view Sone” page. * * @returns {Boolean} true if the current page is a “view Sone” @@ -1155,7 +1200,7 @@ function loadNewPost(postId, soneId, recipientId, time) { if (hasPost(postId)) { return; } - if (!isIndexPage()) { + if (!isIndexPage() || (getPage(".pagination-index") > 1)) { if (!isViewPostPage() || (getShownPostId() != postId)) { if (!isViewSonePage() || ((getShownSoneId() != soneId) && (getShownSoneId() != recipientId))) { return; @@ -1165,12 +1210,12 @@ function loadNewPost(postId, soneId, recipientId, time) { if (getPostTime($("#sone .post").last()) > time) { return; } - $.getJSON("getPost.ajax", { "post" : postId }, function(data, textStatus) { + ajaxGet("getPost.ajax", { "post" : postId }, function(data, textStatus) { if ((data != null) && data.success) { if (hasPost(data.post.id)) { return; } - if (!isIndexPage() && !(isViewSonePage() && ((getShownSoneId() == data.post.sone) || (getShownSoneId() == data.post.recipient)))) { + if ((!isIndexPage() || (getPage(".pagination-index") > 1)) && !(isViewSonePage() && ((getShownSoneId() == data.post.sone) || (getShownSoneId() == data.post.recipient)))) { return; } var firstOlderPost = null; @@ -1199,7 +1244,7 @@ function loadNewReply(replyId, soneId, postId, postSoneId) { if (!hasPost(postId)) { return; } - $.getJSON("getReply.ajax", { "reply": replyId }, function(data, textStatus) { + ajaxGet("getReply.ajax", { "reply": replyId }, function(data, textStatus) { /* find post. */ if ((data != null) && data.success) { if (hasReply(data.reply.id)) { @@ -1243,11 +1288,10 @@ function loadNewReply(replyId, soneId, postId, postSoneId) { * request */ function markSoneAsKnown(soneElement, skipRequest) { - if ($(".new", soneElement).length > 0) { - if ((typeof skipRequest != "undefined") && !skipRequest) { - $.getJSON("maskAsKnown.ajax", {"formPassword": getFormPassword(), "type": "sone", "id": getSoneId(soneElement)}, function(data, textStatus) { - $(soneElement).removeClass("new"); - }); + if ($(soneElement).is(".new")) { + $(soneElement).removeClass("new"); + if ((typeof skipRequest == "undefined") || !skipRequest) { + ajaxGet("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "sone", "id": getSoneId(soneElement)}); } } } @@ -1260,7 +1304,7 @@ function markPostAsKnown(postElements, skipRequest) { $(postElement).removeClass("new"); $(".click-to-show", postElement).removeClass("new"); if ((typeof skipRequest == "undefined") || !skipRequest) { - $.getJSON("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "post", "id": getPostId(postElement)}); + ajaxGet("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "post", "id": getPostId(postElement)}); } })(postElement); } @@ -1275,7 +1319,7 @@ function markReplyAsKnown(replyElements, skipRequest) { (function(replyElement) { $(replyElement).removeClass("new"); if ((typeof skipRequest == "undefined") || !skipRequest) { - $.getJSON("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "reply", "id": getReplyId(replyElement)}); + ajaxGet("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "reply", "id": getReplyId(replyElement)}); } })(replyElement); } @@ -1313,7 +1357,7 @@ function updatePostTime(postId, timeText, refreshTime, tooltip) { * Comma-separated post IDs */ function updatePostTimes(postIds) { - $.getJSON("getTimes.ajax", { "posts" : postIds }, function(data, textStatus) { + ajaxGet("getTimes.ajax", { "posts" : postIds }, function(data, textStatus) { if ((data != null) && data.success) { $.each(data.postTimes, function(index, value) { updatePostTime(index, value.timeText, value.refreshTime, value.tooltip); @@ -1350,7 +1394,7 @@ function updateReplyTime(replyId, timeText, refreshTime, tooltip) { * Comma-separated post IDs */ function updateReplyTimes(replyIds) { - $.getJSON("getTimes.ajax", { "replies" : replyIds }, function(data, textStatus) { + ajaxGet("getTimes.ajax", { "replies" : replyIds }, function(data, textStatus) { if ((data != null) && data.success) { $.each(data.replyTimes, function(index, value) { updateReplyTime(index, value.timeText, value.refreshTime, value.tooltip); @@ -1439,10 +1483,10 @@ function changeIcon(iconUrl) { * true if the notification can be dismissed by the * user */ -function createNotification(id, text, dismissable) { - notification = $("
").addClass("notification").attr("id", id); +function createNotification(id, lastUpdatedTime, text, dismissable) { + notification = $("
").addClass("notification").attr("id", id).attr("lastUpdatedTime", lastUpdatedTime); if (dismissable) { - dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id") + dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id"); dismissForm.find("input[name=notification]").val(id); notification.append(dismissForm); } @@ -1468,7 +1512,7 @@ function showNotificationDetails(notificationId) { * The ID of the field to delete */ function deleteProfileField(fieldId) { - $.getJSON("deleteProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId}, function(data, textStatus) { + ajaxGet("deleteProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId}, function(data, textStatus) { if (data && data.success) { $("#sone .profile-field#" + data.field.id).slideUp(); } @@ -1486,7 +1530,7 @@ function deleteProfileField(fieldId) { * Called when the renaming was successful */ function editProfileField(fieldId, newName, successFunction) { - $.getJSON("editProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "name": newName}, function(data, textStatus) { + ajaxGet("editProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "name": newName}, function(data, textStatus) { if (data && data.success) { successFunction(); } @@ -1504,7 +1548,7 @@ function editProfileField(fieldId, newName, successFunction) { * Function to call on success */ function moveProfileField(fieldId, direction, successFunction) { - $.getJSON("moveProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "direction": direction}, function(data, textStatus) { + ajaxGet("moveProfileField.ajax", {"formPassword": getFormPassword(), "field": fieldId, "direction": direction}, function(data, textStatus) { if (data && data.success) { successFunction(); } @@ -1535,11 +1579,51 @@ function moveProfileFieldDown(fieldId, successFunction) { moveProfileField(fieldId, "down", successFunction); } +var statusRequestQueued = true; + +/** + * Sets the status of the web interface as offline. + */ +function ajaxError() { + online = false; + toggleOfflineMarker(true); + if (!statusRequestQueued) { + setTimeout(getStatus, 5000); + statusRequestQueued = true; + } +} + +/** + * Sets the status of the web interface as online. + */ +function ajaxSuccess() { + online = true; + toggleOfflineMarker(false); +} + +/** + * Shows or hides the offline marker. + * + * @param visible + * {@code true} to display the offline marker, {@code false} to hide + * it + */ +function toggleOfflineMarker(visible) { + /* jQuery documentation says toggle() works the other way around?! */ + $("#sone #offline-marker").toggle(visible); + if (visible) { + $("#sone #main").addClass("offline"); + } else { + $("#sone #main").removeClass("offline"); + } +} + // // EVERYTHING BELOW HERE IS EXECUTED AFTER LOADING THE PAGE // var focus = true; +var online = true; $(document).ready(function() { @@ -1561,7 +1645,7 @@ $(document).ready(function() { } sender = $(this).find(":input[name=sender]").val(); text = $(this).find(":input[name=text]:enabled").val(); - $.getJSON("createPost.ajax", { "formPassword": getFormPassword(), "sender": sender, "text": text }, function(data, textStatus) { + ajaxGet("createPost.ajax", { "formPassword": getFormPassword(), "sender": sender, "text": text }, function(data, textStatus) { button.removeAttr("disabled"); }); $(this).find(":input[name=sender]").val(getCurrentSoneId()); @@ -1590,7 +1674,7 @@ $(document).ready(function() { $("#sone #post-message").submit(function() { sender = $(this).find(":input[name=sender]").val(); text = $(this).find(":input[name=text]:enabled").val(); - $.getJSON("createPost.ajax", { "formPassword": getFormPassword(), "recipient": getShownSoneId(), "sender": sender, "text": text }); + 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(); $(this).find(".sender").hide(); @@ -1655,6 +1739,11 @@ $(document).ready(function() { ajaxifyNotification($(this)); }); + /* disable all permalinks. */ + $(".permalink").click(function() { + return false; + }); + /* activate status polling. */ setTimeout(getStatus, 5000); @@ -1664,6 +1753,6 @@ $(document).ready(function() { resetActivity(); }).blur(function() { focus = false; - }) + }); }); diff --git a/src/main/resources/templates/include/head.html b/src/main/resources/templates/include/head.html index fe9cf34..e80a92c 100644 --- a/src/main/resources/templates/include/head.html +++ b/src/main/resources/templates/include/head.html @@ -1,4 +1,4 @@ -
+
<% formPassword|html>
@@ -7,6 +7,8 @@ +
+
@@ -18,8 +20,8 @@ - <%foreach webInterface.notifications.all notification> -
+ <%foreach notifications notification> +
<%if notification.dismissable>
diff --git a/src/main/resources/templates/include/pagination.html b/src/main/resources/templates/include/pagination.html index a5efc97..f8e6f11 100644 --- a/src/main/resources/templates/include/pagination.html +++ b/src/main/resources/templates/include/pagination.html @@ -1,5 +1,5 @@ <%if pagination.necessary> -
<% reply.time|date format="MMM d, yyyy, HH:mm:ss">
+ · + <%if ! originalText|match key=parsedText> · diff --git a/src/main/resources/templates/include/viewSone.html b/src/main/resources/templates/include/viewSone.html index c403b0d..91c921d 100644 --- a/src/main/resources/templates/include/viewSone.html +++ b/src/main/resources/templates/include/viewSone.html @@ -5,7 +5,7 @@
⬊
⬈
✔
-
<%= View.Sone.Label.LastUpdate|l10n|html> <% sone.time|unknown|date format="MMM d, yyyy, HH:mm:ss">
+
<%= View.Sone.Label.LastUpdate|l10n|html> "><%sone.lastUpdatedText|html>
<% sone.requestUri|substring start=4 length=43|html>
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 59bfee7..f79e52d 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -8,7 +8,7 @@
<%= page|store key=pageParameter> - <%include include/pagination.html> + <%include include/pagination.html paginationName==pagination-index> <%foreach pagination.items post> <%include include/viewPost.html> <%foreachelse> diff --git a/src/main/resources/templates/insert/index.html b/src/main/resources/templates/insert/index.html index 73e35a8..7295361 100644 --- a/src/main/resources/templates/insert/index.html +++ b/src/main/resources/templates/insert/index.html @@ -6,7 +6,7 @@

Sone “<% currentSone.name|html>”

This page should not be viewed directly; it is merely a reminder - that you should install the Sone plugin. + that you should install the Sone plugin.

diff --git a/src/main/resources/templates/notify/newPostNotification.html b/src/main/resources/templates/notify/newPostNotification.html index 54008dc..0cd71ff 100644 --- a/src/main/resources/templates/notify/newPostNotification.html +++ b/src/main/resources/templates/notify/newPostNotification.html @@ -1,15 +1,15 @@ + + + + + + +
-
- - - - - -
<%= Notification.NewPost.Text|l10n|html> <%foreach posts post> diff --git a/src/main/resources/templates/notify/newReplyNotification.html b/src/main/resources/templates/notify/newReplyNotification.html index ee8c410..a4588e5 100644 --- a/src/main/resources/templates/notify/newReplyNotification.html +++ b/src/main/resources/templates/notify/newReplyNotification.html @@ -1,15 +1,15 @@ +
+ + + + + +
-
- - - - - -
<%foreach replies reply><%/foreach> <%= Notification.NewReply.Text|l10n|html> <%foreach replies postGroup|replyGroup> diff --git a/src/main/resources/templates/notify/newSoneNotification.html b/src/main/resources/templates/notify/newSoneNotification.html index aaa0d14..64985d8 100644 --- a/src/main/resources/templates/notify/newSoneNotification.html +++ b/src/main/resources/templates/notify/newSoneNotification.html @@ -1,18 +1,18 @@ +
+ + + + + +
-
- - - - - -
<%= Notification.NewSone.Text|l10n|html> <%foreach sones sone> - + <% sone.niceName|html><%notlast>,<%/notlast><%last>.<%/last> <%/foreach>
diff --git a/src/main/resources/templates/options.html b/src/main/resources/templates/options.html index dfeb3e9..368c14e 100644 --- a/src/main/resources/templates/options.html +++ b/src/main/resources/templates/options.html @@ -31,6 +31,8 @@ <%ifnull currentSone>

<%= Page.Options.Section.SoneSpecificOptions.NotLoggedIn|l10n|html|replace needle="{link}" replacement=''|replace needle="{/link}" replacement=''>

+ <%else> +

<%= Page.Options.Section.SoneSpecificOptions.LoggedIn|l10n|html>

<%/if>

@@ -41,17 +43,34 @@

<%= Page.Options.Section.RuntimeOptions.Title|l10n|html>

<%= Page.Options.Option.InsertionDelay.Description|l10n|html>

+ <%if =insertion-delay|in collection=fieldErrors> +

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

+ <%/if>

<%= Page.Options.Option.PostsPerPage.Description|l10n|html>

+ <%if =posts-per-page|in collection=fieldErrors> +

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

+ <%/if>

+

+ checked="checked"<%/if> /> + <%= Page.Options.Option.RequireFullAccess.Description|l10n|html>

+

+

<%= Page.Options.Section.TrustOptions.Title|l10n|html>

<%= Page.Options.Option.PositiveTrust.Description|l10n|html>

+ <%if =positive-trust|in collection=fieldErrors> +

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

+ <%/if>

<%= Page.Options.Option.NegativeTrust.Description|l10n|html>

+ <%if =negative-trust|in collection=fieldErrors> +

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

+ <%/if>

<%= Page.Options.Option.TrustComment.Description|l10n|html>

@@ -72,7 +91,9 @@

<%= Page.Options.Section.RescueOptions.Title|l10n|html>

-

<%= Page.Options.Option.SoneRescueMode.Description|l10n|html>

+

<%= Page.Options.Option.SoneRescueMode.Description1|l10n|html>

+

<%= Page.Options.Option.SoneRescueMode.Description2|l10n|html>

+

<%= Page.Options.Option.SoneRescueMode.Description3|l10n|html>

<%= Page.Options.Section.Cleaning.Title|l10n|html>

diff --git a/src/main/resources/templates/viewSone.html b/src/main/resources/templates/viewSone.html index 066adef..689ff38 100644 --- a/src/main/resources/templates/viewSone.html +++ b/src/main/resources/templates/viewSone.html @@ -7,7 +7,7 @@

<%= Page.ViewSone.Page.TitleWithoutSone|l10n|html>

-

<%= Page.ViewSone.NoSone.Description|l10n|replace needle="{sone}" replacementKey=sone.id|html>

+

<%= Page.ViewSone.NoSone.Description|l10n|replace needle="{sone}" replacementKey=soneId|html>

<%elseifnull sone.name> @@ -25,7 +25,7 @@
<%= Page.ViewSone.Profile.Label.Name|l10n|html>
- +
<%foreach sone.profile.fields field> @@ -76,7 +76,7 @@ <%foreach repliedPosts post> <%first> -

<%= Page.ViewSone.Replies.Title|l10n|html>

+

<%= Page.ViewSone.Replies.Title|l10n|html|replace needle="{sone}" replacementKey=sone.niceName>

<%include include/pagination.html pagination=repliedPostPagination pageParameter==repliedPostPage> <%/first> diff --git a/src/test/java/net/pterodactylus/sone/text/FreenetLinkParserTest.java b/src/test/java/net/pterodactylus/sone/text/FreenetLinkParserTest.java index 7e05fd5..e667aeb 100644 --- a/src/test/java/net/pterodactylus/sone/text/FreenetLinkParserTest.java +++ b/src/test/java/net/pterodactylus/sone/text/FreenetLinkParserTest.java @@ -41,7 +41,7 @@ public class FreenetLinkParserTest extends TestCase { TemplateContextFactory templateContextFactory = new TemplateContextFactory(); templateContextFactory.addFilter("html", new HtmlFilter()); FreenetLinkParser parser = new FreenetLinkParser(null, templateContextFactory); - FreenetLinkParserContext context = new FreenetLinkParserContext(null); + FreenetLinkParserContext context = new FreenetLinkParserContext(null, null); Part part; part = parser.parse(context, new StringReader("Text."));