}
/**
+ * Gets all Sones that like the given post.
+ *
+ * @param post
+ * The post to check for
+ * @return All Sones that like the post
+ */
+ public Set<Sone> getLikes(final Post post) {
+ return Filters.filteredSet(getSones(), new Filter<Sone>() {
+
+ @Override
+ public boolean filterObject(Sone sone) {
+ return sone.isLikedPostId(post.getId());
+ }
+ });
+ }
+
+ /**
* Deletes the given reply. It is removed from its Sone and from the reply
* cache.
*
sone.addBlockedSoneId(blockedSoneId);
}
+ /* load liked post IDs. */
+ int likedPostIdCounter = 0;
+ while (true) {
+ String likedPostIdPrefix = sonePrefix + "/LikedPostId." + likedPostIdCounter++;
+ String likedPostId = configuration.getStringValue(likedPostIdPrefix + "/ID").getValue(null);
+ if (likedPostId == null) {
+ break;
+ }
+ sone.addLikedPostId(likedPostId);
+ }
+
sone.setModificationCounter(modificationCounter);
addLocalSone(sone);
} catch (MalformedURLException mue1) {
}
configuration.getStringValue(sonePrefix + "/BlockedSone." + blockedSoneCounter + "/ID").setValue(null);
+ /* write all liked posts. */
+ int likedPostIdCounter = 0;
+ for (String soneLikedPostId : sone.getLikedPostIds()) {
+ String likedPostIdPrefix = sonePrefix + "/LikedPostId." + likedPostIdCounter++;
+ configuration.getStringValue(likedPostIdPrefix + "/ID").setValue(soneLikedPostId);
+ }
+ configuration.getStringValue(sonePrefix + "/LikedPostId." + likedPostIdCounter + "/ID").setValue(null);
+
}
/* write null ID as terminator. */
configuration.getStringValue("Sone/Sone." + soneId + "/ID").setValue(null);
}
}
+ /* parse liked post IDs. */
+ SimpleXML likePostIdsXml = soneXml.getNode("post-likes");
+ if (likePostIdsXml == null) {
+ /* TODO - mark Sone as bad. */
+ logger.log(Level.WARNING, "Downloaded Sone %s has no known Sones!", new Object[] { sone });
+ return null;
+ }
+
+ Set<String> likedPostIds = new HashSet<String>();
+ for (SimpleXML likedPostIdXml : likePostIdsXml.getNodes("post-like")) {
+ String postId = likedPostIdXml.getValue();
+ likedPostIds.add(postId);
+ }
+
/* parse known Sones. */
SimpleXML knownSonesXml = soneXml.getNode("known-sones");
if (knownSonesXml == null) {
sone.setProfile(profile);
sone.setPosts(posts);
sone.setReplies(replies);
+ sone.setLikePostIds(likedPostIds);
sone.setModificationCounter(0);
}
soneProperties.put("posts", new ArrayList<Post>(sone.getPosts()));
soneProperties.put("replies", new HashSet<Reply>(sone.getReplies()));
soneProperties.put("blockedSoneIds", new HashSet<String>(sone.getBlockedSoneIds()));
+ soneProperties.put("likedPostIds", new HashSet<String>(sone.getLikedPostIds()));
}
//
/** The IDs of all blocked Sones. */
private final Set<String> blockedSoneIds = new HashSet<String>();
+ /** The IDs of all liked posts. */
+ private final Set<String> likedPostIds = new HashSet<String>();
+
/** Modification count. */
private volatile long modificationCounter = 0;
}
/**
+ * Returns the IDs of all liked posts.
+ *
+ * @return All liked posts’ IDs
+ */
+ public Set<String> getLikedPostIds() {
+ return Collections.unmodifiableSet(likedPostIds);
+ }
+
+ /**
+ * Sets the IDs of all liked posts.
+ *
+ * @param likedPostIds
+ * All liked posts’ IDs
+ * @return This Sone (for method chaining)
+ */
+ public synchronized Sone setLikePostIds(Set<String> likedPostIds) {
+ this.likedPostIds.clear();
+ this.likedPostIds.addAll(likedPostIds);
+ modificationCounter++;
+ return this;
+ }
+
+ /**
+ * Checks whether the given post ID is liked by this Sone.
+ *
+ * @param postId
+ * The ID of the post
+ * @return {@code true} if this Sone likes the given post, {@code false}
+ * otherwise
+ */
+ public boolean isLikedPostId(String postId) {
+ return likedPostIds.contains(postId);
+ }
+
+ /**
+ * Adds the given post ID to the list of posts this Sone likes.
+ *
+ * @param postId
+ * The ID of the post
+ * @return This Sone (for method chaining)
+ */
+ public synchronized Sone addLikedPostId(String postId) {
+ if (likedPostIds.add(postId)) {
+ modificationCounter++;
+ }
+ return this;
+ }
+
+ /**
+ * Removes the given post ID from the list of posts this Sone likes.
+ *
+ * @param postId
+ * The ID of the post
+ * @return This Sone (for method chaining)
+ */
+ public synchronized Sone removeLikedPostId(String postId) {
+ if (likedPostIds.remove(postId)) {
+ modificationCounter++;
+ }
+ return this;
+ }
+
+ /**
* Returns the modification counter.
*
* @return The modification counter
import net.pterodactylus.sone.core.Core;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.util.template.DataProvider;
import net.pterodactylus.util.template.ReflectionAccessor;
*/
@Override
public Object get(DataProvider dataProvider, Object object, String member) {
+ Post post = (Post) object;
if ("replies".equals(member)) {
- return core.getReplies((Post) object);
+ return core.getReplies(post);
+ } else if (member.equals("likes")) {
+ return core.getLikes(post);
+ } else if (member.equals("liked")) {
+ Sone currentSone = (Sone) dataProvider.getData("currentSone");
+ return (currentSone != null) && (currentSone.isLikedPostId(post.getId()));
}
return super.get(dataProvider, object, member);
}
--- /dev/null
+/*
+ * Sone - LikePostPage.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.Template;
+
+/**
+ * Page that lets the user like a {@link Post}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class LikePostPage extends SoneTemplatePage {
+
+ /**
+ * Creates a new “like post” page.
+ *
+ * @param template
+ * The template to render
+ * @param webInterface
+ * The Sone web interface
+ */
+ public LikePostPage(Template template, WebInterface webInterface) {
+ /* TODO */
+ super("likePost.html", template, "Page.LikePost.Title", webInterface);
+ }
+
+ //
+ // TEMPLATEPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void processTemplate(Request request, Template template) throws RedirectException {
+ super.processTemplate(request, template);
+ if (request.getMethod() == Method.POST) {
+ String postId = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
+ String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 64);
+ Sone currentSone = getCurrentSone(request.getToadletContext());
+ currentSone.addLikedPostId(postId);
+ throw new RedirectException(returnPage);
+ }
+ }
+
+ //
+ // SONETEMPLATEPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * Sone - UnlikePostPage.java - Copyright © 2010 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.page.Page.Request.Method;
+import net.pterodactylus.util.template.Template;
+
+/**
+ * Page that lets the user unlike a {@link Post}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UnlikePostPage extends SoneTemplatePage {
+
+ /**
+ * Creates a new “unlike post” page.
+ *
+ * @param template
+ * The template to render
+ * @param webInterface
+ * The Sone web interface
+ */
+ public UnlikePostPage(Template template, WebInterface webInterface) {
+ super("unlikePost.html", template, "Page.UnlikePost.Title", webInterface);
+ }
+
+ //
+ // TEMPLATEPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void processTemplate(Request request, Template template) throws RedirectException {
+ super.processTemplate(request, template);
+ if (request.getMethod() == Method.POST) {
+ String postId = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
+ String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 64);
+ Sone currentSone = getCurrentSone(request.getToadletContext());
+ currentSone.removeLikedPostId(postId);
+ throw new RedirectException(returnPage);
+ }
+ }
+
+ //
+ // SONETEMPLATEPAGE METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean requiresLogin() {
+ return true;
+ }
+
+}
Template blockSoneTemplate = templateFactory.createTemplate(createReader("/templates/blockSone.html"));
Template unblockSoneTemplate = templateFactory.createTemplate(createReader("/templates/unblockSone.html"));
Template viewPostTemplate = templateFactory.createTemplate(createReader("/templates/viewPost.html"));
+ Template likePostTemplate = templateFactory.createTemplate(createReader("/templates/likePost.html"));
+ Template unlikePostTemplate = templateFactory.createTemplate(createReader("/templates/unlikePost.html"));
Template deletePostTemplate = templateFactory.createTemplate(createReader("/templates/deletePost.html"));
Template deleteReplyTemplate = templateFactory.createTemplate(createReader("/templates/deleteReply.html"));
Template followSoneTemplate = templateFactory.createTemplate(createReader("/templates/followSone.html"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new BlockSonePage(blockSoneTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new UnblockSonePage(unblockSoneTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new ViewPostPage(viewPostTemplate, this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new LikePostPage(likePostTemplate, this)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikePostPage(unlikePostTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new DeletePostPage(deletePostTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteReplyPage(deleteReplyTemplate, this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSonePage(followSoneTemplate, this)));
View.Post.DeleteLink=Delete
View.Post.SendReply=Post Reply!
View.Post.Reply.DeleteLink=Delete
+View.Post.LikeLink=Like
+View.Post.UnlikeLink=Unlike
WebInterface.DefaultText.StatusUpdate=What’s on your mind?
WebInterface.DefaultText.CreateSoneName=The name of your Sone
color: #666;
}
-#sone .post .delete {
+#sone .post .delete, #sone .post .likes, #sone .post .like, #sone .post .unlike {
display: inline;
+ font: inherit;
}
-#sone .post .delete button {
- font: inherit;
+#sone .post .like.hidden, #sone .post .unlike.hidden {
+ display: none;
+}
+
+#sone .post .delete button, #sone .post .like button, #sone .post .unlike button {
border: 0px;
background: none;
padding: 0px;
color: rgb(28, 131, 191);
}
-#sone .post .delete button:hover {
- font: inherit;
+#sone .post .delete button:hover, #sone .post .like button:hover, #sone .post .unlike button:hover {
border: 0px;
background: none;
padding: 0px;
cursor: pointer;
}
-#sone .post .delete:before {
+#sone .post .delete:before, #sone .post .likes:before, #sone .post .like:before, #sone .post .unlike:before {
content: ' ‧ ';
}
+#sone .post .likes span {
+ font: inherit;
+ color: green;
+}
+
#sone .post .replies {
clear: both;
padding-top: 0.2ex;
</div>
<div class="status-line">
<div class="time"><a href="viewPost.html?post=<% post.id|html>"><% post.time|date format="MMM d, yyyy, HH:mm:ss"></a></div>
+ <div class="likes"><span>⬆<span class="like-count"><% post.likes.size></span></span></div>
+ <%ifnull ! currentSone>
+ <form class="like<%if post.liked> hidden<%/if>" action="likePost.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" value="1"><%= View.Post.LikeLink|l10n|html></button>
+ </form>
+ <form class="unlike<%if ! post.liked> hidden<%/if>" action="unlikePost.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" value="1"><%= View.Post.UnlikeLink|l10n|html></button>
+ </form>
+ <%/if>
<%if post.sone.current>
<form class="delete" action="deletePost.html" method="post">
<input type="hidden" name="formPassword" value="<% formPassword|html>" />
<%/foreach>
</replies>
+ <post-likes>
+ <%foreach currentSone.likedPostIds postId>
+ <post-like><% postId|xml></post-like>
+ <%/foreach>
+ </post-likes>
+
<known-sones>
<%foreach knownSones sone>
<known-sone>
--- /dev/null
+<%include include/head.html>
+
+<%include include/tail.html>
--- /dev/null
+<%include include/head.html>
+
+<%include include/tail.html>