<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.6.3</version>
+ <version>0.6.4</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
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;
/** 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;
remoteSones.put(identity.getId(), sone);
soneDownloader.addSone(sone);
setSoneStatus(sone, SoneStatus.unknown);
- new Thread(new Runnable() {
+ soneDownloaders.execute(new Runnable() {
@Override
@SuppressWarnings("synthetic-access")
soneDownloader.fetchSone(sone, sone.getRequestUri());
}
- }, "Sone Downloader").start();
+ });
return sone;
}
}
return;
}
+ /* initialize options. */
+ sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
+
/* load Sone. */
String sonePrefix = "Sone/" + sone.getId();
Long soneTime = configuration.getLongValue(sonePrefix + "/Time").getValue(null);
}
/* load options. */
- sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
sone.getOptions().getBooleanOption("AutoFollow").set(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null));
/* if we’re still here, Sone was loaded successfully. */
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());
}));
options.addIntegerOption("PostsPerPage", new DefaultOption<Integer>(10));
+ options.addBooleanOption("RequireFullAccess", new DefaultOption<Boolean>(false));
options.addIntegerOption("PositiveTrust", new DefaultOption<Integer>(75));
options.addIntegerOption("NegativeTrust", new DefaultOption<Integer>(-25));
options.addStringOption("TrustComment", new DefaultOption<String>("Set from Sone Web Interface"));
options.getIntegerOption("InsertionDelay").set(configuration.getIntValue("Option/InsertionDelay").getValue(null));
options.getIntegerOption("PostsPerPage").set(configuration.getIntValue("Option/PostsPerPage").getValue(null));
+ options.getBooleanOption("RequireFullAccess").set(configuration.getBooleanValue("Option/RequireFullAccess").getValue(null));
options.getIntegerOption("PositiveTrust").set(configuration.getIntValue("Option/PositiveTrust").getValue(null));
options.getIntegerOption("NegativeTrust").set(configuration.getIntValue("Option/NegativeTrust").getValue(null));
options.getStringOption("TrustComment").set(configuration.getStringValue("Option/TrustComment").getValue(null));
}
/**
+ * 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
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 = 35;
+ private static final int LATEST_EDITION = 36;
/** The Freenet interface. */
private final FreenetInterface freenetInterface;
private final Map<String, String> properties = Collections.synchronizedMap(new HashMap<String, String>());
/** Cached trust. */
+ /* synchronize on itself. */
private final WritableCache<OwnIdentity, Trust> trustCache = new MemoryCache<OwnIdentity, Trust>(new ValueRetriever<OwnIdentity, Trust>() {
@Override
@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;
* The trust received for this identity
*/
void setTrustPrivate(OwnIdentity ownIdentity, Trust trust) {
- trustCache.put(ownIdentity, trust);
+ synchronized (trustCache) {
+ trustCache.put(ownIdentity, trust);
+ }
}
//
Map<OwnIdentity, Map<String, Identity>> currentIdentities = new HashMap<OwnIdentity, Map<String, Identity>>();
Map<String, OwnIdentity> currentOwnIdentities = new HashMap<String, OwnIdentity>();
+ Set<OwnIdentity> ownIdentities = null;
+ boolean identitiesLoaded = false;
try {
/* get all identities with the wanted context from WoT. */
- Set<OwnIdentity> 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<Identity> trustedIdentities = webOfTrustConnector.loadTrustedIdentities(ownIdentity, context);
Map<String, Identity> identities = new HashMap<String, Identity>();
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()) {
}
}
}
-
- /* 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. */
}
/** The version. */
- public static final Version VERSION = new Version(0, 6, 3);
+ public static final Version VERSION = new Version(0, 6, 4);
/** The logger. */
private static final Logger logger = Logging.getLogger(SonePlugin.class);
* The current Sone, or {@code null} if not logged in
* @return The filtered notifications
*/
- public static List<Notification> filterNotifications(List<Notification> notifications, Sone currentSone) {
- ListNotification<Post> newPostNotification = getNotification(notifications, "new-post-notification", Post.class);
- if (newPostNotification != null) {
- ListNotification<Post> filteredNotification = filterNewPostNotification(newPostNotification, currentSone);
- int notificationIndex = notifications.indexOf(newPostNotification);
- if (filteredNotification == null) {
- notifications.remove(notificationIndex);
- } else {
- notifications.set(notificationIndex, filteredNotification);
- }
- }
- ListNotification<Reply> newReplyNotification = getNotification(notifications, "new-replies-notification", Reply.class);
- if (newReplyNotification != null) {
- ListNotification<Reply> filteredNotification = filterNewReplyNotification(newReplyNotification, currentSone);
- int notificationIndex = notifications.indexOf(newReplyNotification);
- if (filteredNotification == null) {
- notifications.remove(notificationIndex);
+ @SuppressWarnings("unchecked")
+ public static List<Notification> filterNotifications(Collection<? extends Notification> notifications, Sone currentSone) {
+ List<Notification> filteredNotifications = new ArrayList<Notification>();
+ for (Notification notification : notifications) {
+ if (notification.getId().equals("new-post-notification")) {
+ ListNotification<Post> filteredNotification = filterNewPostNotification((ListNotification<Post>) notification, currentSone);
+ if (filteredNotification != null) {
+ filteredNotifications.add(filteredNotification);
+ }
+ } else if (notification.getId().equals("new-replies-notification")) {
+ ListNotification<Reply> filteredNotification = filterNewReplyNotification((ListNotification<Reply>) notification, currentSone);
+ if (filteredNotification != null) {
+ filteredNotifications.add(filteredNotification);
+ }
} else {
- notifications.set(notificationIndex, filteredNotification);
+ filteredNotifications.add(notification);
}
}
- return notifications;
+ return filteredNotifications;
}
/**
}
List<Reply> newReplies = new ArrayList<Reply>();
for (Reply reply : newReplyNotification.getElements()) {
- if (isPostVisible(currentSone, reply.getPost())) {
+ if (isReplyVisible(currentSone, reply)) {
newReplies.add(reply);
}
}
}
/**
- * Finds the notification with the given ID in the list of notifications and
- * returns it.
- *
- * @param <T>
- * 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
- */
- @SuppressWarnings("unchecked")
- private static <T> ListNotification<T> getNotification(Collection<? extends Notification> notifications, String notificationId, Class<T> notificationElementClass) {
- for (Notification notification : notifications) {
- if (!notificationId.equals(notification.getId())) {
- continue;
- }
- return (ListNotification<T>) notification;
- }
- return null;
- }
-
- /**
* Checks whether a post is visible to the given Sone. A post is not
* considered visible if one of the following statements is true:
* <ul>
* Sone.</li>
* <li>The given Sone has not explicitely assigned negative trust to the
* post’s Sone but the implicit trust is negative.</li>
+ * <li>The post’s {@link Post#getTime() time} is in the future.</li>
* </ul>
* If none of these statements is true the post is considered visible.
*
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:
+ * <ul>
+ * <li>The reply does not have a post.</li>
+ * <li>The reply’s post does not have a Sone.</li>
+ * <li>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.</li>
+ * <li>The trust relationship between the two Sones can not be retrieved.</li>
+ * <li>The given Sone has explicitely assigned negative trust to the post’s
+ * Sone.</li>
+ * <li>The given Sone has not explicitely assigned negative trust to the
+ * reply’s post’s Sone but the implicit trust is negative.</li>
+ * <li>The reply’s post’s {@link Post#getTime() time} is in the future.</li>
+ * <li>The reply’s {@link Reply#getTime() time} is in the future.</li>
+ * </ul>
+ * 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 true;
}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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}.
- * <dl>
- * <dd>all</dd>
- * <dt>Returns all notifications, sorted by creation time, oldest first.</dt>
- * <dd>new</dd>
- * <dt>Returns all changed notifications, sorted by last updated time, newest
- * first.</dt>
- * </dl>
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<Notification> notifications = ListNotificationFilters.filterNotifications(new ArrayList<Notification>(notificationManager.getNotifications()), (Sone) templateContext.get("currentSone"));
- Collections.sort(notifications, Notification.CREATED_TIME_SORTER);
- return notifications;
- }
- return super.get(templateContext, object, member);
- }
-
-}
}
emptyLines = 0;
boolean lineComplete = true;
-
- /* filter http(s) links to own node. */
- String hostHeader = (context.getRequest() != null) ? context.getRequest().getHttpRequest().getHeader("host") : null;
- logger.log(Level.FINEST, "hostHeader: %s", hostHeader);
- if (hostHeader != null) {
- for (String toRemove : new String[] { "http://" + hostHeader + "/", "https://" + hostHeader + "/", "http://" + hostHeader, "https://" + hostHeader }) {
- while (line.indexOf(toRemove) != -1) {
- line = line.replace(toRemove, "");
- }
- }
- }
-
while (line.length() > 0) {
int nextKsk = line.indexOf("KSK@");
int nextChk = line.indexOf("CHK@");
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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;
+ }
+
+}
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;
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);
}
*/
@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);
}
*/
@Override
public boolean isEnabled(ToadletContext toadletContext) {
+ if (webInterface.getCore().getPreferences().isRequireFullAccess() && !toadletContext.isAllowedFullAccess()) {
+ return false;
+ }
return getCurrentSone(toadletContext, false) == null;
}
*/
@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);
}
preferences.setInsertionDelay(insertionDelay);
Integer postsPerPage = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("posts-per-page", 4), null);
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);
Integer negativeTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("negative-trust", 4));
}
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());
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.main.SonePlugin;
+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;
@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);
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));
}
/**
* {@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;
}
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<Post> sonePosts = sone.getPosts();
sonePosts.addAll(webInterface.getCore().getDirectedPosts(sone));
Collections.sort(sonePosts, Post.TIME_COMPARATOR);
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;
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());
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;
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);
}
* 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
/**
* Creates a JSON representation of the given reply.
*
+ * @param request
+ * The request being processed
* @param reply
* The reply to convert
* @param currentSone
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<Sone> sones = new HashSet<Sone>(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) {
@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);
}
});
@Override
public boolean filterObject(Reply reply) {
- return (reply.getPost() != null) && (reply.getPost().getSone() != null) && (currentSone.hasFriend(reply.getPost().getSone().getId()) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient()));
+ return ListNotificationFilters.isReplyVisible(currentSone, reply);
}
});
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");
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);
*/
@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);
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. */
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
//
*/
@Override
public boolean isEnabled(ToadletContext toadletContext) {
- return true;
+ return !isFullAccessOnly();
}
/**
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.
Page.Options.Option.TrustComment.Description=The comment that will be set in the web of trust for any trust you assign from Sone.
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.
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;
}
/* 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) {
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. */
});
}
*/
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;
}
*/
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;
}
}
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;
}
}
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;
}
}
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));
}
}
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;
}
}
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;
}
* 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);
}
* 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);
}
* 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);
}
*/
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);
* 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);
}
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));
}
* 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;
*/
$(".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");
});
});
$(".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");
});
});
$(".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");
});
});
$(".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");
});
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) {
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);
}
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) {
});
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);
});
/* 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();
+ });
}
/**
* Array of IDs of the notifications to load
*/
function loadNotifications(notificationIds) {
- $.getJSON("getNotification.ajax", {"notifications": notificationIds.join(",")}, function(data, textStatus) {
+ ajaxGet("getNotification.ajax", {"notifications": notificationIds.join(",")}, function(data, textStatus) {
if (!data || !data.success) {
// TODO - show error
return;
notification.slideDown();
setActivity();
}
- })
+ });
});
}
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 (!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)) {
if ($(soneElement).is(".new")) {
$(soneElement).removeClass("new");
if ((typeof skipRequest == "undefined") || !skipRequest) {
- $.getJSON("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "sone", "id": getSoneId(soneElement)});
+ ajaxGet("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "sone", "id": getSoneId(soneElement)});
}
}
}
$(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);
}
(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);
}
* 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);
* 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);
function createNotification(id, lastUpdatedTime, text, dismissable) {
notification = $("<div></div>").addClass("notification").attr("id", id).attr("lastUpdatedTime", lastUpdatedTime);
if (dismissable) {
- dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id")
+ dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id");
dismissForm.find("input[name=notification]").val(id);
notification.append(dismissForm);
}
* 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();
}
* 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();
}
* 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();
}
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() {
}
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());
$("#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();
resetActivity();
}).blur(function() {
focus = false;
- })
+ });
});
-<div id="sone" class="<%ifnull ! currentSone>online<%else>offline<%/if>">
+<div id="sone">
<div id="formPassword"><% formPassword|html></div>
<div id="currentSoneId" class="hidden"><% currentSone.id|html></div>
<script src="javascript/jquery.url.js" language="javascript"></script>
<script src="javascript/sone.js" language="javascript"></script>
+ <div id="offline-marker"></div>
+
<div id="main">
<div id="notification-area">
<button type="submit"><%= Notification.Button.Dismiss|l10n|html></button>
</form>
- <%foreach webInterface.notifications.all notification>
+ <%foreach notifications notification>
<div class="notification" id="<% notification.id|html>" lastUpdatedTime="<%notification.lastUpdatedTime|html>">
<%if notification.dismissable>
<form class="dismiss" action="dismissNotification.html" method="post">
<div class="text">
<%= Notification.NewSone.Text|l10n|html>
<%foreach sones sone>
- <div class="hidden sone-id"><% sone.id|html></div>
+ <div class="hidden new-sone-id"><% sone.id|html></div>
<a href="viewSone.html?sone=<% sone.id|html>" title="<% sone.requestUri|html>"><% sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
<%/foreach>
</div>
<p><%= Page.Options.Option.PostsPerPage.Description|l10n|html></p>
<p><input type="text" name="posts-per-page" value="<% posts-per-page|html>" /></p>
+ <p>
+ <input type="checkbox" name="require-full-access"<%if require-full-access> checked="checked"<%/if> />
+ <%= Page.Options.Option.RequireFullAccess.Description|l10n|html></p>
+ </p>
+
<h2><%= Page.Options.Section.TrustOptions.Title|l10n|html></h2>
<p><%= Page.Options.Option.PositiveTrust.Description|l10n|html></p>
<h2><%= Page.Options.Section.RescueOptions.Title|l10n|html></h2>
- <p><%= Page.Options.Option.SoneRescueMode.Description|l10n|html></p>
+ <p><%= Page.Options.Option.SoneRescueMode.Description1|l10n|html></p>
+ <p><%= Page.Options.Option.SoneRescueMode.Description2|l10n|html></p>
+ <p><%= Page.Options.Option.SoneRescueMode.Description3|l10n|html></p>
<p><select name="sone-rescue-mode"><option disabled="disabled"><%= WebInterface.SelectBox.Choose|l10n|html></option><option value="true"<%if sone-rescue-mode> selected="selected"<%/if>><%= WebInterface.SelectBox.Yes|l10n|html></option><option value="false"<%if !sone-rescue-mode> selected="selected"<%/if>><%= WebInterface.SelectBox.No|l10n|html></option></select>
<h2><%= Page.Options.Section.Cleaning.Title|l10n|html></h2>
<h1><%= Page.ViewSone.Page.TitleWithoutSone|l10n|html></h1>
- <p><%= Page.ViewSone.NoSone.Description|l10n|replace needle="{sone}" replacementKey=sone.id|html></p>
+ <p><%= Page.ViewSone.NoSone.Description|l10n|replace needle="{sone}" replacementKey=soneId|html></p>
<%elseifnull sone.name>