<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.6.1</version>
+ <version>0.6.2</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
<artifactId>utils</artifactId>
- <version>0.9.3</version>
+ <version>0.9.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
return null;
}
Sone sone = addLocalSone(ownIdentity);
+ sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
+ saveSone(sone);
return sone;
}
synchronized (posts) {
posts.remove(post.getId());
}
+ coreListenerManager.firePostRemoved(post);
synchronized (newPosts) {
markPostKnown(post);
knownPosts.remove(post.getId());
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);
}
/*
- * 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
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
package net.pterodactylus.sone.core;
import java.util.ArrayList;
* The Sone to fetch
*/
public void fetchSone(Sone sone) {
- fetchSone(sone, sone.getRequestUri());
+ fetchSone(sone, sone.getRequestUri().sskForUSK());
}
/**
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
import java.util.EventListener;
-
import freenet.support.SimpleFieldSet;
import freenet.support.api.Bucket;
/*
- * 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
}
/** The version. */
- public static final Version VERSION = new Version(0, 6, 1);
+ public static final Version VERSION = new Version(0, 6, 2);
/** The logger. */
private static final Logger logger = Logging.getLogger(SonePlugin.class);
}
/**
- * 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
public void setElements(Collection<? extends T> elements) {
this.elements.clear();
this.elements.addAll(elements);
- touch();
}
/**
* @return The filtered new-post notification, or {@code null} if the
* notification should be removed
*/
- private static ListNotification<Post> filterNewPostNotification(ListNotification<Post> newPostNotification, Sone currentSone) {
+ public static ListNotification<Post> filterNewPostNotification(ListNotification<Post> newPostNotification, Sone currentSone) {
if (currentSone == null) {
return null;
}
* @return The filtered new-reply notification, or {@code null} if the
* notification should be removed
*/
- private static ListNotification<Reply> filterNewReplyNotification(ListNotification<Reply> newReplyNotification, Sone currentSone) {
+ public static ListNotification<Reply> filterNewReplyNotification(ListNotification<Reply> newReplyNotification, Sone currentSone) {
if (currentSone == null) {
return null;
}
List<Reply> newReplies = new ArrayList<Reply>();
for (Reply reply : newReplyNotification.getElements()) {
- if (currentSone.hasFriend(reply.getPost().getSone().getId()) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient())) {
+ if (((reply.getPost().getSone() != null) && currentSone.hasFriend(reply.getPost().getSone().getId())) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient())) {
newReplies.add(reply);
}
}
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;
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) {
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
package net.pterodactylus.sone.text;
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
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());
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
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.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;
*/
public class SearchPage extends SoneTemplatePage {
+ /** The logger. */
+ private static final Logger logger = Logging.getLogger(SearchPage.class);
+
/**
* Creates a new search page.
*
Set<Hit<T>> hits = new HashSet<Hit<T>>();
for (T object : objects) {
String objectString = stringGenerator.generateString(object);
- int score = calculateScore(phrases, objectString);
+ double score = calculateScore(phrases, objectString);
hits.add(new Hit<T>(object, score));
}
return hits;
* The expression to search
* @return The score of the expression
*/
- private int calculateScore(List<Phrase> phrases, String expression) {
- int optionalHits = 0;
- int requiredHits = 0;
+ private double calculateScore(List<Phrase> 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) {
}
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;
@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);
}
};
private final T object;
/** The score of the object. */
- private final int score;
+ private final double score;
/**
* Creates a new hit.
* @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;
}
*
* @return The score of the object
*/
- public int getScore() {
+ public double getScore() {
return score;
}
/*
- * 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
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.main.SonePlugin;
-import net.pterodactylus.sone.web.page.Page;
import net.pterodactylus.sone.web.page.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;
import freenet.support.api.HTTPRequest;
/**
- * Base page for the Freetalk web interface.
+ * Base page for the Sone web interface.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
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
}
/**
- * 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
}
/**
- * Creates a new template page for Freetalk.
+ * Creates a new template page for Sone.
*
* @param path
* The path of the page
}
/**
- * Creates a new template page for Freetalk.
+ * Creates a new template page for Sone.
*
* @param path
* The path of the page
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
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;
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)));
/*
- * 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
/*
- * 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
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<Notification>(), currentSone);
+ if ("new-post-notification".equals(notificationId)) {
+ notification = ListNotificationFilters.filterNewPostNotification((ListNotification<Post>) notification, currentSone);
+ } else if ("new-reply-notification".equals(notificationId)) {
+ notification = ListNotificationFilters.filterNewReplyNotification((ListNotification<Reply>) 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;
+ }
+
+}
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;
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
/* load notifications. */
List<Notification> notifications = ListNotificationFilters.filterNotifications(new ArrayList<Notification>(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<Post> newPosts = webInterface.getNewPosts();
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);
}
/**
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;
}
* @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) {
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
- private static class Time {
+ public static class Time {
/** Number of milliseconds in a second. */
private static final long SECOND = 1000;
return refresh;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return text;
+ }
+
}
}
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
/*
- * 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
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.Replies.Title=Posts {sone} has replied to
Page.ViewPost.Title=View Post - Sone
Page.ViewPost.Page.Title=View Post by {sone}
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
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
display: inline;
}
+#sone .permalink {
+ display: inline;
+}
+
#sone .post .bookmarks {
display: inline;
color: rgb(28, 131, 191);
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");
* @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 = $("<span> · </span>").addClass("separator");
var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
replyElement = $("#sone .post#" + postId + " .create-reply");
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);
}
* @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").
$("#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);
*/
function getSone(soneId) {
return $("#sone .sone").filter(function(index) {
- return $(".id").text() == soneId;
+ return $(".id", this).text() == soneId;
});
}
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) {
if ((data == null) || !data.success) {
/* mark Sone as known when clicking it. */
$(soneElement).click(function() {
- markSoneAsKnown(soneElement);
+ markSoneAsKnown(this);
});
}
});
/* 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 = [];
});
});
})(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() {
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() {
}
});
/* 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);
}
/**
+ * Requests multiple notifications from Sone and displays them.
+ *
+ * @param notificationIds
+ * Array of IDs of the notifications to load
+ */
+function loadNotifications(notificationIds) {
+ $.getJSON("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();
+ }
+ })
+ });
+}
+
+/**
* Returns the ID of the currently logged in Sone.
*
* @return The ID of the current Sone, or an empty string if no Sone is logged
}
/**
+ * 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} <code>true</code> if the current page is a “view Sone”
if (hasPost(postId)) {
return;
}
- if (!isIndexPage()) {
+ if (!isIndexPage() || (getPage(".pagination-index") > 1)) {
if (!isViewPostPage() || (getShownPostId() != postId)) {
if (!isViewSonePage() || ((getShownSoneId() != soneId) && (getShownSoneId() != recipientId))) {
return;
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;
* 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) {
+ $.getJSON("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "sone", "id": getSoneId(soneElement)});
}
}
}
* <code>true</code> if the notification can be dismissed by the
* user
*/
-function createNotification(id, text, dismissable) {
- notification = $("<div></div>").addClass("notification").attr("id", id);
+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.find("input[name=notification]").val(id);
ajaxifyNotification($(this));
});
+ /* disable all permalinks. */
+ $(".permalink").click(function() {
+ return false;
+ });
+
/* activate status polling. */
setTimeout(getStatus, 5000);
</form>
<%foreach webInterface.notifications.all notification>
- <div class="notification" id="<% notification.id|html>">
+ <div class="notification" id="<% notification.id|html>" lastUpdatedTime="<%notification.lastUpdatedTime|html>">
<%if notification.dismissable>
<form class="dismiss" action="dismissNotification.html" method="post">
<input type="hidden" name="formPassword" value="<% formPassword|html>" />
<%if pagination.necessary>
- <div class="navigation">
+ <div class="navigation <%paginationName|html>">
<div class="first"><%if ! pagination.first><a href="<% request|change nameKey=pageParameter value=0>">«</a><%else><span>«</span><%/if></div>
<div class="previous"><%if ! pagination.first><a href="<% request|change nameKey=pageParameter key=pagination.previousPage>">‹</a><%else><span>‹</span><%/if></div>
<div class="current-page"><% pagination.pageNumber></div>
</div>
<span class='separator'>·</span>
<div class="time"><a href="viewPost.html?post=<% post.id|html>"><% post.time|date format="MMM d, yyyy, HH:mm:ss"></a></div>
+ <span class='separator'>·</span>
+ <div class="permalink permalink-post"><a href="post://<%post.id|html>">[<%= View.Post.Permalink|l10n|html>]</a></div>
+ <span class='separator'>·</span>
+ <div class="permalink permalink-author"><a href="sone://<%post.sone.id|html>">[<%= View.Post.PermalinkAuthor|l10n|html>]</a></div>
<%if ! originalText|match key=parsedText>
<span class='separator'>·</span>
<div class="show-source"><a href="viewPost.html?post=<% post.id|html>&raw=<%if raw>false<%else>true<%/if>"><%= View.Post.ShowSource|l10n|html></a></div>
</div>
<div class="reply-status-line status-line">
<div class="time"><% reply.time|date format="MMM d, yyyy, HH:mm:ss"></div>
+ <span class='separator'>·</span>
+ <div class="permalink permalink-author"><a href="sone://<%reply.sone.id|html>">[<%= View.Post.PermalinkAuthor|l10n|html>]</a></div>
<%if ! originalText|match key=parsedText>
<span class='separator'>·</span>
<div class="show-reply-source"><a href="viewPost.html?post=<% post.id|html>&raw=<%if raw>false<%else>true<%/if>"><%= View.Post.ShowSource|l10n|html></a></div>
<div class="download-marker" title="<%= View.Sone.Status.Downloading|l10n|html>">⬊</div>
<div class="insert-marker" title="<%= View.Sone.Status.Inserting|l10n|html>">⬈</div>
<div class="idle-marker" title="<%= View.Sone.Status.Idle|l10n|html>">✔</div>
- <div class="last-update"><%= View.Sone.Label.LastUpdate|l10n|html> <span class="time"><% sone.time|unknown|date format="MMM d, yyyy, HH:mm:ss"></span></div>
+ <div class="last-update"><%= View.Sone.Label.LastUpdate|l10n|html> <span class="time" title="<% sone.time|unknown|date format="MMM d, yyyy, HH:mm:ss">"><%sone.lastUpdatedText|html></span></div>
<div class="profile-link"><a href="viewSone.html?sone=<% sone.id|html>" title="<% sone.requestUri|html>"><% sone.niceName|html></a></div>
<div class="short-request-uri"><% sone.requestUri|substring start=4 length=43|html></div>
<div class="hidden"><% sone.blacklisted></div>
<div id="posts">
<%= page|store key=pageParameter>
- <%include include/pagination.html>
+ <%include include/pagination.html paginationName==pagination-index>
<%foreach pagination.items post>
<%include include/viewPost.html>
<%foreachelse>
<%foreach repliedPosts post>
<%first>
- <h2><%= Page.ViewSone.Replies.Title|l10n|html></h2>
+ <h2><%= Page.ViewSone.Replies.Title|l10n|html|replace needle="{sone}" replacementKey=sone.niceName></h2>
<div id="replied-posts">
<%include include/pagination.html pagination=repliedPostPagination pageParameter==repliedPostPage>
<%/first>