From: David ‘Bombe’ Roden Date: Tue, 8 Mar 2011 05:51:44 +0000 (+0100) Subject: Merge branch 'release-0.4.4' X-Git-Tag: 0.4.4 X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=refs%2Ftags%2F0.4.4;hp=07ca98b45465f7a442e695fbfb6f59100260f39b;p=Sone.git Merge branch 'release-0.4.4' --- diff --git a/pom.xml b/pom.xml index 3546ccc..78f1e32 100644 --- a/pom.xml +++ b/pom.xml @@ -2,12 +2,12 @@ 4.0.0 net.pterodactylus sone - 0.4.3 + 0.4.4 net.pterodactylus utils - 0.8 + 0.9 junit diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 99a119b..7dc398e 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -85,6 +85,9 @@ public class Core implements IdentityListener, UpdateListener { /** 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); @@ -155,6 +158,10 @@ public class Core implements IdentityListener, UpdateListener { /** All known replies. */ private Set knownReplies = new HashSet(); + /** All bookmarked posts. */ + /* synchronize access on itself. */ + private Set bookmarkedPosts = new HashSet(); + /** Trusted identities, sorted by own identities. */ private Map> trustedIdentities = Collections.synchronizedMap(new HashMap>()); @@ -221,18 +228,8 @@ public class Core implements IdentityListener, UpdateListener { * * @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; } /** @@ -681,6 +678,50 @@ public class Core implements IdentityListener, UpdateListener { 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 getBookmarkedPosts() { + Set posts = new HashSet(); + synchronized (bookmarkedPosts) { + for (String bookmarkedPostId : bookmarkedPosts) { + Post post = getPost(bookmarkedPostId, false); + if (post != null) { + posts.add(post); + } + } + } + return posts; + } + // // ACTIONS // @@ -767,7 +808,7 @@ public class Core implements IdentityListener, UpdateListener { soneInserters.put(sone, soneInserter); setSoneStatus(sone, SoneStatus.idle); loadSone(sone); - if (!isSoneRescueMode()) { + if (!preferences.isSoneRescueMode()) { soneInserter.start(); } new Thread(new Runnable() { @@ -775,7 +816,7 @@ public class Core implements IdentityListener, UpdateListener { @Override @SuppressWarnings("synthetic-access") public void run() { - if (!isSoneRescueMode()) { + if (!preferences.isSoneRescueMode()) { soneDownloader.fetchSone(sone); return; } @@ -783,7 +824,7 @@ public class Core implements IdentityListener, UpdateListener { 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; @@ -893,7 +934,7 @@ public class Core implements IdentityListener, UpdateListener { 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); } @@ -925,7 +966,7 @@ public class Core implements IdentityListener, UpdateListener { * The trust target */ public void trustSone(Sone origin, Sone target) { - setTrust(origin, target, options.getIntegerOption("PositiveTrust").get()); + setTrust(origin, target, preferences.getPositiveTrust()); } /** @@ -937,7 +978,7 @@ public class Core implements IdentityListener, UpdateListener { * The trust target */ public void distrustSone(Sone origin, Sone target) { - setTrust(origin, target, options.getIntegerOption("NegativeTrust").get()); + setTrust(origin, target, preferences.getNegativeTrust()); } /** @@ -960,7 +1001,7 @@ public class Core implements IdentityListener, UpdateListener { */ 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 }); @@ -1439,6 +1480,50 @@ public class Core implements IdentityListener, UpdateListener { } /** + * 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 @@ -1594,6 +1679,15 @@ public class Core implements IdentityListener, UpdateListener { 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(); @@ -1684,6 +1778,18 @@ public class Core implements IdentityListener, UpdateListener { } } + /* 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); + } + } + } /** @@ -1750,6 +1856,7 @@ public class Core implements IdentityListener, UpdateListener { public void run() { Sone sone = getRemoteSone(identity.getId()); sone.setIdentity(identity); + soneDownloader.addSone(sone); soneDownloader.fetchSone(sone); } }).start(); @@ -1775,4 +1882,191 @@ public class Core implements IdentityListener, UpdateListener { coreListenerManager.fireUpdateFound(version, releaseTime, latestEdition); } + /** + * Convenience interface for external classes that want to access the core’s + * configuration. + * + * @author David ‘Bombe’ Roden + */ + 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; + } + + } + } diff --git a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java index 3ccda5b..e22f407 100644 --- a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java +++ b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java @@ -176,7 +176,7 @@ public class FreenetInterface { } }; 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); } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java index c883301..b2d1da4 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java @@ -25,6 +25,7 @@ import java.util.Set; 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; @@ -93,6 +94,7 @@ public class SoneDownloader extends AbstractService { */ public void addSone(Sone sone) { if (sones.add(sone)) { + freenetInterface.unregisterUsk(sone); freenetInterface.registerUsk(sone, this); } } @@ -122,8 +124,8 @@ public class SoneDownloader extends AbstractService { /** * 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 @@ -131,9 +133,6 @@ public class SoneDownloader extends AbstractService { * 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); @@ -146,6 +145,7 @@ public class SoneDownloader extends AbstractService { 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 { diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index f183b39..cc54697 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -40,7 +40,7 @@ import freenet.keys.FreenetURI; * * @author David ‘Bombe’ Roden */ -public class Sone implements Fingerprintable { +public class Sone implements Fingerprintable, Comparable { /** comparator that sorts Sones by their nice name. */ public static final Comparator NICE_NAME_COMPARATOR = new Comparator() { @@ -626,6 +626,18 @@ public class Sone implements Fingerprintable { } // + // INTERFACE Comparable + // + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(Sone sone) { + return NICE_NAME_COMPARATOR.compare(this, sone); + } + + // // OBJECT METHODS // diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java index e51f9a1..4bdce70 100644 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java +++ b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java @@ -78,7 +78,7 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10 } /** 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); diff --git a/src/main/java/net/pterodactylus/sone/template/ParserFilter.java b/src/main/java/net/pterodactylus/sone/template/ParserFilter.java index f1190e0..1e274cc 100644 --- a/src/main/java/net/pterodactylus/sone/template/ParserFilter.java +++ b/src/main/java/net/pterodactylus/sone/template/ParserFilter.java @@ -21,6 +21,7 @@ import java.io.IOException; 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; @@ -35,6 +36,9 @@ import net.pterodactylus.util.template.TemplateContextFactory; */ public class ParserFilter implements Filter { + /** The core. */ + private final Core core; + /** The link parser. */ private final FreenetLinkParser linkParser; @@ -42,10 +46,13 @@ public class ParserFilter implements Filter { * 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); } @@ -60,6 +67,9 @@ public class ParserFilter implements Filter { 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)); diff --git a/src/main/java/net/pterodactylus/sone/template/PostAccessor.java b/src/main/java/net/pterodactylus/sone/template/PostAccessor.java index f6a9c06..ca0c84a 100644 --- a/src/main/java/net/pterodactylus/sone/template/PostAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/PostAccessor.java @@ -62,6 +62,10 @@ public class PostAccessor extends ReflectionAccessor { 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); } diff --git a/src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java b/src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java index f439abe..1e54d53 100644 --- a/src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java @@ -58,6 +58,8 @@ public class ReplyAccessor extends ReflectionAccessor { 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); } diff --git a/src/main/java/net/pterodactylus/sone/web/BookmarkPage.java b/src/main/java/net/pterodactylus/sone/web/BookmarkPage.java new file mode 100644 index 0000000..12a0934 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/BookmarkPage.java @@ -0,0 +1,59 @@ +/* + * 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 . + */ + +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 David ‘Bombe’ Roden + */ +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); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/BookmarksPage.java b/src/main/java/net/pterodactylus/sone/web/BookmarksPage.java new file mode 100644 index 0000000..d8a18c8 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/BookmarksPage.java @@ -0,0 +1,68 @@ +/* + * 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 . + */ + +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 David ‘Bombe’ Roden + */ +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 posts = webInterface.getCore().getBookmarkedPosts(); + List sortedPosts = new ArrayList(posts); + Collections.sort(sortedPosts, Post.TIME_COMPARATOR); + Pagination pagination = new Pagination(sortedPosts, 25).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0)); + templateContext.set("pagination", pagination); + templateContext.set("posts", pagination.getItems()); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java index 421b2a2..84d7c79 100644 --- a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java +++ b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java @@ -17,7 +17,7 @@ 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; @@ -52,35 +52,35 @@ public class OptionsPage extends SoneTemplatePage { @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()); } } diff --git a/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java b/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java new file mode 100644 index 0000000..85a0359 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java @@ -0,0 +1,59 @@ +/* + * 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 . + */ + +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 David ‘Bombe’ Roden + */ +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); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java b/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java index 864dc84..967b9c5 100644 --- a/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java @@ -52,8 +52,10 @@ public class ViewPostPage extends SoneTemplatePage { 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); } /** diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 9023ccf..89cec92 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -58,6 +58,7 @@ import net.pterodactylus.sone.template.SoneAccessor; 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; @@ -77,6 +78,7 @@ import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage; 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; @@ -94,6 +96,7 @@ import net.pterodactylus.util.logging.Logging; 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; @@ -198,9 +201,10 @@ public class WebInterface implements CoreListener { 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); @@ -518,6 +522,7 @@ public class WebInterface implements CoreListener { 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")); @@ -556,6 +561,9 @@ public class WebInterface implements CoreListener { 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")); @@ -587,6 +595,8 @@ public class WebInterface implements CoreListener { 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))); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.java new file mode 100644 index 0000000..760bd34 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.java @@ -0,0 +1,65 @@ +/* + * 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 . + */ + +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 David ‘Bombe’ Roden + */ +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; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java index 127834b..4e5f1b8 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java @@ -97,6 +97,7 @@ public class GetPostAjaxPage extends JsonPage { 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) { diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java new file mode 100644 index 0000000..ad1689e --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java @@ -0,0 +1,65 @@ +/* + * 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 . + */ + +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 David ‘Bombe’ Roden + */ +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; + } + +} diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index 35bc436..4d912e9 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -8,6 +8,8 @@ Navigation.Menu.Item.CreateSone.Name=Create Sone 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 @@ -160,6 +162,13 @@ Page.Untrust.Title=Untrust Sone - 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! @@ -197,11 +206,14 @@ View.Sone.Status.Downloading=This Sone is currently being downloaded. View.Sone.Status.Inserting=This Sone is currently being inserted. View.Post.UnknownAuthor=(unknown) +View.Post.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 diff --git a/src/main/resources/static/css/sone.css b/src/main/resources/static/css/sone.css index c5d8e9a..8eb8449 100644 --- a/src/main/resources/static/css/sone.css +++ b/src/main/resources/static/css/sone.css @@ -164,7 +164,7 @@ textarea { margin-bottom: 1em; } -#sone #update-status label { +#sone #update-status label, #sone #post-message label { display: none; } @@ -180,11 +180,11 @@ textarea { 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; } @@ -239,11 +239,15 @@ textarea { 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%; @@ -254,6 +258,21 @@ textarea { 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; @@ -269,11 +288,11 @@ textarea { 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; @@ -299,7 +318,7 @@ textarea { 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; @@ -392,7 +411,7 @@ textarea { float: right; } -#sone .create-reply .select-sender button { +#sone .create-reply .select-sender button, #sone #post-message .select-sender button { display: inline; float: left; } diff --git a/src/main/resources/static/images/icon-activity.png b/src/main/resources/static/images/icon-activity.png new file mode 100644 index 0000000..5803edb Binary files /dev/null and b/src/main/resources/static/images/icon-activity.png differ diff --git a/src/main/resources/static/javascript/sone.js b/src/main/resources/static/javascript/sone.js index 251105a..9ef6812 100644 --- a/src/main/resources/static/javascript/sone.js +++ b/src/main/resources/static/javascript/sone.js @@ -297,6 +297,17 @@ function getSoneId(element) { 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"); } @@ -475,6 +486,38 @@ function updateTrustControls(soneId, trustValue) { }); } +/** + * 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) { @@ -649,6 +692,25 @@ function ajaxifyPost(postElement) { 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")); @@ -709,6 +771,15 @@ function ajaxifyReply(replyElement) { })(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)); @@ -742,6 +813,15 @@ function ajaxifyNotification(notification) { 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. */ @@ -1026,7 +1106,7 @@ function markReplyAsKnown(replyElements) { function resetActivity() { title = document.title; if (title.indexOf('(') == 0) { - document.title = title.substr(title.indexOf(' ') + 1); + setTitle(title.substr(title.indexOf(' ') + 1)); } } @@ -1034,12 +1114,65 @@ function setActivity() { 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($("").attr("rel", "icon").attr("type", "image/png").attr("href", iconUrl)); + $("iframe[id=icon-update]")[0].src += ""; +} + +/** * Creates a new notification. * * @param id @@ -1186,14 +1319,25 @@ $(document).ready(function() { /* ajaxify input field on “view Sone” page. */ getTranslation("WebInterface.DefaultText.Message", function(defaultText) { registerInputTextareaSwap("#sone #post-message input[name=text]", defaultText, "text", false, false); + $("#sone #post-message .select-sender").css("display", "inline"); + $("#sone #post-message .sender").hide(); + $("#sone #post-message .select-sender button").click(function() { + $("#sone #post-message .sender").show(); + $("#sone #post-message .select-sender").hide(); + 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; }); }); diff --git a/src/main/resources/templates/bookmarks.html b/src/main/resources/templates/bookmarks.html new file mode 100644 index 0000000..af592db --- /dev/null +++ b/src/main/resources/templates/bookmarks.html @@ -0,0 +1,27 @@ +<%include include/head.html> + + + +

<%= Page.Bookmarks.Page.Title|l10n|html>

+ +
+ <%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> +

<%= Page.Bookmarks.Text.PostsNotLoaded|l10n|html>

+ <%else> + <%if !postShown> +

<%= Page.Bookmarks.Text.NoBookmarks|l10n|html>

+ <%/if> + <%/if> + <%include include/pagination.html> +
+ +<%include include/tail.html> diff --git a/src/main/resources/templates/include/tail.html b/src/main/resources/templates/include/tail.html index dd2e25e..ec2b587 100644 --- a/src/main/resources/templates/include/tail.html +++ b/src/main/resources/templates/include/tail.html @@ -16,4 +16,6 @@ + + diff --git a/src/main/resources/templates/include/viewPost.html b/src/main/resources/templates/include/viewPost.html index 74e3450..3023566 100644 --- a/src/main/resources/templates/include/viewPost.html +++ b/src/main/resources/templates/include/viewPost.html @@ -1,5 +1,5 @@ -
+
@@ -16,10 +16,28 @@ <%/if> <%/if> -
<% post.text|parse sone=post.sone>
+
<% post.text|html>
+
<% post.text|parse sone=post.sone>
+
+
+ + + + +
+
+ + + + +
+
+ · + · +
· ↑ @@ -84,7 +102,7 @@
diff --git a/src/main/resources/templates/include/viewReply.html b/src/main/resources/templates/include/viewReply.html index 6b13776..cdb837c 100644 --- a/src/main/resources/templates/include/viewReply.html +++ b/src/main/resources/templates/include/viewReply.html @@ -1,5 +1,5 @@ -
+
@@ -8,10 +8,13 @@
-
<% reply.text|parse sone=reply.sone>
+
<% reply.text|html>
+
<% reply.text|parse sone=reply.sone>
<% reply.time|date format="MMM d, yyyy, HH:mm:ss">
+ · +
· ↑ diff --git a/src/main/resources/templates/notify/newPostNotification.html b/src/main/resources/templates/notify/newPostNotification.html index e5d7792..177b470 100644 --- a/src/main/resources/templates/notify/newPostNotification.html +++ b/src/main/resources/templates/notify/newPostNotification.html @@ -12,6 +12,6 @@ <%= Notification.NewPost.Text|l10n|html> <%foreach posts post> - <% post.sone.niceName|html><%notlast>,<%/notlast><%last>.<%/last> + <% post.sone.niceName|html><%notlast>,<%/notlast><%last>.<%/last> <%/foreach>
diff --git a/src/main/resources/templates/notify/newReplyNotification.html b/src/main/resources/templates/notify/newReplyNotification.html index 2a194f2..21a5ca0 100644 --- a/src/main/resources/templates/notify/newReplyNotification.html +++ b/src/main/resources/templates/notify/newReplyNotification.html @@ -12,6 +12,6 @@ <%= Notification.NewReply.Text|l10n|html> <%foreach replies reply> - <% reply.sone.niceName|html><%notlast>,<%/notlast><%last>.<%/last> + <% reply.sone.niceName|html><%notlast>,<%/notlast><%last>.<%/last> <%/foreach>
diff --git a/src/main/resources/templates/notify/newVersionNotification.html b/src/main/resources/templates/notify/newVersionNotification.html index fdb8b2a..5dfc25a 100644 --- a/src/main/resources/templates/notify/newVersionNotification.html +++ b/src/main/resources/templates/notify/newVersionNotification.html @@ -1 +1 @@ -
<%= Notification.NewVersion.Text|l10n|replace needle="{version}" replacementKey=latestVersion|replace needle="{edition}" replacementKey=latestEdition|parse sone=currentSone>
+
<%= Notification.NewVersion.Text|l10n|replace needle="{version}" replacementKey=latestVersion|replace needle="{edition}" replacementKey=latestEdition|parse sone="nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI">
diff --git a/src/main/resources/templates/viewSone.html b/src/main/resources/templates/viewSone.html index 4b2502a..d2a8f6e 100644 --- a/src/main/resources/templates/viewSone.html +++ b/src/main/resources/templates/viewSone.html @@ -43,6 +43,15 @@ + +
+ +
+