<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.4.3</version>
+ <version>0.4.4</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
<artifactId>utils</artifactId>
- <version>0.8</version>
+ <version>0.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
/** The options. */
private final Options options = new Options();
+ /** The preferences. */
+ private final Preferences preferences = new Preferences(options);
+
/** The core listener manager. */
private final CoreListenerManager coreListenerManager = new CoreListenerManager(this);
/** All known replies. */
private Set<String> knownReplies = new HashSet<String>();
+ /** All bookmarked posts. */
+ /* synchronize access on itself. */
+ private Set<String> bookmarkedPosts = new HashSet<String>();
+
/** Trusted identities, sorted by own identities. */
private Map<OwnIdentity, Set<Identity>> trustedIdentities = Collections.synchronizedMap(new HashMap<OwnIdentity, Set<Identity>>());
*
* @return The options of the core
*/
- public Options getOptions() {
- return options;
- }
-
- /**
- * Returns whether the “Sone rescue mode” is currently activated.
- *
- * @return {@code true} if the “Sone rescue mode” is currently activated,
- * {@code false} if it is not
- */
- public boolean isSoneRescueMode() {
- return options.getBooleanOption("SoneRescueMode").get();
+ public Preferences getPreferences() {
+ return preferences;
}
/**
return sones;
}
+ /**
+ * Returns whether the given post is bookmarked.
+ *
+ * @param post
+ * The post to check
+ * @return {@code true} if the given post is bookmarked, {@code false}
+ * otherwise
+ */
+ public boolean isBookmarked(Post post) {
+ return isPostBookmarked(post.getId());
+ }
+
+ /**
+ * Returns whether the post with the given ID is bookmarked.
+ *
+ * @param id
+ * The ID of the post to check
+ * @return {@code true} if the post with the given ID is bookmarked,
+ * {@code false} otherwise
+ */
+ public boolean isPostBookmarked(String id) {
+ synchronized (bookmarkedPosts) {
+ return bookmarkedPosts.contains(id);
+ }
+ }
+
+ /**
+ * Returns all currently known bookmarked posts.
+ *
+ * @return All bookmarked posts
+ */
+ public Set<Post> getBookmarkedPosts() {
+ Set<Post> posts = new HashSet<Post>();
+ synchronized (bookmarkedPosts) {
+ for (String bookmarkedPostId : bookmarkedPosts) {
+ Post post = getPost(bookmarkedPostId, false);
+ if (post != null) {
+ posts.add(post);
+ }
+ }
+ }
+ return posts;
+ }
+
//
// ACTIONS
//
soneInserters.put(sone, soneInserter);
setSoneStatus(sone, SoneStatus.idle);
loadSone(sone);
- if (!isSoneRescueMode()) {
+ if (!preferences.isSoneRescueMode()) {
soneInserter.start();
}
new Thread(new Runnable() {
@Override
@SuppressWarnings("synthetic-access")
public void run() {
- if (!isSoneRescueMode()) {
+ if (!preferences.isSoneRescueMode()) {
soneDownloader.fetchSone(sone);
return;
}
coreListenerManager.fireRescuingSone(sone);
lockSone(sone);
long edition = sone.getLatestEdition();
- while (!stopped && (edition >= 0) && isSoneRescueMode()) {
+ while (!stopped && (edition >= 0) && preferences.isSoneRescueMode()) {
logger.log(Level.FINE, "Downloading edition " + edition + "…");
soneDownloader.fetchSone(sone, sone.getRequestUri().setKeyType("SSK").setDocName("Sone-" + edition));
--edition;
public void setTrust(Sone origin, Sone target, int trustValue) {
Validation.begin().isNotNull("Trust Origin", origin).check().isInstanceOf("Trust Origin", origin.getIdentity(), OwnIdentity.class).isNotNull("Trust Target", target).isLessOrEqual("Trust Value", trustValue, 100).isGreaterOrEqual("Trust Value", trustValue, -100).check();
try {
- ((OwnIdentity) origin.getIdentity()).setTrust(target.getIdentity(), trustValue, options.getStringOption("TrustComment").get());
+ ((OwnIdentity) origin.getIdentity()).setTrust(target.getIdentity(), trustValue, preferences.getTrustComment());
} catch (WebOfTrustException wote1) {
logger.log(Level.WARNING, "Could not set trust for Sone: " + target, wote1);
}
* The trust target
*/
public void trustSone(Sone origin, Sone target) {
- setTrust(origin, target, options.getIntegerOption("PositiveTrust").get());
+ setTrust(origin, target, preferences.getPositiveTrust());
}
/**
* The trust target
*/
public void distrustSone(Sone origin, Sone target) {
- setTrust(origin, target, options.getIntegerOption("NegativeTrust").get());
+ setTrust(origin, target, preferences.getNegativeTrust());
}
/**
*/
public void updateSone(Sone sone) {
if (hasSone(sone.getId())) {
- boolean soneRescueMode = isLocalSone(sone) && isSoneRescueMode();
+ boolean soneRescueMode = isLocalSone(sone) && preferences.isSoneRescueMode();
Sone storedSone = getSone(sone.getId());
if (!soneRescueMode && !(sone.getTime() > storedSone.getTime())) {
logger.log(Level.FINE, "Downloaded Sone %s is not newer than stored Sone %s.", new Object[] { sone, storedSone });
}
/**
+ * Bookmarks the given post.
+ *
+ * @param post
+ * The post to bookmark
+ */
+ public void bookmark(Post post) {
+ bookmarkPost(post.getId());
+ }
+
+ /**
+ * Bookmarks the post with the given ID.
+ *
+ * @param id
+ * The ID of the post to bookmark
+ */
+ public void bookmarkPost(String id) {
+ synchronized (bookmarkedPosts) {
+ bookmarkedPosts.add(id);
+ }
+ }
+
+ /**
+ * Removes the given post from the bookmarks.
+ *
+ * @param post
+ * The post to unbookmark
+ */
+ public void unbookmark(Post post) {
+ unbookmarkPost(post.getId());
+ }
+
+ /**
+ * Removes the post with the given ID from the bookmarks.
+ *
+ * @param id
+ * The ID of the post to unbookmark
+ */
+ public void unbookmarkPost(String id) {
+ synchronized (bookmarkedPosts) {
+ bookmarkedPosts.remove(id);
+ }
+ }
+
+ /**
* Creates a new reply.
*
* @param sone
configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
}
+ /* save bookmarked posts. */
+ int bookmarkedPostCounter = 0;
+ synchronized (bookmarkedPosts) {
+ for (String bookmarkedPostId : bookmarkedPosts) {
+ configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").setValue(bookmarkedPostId);
+ }
+ }
+ configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").setValue(null);
+
/* now save it. */
configuration.save();
}
}
+ /* load bookmarked posts. */
+ int bookmarkedPostCounter = 0;
+ while (true) {
+ String bookmarkedPostId = configuration.getStringValue("Bookmarks/Post/" + bookmarkedPostCounter++ + "/ID").getValue(null);
+ if (bookmarkedPostId == null) {
+ break;
+ }
+ synchronized (bookmarkedPosts) {
+ bookmarkedPosts.add(bookmarkedPostId);
+ }
+ }
+
}
/**
public void run() {
Sone sone = getRemoteSone(identity.getId());
sone.setIdentity(identity);
+ soneDownloader.addSone(sone);
soneDownloader.fetchSone(sone);
}
}).start();
coreListenerManager.fireUpdateFound(version, releaseTime, latestEdition);
}
+ /**
+ * Convenience interface for external classes that want to access the core’s
+ * configuration.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ public static class Preferences {
+
+ /** The wrapped options. */
+ private final Options options;
+
+ /**
+ * Creates a new preferences object wrapped around the given options.
+ *
+ * @param options
+ * The options to wrap
+ */
+ public Preferences(Options options) {
+ this.options = options;
+ }
+
+ /**
+ * Returns the insertion delay.
+ *
+ * @return The insertion delay
+ */
+ public int getInsertionDelay() {
+ return options.getIntegerOption("InsertionDelay").get();
+ }
+
+ /**
+ * Sets the insertion delay
+ *
+ * @param insertionDelay
+ * The new insertion delay, or {@code null} to restore it to
+ * the default value
+ * @return This preferences
+ */
+ public Preferences setInsertionDelay(Integer insertionDelay) {
+ options.getIntegerOption("InsertionDelay").set(insertionDelay);
+ return this;
+ }
+
+ /**
+ * Returns the positive trust.
+ *
+ * @return The positive trust
+ */
+ public int getPositiveTrust() {
+ return options.getIntegerOption("PositiveTrust").get();
+ }
+
+ /**
+ * Sets the positive trust.
+ *
+ * @param positiveTrust
+ * The new positive trust, or {@code null} to restore it to
+ * the default vlaue
+ * @return This preferences
+ */
+ public Preferences setPositiveTrust(Integer positiveTrust) {
+ options.getIntegerOption("PositiveTrust").set(positiveTrust);
+ return this;
+ }
+
+ /**
+ * Returns the negative trust.
+ *
+ * @return The negative trust
+ */
+ public int getNegativeTrust() {
+ return options.getIntegerOption("NegativeTrust").get();
+ }
+
+ /**
+ * Sets the negative trust.
+ *
+ * @param negativeTrust
+ * The negative trust, or {@code null} to restore it to the
+ * default value
+ * @return The preferences
+ */
+ public Preferences setNegativeTrust(Integer negativeTrust) {
+ options.getIntegerOption("NegativeTrust").set(negativeTrust);
+ return this;
+ }
+
+ /**
+ * Returns the trust comment. This is the comment that is set in the web
+ * of trust when a trust value is assigned to an identity.
+ *
+ * @return The trust comment
+ */
+ public String getTrustComment() {
+ return options.getStringOption("TrustComment").get();
+ }
+
+ /**
+ * Sets the trust comment.
+ *
+ * @param trustComment
+ * The trust comment, or {@code null} to restore it to the
+ * default value
+ * @return This preferences
+ */
+ public Preferences setTrustComment(String trustComment) {
+ options.getStringOption("TrustComment").set(trustComment);
+ return this;
+ }
+
+ /**
+ * Returns whether the rescue mode is active.
+ *
+ * @return {@code true} if the rescue mode is active, {@code false}
+ * otherwise
+ */
+ public boolean isSoneRescueMode() {
+ return options.getBooleanOption("SoneRescueMode").get();
+ }
+
+ /**
+ * Sets whether the rescue mode is active.
+ *
+ * @param soneRescueMode
+ * {@code true} if the rescue mode is active, {@code false}
+ * otherwise
+ * @return This preferences
+ */
+ public Preferences setSoneRescueMode(Boolean soneRescueMode) {
+ options.getBooleanOption("SoneRescueMode").set(soneRescueMode);
+ return this;
+ }
+
+ /**
+ * Returns whether Sone should clear its settings on the next restart.
+ * In order to be effective, {@link #isReallyClearOnNextRestart()} needs
+ * to return {@code true} as well!
+ *
+ * @return {@code true} if Sone should clear its settings on the next
+ * restart, {@code false} otherwise
+ */
+ public boolean isClearOnNextRestart() {
+ return options.getBooleanOption("ClearOnNextRestart").get();
+ }
+
+ /**
+ * Sets whether Sone will clear its settings on the next restart.
+ *
+ * @param clearOnNextRestart
+ * {@code true} if Sone should clear its settings on the next
+ * restart, {@code false} otherwise
+ * @return This preferences
+ */
+ public Preferences setClearOnNextRestart(Boolean clearOnNextRestart) {
+ options.getBooleanOption("ClearOnNextRestart").set(clearOnNextRestart);
+ return this;
+ }
+
+ /**
+ * Returns whether Sone should really clear its settings on next
+ * restart. This is a confirmation option that needs to be set in
+ * addition to {@link #isClearOnNextRestart()} in order to clear Sone’s
+ * settings on the next restart.
+ *
+ * @return {@code true} if Sone should really clear its settings on the
+ * next restart, {@code false} otherwise
+ */
+ public boolean isReallyClearOnNextRestart() {
+ return options.getBooleanOption("ReallyClearOnNextRestart").get();
+ }
+
+ /**
+ * Sets whether Sone should really clear its settings on the next
+ * restart.
+ *
+ * @param reallyClearOnNextRestart
+ * {@code true} if Sone should really clear its settings on
+ * the next restart, {@code false} otherwise
+ * @return This preferences
+ */
+ public Preferences setReallyClearOnNextRestart(Boolean reallyClearOnNextRestart) {
+ options.getBooleanOption("ReallyClearOnNextRestart").set(reallyClearOnNextRestart);
+ return this;
+ }
+
+ }
+
}
}
};
soneUskCallbacks.put(sone.getId(), uskCallback);
- node.clientCore.uskManager.subscribe(USK.create(sone.getRequestUri()), uskCallback, true, (HighLevelSimpleClientImpl) client);
+ node.clientCore.uskManager.subscribe(USK.create(sone.getRequestUri()), uskCallback, (System.currentTimeMillis() - sone.getTime()) < 7 * 24 * 60 * 60 * 1000, (HighLevelSimpleClientImpl) client);
} catch (MalformedURLException mue1) {
logger.log(Level.WARNING, "Could not subscribe USK “" + sone.getRequestUri() + "”!", mue1);
}
import java.util.logging.Level;
import java.util.logging.Logger;
+import net.pterodactylus.sone.core.Core.Preferences;
import net.pterodactylus.sone.core.Core.SoneStatus;
import net.pterodactylus.sone.data.Client;
import net.pterodactylus.sone.data.Post;
*/
public void addSone(Sone sone) {
if (sones.add(sone)) {
+ freenetInterface.unregisterUsk(sone);
freenetInterface.registerUsk(sone, this);
}
}
/**
* Fetches the updated Sone. This method can be used to fetch a Sone from a
- * specific URI (which happens when {@link Core#isSoneRescueMode() „Sone
- * rescue mode“} is active).
+ * specific URI (which happens when {@link Preferences#isSoneRescueMode()
+ * „Sone rescue mode“} is active).
*
* @param sone
* The Sone to fetch
* The URI to fetch the Sone from
*/
public void fetchSone(Sone sone, FreenetURI soneUri) {
- if (core.getSoneStatus(sone) == SoneStatus.downloading) {
- return;
- }
logger.log(Level.FINE, "Starting fetch for Sone “%s” from %s…", new Object[] { sone, soneUri });
FreenetURI requestUri = soneUri.setMetaString(new String[] { "sone.xml" });
core.setSoneStatus(sone, SoneStatus.downloading);
logger.log(Level.FINEST, "Got %d bytes back.", fetchResults.getRight().size());
Sone parsedSone = parseSone(sone, fetchResults.getRight(), fetchResults.getLeft());
if (parsedSone != null) {
+ addSone(parsedSone);
core.updateSone(parsedSone);
}
} finally {
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class Sone implements Fingerprintable {
+public class Sone implements Fingerprintable, Comparable<Sone> {
/** comparator that sorts Sones by their nice name. */
public static final Comparator<Sone> NICE_NAME_COMPARATOR = new Comparator<Sone>() {
}
//
+ // INTERFACE Comparable<Sone>
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int compareTo(Sone sone) {
+ return NICE_NAME_COMPARATOR.compare(this, sone);
+ }
+
+ //
// OBJECT METHODS
//
}
/** The version. */
- public static final Version VERSION = new Version(0, 4, 3);
+ public static final Version VERSION = new Version(0, 4, 4);
/** The logger. */
private static final Logger logger = Logging.getLogger(SonePlugin.class);
import java.io.StringReader;
import java.util.Map;
+import net.pterodactylus.sone.core.Core;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.text.FreenetLinkParser;
import net.pterodactylus.sone.text.FreenetLinkParserContext;
*/
public class ParserFilter implements Filter {
+ /** The core. */
+ private final Core core;
+
/** The link parser. */
private final FreenetLinkParser linkParser;
* Creates a new filter that runs its input through a
* {@link FreenetLinkParser}.
*
+ * @param core
+ * The core
* @param templateContextFactory
* The context factory for rendering the parts
*/
- public ParserFilter(TemplateContextFactory templateContextFactory) {
+ public ParserFilter(Core core, TemplateContextFactory templateContextFactory) {
+ this.core = core;
linkParser = new FreenetLinkParser(templateContextFactory);
}
soneKey = "sone";
}
Sone sone = (Sone) templateContext.get(soneKey);
+ if (sone == null) {
+ sone = core.getSone(soneKey, false);
+ }
FreenetLinkParserContext context = new FreenetLinkParserContext(sone);
try {
return linkParser.parse(context, new StringReader(text));
return (currentSone != null) && (currentSone.isLikedPostId(post.getId()));
} else if (member.equals("new")) {
return core.isNewPost(post.getId());
+ } else if (member.equals("bookmarked")) {
+ return core.isBookmarked(post);
+ } else if (member.equals("loaded")) {
+ return post.getSone() != null;
}
return super.get(templateContext, object, member);
}
return (currentSone != null) && (currentSone.isLikedReplyId(reply.getId()));
} else if (member.equals("new")) {
return core.isNewReply(reply.getId());
+ } else if (member.equals("loaded")) {
+ return reply.getSone() != null;
}
return super.get(templateContext, object, member);
}
--- /dev/null
+/*
+ * Sone - BookmarkPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web;
+
+import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+
+/**
+ * Page that lets the user bookmark a post.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class BookmarkPage extends SoneTemplatePage {
+
+ /**
+ * @param template
+ * The template to render
+ * @param webInterface
+ * The Sone web interface
+ */
+ public BookmarkPage(Template template, WebInterface webInterface) {
+ super("bookmark.html", template, "Page.Bookmark.Title", webInterface);
+ }
+
+ //
+ // SONETEMPLATEPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+ super.processTemplate(request, templateContext);
+ if (request.getMethod() == Method.POST) {
+ String id = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
+ String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
+ webInterface.getCore().bookmarkPost(id);
+ throw new RedirectException(returnPage);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Sone - BookmarksPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.util.collection.Pagination;
+import net.pterodactylus.util.number.Numbers;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+
+/**
+ * Page that lets the user browse all his bookmarked posts.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class BookmarksPage extends SoneTemplatePage {
+
+ /**
+ * Creates a new bookmarks page.
+ *
+ * @param template
+ * The template to render
+ * @param webInterface
+ * The Sone web interface
+ */
+ public BookmarksPage(Template template, WebInterface webInterface) {
+ super("bookmarks.html", template, "Page.Bookmarks.Title", webInterface);
+ }
+
+ //
+ // SONETEMPLATEPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+ super.processTemplate(request, templateContext);
+ Set<Post> posts = webInterface.getCore().getBookmarkedPosts();
+ List<Post> sortedPosts = new ArrayList<Post>(posts);
+ Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
+ Pagination<Post> pagination = new Pagination<Post>(sortedPosts, 25).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
+ templateContext.set("pagination", pagination);
+ templateContext.set("posts", pagination.getItems());
+ }
+
+}
package net.pterodactylus.sone.web;
-import net.pterodactylus.sone.core.Options;
+import net.pterodactylus.sone.core.Core.Preferences;
import net.pterodactylus.sone.web.page.Page.Request.Method;
import net.pterodactylus.util.number.Numbers;
import net.pterodactylus.util.template.Template;
@Override
protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
super.processTemplate(request, templateContext);
- Options options = webInterface.getCore().getOptions();
+ Preferences preferences = webInterface.getCore().getPreferences();
if (request.getMethod() == Method.POST) {
Integer insertionDelay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16));
- options.getIntegerOption("InsertionDelay").set(insertionDelay);
- Integer positiveTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("positive-trust", 3), options.getIntegerOption("PositiveTrust").getReal());
- options.getIntegerOption("PositiveTrust").set(positiveTrust);
- Integer negativeTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("negative-trust", 3), options.getIntegerOption("NegativeTrust").getReal());
- options.getIntegerOption("NegativeTrust").set(negativeTrust);
+ preferences.setInsertionDelay(insertionDelay);
+ Integer positiveTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("positive-trust", 3));
+ preferences.setPositiveTrust(positiveTrust);
+ Integer negativeTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("negative-trust", 4));
+ preferences.setNegativeTrust(negativeTrust);
String trustComment = request.getHttpRequest().getPartAsStringFailsafe("trust-comment", 256);
if (trustComment.trim().length() == 0) {
trustComment = null;
}
- options.getStringOption("TrustComment").set(trustComment);
+ preferences.setTrustComment(trustComment);
boolean soneRescueMode = Boolean.parseBoolean(request.getHttpRequest().getPartAsStringFailsafe("sone-rescue-mode", 5));
- options.getBooleanOption("SoneRescueMode").set(soneRescueMode);
+ preferences.setSoneRescueMode(soneRescueMode);
boolean clearOnNextRestart = Boolean.parseBoolean(request.getHttpRequest().getPartAsStringFailsafe("clear-on-next-restart", 5));
- options.getBooleanOption("ClearOnNextRestart").set(clearOnNextRestart);
+ preferences.setClearOnNextRestart(clearOnNextRestart);
boolean reallyClearOnNextRestart = Boolean.parseBoolean(request.getHttpRequest().getPartAsStringFailsafe("really-clear-on-next-restart", 5));
- options.getBooleanOption("ReallyClearOnNextRestart").set(reallyClearOnNextRestart);
+ preferences.setReallyClearOnNextRestart(reallyClearOnNextRestart);
webInterface.getCore().saveConfiguration();
throw new RedirectException(getPath());
}
- templateContext.set("insertion-delay", options.getIntegerOption("InsertionDelay").get());
- templateContext.set("positive-trust", options.getIntegerOption("PositiveTrust").get());
- templateContext.set("negative-trust", options.getIntegerOption("NegativeTrust").get());
- templateContext.set("trust-comment", options.getStringOption("TrustComment").get());
- templateContext.set("sone-rescue-mode", options.getBooleanOption("SoneRescueMode").get());
- templateContext.set("clear-on-next-restart", options.getBooleanOption("ClearOnNextRestart").get());
- templateContext.set("really-clear-on-next-restart", options.getBooleanOption("ReallyClearOnNextRestart").get());
+ templateContext.set("insertion-delay", preferences.getInsertionDelay());
+ templateContext.set("positive-trust", preferences.getPositiveTrust());
+ templateContext.set("negative-trust", preferences.getNegativeTrust());
+ templateContext.set("trust-comment", preferences.getTrustComment());
+ templateContext.set("sone-rescue-mode", preferences.isSoneRescueMode());
+ templateContext.set("clear-on-next-restart", preferences.isClearOnNextRestart());
+ templateContext.set("really-clear-on-next-restart", preferences.isReallyClearOnNextRestart());
}
}
--- /dev/null
+/*
+ * Sone - BookmarkPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web;
+
+import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+
+/**
+ * Page that lets the user unbookmark a post.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UnbookmarkPage extends SoneTemplatePage {
+
+ /**
+ * @param template
+ * The template to render
+ * @param webInterface
+ * The Sone web interface
+ */
+ public UnbookmarkPage(Template template, WebInterface webInterface) {
+ super("unbookmark.html", template, "Page.Unbookmark.Title", webInterface);
+ }
+
+ //
+ // SONETEMPLATEPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
+ super.processTemplate(request, templateContext);
+ if (request.getMethod() == Method.POST) {
+ String id = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
+ String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
+ webInterface.getCore().unbookmarkPost(id);
+ throw new RedirectException(returnPage);
+ }
+ }
+
+}
protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
super.processTemplate(request, templateContext);
String postId = request.getHttpRequest().getParam("post");
+ boolean raw = request.getHttpRequest().getParam("raw").equals("true");
Post post = webInterface.getCore().getPost(postId);
templateContext.set("post", post);
+ templateContext.set("raw", raw);
}
/**
import net.pterodactylus.sone.template.SubstringFilter;
import net.pterodactylus.sone.template.TrustAccessor;
import net.pterodactylus.sone.template.UnknownDateFilter;
+import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
import net.pterodactylus.sone.web.ajax.TrustAjaxPage;
+import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
import net.pterodactylus.util.notify.Notification;
import net.pterodactylus.util.notify.NotificationManager;
import net.pterodactylus.util.notify.TemplateNotification;
+import net.pterodactylus.util.template.CollectionSortFilter;
import net.pterodactylus.util.template.DateFilter;
import net.pterodactylus.util.template.FormatFilter;
import net.pterodactylus.util.template.HtmlFilter;
templateContextFactory.addFilter("match", new MatchFilter());
templateContextFactory.addFilter("css", new CssClassNameFilter());
templateContextFactory.addFilter("js", new JavascriptFilter());
- templateContextFactory.addFilter("parse", new ParserFilter(templateContextFactory));
+ templateContextFactory.addFilter("parse", new ParserFilter(getCore(), templateContextFactory));
templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate"));
templateContextFactory.addFilter("format", new FormatFilter());
+ templateContextFactory.addFilter("sort", new CollectionSortFilter());
templateContextFactory.addPlugin("getpage", new GetPagePlugin());
templateContextFactory.addPlugin("paginate", new PaginationPlugin());
templateContextFactory.addProvider(Provider.TEMPLATE_CONTEXT_PROVIDER);
Template createSoneTemplate = TemplateParser.parse(createReader("/templates/createSone.html"));
Template createPostTemplate = TemplateParser.parse(createReader("/templates/createPost.html"));
Template createReplyTemplate = TemplateParser.parse(createReader("/templates/createReply.html"));
+ Template bookmarksTemplate = TemplateParser.parse(createReader("/templates/bookmarks.html"));
Template editProfileTemplate = TemplateParser.parse(createReader("/templates/editProfile.html"));
Template editProfileFieldTemplate = TemplateParser.parse(createReader("/templates/editProfileField.html"));
Template deleteProfileFieldTemplate = TemplateParser.parse(createReader("/templates/deleteProfileField.html"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustPage(emptyTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustPage(emptyTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkAsKnownPage(emptyTemplate, this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new BookmarkPage(emptyTemplate, this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new UnbookmarkPage(emptyTemplate, this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new BookmarksPage(bookmarksTemplate, this), "Bookmarks"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteSonePage(deleteSoneTemplate, this), "DeleteSone"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new LoginPage(loginTemplate, this), "Login"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new LogoutPage(emptyTemplate, this), "Logout"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new LikeAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikeAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new GetLikesAjaxPage(this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new BookmarkAjaxPage(this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new UnbookmarkAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfileFieldAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteProfileFieldAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new MoveProfileFieldAjaxPage(this)));
--- /dev/null
+/*
+ * Sone - BookmarkAjaxPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.util.json.JsonObject;
+
+/**
+ * AJAX page that lets the user bookmark a post.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class BookmarkAjaxPage extends JsonPage {
+
+ /**
+ * Creates a new bookmark AJAX page.
+ *
+ * @param webInterface
+ * The Sone web interface
+ */
+ public BookmarkAjaxPage(WebInterface webInterface) {
+ super("bookmark.ajax", webInterface);
+ }
+
+ //
+ // JSONPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected JsonObject createJsonObject(Request request) {
+ String id = request.getHttpRequest().getParam("post", null);
+ if ((id == null) || (id.length() == 0)) {
+ return createErrorJsonObject("invalid-post-id");
+ }
+ webInterface.getCore().bookmarkPost(id);
+ return createSuccessJsonObject();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
+}
TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext();
templateContext.set("post", post);
templateContext.set("currentSone", currentSone);
+ templateContext.set("localSones", webInterface.getCore().getLocalSones());
try {
postTemplate.render(templateContext, stringWriter);
} catch (TemplateException te1) {
--- /dev/null
+/*
+ * Sone - BookmarkAjaxPage.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.util.json.JsonObject;
+
+/**
+ * AJAX page that lets the user unbookmark a post.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UnbookmarkAjaxPage extends JsonPage {
+
+ /**
+ * Creates a new unbookmark AJAX page.
+ *
+ * @param webInterface
+ * The Sone web interface
+ */
+ public UnbookmarkAjaxPage(WebInterface webInterface) {
+ super("unbookmark.ajax", webInterface);
+ }
+
+ //
+ // JSONPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected JsonObject createJsonObject(Request request) {
+ String id = request.getHttpRequest().getParam("post", null);
+ if ((id == null) || (id.length() == 0)) {
+ return createErrorJsonObject("invalid-post-id");
+ }
+ webInterface.getCore().unbookmarkPost(id);
+ return createSuccessJsonObject();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return false;
+ }
+
+}
Navigation.Menu.Item.CreateSone.Tooltip=Create a new Sone
Navigation.Menu.Item.KnownSones.Name=Known Sones
Navigation.Menu.Item.KnownSones.Tooltip=Shows all known Sones
+Navigation.Menu.Item.Bookmarks.Name=Bookmarks
+Navigation.Menu.Item.Bookmarks.Tooltip=Show bookmarked posts
Navigation.Menu.Item.EditProfile.Name=Edit Profile
Navigation.Menu.Item.EditProfile.Tooltip=Edit the Profile of your Sone
Navigation.Menu.Item.DeleteSone.Name=Delete Sone
Page.MarkAsKnown.Title=Mark as Known - Sone
+Page.Bookmark.Title=Bookmark - Sone
+Page.Unbookmark.Title=Remove Bookmark - Sone
+Page.Bookmarks.Title=Bookmarks - Sone
+Page.Bookmarks.Page.Title=Bookmarks
+Page.Bookmarks.Text.NoBookmarks=You don’t have any bookmarks defined right now. You can bookmark posts by clicking the star below the post.
+Page.Bookmarks.Text.PostsNotLoaded=Some of your bookmarked posts have not been shown because they could not be loaded. This can happen if your restarted Sone recently or if the originating Sone has deleted the post.
+
Page.NoPermission.Title=Unauthorized Access - Sone
Page.NoPermission.Page.Title=Unauthorized Access
Page.NoPermission.Text.NoPermission=You tried to do something that you do not have sufficient authorization for. Please refrain from such actions in the future or we will be forced to take counter-measures!
View.Sone.Status.Inserting=This Sone is currently being inserted.
View.Post.UnknownAuthor=(unknown)
+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.Post.SendReply=Post Reply!
View.Post.Reply.DeleteLink=Delete
View.Post.LikeLink=Like
View.Post.UnlikeLink=Unlike
+View.Post.ShowSource=Toggle Parser
View.UpdateStatus.Text.ChooseSenderIdentity=Choose the sender identity
margin-bottom: 1em;
}
-#sone #update-status label {
+#sone #update-status label, #sone #post-message label {
display: none;
}
float: right;
}
-#sone #update-status .select-sender, #sone .create-reply .select-sender {
+#sone #update-status .select-sender, #sone .create-reply .select-sender, #sone #post-message .select-sender {
display: none;
}
-#sone #update-status .select-sender button {
+#sone #update-status .select-sender button, #sone #post-message .select-sender button {
display: inline;
float: left;
}
font-weight: bold;
}
-#sone .post .text {
+#sone .post .text, #sone .post .raw-text {
display: inline;
white-space: pre-wrap;
}
+#sone .post .text.hidden, #sone .post .raw-text.hidden {
+ display: none;
+}
+
#sone .post .status-line {
margin-top: 0.5ex;
font-size: 85%;
color: rgb(28, 131, 191);
}
+#sone .show-source, #sone .show-reply-source {
+ display: inline;
+}
+
+#sone .post .bookmarks {
+ display: inline;
+ color: rgb(28, 131, 191);
+}
+
+#sone .post .bookmark, #sone .post .unbookmark {
+ display: inline;
+ font: inherit;
+ margin: 0px;
+}
+
#sone .post .time {
display: inline;
color: #666;
display: none;
}
-#sone .post .like.hidden, #sone .post .unlike.hidden, #sone .post .trust.hidden, #sone .post .distrust.hidden, #sone .post .untrust.hidden {
+#sone .post .like.hidden, #sone .post .unlike.hidden, #sone .post .trust.hidden, #sone .post .distrust.hidden, #sone .post .untrust.hidden, #sone .post .bookmark.hidden, #sone .post .unbookmark.hidden {
display: none;
}
-#sone .post .delete button, #sone .post .like button, #sone .post .unlike button, #sone .post .trust button, #sone .post .distrust button, #sone .post .untrust button {
+#sone .post .delete button, #sone .post .like button, #sone .post .unlike button, #sone .post .trust button, #sone .post .distrust button, #sone .post .untrust button, #sone .post .bookmark button, #sone .post .unbookmark button {
border: 0px;
background: none;
padding: 0px;
color: rgb(64, 64, 64);
}
-#sone .post .delete button:hover, #sone .post .like button:hover, #sone .post .unlike button:hover, #sone .post .trust button:hover, #sone .post .distrust button:hover, #sone .post .untrust button:hover {
+#sone .post .delete button:hover, #sone .post .like button:hover, #sone .post .unlike button:hover, #sone .post .trust button:hover, #sone .post .distrust button:hover, #sone .post .untrust button:hover, #sone .post .bookmark button:hover, #sone .post .unbookmark button:hover {
border: 0px;
background: none;
padding: 0px;
float: right;
}
-#sone .create-reply .select-sender button {
+#sone .create-reply .select-sender button, #sone #post-message .select-sender button {
display: inline;
float: left;
}
return getSoneElement(element).find(".id").text();
}
+/**
+ * Returns the element of the post with the given ID.
+ *
+ * @param postId
+ * The ID of the post
+ * @returns The element of the post
+ */
+function getPost(postId) {
+ return $("#sone .post#" + postId);
+}
+
function getPostElement(element) {
return $(element).closest(".post");
}
});
}
+/**
+ * Bookmarks the post with the given ID.
+ *
+ * @param postId
+ * The ID of the post to bookmark
+ */
+function bookmarkPost(postId) {
+ (function(postId) {
+ $.getJSON("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);
+ }
+ });
+ })(postId);
+}
+
+/**
+ * Unbookmarks the post with the given ID.
+ *
+ * @param postId
+ * The ID of the post to unbookmark
+ */
+function unbookmarkPost(postId) {
+ $.getJSON("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) {
if ((data != null) && data.success) {
return false;
});
+ /* convert bookmark/unbookmark buttons to javascript functions. */
+ $(postElement).find(".bookmark").submit(function() {
+ bookmarkPost(getPostId(this));
+ return false;
+ });
+ $(postElement).find(".unbookmark").submit(function() {
+ unbookmarkPost(getPostId(this));
+ return false;
+ });
+
+ /* convert “show source” link into javascript function. */
+ $(postElement).find(".show-source").each(function() {
+ $("a", this).click(function() {
+ $(".post-text.text", getPostElement(this)).toggleClass("hidden");
+ $(".post-text.raw-text", getPostElement(this)).toggleClass("hidden");
+ return false;
+ });
+ });
+
/* add “comment” link. */
addCommentLink(getPostId(postElement), postElement, $(postElement).find(".post-status-line .time"));
})(replyElement);
addCommentLink(getPostId(replyElement), replyElement, $(replyElement).find(".reply-status-line .time"));
+ /* convert “show source” link into javascript function. */
+ $(replyElement).find(".show-reply-source").each(function() {
+ $("a", this).click(function() {
+ $(".reply-text.text", getReplyElement(this)).toggleClass("hidden");
+ $(".reply-text.raw-text", getReplyElement(this)).toggleClass("hidden");
+ return false;
+ });
+ });
+
/* convert trust control buttons to javascript functions. */
$(replyElement).find(".reply-trust").submit(function() {
trustSone(getReplyAuthor(this));
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()});
});
+ 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);
+ }
+ }
+ });
notification.find("form.dismiss button").click(function() {
$.getJSON("dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) {
/* dismiss in case of error, too. */
function resetActivity() {
title = document.title;
if (title.indexOf('(') == 0) {
- document.title = title.substr(title.indexOf(' ') + 1);
+ setTitle(title.substr(title.indexOf(' ') + 1));
}
}
if (!focus) {
title = document.title;
if (title.indexOf('(') != 0) {
- document.title = "(!) " + title;
+ setTitle("(!) " + title);
+ }
+ if (!iconBlinking) {
+ setTimeout(toggleIcon, 1500);
+ iconBlinking = true;
+ }
+ }
+}
+
+/**
+ * Sets the window title after a small delay to prevent race-condition issues.
+ *
+ * @param title
+ * The title to set
+ */
+function setTitle(title) {
+ setTimeout(function() {
+ document.title = title;
+ }, 50);
+}
+
+/** Whether the icon is currently showing activity. */
+var iconActive = false;
+
+/** Whether the icon is currently supposed to blink. */
+var iconBlinking = false;
+
+/**
+ * Toggles the icon. If the window has gained focus and the icon is still
+ * showing the activity state, it is returned to normal.
+ */
+function toggleIcon() {
+ if (focus) {
+ if (iconActive) {
+ changeIcon("images/icon.png");
+ iconActive = false;
}
+ iconBlinking = false;
+ } else {
+ iconActive = !iconActive;
+ console.log("showing icon: " + iconActive);
+ changeIcon(iconActive ? "images/icon-activity.png" : "images/icon.png");
+ setTimeout(toggleIcon, 1500);
}
}
/**
+ * Changes the icon of the page.
+ *
+ * @param iconUrl
+ * The new URL of the icon
+ */
+function changeIcon(iconUrl) {
+ $("link[rel=icon]").remove();
+ $("head").append($("<link>").attr("rel", "icon").attr("type", "image/png").attr("href", iconUrl));
+ $("iframe[id=icon-update]")[0].src += "";
+}
+
+/**
* Creates a new notification.
*
* @param id
/* ajaxify input field on “view Sone” page. */
getTranslation("WebInterface.DefaultText.Message", function(defaultText) {
registerInputTextareaSwap("#sone #post-message input[name=text]", defaultText, "text", false, false);
+ $("#sone #post-message .select-sender").css("display", "inline");
+ $("#sone #post-message .sender").hide();
+ $("#sone #post-message .select-sender button").click(function() {
+ $("#sone #post-message .sender").show();
+ $("#sone #post-message .select-sender").hide();
+ return false;
+ });
$("#sone #post-message").submit(function() {
- text = $(this).find(":input:enabled").val();
- $.getJSON("createPost.ajax", { "formPassword": getFormPassword(), "recipient": getShownSoneId(), "text": text }, function(data, textStatus) {
+ 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 }, function(data, textStatus) {
if ((data != null) && data.success) {
loadNewPost(data.postId, getCurrentSoneId());
}
});
- $(this).find(":input:enabled").val("").blur();
+ $(this).find(":input[name=sender]").val(getCurrentSoneId());
+ $(this).find(":input[name=text]:enabled").val("").blur();
+ $(this).find(".sender").hide();
+ $(this).find(".select-sender").show();
return false;
});
});
--- /dev/null
+<%include include/head.html>
+
+ <div class="page-id hidden">bookmarks</div>
+
+ <h1><%= Page.Bookmarks.Page.Title|l10n|html></h1>
+
+ <div id="posts">
+ <%include include/pagination.html>
+ <%foreach posts post>
+ <%if post.loaded>
+ <%= true|store key=postShown>
+ <%include include/viewPost.html>
+ <%else>
+ <%= true|store key=postNotLoaded>
+ <%/if>
+ <%/foreach>
+ <%if postNotLoaded>
+ <p><%= Page.Bookmarks.Text.PostsNotLoaded|l10n|html></p>
+ <%else>
+ <%if !postShown>
+ <p><%= Page.Bookmarks.Text.NoBookmarks|l10n|html></p>
+ <%/if>
+ <%/if>
+ <%include include/pagination.html>
+ </div>
+
+<%include include/tail.html>
</div>
</div>
+ <iframe id="icon-update" class="hidden" src="about:blank"></iframe>
+
</div>
-<a name="post-<% post.id|html>"></a>
<div id="<% post.id|html>" class="post<%if loop.last> last<%/if><%if post.new> new<%/if>">
+ <a name="post-<% post.id|html>"></a>
<div class="post-time hidden"><% post.time|html></div>
<div class="post-author hidden"><% post.sone.id|html></div>
<div class="avatar">
<div class="recipient profile-link"><a href="viewSone.html?sone=<% post.recipient.id|html>"><% post.recipient.niceName|html></a></div>
<%/if>
<%/if>
- <div class="text"><% post.text|parse sone=post.sone></div>
+ <div class="post-text raw-text<%if !raw> hidden<%/if>"><% post.text|html></div>
+ <div class="post-text text<%if raw> hidden<%/if>"><% post.text|parse sone=post.sone></div>
</div>
<div class="post-status-line status-line">
+ <div class="bookmarks">
+ <form class="unbookmark<%if !post.bookmarked> hidden<%/if>" action="unbookmark.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="post" value="<% post.id|html>" />
+ <button type="submit" title="<%= View.Post.Bookmarks.PostIsBookmarked|l10n|html>">★</button>
+ </form>
+ <form class="bookmark<%if post.bookmarked> hidden<%/if>" action="bookmark.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="post" value="<% post.id|html>" />
+ <button type="submit" title="<%= View.Post.Bookmarks.PostIsNotBookmarked|l10n|html>">☆</button>
+ </form>
+ </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="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 class="likes<%if post.likes.size|match value=0> hidden<%/if>">
<span class='separator'>·</span>
<span title="<% post.likes.soneNames|html>">↑<span class="like-count"><% post.likes.size></span></span>
<input type="hidden" name="post" value="<% post.id|html>" />
<div class="sender">
<select name="sender" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">
- <%foreach localSones localSone>
+ <%foreach localSones localSone|sort>
<option value="<% localSone.id|html>"<%if localSone.current> selected="selected"<%/if>><% localSone.niceName|html></option>
<%/foreach>
</select>
-<a name="reply-<% reply.id|html>"></a>
<div id="<% reply.id|html>" class="reply<%if reply.new> new<%/if>">
+ <a name="reply-<% reply.id|html>"></a>
<div class="reply-time hidden"><% reply.time|html></div>
<div class="reply-author hidden"><% reply.sone.id|html></div>
<div class="avatar">
<div class="inner-part">
<div>
<div class="author profile-link"><a href="viewSone.html?sone=<% reply.sone.id|html>"><% reply.sone.niceName|html></a></div>
- <div class="text"><% reply.text|parse sone=reply.sone></div>
+ <div class="reply-text raw-text<%if !raw> hidden<%/if>"><% reply.text|html></div>
+ <div class="reply-text text<%if raw> hidden<%/if>"><% reply.text|parse sone=reply.sone></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="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="likes<%if reply.likes.size|match value=0> hidden<%/if>">
<span class='separator'>·</span>
<span title="<% reply.likes.soneNames|html>">↑<span class="like-count"><% reply.likes.size></span></span>
</form>
<%= Notification.NewPost.Text|l10n|html>
<%foreach posts post>
- <a href="viewPost.html?post=<% post.id|html>"><% post.sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
+ <a class="link-<% post.id|html>" href="viewPost.html?post=<% post.id|html>"><% post.sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
<%/foreach>
</div>
</form>
<%= Notification.NewReply.Text|l10n|html>
<%foreach replies reply>
- <a href="viewPost.html?post=<% reply.post.id|html>"><% reply.sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
+ <a class="link-<% reply.post.id|html>" href="viewPost.html?post=<% reply.post.id|html>"><% reply.sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
<%/foreach>
</div>
-<div class="text"><%= Notification.NewVersion.Text|l10n|replace needle="{version}" replacementKey=latestVersion|replace needle="{edition}" replacementKey=latestEdition|parse sone=currentSone></div>
+<div class="text"><%= Notification.NewVersion.Text|l10n|replace needle="{version}" replacementKey=latestVersion|replace needle="{edition}" replacementKey=latestEdition|parse sone="nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"></div>
<input type="hidden" name="formPassword" value="<% formPassword|html>" />
<input type="hidden" name="returnPage" value="<% request.uri|html>" />
<input type="hidden" name="recipient" value="<% sone.id|html>" />
+ <label for="sender"><%= Page.Index.Label.Sender|l10n|html></label>
+ <div class="sender">
+ <select name="sender" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">
+ <%foreach localSones localSone>
+ <option value="<% localSone.id|html>"<%if localSone.current> selected="selected"<%/if>><% localSone.niceName|html></option>
+ <%/foreach>
+ </select>
+ </div>
+ <div class="select-sender"><button type="button" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">+</button></div><label for="text"><%= Page.Index.Label.Text|l10n|html></label>
<input type="text" name="text" value="" />
<button type="submit"><%= Page.CreatePost.Button.Post|l10n|html></button>
</form>