diff --git a/src/main/java/com/moandjiezana/essayist/posts/Bookmark.java b/src/main/java/com/moandjiezana/essayist/posts/Bookmark.java new file mode 100644 index 0000000..f86b1c2 --- /dev/null +++ b/src/main/java/com/moandjiezana/essayist/posts/Bookmark.java @@ -0,0 +1,97 @@ +package com.moandjiezana.essayist.posts; + +import com.moandjiezana.tent.client.posts.content.PostContent; + +import java.net.URL; +import java.util.Locale; + +public class Bookmark implements PostContent { + + public static final String URI = "http://www.beberlei.de/tent/bookmark/v0.0.1"; + + private URL url; + private String title; + private URL image; + private String description; + private Locale[] locale; + private String siteName; + private String[] tags; + private String content; + + public Bookmark(URL url, String title) { + this.url = url; + this.title = title; + } + + public Bookmark() {} + + @Override + public String getType() { + return URI; + } + + public URL getUrl() { + return url; + } + + public void setUrl(URL url) { + this.url = url; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public URL getImage() { + return image; + } + + public void setImage(URL image) { + this.image = image; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Locale[] getLocale() { + return locale; + } + + public void setLocale(Locale[] locale) { + this.locale = locale; + } + + public String getSiteName() { + return siteName; + } + + public void setSiteName(String siteName) { + this.siteName = siteName; + } + + public String[] getTags() { + return tags; + } + + public void setTags(String[] tags) { + this.tags = tags; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + +} diff --git a/src/main/java/com/moandjiezana/essayist/posts/EssayistMetadataContent.java b/src/main/java/com/moandjiezana/essayist/posts/EssayistMetadataContent.java new file mode 100644 index 0000000..95362e3 --- /dev/null +++ b/src/main/java/com/moandjiezana/essayist/posts/EssayistMetadataContent.java @@ -0,0 +1,47 @@ +package com.moandjiezana.essayist.posts; + +import com.moandjiezana.tent.client.posts.content.PostContent; + +public class EssayistMetadataContent implements PostContent { + + public static final String URI = "http://moandjiezana.com/tent/essayist/types/post/metadata/v0.1.0"; + + private String format = "markdown"; + private String raw; + private String statusId; + + public EssayistMetadataContent(String raw) { + this.raw = raw; + } + + public EssayistMetadataContent() {} + + @Override + public String getType() { + return URI; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + public String getStatusId() { + return statusId; + } + + public void setStatusId(String statusId) { + this.statusId = statusId; + } +} diff --git a/src/main/java/com/moandjiezana/essayist/posts/Favorite.java b/src/main/java/com/moandjiezana/essayist/posts/Favorite.java new file mode 100644 index 0000000..03881e6 --- /dev/null +++ b/src/main/java/com/moandjiezana/essayist/posts/Favorite.java @@ -0,0 +1,40 @@ +package com.moandjiezana.essayist.posts; + +import com.moandjiezana.tent.client.posts.content.PostContent; + +public class Favorite implements PostContent { + + public static final String URI = "http://www.beberlei.de/tent/favorite/v0.0.1"; + + private String entity; + private String post; + + public Favorite(String entity, String post) { + this.entity = entity; + this.post = post; + } + + public Favorite() {} + + @Override + public String getType() { + return URI; + } + + public String getEntity() { + return entity; + } + + public void setEntity(String entity) { + this.entity = entity; + } + + public String getPost() { + return post; + } + + public void setPost(String post) { + this.post = post; + } + +} diff --git a/src/main/java/com/moandjiezana/essayist/posts/UserReactions.java b/src/main/java/com/moandjiezana/essayist/posts/UserReactions.java new file mode 100644 index 0000000..d9e6dc3 --- /dev/null +++ b/src/main/java/com/moandjiezana/essayist/posts/UserReactions.java @@ -0,0 +1,8 @@ +package com.moandjiezana.essayist.posts; + +public class UserReactions { + + public boolean favorited; + public boolean reposted; + public boolean bookmarked; +} diff --git a/src/main/java/com/moandjiezana/tent/essayist/CommentsServlet.java b/src/main/java/com/moandjiezana/tent/essayist/CommentsServlet.java deleted file mode 100644 index 716a86f..0000000 --- a/src/main/java/com/moandjiezana/tent/essayist/CommentsServlet.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.moandjiezana.tent.essayist; - -import com.google.common.base.Strings; -import com.moandjiezana.tent.client.TentClient; -import com.moandjiezana.tent.client.posts.Mention; -import com.moandjiezana.tent.client.posts.Post; -import com.moandjiezana.tent.client.posts.content.StatusContent; -import com.moandjiezana.tent.client.users.Permissions; -import com.moandjiezana.tent.essayist.auth.Authenticated; -import com.moandjiezana.tent.essayist.config.Routes; -import com.moandjiezana.tent.essayist.tent.Entities; - -import java.io.IOException; - -import javax.inject.Inject; -import javax.inject.Provider; -import javax.inject.Singleton; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@Singleton -@Authenticated -public class CommentsServlet extends HttpServlet { - - private final Provider sessions; - private final Provider routes; - - @Inject - public CommentsServlet(Provider routes, Provider sessions) { - this.routes = routes; - this.sessions = sessions; - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String[] parts = req.getPathInfo().split("/essay/"); - String authorEntity = Entities.expandFromUrl(parts[0]); - String essayId = parts[1].split("/")[0]; - - String commentText = req.getParameter("comment"); - - Post essay = new Post(); - essay.setEntity(authorEntity); - essay.setId(essayId); - - if (Strings.isNullOrEmpty(commentText)) { - resp.sendRedirect(routes.get().essay(essay)); - - return; - } - - User user = sessions.get().getUser(); - TentClient tentClient = new TentClient(user.getProfile()); - tentClient.getAsync().setAccessToken(user.getAccessToken()); - tentClient.getAsync().setRegistrationResponse(user.getRegistration()); - - Post comment = new Post(); - comment.setEntity(user.getProfile().getCore().getEntity()); - commentText = commentText.substring(0, Math.min(commentText.length(), 256)); - comment.setContent(new StatusContent(commentText)); - comment.setMentions(new Mention[] { new Mention(authorEntity, essayId) }); - Permissions permissions = new Permissions(); - permissions.setPublic(true); - comment.setPermissions(permissions); - comment.setLicenses(new String[] { "http://creativecommons.org/licenses/by/3.0/" }); - - tentClient.write(comment); - - resp.sendRedirect(routes.get().essay(essay)); - } -} diff --git a/src/main/java/com/moandjiezana/tent/essayist/EssayActionServlet.java b/src/main/java/com/moandjiezana/tent/essayist/EssayActionServlet.java new file mode 100644 index 0000000..057e935 --- /dev/null +++ b/src/main/java/com/moandjiezana/tent/essayist/EssayActionServlet.java @@ -0,0 +1,146 @@ +package com.moandjiezana.tent.essayist; + +import com.google.common.base.Strings; +import com.moandjiezana.essayist.posts.Bookmark; +import com.moandjiezana.essayist.posts.Favorite; +import com.moandjiezana.essayist.posts.UserReactions; +import com.moandjiezana.tent.client.TentClient; +import com.moandjiezana.tent.client.posts.Mention; +import com.moandjiezana.tent.client.posts.Post; +import com.moandjiezana.tent.client.posts.PostQuery; +import com.moandjiezana.tent.client.posts.content.Repost; +import com.moandjiezana.tent.client.posts.content.StatusContent; +import com.moandjiezana.tent.client.users.Permissions; +import com.moandjiezana.tent.essayist.auth.Authenticated; +import com.moandjiezana.tent.essayist.config.Routes; +import com.moandjiezana.tent.essayist.tent.Entities; + +import java.io.IOException; +import java.net.URL; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Singleton +public class EssayActionServlet extends HttpServlet { + + private final Provider sessions; + private final Provider routes; + private Users users; + private Templates templates; + + @Inject + public EssayActionServlet(Users users, Provider routes, Provider sessions, Templates templates) { + this.users = users; + this.routes = routes; + this.sessions = sessions; + this.templates = templates; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String[] urlParts = getEntityAndIdAndAction(req); + String authorEntity = req.getParameter("entity") != null ? req.getParameter("entity") : urlParts[0]; + String postId = urlParts[1]; + String action = urlParts[2]; + + + if ("user".equals(action)) { + User loggedUser = sessions.get().getUser(); + String loggedEntity = loggedUser.getProfile().getCore().getEntity(); + TentClient tentClient = new TentClient(loggedUser.getProfile()); + List reactions = tentClient.getPosts(new PostQuery().mentionedPost(postId).entity(loggedEntity).postTypes(Bookmark.URI, Favorite.URI, Post.Types.repost("v0.1.0"))); + UserReactions userReactions = new UserReactions(); + for (Post reaction : reactions) { + String reactionType = reaction.getType(); + if (Post.Types.equalsIgnoreVersion(Bookmark.URI, reactionType)) { + userReactions.bookmarked = true; + } else if (Post.Types.equalsIgnoreVersion(Favorite.URI, reactionType)) { + userReactions.favorited = true; + } + } + } else if ("reactions".equals(action)) { + User user = users.getByEntityOrNull(authorEntity); + TentClient tentClient; + if (user != null) { + tentClient = new TentClient(user.getProfile()); + tentClient.getAsync().setAccessToken(user.getAccessToken()); + tentClient.getAsync().setRegistrationResponse(user.getRegistration()); + } else { + tentClient = new TentClient(authorEntity); + tentClient.getProfile(); + } + //.postTypes(Bookmark.URI, Favorite.URI, Post.Types.repost("v0.1.0"), Post.Types.status("v0.1.0")) + List reactions = tentClient.getPosts(new PostQuery().mentionedPost(postId)); + templates.reactions().render(resp.getWriter(), reactions); + } + } + + @Override + @Authenticated + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String[] entityAndId = getEntityAndIdAndAction(req); + String authorEntity = entityAndId[0]; + String essayId = entityAndId[1]; + String action = entityAndId[2]; + + Post essay = new Post(); + essay.setEntity(authorEntity); + essay.setId(essayId); + + User user = sessions.get().getUser(); + TentClient tentClient = new TentClient(user.getProfile()); + tentClient.getAsync().setAccessToken(user.getAccessToken()); + tentClient.getAsync().setRegistrationResponse(user.getRegistration()); + + Post post = new Post(); + post.setEntity(user.getProfile().getCore().getEntity()); + post.setMentions(new Mention[] { new Mention(authorEntity, essayId) }); + Permissions permissions = new Permissions(); + permissions.setPublic(true); + post.setPermissions(permissions); + post.setLicenses(new String[] { "http://creativecommons.org/licenses/by/3.0/" }); + + if ("status".equals(action)) { + String commentText = req.getParameter("comment"); + + if (Strings.isNullOrEmpty(commentText)) { + resp.sendRedirect(routes.get().essay(essay)); + + return; + } + + commentText = commentText.substring(0, Math.min(commentText.length(), 256)); + post.setContent(new StatusContent(commentText)); + } else if ("favorite".equals(action)) { + post.setContent(new Favorite(authorEntity, essayId)); + } else if ("bookmark".equals(action)) { + String requestUrl = req.getRequestURL().toString(); + Bookmark bookmark = new Bookmark(new URL(requestUrl.substring(0, requestUrl.lastIndexOf('/'))), req.getParameter("title")); + bookmark.setDescription(req.getParameter("description")); + bookmark.setSiteName(req.getParameter("name") + " on Essayist"); + post.setContent(bookmark); + } else if ("repost".equals(action)) { + post.setContent(new Repost(authorEntity, essayId)); + } + + tentClient.write(post); + + resp.sendRedirect(routes.get().essay(essay)); + } + + private String[] getEntityAndIdAndAction(HttpServletRequest req) { + String[] parts = req.getPathInfo().split("/"); + String authorEntity = Entities.expandFromUrl(parts[0]); + String essayId = parts[2]; + String action = parts[3]; + + return new String[] { authorEntity, essayId, action }; + } +} diff --git a/src/main/java/com/moandjiezana/tent/essayist/EssayServlet.java b/src/main/java/com/moandjiezana/tent/essayist/EssayServlet.java index 71f56c1..f329374 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/EssayServlet.java +++ b/src/main/java/com/moandjiezana/tent/essayist/EssayServlet.java @@ -66,16 +66,19 @@ public class EssayServlet extends HttpServlet { EssayistPostContent essayContent = post.getContentAs(EssayistPostContent.class); essayContent.setBody(csrf.stripScripts(essayContent.getBody())); - EssayTemplate essayPage = templates.essay(); + EssayPage essayPage = templates.essay(); if (user.owns(post)) { essayPage.setActive("Written"); } + + if (Boolean.parseBoolean(req.getParameter("reactions"))) { + tentClient.getAsync().setAccessToken(author.getAccessToken()); + tentClient.getAsync().setRegistrationResponse(author.getRegistration()); + List reactions = tentClient.getPosts(new PostQuery().mentionedPost(essayId)); + essayPage.setReactions(reactions); + } - tentClient.getAsync().setAccessToken(author.getAccessToken()); - tentClient.getAsync().setRegistrationResponse(author.getRegistration()); - List comments = tentClient.getPosts(new PostQuery().mentionedPost(essayId)); - - essayPage.render(resp.getWriter(), post, author.getProfile(), comments); + essayPage.render(resp.getWriter(), post, author.getProfile()); } @Override diff --git a/src/main/java/com/moandjiezana/tent/essayist/Essays.java b/src/main/java/com/moandjiezana/tent/essayist/Essays.java index 72be257..cb27838 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/Essays.java +++ b/src/main/java/com/moandjiezana/tent/essayist/Essays.java @@ -1,8 +1,8 @@ package com.moandjiezana.tent.essayist; import com.google.common.base.Throwables; +import com.moandjiezana.tent.client.HttpTentDataSource; import com.moandjiezana.tent.client.TentClient; -import com.moandjiezana.tent.client.TentClientAsync; import com.moandjiezana.tent.client.posts.Post; import com.moandjiezana.tent.client.posts.PostQuery; @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; @@ -25,13 +26,23 @@ public class Essays { CopyOnWriteArrayList>> futurePosts = new CopyOnWriteArrayList>>(); for (User user : users) { - futurePosts.add(new TentClientAsync(user.getProfile()).getPosts(new PostQuery().entity(user.getProfile().getCore().getEntity()).postTypes(Post.Types.essay("v0.1.0")))); + futurePosts.add(new HttpTentDataSource(user.getProfile()).getPosts(new PostQuery().entity(user.getProfile().getCore().getEntity()).postTypes(Post.Types.essay("v0.1.0")))); } List posts = new ArrayList(futurePosts.size()); for (Future> futurePost : futurePosts) { try { - posts.addAll(futurePost.get()); + List post = futurePost.get(); + if (post == null) { + continue; + } + Iterator iterator = post.iterator(); + while (iterator.hasNext()) { + if (iterator.next() == null) { + iterator.remove(); + } + } + posts.addAll(post); } catch (Exception e) { LOGGER.error("Could not load Post", Throwables.getRootCause(e)); } @@ -55,6 +66,10 @@ public class Essays { return tentClient.getPosts(new PostQuery().postTypes(Post.Types.essay("v0.1.0"))); } + public List getReactions(User user, String postId) { + return new TentClient(user.getProfile()).getPosts(new PostQuery().mentionedPost(postId)); + } + public Post get(String essayId) { return null; } diff --git a/src/main/java/com/moandjiezana/tent/essayist/LoginServlet.java b/src/main/java/com/moandjiezana/tent/essayist/LoginServlet.java index 5c8b073..6962827 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/LoginServlet.java +++ b/src/main/java/com/moandjiezana/tent/essayist/LoginServlet.java @@ -1,5 +1,8 @@ package com.moandjiezana.tent.essayist; +import com.moandjiezana.essayist.posts.Bookmark; +import com.moandjiezana.essayist.posts.EssayistMetadataContent; +import com.moandjiezana.essayist.posts.Favorite; import com.moandjiezana.tent.client.TentClient; import com.moandjiezana.tent.client.apps.AuthorizationRequest; import com.moandjiezana.tent.client.apps.RegistrationRequest; @@ -107,7 +110,8 @@ public class LoginServlet extends HttpServlet { tentClient.getProfile(); Map scopes = new HashMap(); - scopes.put("write_posts", "Will post Essays and optionally Statuses to announce or comment on Essays."); + scopes.put("write_posts", "Allows you to write Essays and re-post, bookmark or favorite other people's Essays."); + scopes.put("read_posts", "Read Essays and your reactions to Essays."); URL url = new URL(req.getRequestURL().toString()); String baseUrl = url.getProtocol() + "://" + url.getAuthority() + req.getContextPath(); @@ -122,7 +126,7 @@ public class LoginServlet extends HttpServlet { private String authorize(TentClient tentClient, RegistrationResponse registrationResponse, String redirectUri, HttpServletRequest req) { AuthorizationRequest authorizationRequest = new AuthorizationRequest(registrationResponse.getMacKeyId(), redirectUri); authorizationRequest.setScope("write_posts", "read_posts"); - authorizationRequest.setTentPostTypes(Post.Types.essay("v0.1.0"), Post.Types.status("v0.1.0"), Post.Types.photo("v0.1.0")); + authorizationRequest.setTentPostTypes(Post.Types.essay("v0.1.0"), Post.Types.status("v0.1.0"), Post.Types.photo("v0.1.0"), Post.Types.repost("v0.1.0"), EssayistMetadataContent.URI, Bookmark.URI, Favorite.URI); authorizationRequest.setState(UUID.randomUUID().toString()); String authorizationUrl = tentClient.buildAuthorizationUrl(authorizationRequest); @@ -131,6 +135,7 @@ public class LoginServlet extends HttpServlet { authResult.registrationResponse = registrationResponse; req.getSession().setAttribute(authorizationRequest.getState(), authResult); req.getSession().setAttribute("entity", tentClient.getProfile().getCore().getEntity()); + return authorizationUrl; } } diff --git a/src/main/java/com/moandjiezana/tent/essayist/NewEssayServlet.java b/src/main/java/com/moandjiezana/tent/essayist/NewEssayServlet.java deleted file mode 100644 index f178595..0000000 --- a/src/main/java/com/moandjiezana/tent/essayist/NewEssayServlet.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.moandjiezana.tent.essayist; - -import com.moandjiezana.tent.client.TentClient; -import com.moandjiezana.tent.client.posts.Post; -import com.moandjiezana.tent.client.users.Permissions; -import com.moandjiezana.tent.essayist.auth.Authenticated; -import com.moandjiezana.tent.essayist.tent.Entities; -import com.moandjiezana.tent.essayist.tent.EssayistPostContent; - -import java.io.IOException; - -import javax.inject.Inject; -import javax.inject.Provider; -import javax.inject.Singleton; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.pegdown.PegDownProcessor; - -@Singleton -@Authenticated -public class NewEssayServlet extends HttpServlet { - - private Templates templates; - private Provider sessions; - - @Inject - public NewEssayServlet(Provider sessions, Templates templates) { - this.sessions = sessions; - this.templates = templates; - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - templates.newEssay().render(resp.getWriter()); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - TentClient tentClient = tentClient(req); - - Post post = new Post(); - post.setPublishedAt(System.currentTimeMillis() / 1000); - Permissions permissions = new Permissions(); - permissions.setPublic(true); - post.setPermissions(permissions); - post.setLicenses(new String[] { "http://creativecommons.org/licenses/by/3.0/" }); - EssayistPostContent essay = new EssayistPostContent(); - essay.setTitle(req.getParameter("title")); - essay.setBody(new PegDownProcessor().markdownToHtml(req.getParameter("body"))); - essay.setRaw(req.getParameter("body")); - essay.setType("markdown"); - essay.setExcerpt(req.getParameter("excerpt")); - post.setContent(essay); - - Post newPost = tentClient.write(post); - - resp.sendRedirect(req.getContextPath() + "/" + Entities.getForUrl(tentClient.getProfile().getCore().getEntity()) + "/essay/" + newPost.getId()); - } - - private TentClient tentClient(HttpServletRequest req) { - User user = sessions.get().getUser(); - - TentClient tentClient = new TentClient(user.getProfile()); - tentClient.getAsync().setAccessToken(user.getAccessToken()); - - return tentClient; - } -} diff --git a/src/main/java/com/moandjiezana/tent/essayist/PreviewServlet.java b/src/main/java/com/moandjiezana/tent/essayist/PreviewServlet.java new file mode 100644 index 0000000..e375552 --- /dev/null +++ b/src/main/java/com/moandjiezana/tent/essayist/PreviewServlet.java @@ -0,0 +1,37 @@ +package com.moandjiezana.tent.essayist; + +import com.google.common.io.CharStreams; +import com.moandjiezana.tent.essayist.auth.Authenticated; +import com.moandjiezana.tent.essayist.security.Csrf; + +import java.io.IOException; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.pegdown.PegDownProcessor; + +@Singleton +@Authenticated +public class PreviewServlet extends HttpServlet { + + private final Csrf csrf; + + @Inject + public PreviewServlet(Csrf csrf) { + this.csrf = csrf; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String body = CharStreams.toString(req.getReader()); + String html = new PegDownProcessor().markdownToHtml(body); + String sanitized = csrf.stripScripts(html); + + resp.getWriter().write(sanitized); + } +} diff --git a/src/main/java/com/moandjiezana/tent/essayist/Templates.java b/src/main/java/com/moandjiezana/tent/essayist/Templates.java index ce64cc0..2602535 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/Templates.java +++ b/src/main/java/com/moandjiezana/tent/essayist/Templates.java @@ -1,6 +1,7 @@ package com.moandjiezana.tent.essayist; import com.moandjiezana.tent.essayist.config.JamonContext; +import com.moandjiezana.tent.essayist.partials.ReactionList; import javax.inject.Inject; import javax.inject.Provider; @@ -20,12 +21,12 @@ public class Templates { return new LoginTemplate().setJamonContext(jamonContext.get()); } - public EssaysTemplate essays(String active) { - return new EssaysTemplate().setActive(active).setJamonContext(jamonContext.get()); + public EssaysPage essays(String active) { + return new EssaysPage().setActive(active).setJamonContext(jamonContext.get()); } - public EssayTemplate essay() { - return new EssayTemplate().setJamonContext(jamonContext.get()); + public EssayPage essay() { + return new EssayPage().setJamonContext(jamonContext.get()); } public NewEssayTemplate newEssay() { @@ -35,4 +36,12 @@ public class Templates { public ReadPage read() { return new ReadPage().setJamonContext(jamonContext.get()); } + + public ReactionList reactions() { + return new ReactionList().setJamonContext(jamonContext.get()); + } + + public NonEditableEssayPage nonEditableEssay() { + return new NonEditableEssayPage().setJamonContext(jamonContext.get()); + } } diff --git a/src/main/java/com/moandjiezana/tent/essayist/User.java b/src/main/java/com/moandjiezana/tent/essayist/User.java index aba8762..9610026 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/User.java +++ b/src/main/java/com/moandjiezana/tent/essayist/User.java @@ -13,6 +13,12 @@ public class User { public User() {} + public User(String entity) { + this.profile = new Profile(); + this.profile.setCore(new Profile.Core()); + this.profile.getCore().setEntity(entity); + } + public User(Profile profile) { this(null, profile, null, null); } diff --git a/src/main/java/com/moandjiezana/tent/essayist/WriteServlet.java b/src/main/java/com/moandjiezana/tent/essayist/WriteServlet.java new file mode 100644 index 0000000..6a79522 --- /dev/null +++ b/src/main/java/com/moandjiezana/tent/essayist/WriteServlet.java @@ -0,0 +1,157 @@ +package com.moandjiezana.tent.essayist; + +import com.moandjiezana.essayist.posts.EssayistMetadataContent; +import com.moandjiezana.essayist.utils.Tasks; +import com.moandjiezana.tent.client.TentClient; +import com.moandjiezana.tent.client.posts.Mention; +import com.moandjiezana.tent.client.posts.Post; +import com.moandjiezana.tent.client.posts.PostQuery; +import com.moandjiezana.tent.client.posts.content.EssayContent; +import com.moandjiezana.tent.client.users.Permissions; +import com.moandjiezana.tent.essayist.auth.Authenticated; +import com.moandjiezana.tent.essayist.config.Routes; +import com.moandjiezana.tent.essayist.tent.Entities; + +import java.io.IOException; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.pegdown.PegDownProcessor; + +@Singleton +@Authenticated +public class WriteServlet extends HttpServlet { + + private Templates templates; + private Provider sessions; + private Tasks tasks; + private Provider routes; + + @Inject + public WriteServlet(Provider sessions, Templates templates, Provider routes, Tasks tasks) { + this.sessions = sessions; + this.templates = templates; + this.routes = routes; + this.tasks = tasks; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (req.getRequestURI().endsWith("/write")) { + templates.newEssay().render(resp.getWriter()); + return; + } + + String essayId = req.getPathInfo().substring(1); + TentClient tentClient = newTentClient(); + List metadataPosts = tentClient.getPosts(new PostQuery().mentionedPost(essayId).postTypes(EssayistMetadataContent.URI)); + if (metadataPosts.isEmpty()) { + templates.nonEditableEssay().render(resp.getWriter()); + return; + } + + Post essay = tentClient.getPost(essayId); + Post metadata = metadataPosts.get(0); + + templates.newEssay().setMetadata(metadata).setEssay(essay).setMetadata(metadata).render(resp.getWriter()); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + final TentClient tentClient = newTentClient(); + + final Post post = newPost(); + EssayContent essay = new EssayContent(); + essay.setTitle(req.getParameter("title")); + final String body = req.getParameter("body"); + essay.setBody(new PegDownProcessor().markdownToHtml(body)); + essay.setExcerpt(req.getParameter("excerpt")); + post.setContent(essay); + + final Post newPost = tentClient.write(post); + final String newPostId = newPost.getId(); + final User user = sessions.get().getUser(); + + Post metadataPost = newPost(); + metadataPost.getPermissions().setPublic(false); + EssayistMetadataContent metadata = new EssayistMetadataContent(body); + metadataPost.setContent(metadata); + String essayId = newPostId; + metadataPost.setMentions(new Mention[] { new Mention(user.getProfile().getCore().getEntity(), essayId) }); + + tentClient.write(metadataPost); + + resp.sendRedirect(req.getContextPath() + "/" + Entities.getForUrl(tentClient.getProfile().getCore().getEntity()) + "/essay/" + newPost.getId()); + +// tasks.run(new Runnable() { +// @Override +// public void run() { +// } else { +// Post originalPost = posts.get(0); +// +// Post newPost = newPost(); +// +// EssayistMetadataContent content = originalPost.getContentAs(EssayistMetadataContent.class); +// content.setRaw(body); +// } +// +// } +// }); + } + + @Override + protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + TentClient tentClient = newTentClient(); +// List posts = tentClient.getPosts(new PostQuery().mentionedPost(essayId).postTypes(EssayistMetadataContent.URI)); + + String essayId = req.getPathInfo().substring(1); + Post post = newPost(); + post.setId(essayId); + post.setEntity(sessions.get().getUser().getProfile().getCore().getEntity()); + EssayContent essay = new EssayContent(); + essay.setTitle(req.getParameter("title")); + final String body = req.getParameter("body"); + essay.setBody(new PegDownProcessor().markdownToHtml(body)); + essay.setExcerpt(req.getParameter("excerpt")); + post.setContent(essay); + + tentClient.put(post); + + List posts = tentClient.getPosts(new PostQuery().mentionedPost(essayId).postTypes(EssayistMetadataContent.URI)); + + Post metadataPost = posts.get(0); + EssayistMetadataContent metadata = metadataPost.getContentAs(EssayistMetadataContent.class); + metadata.setRaw(body); + metadataPost.setContent(metadata); + + tentClient.put(metadataPost); + + resp.sendRedirect(routes.get().essay(post)); + } + + private Post newPost() { + Post post = new Post(); + post.setPublishedAt(System.currentTimeMillis() / 1000); + Permissions permissions = new Permissions(); + permissions.setPublic(true); + post.setPermissions(permissions); + post.setLicenses(new String[] { "http://creativecommons.org/licenses/by/3.0/" }); + return post; + } + + private TentClient newTentClient() { + User user = sessions.get().getUser(); + + TentClient tentClient = new TentClient(user.getProfile()); + tentClient.getAsync().setAccessToken(user.getAccessToken()); + + return tentClient; + } +} diff --git a/src/main/java/com/moandjiezana/tent/essayist/config/EssayistServletContextListener.java b/src/main/java/com/moandjiezana/tent/essayist/config/EssayistServletContextListener.java index c22296b..999ca13 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/config/EssayistServletContextListener.java +++ b/src/main/java/com/moandjiezana/tent/essayist/config/EssayistServletContextListener.java @@ -11,14 +11,15 @@ import com.google.inject.servlet.GuiceServletContextListener; import com.google.inject.servlet.ServletModule; import com.moandjiezana.tent.client.internal.com.google.common.base.Throwables; import com.moandjiezana.tent.essayist.AccessTokenServlet; -import com.moandjiezana.tent.essayist.CommentsServlet; +import com.moandjiezana.tent.essayist.EssayActionServlet; import com.moandjiezana.tent.essayist.EssayServlet; import com.moandjiezana.tent.essayist.EssaysServlet; import com.moandjiezana.tent.essayist.GlobalFeedServlet; import com.moandjiezana.tent.essayist.LoginServlet; import com.moandjiezana.tent.essayist.LogoutServlet; import com.moandjiezana.tent.essayist.MyFeedServlet; -import com.moandjiezana.tent.essayist.NewEssayServlet; +import com.moandjiezana.tent.essayist.PreviewServlet; +import com.moandjiezana.tent.essayist.WriteServlet; import com.moandjiezana.tent.essayist.auth.Authenticated; import com.moandjiezana.tent.essayist.auth.AuthenticationInterceptor; import com.moandjiezana.tent.essayist.db.migrations.Migration_1; @@ -66,6 +67,9 @@ public class EssayistServletContextListener extends GuiceServletContextListener poolProperties.setUrl(properties.getProperty("db.url")); poolProperties.setDriverClassName(properties.getProperty("db.driverClassName")); poolProperties.setInitialSize(Integer.parseInt(properties.getProperty("db.initialSize"))); + poolProperties.setTestWhileIdle(true); + poolProperties.setTestOnBorrow(true); + poolProperties.setValidationQuery("SELECT 1"); dataSource = new DataSource(poolProperties); @@ -96,9 +100,10 @@ public class EssayistServletContextListener extends GuiceServletContextListener serve("/accessToken").with(AccessTokenServlet.class); serve("/read").with(MyFeedServlet.class); serve("/global").with(GlobalFeedServlet.class); - serve("/write").with(NewEssayServlet.class); + serve("/write", "/write/*").with(WriteServlet.class); + serve("/preview").with(PreviewServlet.class); serveRegex("/(.*)/essays").with(EssaysServlet.class); - serveRegex("/(.*)/essay/(.*)/comment").with(CommentsServlet.class); + serveRegex("/(.*)/essay/(.*)/(status|favorite|bookmark|repost|reactions|user)").with(EssayActionServlet.class); serveRegex("/(.*)/essay/(.*)").with(EssayServlet.class); filter("/*").through(Utf8Filter.class); filter("/*").through(HttpMethodFilter.class); diff --git a/src/main/java/com/moandjiezana/tent/essayist/config/Routes.java b/src/main/java/com/moandjiezana/tent/essayist/config/Routes.java index 38f81dc..edf80fc 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/config/Routes.java +++ b/src/main/java/com/moandjiezana/tent/essayist/config/Routes.java @@ -1,9 +1,13 @@ package com.moandjiezana.tent.essayist.config; +import com.google.common.base.Joiner; import com.google.inject.servlet.RequestScoped; import com.moandjiezana.tent.client.posts.Post; import com.moandjiezana.tent.essayist.tent.Entities; +import java.util.ArrayList; +import java.util.List; + import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -17,8 +21,26 @@ public class Routes { this.req = req; } - public String assets(String asset) { - return req.getContextPath() + "/assets/" + asset; + public String asset(String asset) { + String url = req.getContextPath() + "/assets/" + asset; + + if (asset.endsWith(".css")) { + return ""; + } else if (asset.endsWith(".js")) { + return ""; + } + + throw new IllegalArgumentException("Unknown asset type: " + asset); + } + + public String assets(String name, String asset, String... assets) { + List tags = new ArrayList(assets.length); + tags.add(asset(asset)); + for (String asset2 : assets) { + tags.add(asset(asset2)); + } + + return Joiner.on('\n').join(tags); } public String essay(Post essay) { @@ -26,6 +48,6 @@ public class Routes { } public String comment(Post essay) { - return essay(essay) + "/comment"; + return essay(essay) + "/status"; } } diff --git a/src/main/java/com/moandjiezana/tent/essayist/security/Csrf.java b/src/main/java/com/moandjiezana/tent/essayist/security/Csrf.java index 4c26f4d..1d56dcb 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/security/Csrf.java +++ b/src/main/java/com/moandjiezana/tent/essayist/security/Csrf.java @@ -12,7 +12,7 @@ public class Csrf { .allowCommonInlineFormattingElements() .allowStandardUrlProtocols() .allowStyling() - .allowElements("iframe", "img", "a", "table", "thead", "tbody", "tr", "th", "td") + .allowElements("iframe", "img", "a", "table", "thead", "tbody", "tr", "th", "td", "em") .allowAttributes("width", "height", "title").globally() .allowAttributes("src", "frameborder", "webkitAllowFullScreen", "mozallowfullscreen", "allowFullScreen").onElements("iframe") .allowAttributes("src", "alt").onElements("img") diff --git a/src/main/java/com/moandjiezana/tent/essayist/tent/Entities.java b/src/main/java/com/moandjiezana/tent/essayist/tent/Entities.java index 33fb2cf..eb36057 100644 --- a/src/main/java/com/moandjiezana/tent/essayist/tent/Entities.java +++ b/src/main/java/com/moandjiezana/tent/essayist/tent/Entities.java @@ -1,6 +1,7 @@ package com.moandjiezana.tent.essayist.tent; import com.google.common.base.Strings; +import com.moandjiezana.tent.client.posts.content.EssayContent; import com.moandjiezana.tent.client.users.Profile; public class Entities { @@ -41,4 +42,18 @@ public class Entities { public static String getName(Profile profile, String fallback) { return profile != null && profile.getBasic() != null && !Strings.isNullOrEmpty(profile.getBasic().getName()) ? profile.getBasic().getName() : fallback; } + + public static String essayTitle(EssayContent essay) { + if (!Strings.isNullOrEmpty(essay.getTitle())) { + return essay.getTitle(); + } + + String title = Strings.nullToEmpty(!Strings.isNullOrEmpty(essay.getExcerpt()) ? essay.getExcerpt() : essay.getBody()); + String shortTitle = title.substring(0, Math.min(40, title.length())); + if (title.length() > 40) { + shortTitle += "..."; + } + + return shortTitle; + } } diff --git a/src/main/templates/com/moandjiezana/tent/essayist/EssayPage.jamon b/src/main/templates/com/moandjiezana/tent/essayist/EssayPage.jamon new file mode 100644 index 0000000..e79c022 --- /dev/null +++ b/src/main/templates/com/moandjiezana/tent/essayist/EssayPage.jamon @@ -0,0 +1,26 @@ +<%import> +com.moandjiezana.tent.client.users.Profile; +com.moandjiezana.tent.client.posts.*; +com.moandjiezana.tent.client.posts.content.EssayContent; +com.moandjiezana.tent.client.posts.content.StatusContent; +com.moandjiezana.tent.essayist.tent.*; +com.moandjiezana.essayist.posts.*; +java.text.SimpleDateFormat; +java.util.*; + +<%args> +Post essay; +Profile profile; +String active = "My Feed"; +List reactions = null; + +<%java> +final EssayContent content = essay.getContentAs(EssayContent.class); +final SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); +final SimpleDateFormat dateTimeFormat = new SimpleDateFormat("dd MMMM yyyy HH:mm ZZZZ"); + +<&| Layout; active = active &> + <& partials/Essay; essay=essay; entityForUrl=Entities.getForUrl(essay.getEntity()); entityName=Entities.getName(profile); essayId=essay.getId(); formattedPublicationDate=dateFormat.format(new Date(essay.getPublishedAt() * 1000)); display=true; &> + + <& partials/Reactions; reactions=reactions; essay=essay; autoLoad=true; &> + \ No newline at end of file diff --git a/src/main/templates/com/moandjiezana/tent/essayist/EssayTemplate.jamon b/src/main/templates/com/moandjiezana/tent/essayist/EssayTemplate.jamon deleted file mode 100644 index 837eb8d..0000000 --- a/src/main/templates/com/moandjiezana/tent/essayist/EssayTemplate.jamon +++ /dev/null @@ -1,61 +0,0 @@ -<%import> -com.moandjiezana.tent.client.users.Profile; -com.moandjiezana.tent.client.posts.*; -com.moandjiezana.tent.client.posts.content.EssayContent; -com.moandjiezana.tent.client.posts.content.StatusContent; -com.moandjiezana.tent.essayist.tent.*; -java.text.SimpleDateFormat; -java.util.*; - -<%args> -Post essay; -Profile profile; -String active = "My Feed"; -List comments; - -<%java> -final EssayContent content = essay.getContentAs(EssayContent.class); -final SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); -final SimpleDateFormat dateTimeFormat = new SimpleDateFormat("dd MMMM yyyy 'at' HH:mm ZZZZ"); - -<&| Layout; active = active &> -

<% content.getTitle() %> <% Character.valueOf('\u2693') %>

-

by <% Entities.getName(profile) %> <% dateFormat.format(new Date(essay.getPublishedAt() * 1000)) %>

-
- <% content.getBody() #n %> -
- <%if jamonContext.isLoggedIn() || !comments.isEmpty() %> -
-
-

Comments

-
-
- - <%if jamonContext.isLoggedIn() %> -
-
-
- -
-
-
-
- -
-
-
- - - <%if !comments.isEmpty() %> -
- <%for Post comment : comments %> -
- <% Entities.stripScheme(comment.getEntity()) %> <% dateTimeFormat.format(new Date(comment.getPublishedAt() * 1000)) %>
-

- <% comment.getContentAs(StatusContent.class).getText() %> -

-
- -
- - \ No newline at end of file diff --git a/src/main/templates/com/moandjiezana/tent/essayist/EssaysTemplate.jamon b/src/main/templates/com/moandjiezana/tent/essayist/EssaysPage.jamon similarity index 100% rename from src/main/templates/com/moandjiezana/tent/essayist/EssaysTemplate.jamon rename to src/main/templates/com/moandjiezana/tent/essayist/EssaysPage.jamon diff --git a/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon b/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon index 50522fe..5ff4996 100644 --- a/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon +++ b/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon @@ -25,9 +25,9 @@ String url; - - - + <% jamonContext.routes.assets("application.css", "bootstrap-2.1.1/css/bootstrap.min.css") #n %> + + - + <% jamonContext.routes.asset("bootstrap-2.1.1/css/bootstrap-responsive.min.css") #n %> + + <% jamonContext.routes.assets("application.js", "underscore-1.4.2/underscore.min.js", "spin/spin.min.js", "essayist.js") #n %> - @@ -87,7 +98,6 @@ String url; - @@ -96,7 +106,11 @@ String url; <& body &>
- Version: 20121030 +
+
+ Version: 20121114 +
+
diff --git a/src/main/templates/com/moandjiezana/tent/essayist/NewEssayTemplate.jamon b/src/main/templates/com/moandjiezana/tent/essayist/NewEssayTemplate.jamon index 866ced0..f15bdf9 100644 --- a/src/main/templates/com/moandjiezana/tent/essayist/NewEssayTemplate.jamon +++ b/src/main/templates/com/moandjiezana/tent/essayist/NewEssayTemplate.jamon @@ -3,36 +3,65 @@ java.util.*; com.moandjiezana.tent.client.posts.*; com.moandjiezana.tent.client.posts.content.*; com.moandjiezana.tent.essayist.tent.*; +com.moandjiezana.essayist.posts.*; +<%args> +Post essay = null; +Post metadata = null; + +<%java> +final EssayContent content = essay != null ? essay.getContentAs(EssayContent.class) : null; +final EssayistMetadataContent metadataContent; +final String raw; +if (metadata != null) { + metadataContent = metadata.getContentAs(EssayistMetadataContent.class); + raw = metadataContent.getRaw(); +} else { + metadataContent = null; + raw = ""; +} + <&| Layout &> -
-
- -
- -
+
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ <%if essay != null %> + + + + +
+
-
- -
- -
-
-
- -
-
-
-
-
- -
- +
+ +
+
+
\ No newline at end of file diff --git a/src/main/templates/com/moandjiezana/tent/essayist/NonEditableEssayPage.jamon b/src/main/templates/com/moandjiezana/tent/essayist/NonEditableEssayPage.jamon new file mode 100644 index 0000000..1f1701a --- /dev/null +++ b/src/main/templates/com/moandjiezana/tent/essayist/NonEditableEssayPage.jamon @@ -0,0 +1,3 @@ +<&| Layout &> + Sorry, this essay cannot be edited. + \ No newline at end of file diff --git a/src/main/templates/com/moandjiezana/tent/essayist/partials/CommentForm.jamon b/src/main/templates/com/moandjiezana/tent/essayist/partials/CommentForm.jamon new file mode 100644 index 0000000..19338fc --- /dev/null +++ b/src/main/templates/com/moandjiezana/tent/essayist/partials/CommentForm.jamon @@ -0,0 +1,14 @@ +<%import> +com.moandjiezana.tent.client.posts.*; + +<%args> +Post essay; + +
+
+ +
+
+ +
+
diff --git a/src/main/templates/com/moandjiezana/tent/essayist/partials/Essay.jamon b/src/main/templates/com/moandjiezana/tent/essayist/partials/Essay.jamon new file mode 100644 index 0000000..ce97820 --- /dev/null +++ b/src/main/templates/com/moandjiezana/tent/essayist/partials/Essay.jamon @@ -0,0 +1,50 @@ +<%import> +com.moandjiezana.tent.client.posts.*; +com.moandjiezana.tent.client.posts.content.*; +com.moandjiezana.tent.essayist.tent.Entities; + +<%args> +Post essay; +String entityForUrl; +String entityName; +String essayId; +String formattedPublicationDate; +boolean display = false; + +<%java> +EssayContent essayContent = essay.getContentAs(EssayContent.class); +String essayUrl = jamonContext.routes.essay(essay); + +
+
+

<% Entities.essayTitle(essayContent) %> <% Character.valueOf('\u2693') %>

+

by <% entityName %> <% formattedPublicationDate %>

+

<% essayContent.getExcerpt() %>

+
+ <% jamonContext.csrf.stripScripts(essayContent.getBody()) #n %> +
+ <%if jamonContext.isLoggedIn() %> +
+
+ +
+
+ + + + +
+
+ +
+ <%if jamonContext.getCurrentUser().owns(essay) %> + +
+ + +
+ +
+ +
+
diff --git a/src/main/templates/com/moandjiezana/tent/essayist/partials/EssayLink.jamon b/src/main/templates/com/moandjiezana/tent/essayist/partials/EssayLink.jamon index d94fe6b..13e7071 100644 --- a/src/main/templates/com/moandjiezana/tent/essayist/partials/EssayLink.jamon +++ b/src/main/templates/com/moandjiezana/tent/essayist/partials/EssayLink.jamon @@ -4,6 +4,7 @@ com.moandjiezana.tent.client.users.Profile; com.moandjiezana.tent.client.posts.content.*; com.moandjiezana.tent.essayist.tent.*; java.text.SimpleDateFormat; +com.google.common.base.Strings; java.util.*; <%args> @@ -20,7 +21,7 @@ String authorPageUrl = jamonContext.contextPath + "/" + entityForUrl + "/essays" String formattedPublicationDate = dateFormat.format(new Date(essay.getPublishedAt() * 1000));
-
+
<%if showProfile %>
<%if profile != null && profile.getBasic() != null && profile.getBasic().getAvatarUrl() != null %> @@ -29,12 +30,13 @@ String formattedPublicationDate = dateFormat.format(new Date(essay.getPublishedA
-

<% essayContent.getTitle() %>

+

<% Entities.essayTitle(essayContent) %>

<%if showProfile %><% entityName %> <% formattedPublicationDate %>

<% essayContent.getExcerpt() %>

<%if jamonContext.getCurrentUser().owns(essay) %>
-
+ Edit +
@@ -43,14 +45,9 @@ String formattedPublicationDate = dateFormat.format(new Date(essay.getPublishedA
- diff --git a/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionList.jamon b/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionList.jamon new file mode 100644 index 0000000..4b7033e --- /dev/null +++ b/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionList.jamon @@ -0,0 +1,10 @@ +<%import> +com.moandjiezana.tent.client.posts.*; +java.util.*; + +<%args> +List reactions; + +<%for Post reaction : reactions %> + <& ReactionTemplate; reaction=reaction &> + diff --git a/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionTemplate.jamon b/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionTemplate.jamon new file mode 100644 index 0000000..084c3e5 --- /dev/null +++ b/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionTemplate.jamon @@ -0,0 +1,30 @@ +<%import> +com.moandjiezana.tent.client.posts.*; +com.moandjiezana.tent.client.posts.content.*; +com.moandjiezana.essayist.posts.*; +com.moandjiezana.tent.essayist.tent.*; +java.util.*; +java.text.SimpleDateFormat; + +<%args> +Post reaction; + +<%java> +final SimpleDateFormat dateTimeFormat = new SimpleDateFormat("dd MMMM yyyy HH:mm ZZZZ"); +String displayEntity = Entities.stripScheme(reaction.getEntity()); +String formattedDate = dateTimeFormat.format(new Date(reaction.getPublishedAt() * 1000)); + +
+<%if Post.Types.equalsIgnoreVersion(Post.Types.status("v0.1.0"), reaction.getType()) %> + <% displayEntity %> commented <% formattedDate %>
+

+ <% reaction.getContentAs(StatusContent.class).getText() %> +

+<%elseif Post.Types.equalsIgnoreVersion(Favorite.URI, reaction.getType()) %> + Favourited by <% displayEntity %> <% formattedDate %> +<%elseif Post.Types.equalsIgnoreVersion(Bookmark.URI, reaction.getType()) %> + Bookmarked by <% displayEntity %> <% formattedDate %> +<%elseif Post.Types.equalsIgnoreVersion(Post.Types.repost("v0.1.0"), reaction.getType()) %> + Reposted by <% displayEntity %> <% formattedDate %> + +
diff --git a/src/main/templates/com/moandjiezana/tent/essayist/partials/Reactions.jamon b/src/main/templates/com/moandjiezana/tent/essayist/partials/Reactions.jamon new file mode 100644 index 0000000..5f3bce7 --- /dev/null +++ b/src/main/templates/com/moandjiezana/tent/essayist/partials/Reactions.jamon @@ -0,0 +1,28 @@ +<%import> +com.moandjiezana.tent.client.posts.*; +com.moandjiezana.tent.client.posts.content.*; +com.moandjiezana.tent.essayist.tent.*; +java.util.*; + +<%args> +List reactions = null; +Post essay; +boolean autoLoad = false; + +
+
+

Reactions

+ <%if jamonContext.isLoggedIn() %> + <& CommentForm; essay=essay; &> + +
+
+
+
+
+ <%if reactions != null %> + <& ReactionList; reactions=reactions &> + +
+
+
diff --git a/src/main/webapp/assets/essayist.js b/src/main/webapp/assets/essayist.js index 8194ec4..43750cd 100644 --- a/src/main/webapp/assets/essayist.js +++ b/src/main/webapp/assets/essayist.js @@ -1,4 +1,4 @@ -function init() { +function initNavigation() { var essayContainers = document.querySelectorAll("[data-essay=container]"); var i; @@ -36,9 +36,37 @@ function init() { event.preventDefault(); event.stopPropagation(); + var container = null; + var currentTarget = event.target; + while (container === null) { + currentTarget = currentTarget.parentNode; + if (currentTarget.dataset.essay === "container") { + container = currentTarget; + } + } + + var scrollPosition = document.body.scrollTop; displaySection("essay", event.currentTarget); - history.pushState({ essayId: event.currentTarget.dataset.essayId, essayAuthor: event.currentTarget.dataset.essayAuthor }, "essay title", event.target.href); + var reactionsContainer = container.querySelector('[data-essay="reactions"]'); + fetchReactions(reactionsContainer); +// if (reactionsContainer.dataset.essayLoaded === "false") { +// var spinner = new Spinner().spin(container.querySelector('[data-essay="reactionsSpinner"]')); +// var xhr = new XMLHttpRequest(); +// xhr.onreadystatechange = function () { +// if (xhr.readyState === 4) { +// spinner.stop(); +// reactionsContainer.innerHTML = xhr.responseText; +// reactionsContainer.dataset.essayLoaded = "true"; +// } +// }; +// xhr.open("GET", E.contextPath + "/" + container.dataset.essayAuthor + "/essay/" + container.dataset.essayId + "/reactions", true); +// xhr.setRequestHeader("Accept", "text/html"); +// xhr.setRequestHeader("X-Essayist-Partial", "true"); +// xhr.send(); +// } + + history.pushState({ essayId: event.currentTarget.dataset.essayId, essayAuthor: event.currentTarget.dataset.essayAuthor, scrollPosition: scrollPosition }, "essay title", event.target.href); return false; }; @@ -47,6 +75,7 @@ function init() { var essayContainer; if (window.location.href.indexOf("/essays") > -1 || window.location.href.indexOf("/global") > -1 || window.location.href.indexOf("/read") > -1) { displaySection("list", document); + window.scrollTo(0); } else if (event.state !== null) { essayContainer = document.querySelector("[data-essay-author=\"" + event.state.essayAuthor + "\"][data-essay-id=\"" + event.state.essayId + "\"]"); displaySection("essay", essayContainer); @@ -60,6 +89,62 @@ function init() { window.addEventListener("popstate", essayPopStateHandler, false); } -if (history.pushState !== undefined) { - document.addEventListener("DOMContentLoaded", init, false); +function initPreview() { + var trigger = document.querySelector("#newEssayPreviewTrigger"); + + if (trigger === null) { + return; + } + + trigger.style.display = "inline"; + + var body = document.querySelector("textarea[name='body']"); + + var updatePreview = function () { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + document.querySelector("#preview").innerHTML = xhr.responseText; + } + }; + xhr.open("POST", E.contextPath + "/preview", true); + xhr.setRequestHeader("Accept", "text/html"); + xhr.setRequestHeader("X-Essayist-Partial", "true"); + xhr.send(body.value); + }; + + trigger.addEventListener("click", updatePreview, false); + body.addEventListener("keyup", _.debounce(updatePreview, 2000), false); +} + +function initReactions() { + var reactionsContainers = document.querySelectorAll('[data-essay="reactions"][data-essay-autoLoad="true"]'); + var i; + for (i = 0; i < reactionsContainers.length; i++) { + fetchReactions(reactionsContainers[i]); + } +} + +function fetchReactions(reactionsContainer) { + if (reactionsContainer.dataset.essayLoaded === "false") { + var spinner = new Spinner().spin(reactionsContainer); + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + spinner.stop(); + reactionsContainer.innerHTML = xhr.responseText; + reactionsContainer.dataset.essayLoaded = "true"; + } + }; + xhr.open("GET", E.contextPath + "/" + reactionsContainer.dataset.essayAuthor + "/essay/" + reactionsContainer.dataset.essayId + "/reactions", true); + xhr.setRequestHeader("Accept", "text/html"); + xhr.setRequestHeader("X-Essayist-Partial", "true"); + xhr.send(); + } +} + +if (history.pushState !== undefined && document.querySelector !== undefined && XMLHttpRequest !== undefined) { + document.addEventListener("DOMContentLoaded", initNavigation, false); + document.addEventListener("DOMContentLoaded", initPreview, false); + document.addEventListener("DOMContentLoaded", initReactions, false); } diff --git a/src/main/webapp/assets/spin/spin.min.js b/src/main/webapp/assets/spin/spin.min.js new file mode 100644 index 0000000..942980c --- /dev/null +++ b/src/main/webapp/assets/spin/spin.min.js @@ -0,0 +1,2 @@ +//fgnass.github.com/spin.js#v1.2.7 +!function(e,t,n){function o(e,n){var r=t.createElement(e||"div"),i;for(i in n)r[i]=n[i];return r}function u(e){for(var t=1,n=arguments.length;t>1):parseInt(n.left,10)+i)+"px",top:(n.top=="auto"?a.y-u.y+(e.offsetHeight>>1):parseInt(n.top,10)+i)+"px"})),r.setAttribute("aria-role","progressbar"),t.lines(r,t.opts);if(!s){var f=0,l=n.fps,h=l/n.speed,d=(1-n.opacity)/(h*n.trail/100),v=h/n.lines;(function m(){f++;for(var e=n.lines;e;e--){var i=Math.max(1-(f+e*v)%h*d,n.opacity);t.opacity(r,n.lines-e,i,n)}t.timeout=t.el&&setTimeout(m,~~(1e3/l))})()}return t},stop:function(){var e=this.el;return e&&(clearTimeout(this.timeout),e.parentNode&&e.parentNode.removeChild(e),this.el=n),this},lines:function(e,t){function i(e,r){return c(o(),{position:"absolute",width:t.length+t.width+"px",height:t.width+"px",background:e,boxShadow:r,transformOrigin:"left",transform:"rotate("+~~(360/t.lines*n+t.rotate)+"deg) translate("+t.radius+"px"+",0)",borderRadius:(t.corners*t.width>>1)+"px"})}var n=0,r;for(;n',t)}var t=c(o("group"),{behavior:"url(#default#VML)"});!l(t,"transform")&&t.adj?(a.addRule(".spin-vml","behavior:url(#default#VML)"),v.prototype.lines=function(t,n){function s(){return c(e("group",{coordsize:i+" "+i,coordorigin:-r+" "+ -r}),{width:i,height:i})}function l(t,i,o){u(a,u(c(s(),{rotation:360/n.lines*t+"deg",left:~~i}),u(c(e("roundrect",{arcsize:n.corners}),{width:r,height:n.width,left:n.radius,top:-n.width>>1,filter:o}),e("fill",{color:n.color,opacity:n.opacity}),e("stroke",{opacity:0}))))}var r=n.length+n.width,i=2*r,o=-(n.width+n.length)*2+"px",a=c(s(),{position:"absolute",top:o,left:o}),f;if(n.shadow)for(f=1;f<=n.lines;f++)l(f,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(f=1;f<=n.lines;f++)l(f);return u(t,a)},v.prototype.opacity=function(e,t,n,r){var i=e.firstChild;r=r.shadow&&r.lines||0,i&&t+r2;e==null&&(e=[]);if(d&&e.reduce===d)return r&&(t=T.bind(t,r)),i?e.reduce(t,n):e.reduce(t);N(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.reduceRight=T.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(v&&e.reduceRight===v)return r&&(t=T.bind(t,r)),arguments.length>2?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=T.keys(e);s=o.length}N(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.find=T.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},T.filter=T.select=function(e,t,n){var r=[];return e==null?r:m&&e.filter===m?e.filter(t,n):(N(e,function(e,i,s){t.call(n,e,i,s)&&(r[r.length]=e)}),r)},T.reject=function(e,t,n){var r=[];return e==null?r:(N(e,function(e,i,s){t.call(n,e,i,s)||(r[r.length]=e)}),r)},T.every=T.all=function(e,t,r){t||(t=T.identity);var i=!0;return e==null?i:g&&e.every===g?e.every(t,r):(N(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=T.some=T.any=function(e,t,r){t||(t=T.identity);var i=!1;return e==null?i:y&&e.some===y?e.some(t,r):(N(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};T.contains=T.include=function(e,t){var n=!1;return e==null?n:b&&e.indexOf===b?e.indexOf(t)!=-1:(n=C(e,function(e){return e===t}),n)},T.invoke=function(e,t){var n=u.call(arguments,2);return T.map(e,function(e){return(T.isFunction(t)?t:e[t]).apply(e,n)})},T.pluck=function(e,t){return T.map(e,function(e){return e[t]})},T.where=function(e,t){return T.isEmpty(t)?[]:T.filter(e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},T.max=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&T.isEmpty(e))return-Infinity;var r={computed:-Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},T.min=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&T.isEmpty(e))return Infinity;var r={computed:Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;or||n===void 0)return 1;if(n>>1;n.call(r,e[u])=0})})},T.difference=function(e){var t=a.apply(r,u.call(arguments,1));return T.filter(e,function(e){return!T.contains(t,e)})},T.zip=function(){var e=u.call(arguments),t=T.max(T.pluck(e,"length")),n=new Array(t);for(var r=0;r=0;n--)t=[e[n].apply(this,t)];return t[0]}},T.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},T.keys=S||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)T.has(e,n)&&(t[t.length]=n);return t},T.values=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push(e[n]);return t},T.pairs=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push([n,e[n]]);return t},T.invert=function(e){var t={};for(var n in e)T.has(e,n)&&(t[e[n]]=n);return t},T.functions=T.methods=function(e){var t=[];for(var n in e)T.isFunction(e[n])&&t.push(n);return t.sort()},T.extend=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]=t[n]}),e},T.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return N(n,function(n){n in e&&(t[n]=e[n])}),t},T.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)T.contains(n,i)||(t[i]=e[i]);return t},T.defaults=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]==null&&(e[n]=t[n])}),e},T.clone=function(e){return T.isObject(e)?T.isArray(e)?e.slice():T.extend({},e):e},T.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof T&&(e=e._wrapped),t instanceof T&&(t=t._wrapped);var i=l.call(e);if(i!=l.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,f=t.constructor;if(a!==f&&!(T.isFunction(a)&&a instanceof a&&T.isFunction(f)&&f instanceof f))return!1;for(var c in e)if(T.has(e,c)){o++;if(!(u=T.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(T.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};T.isEqual=function(e,t){return M(e,t,[],[])},T.isEmpty=function(e){if(e==null)return!0;if(T.isArray(e)||T.isString(e))return e.length===0;for(var t in e)if(T.has(e,t))return!1;return!0},T.isElement=function(e){return!!e&&e.nodeType===1},T.isArray=E||function(e){return l.call(e)=="[object Array]"},T.isObject=function(e){return e===Object(e)},N(["Arguments","Function","String","Number","Date","RegExp"],function(e){T["is"+e]=function(t){return l.call(t)=="[object "+e+"]"}}),T.isArguments(arguments)||(T.isArguments=function(e){return!!e&&!!T.has(e,"callee")}),typeof /./!="function"&&(T.isFunction=function(e){return typeof e=="function"}),T.isFinite=function(e){return T.isNumber(e)&&isFinite(e)},T.isNaN=function(e){return T.isNumber(e)&&e!=+e},T.isBoolean=function(e){return e===!0||e===!1||l.call(e)=="[object Boolean]"},T.isNull=function(e){return e===null},T.isUndefined=function(e){return e===void 0},T.has=function(e,t){return c.call(e,t)},T.noConflict=function(){return e._=t,this},T.identity=function(e){return e},T.times=function(e,t,n){for(var r=0;r":">",'"':""","'":"'","/":"/"}};_.unescape=T.invert(_.escape);var D={escape:new RegExp("["+T.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+T.keys(_.unescape).join("|")+")","g")};T.each(["escape","unescape"],function(e){T[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),T.result=function(e,t){if(e==null)return null;var n=e[t];return T.isFunction(n)?n.call(e):n},T.mixin=function(e){N(T.functions(e),function(t){var n=T[t]=e[t];T.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(T,e))}})};var P=0;T.uniqueId=function(e){var t=P++;return e?e+t:t},T.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;T.template=function(e,t,n){n=T.defaults({},n,T.templateSettings);var r=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),i=0,s="__p+='";e.replace(r,function(t,n,r,o,u){s+=e.slice(i,u).replace(j,function(e){return"\\"+B[e]}),s+=n?"'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":r?"'+\n((__t=("+r+"))==null?'':__t)+\n'":o?"';\n"+o+"\n__p+='":"",i=u+t.length}),s+="';\n",n.variable||(s="with(obj||{}){\n"+s+"}\n"),s="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";try{var o=new Function(n.variable||"obj","_",s)}catch(u){throw u.source=s,u}if(t)return o(t,T);var a=function(e){return o.call(this,e,T)};return a.source="function("+(n.variable||"obj")+"){\n"+s+"}",a},T.chain=function(e){return T(e).chain()};var F=function(e){return this._chain?T(e).chain():e};T.mixin(T),N(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];T.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),N(["concat","join","slice"],function(e){var t=r[e];T.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),T.extend(T.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); \ No newline at end of file diff --git a/src/test/java/com/moandjiezana/tent/essayist/security/CsrfTest.java b/src/test/java/com/moandjiezana/tent/essayist/security/CsrfTest.java index 910bae9..aa81000 100644 --- a/src/test/java/com/moandjiezana/tent/essayist/security/CsrfTest.java +++ b/src/test/java/com/moandjiezana/tent/essayist/security/CsrfTest.java @@ -48,4 +48,11 @@ public class CsrfTest { assertEquals(table, new Csrf().stripScripts(table)); } + + @Test + public void should_allow_emphasis() { + String emphasis = "emphasised"; + + assertEquals(emphasis, new Csrf().stripScripts(emphasis)); + } }