diff options
23 files changed, 877 insertions, 1164 deletions
diff --git a/juick-www/src/main/java/com/juick/www/controllers/Home.java b/juick-www/src/main/java/com/juick/www/controllers/Home.java deleted file mode 100644 index a8a9f1bf..00000000 --- a/juick-www/src/main/java/com/juick/www/controllers/Home.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -package com.juick.www.controllers; - -import com.juick.server.util.HttpNotFoundException; -import com.juick.server.util.UserUtils; -import com.juick.server.util.WebUtils; -import com.juick.service.MessagesService; -import com.juick.service.TagService; -import com.juick.service.UserService; -import com.juick.www.Utils; -import org.apache.commons.codec.CharEncoding; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.apache.commons.text.StringEscapeUtils; -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.CookieValue; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import org.springframework.web.util.UriComponents; -import ru.sape.Sape; - -import javax.inject.Inject; -import java.io.IOException; -import java.net.URLEncoder; -import java.util.List; -import java.util.stream.Collectors; - -/** - * - * @author Ugnich Anton - */ -@Controller -public class Home { - @Inject - private UserService userService; - @Inject - private MessagesService messagesService; - @Inject - private TagService tagService; - @Inject - private Sape sape; - - @GetMapping("/{anything}/**") - protected String parseAnyThing(@PathVariable String anything, - @RequestParam(required = false, defaultValue = "0") int before) throws IOException { - if (before == 0) { - boolean isPostNumber = WebUtils.isPostNumber(anything); - int messageId = isPostNumber ? - NumberUtils.toInt(anything) : 0; - - if (isPostNumber && anything.equals(Integer.toString(messageId))) { - if (messageId > 0) { - com.juick.User author = messagesService.getMessageAuthor(messageId); - - if (author != null) { - return "redirect:/" + author.getName() + "/" + anything; - } - } - } - com.juick.User user = userService.getUserByName(anything); - if (user.getUid() > 0) { - return "redirect:/" + user.getName() + "/"; - } - throw new HttpNotFoundException(); - } - com.juick.User user = userService.getUserByName(anything); - if (user.getUid() > 0) { - return "redirect:/" + user.getName() + "/?before=" + before; - } else { - throw new HttpNotFoundException(); - } - } - - @GetMapping("/") - protected String doGet( - @RequestParam(required = false) String tag, - @RequestParam(name = "show", required = false) String paramShow, - @RequestParam(name = "search", required = false) String paramSearch, - @RequestParam(name = "before", required = false, defaultValue = "0") Integer paramBefore, - @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, - ModelMap model) throws IOException { - if (tag != null) { - return "redirect:/tag/" + URLEncoder.encode(tag, CharEncoding.UTF_8); - } - com.juick.User visitor = UserUtils.getCurrentUser(); - - if (paramSearch != null && paramSearch.length() > 64) { - paramSearch = null; - } - - String title; - List<Integer> mids; - - if (paramSearch != null) { - title = "Поиск: " + StringEscapeUtils.escapeHtml4(paramSearch); - mids = messagesService.getSearch(Utils.encodeSphinx(paramSearch), paramBefore); - } else if (paramShow == null) { - if (visitor.getUid() > 0) { - title = "Популярные"; - mids = messagesService.getPopular(visitor.getUid(), paramBefore); - } else { - title = "Микроблоги Juick: популярные записи"; - mids = messagesService.getPopular(0, paramBefore); - } - - } else if (paramShow.equals("top")) { - return "redirect:/"; - } else if (paramShow.equals("my") && !visitor.isAnonymous()) { - title = "Моя лента"; - mids = messagesService.getMyFeed(visitor.getUid(), paramBefore, true); - } else if (paramShow.equals("private") && !visitor.isAnonymous()) { - title = "Приватные"; - mids = messagesService.getPrivate(visitor.getUid(), paramBefore); - } else if (paramShow.equals("discuss") && !visitor.isAnonymous()) { - title = "Обсуждения"; - mids = messagesService.getDiscussions(visitor.getUid(), paramBefore); - } else if (paramShow.equals("recommended") && !visitor.isAnonymous()) { - title = "Рекомендации"; - mids = messagesService.getRecommended(visitor.getUid(), paramBefore); - } else if (paramShow.equals("photos")) { - title = "Фотографии"; - mids = messagesService.getPhotos(visitor.getUid(), paramBefore); - } else if (paramShow.equals("all")) { - title = "Все сообщения"; - mids = messagesService.getAll(visitor.getUid(), paramBefore); - } else { - throw new HttpNotFoundException(); - } - - String head = StringUtils.EMPTY; - if (paramBefore > 0 || paramShow != null) { - head = "<meta name=\"robots\" content=\"noindex\"/>"; - } - model.addAttribute("title", title); - model.addAttribute("headers", head); - model.addAttribute("visitor", visitor); - model.addAttribute("noindex", !(paramShow == null && paramBefore == 0)); - List<com.juick.Message> msgs = messagesService.getMessages(mids); - - if (visitor.getUid() != 0) { - List<Integer> blUIDs = userService.checkBL(visitor.getUid(), - msgs.stream().map(m -> m.getUser().getUid()).collect(Collectors.toList())); - msgs.forEach(m -> m.ReadOnly |= blUIDs.contains(m.getUser().getUid())); - } - model.addAttribute("msgs", msgs); - model.addAttribute("tags", tagService.getPopularTags()); - model.addAttribute("headers", head); - model.addAttribute("showAdv", - paramShow == null && paramBefore == 0 && paramSearch == null && visitor.getUid() == 0); - if (mids.size() >= 20) { - String nextpage = "?before=" + mids.get(mids.size() - 1); - if (paramShow != null) { - nextpage += "&show=" + paramShow; - } - if (paramSearch != null) { - nextpage += "&search=" + URLEncoder.encode(paramSearch, CharEncoding.UTF_8); - } - model.addAttribute("nextpage", nextpage); - } - UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); - String queryString = builder.getQuery(); - String requestURI = builder.toUri().getPath(); - if (sape != null && visitor.getUid() == 0 && queryString == null) { - String links = sape.getPageLinks(requestURI, sapeCookie).render(); - model.addAttribute("links", links); - } - model.addAttribute("isModerator", visitor.getUid() == 3694); - return "views/index"; - } -} diff --git a/juick-www/src/main/java/com/juick/www/controllers/Messages.java b/juick-www/src/main/java/com/juick/www/controllers/Messages.java new file mode 100644 index 00000000..ce678902 --- /dev/null +++ b/juick-www/src/main/java/com/juick/www/controllers/Messages.java @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2008-2017, Juick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.juick.www.controllers; + +import com.juick.Tag; +import com.juick.formatters.PlainTextFormatter; +import com.juick.server.util.HttpForbiddenException; +import com.juick.server.util.HttpNotFoundException; +import com.juick.server.util.UserUtils; +import com.juick.server.util.WebUtils; +import com.juick.service.*; +import com.juick.www.Utils; +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponents; +import ru.sape.Sape; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; +import java.util.stream.Collectors; + +/** + * + * @author Ugnich Anton + */ +@Controller +public class Messages { + @Inject + private UserService userService; + @Inject + private TagService tagService; + @Inject + private MessagesService messagesService; + @Inject + private Sape sape; + @Inject + private PMQueriesService pmQueriesService; + @Inject + private CrosspostService crosspostService; + + void fillUserModel(ModelMap model, com.juick.User user, com.juick.User visitor) { + model.addAttribute("user", user); + model.addAttribute("isSubscribed", userService.isSubscribed(visitor.getUid(), user.getUid())); + model.addAttribute("isInBL", userService.isInBL(visitor.getUid(), user.getUid())); + model.addAttribute("isInBLAny", userService.isInBLAny(user.getUid(), visitor.getUid())); + model.addAttribute("statsIRead", userService.getStatsIRead(user.getUid())); + model.addAttribute("statsMyReaders", userService.getStatsMyReaders(user.getUid())); + model.addAttribute("statsMyBL", userService.getUserBLUsers(user.getUid()).size()); + model.addAttribute("statsMessages", userService.getStatsMessages(user.getUid())); + model.addAttribute("statsReplies", userService.getStatsReplies(user.getUid())); + model.addAttribute("iread", userService.getUserReadLeastPopular(user.getUid(), 8)); + model.addAttribute("tagStats", tagService.getUserTagStats(user.getUid()) + .stream().sorted((e1, e2) -> Integer.compare(e2.getUsageCount(), e1.getUsageCount())).limit(20).map(t -> t.getTag().getName()).collect(Collectors.toList())); + } + + @GetMapping("/{anything}/**") + protected String parseAnyThing(@PathVariable String anything, + @RequestParam(required = false, defaultValue = "0") int before) throws IOException { + if (before == 0) { + boolean isPostNumber = WebUtils.isPostNumber(anything); + int messageId = isPostNumber ? + NumberUtils.toInt(anything) : 0; + + if (isPostNumber && anything.equals(Integer.toString(messageId))) { + if (messageId > 0) { + com.juick.User author = messagesService.getMessageAuthor(messageId); + + if (author != null) { + return "redirect:/" + author.getName() + "/" + anything; + } + } + } + com.juick.User user = userService.getUserByName(anything); + if (user.getUid() > 0) { + return "redirect:/" + user.getName() + "/"; + } + throw new HttpNotFoundException(); + } + com.juick.User user = userService.getUserByName(anything); + if (user.getUid() > 0) { + return "redirect:/" + user.getName() + "/?before=" + before; + } else { + throw new HttpNotFoundException(); + } + } + + @GetMapping("/") + protected String doGet( + @RequestParam(required = false) String tag, + @RequestParam(name = "show", required = false) String paramShow, + @RequestParam(name = "search", required = false) String paramSearch, + @RequestParam(name = "before", required = false, defaultValue = "0") Integer paramBefore, + @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, + ModelMap model) throws IOException { + if (tag != null) { + return "redirect:/tag/" + URLEncoder.encode(tag, CharEncoding.UTF_8); + } + com.juick.User visitor = UserUtils.getCurrentUser(); + + if (paramSearch != null && paramSearch.length() > 64) { + paramSearch = null; + } + + String title; + List<Integer> mids; + + if (paramSearch != null) { + title = "Поиск: " + StringEscapeUtils.escapeHtml4(paramSearch); + mids = messagesService.getSearch(Utils.encodeSphinx(paramSearch), paramBefore); + } else if (paramShow == null) { + if (visitor.getUid() > 0) { + title = "Популярные"; + mids = messagesService.getPopular(visitor.getUid(), paramBefore); + } else { + title = "Микроблоги Juick: популярные записи"; + mids = messagesService.getPopular(0, paramBefore); + } + + } else if (paramShow.equals("top")) { + return "redirect:/"; + } else if (paramShow.equals("my") && !visitor.isAnonymous()) { + title = "Моя лента"; + mids = messagesService.getMyFeed(visitor.getUid(), paramBefore, true); + } else if (paramShow.equals("private") && !visitor.isAnonymous()) { + title = "Приватные"; + mids = messagesService.getPrivate(visitor.getUid(), paramBefore); + } else if (paramShow.equals("discuss") && !visitor.isAnonymous()) { + title = "Обсуждения"; + mids = messagesService.getDiscussions(visitor.getUid(), paramBefore); + } else if (paramShow.equals("recommended") && !visitor.isAnonymous()) { + title = "Рекомендации"; + mids = messagesService.getRecommended(visitor.getUid(), paramBefore); + } else if (paramShow.equals("photos")) { + title = "Фотографии"; + mids = messagesService.getPhotos(visitor.getUid(), paramBefore); + } else if (paramShow.equals("all")) { + title = "Все сообщения"; + mids = messagesService.getAll(visitor.getUid(), paramBefore); + } else { + throw new HttpNotFoundException(); + } + + String head = StringUtils.EMPTY; + if (paramBefore > 0 || paramShow != null) { + head = "<meta name=\"robots\" content=\"noindex\"/>"; + } + model.addAttribute("title", title); + model.addAttribute("headers", head); + model.addAttribute("visitor", visitor); + model.addAttribute("noindex", !(paramShow == null && paramBefore == 0)); + List<com.juick.Message> msgs = messagesService.getMessages(mids); + + if (visitor.getUid() != 0) { + fillUserModel(model, visitor, visitor); + List<Integer> blUIDs = userService.checkBL(visitor.getUid(), + msgs.stream().map(m -> m.getUser().getUid()).collect(Collectors.toList())); + msgs.forEach(m -> m.ReadOnly |= blUIDs.contains(m.getUser().getUid())); + } + model.addAttribute("msgs", msgs); + model.addAttribute("tags", tagService.getPopularTags()); + model.addAttribute("headers", head); + model.addAttribute("showAdv", + paramShow == null && paramBefore == 0 && paramSearch == null && visitor.getUid() == 0); + if (mids.size() >= 20) { + String nextpage = "?before=" + mids.get(mids.size() - 1); + if (paramShow != null) { + nextpage += "&show=" + paramShow; + } + if (paramSearch != null) { + nextpage += "&search=" + URLEncoder.encode(paramSearch, CharEncoding.UTF_8); + } + model.addAttribute("nextpage", nextpage); + } + UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); + String queryString = builder.getQuery(); + String requestURI = builder.toUri().getPath(); + if (sape != null && visitor.getUid() == 0 && queryString == null) { + String links = sape.getPageLinks(requestURI, sapeCookie).render(); + model.addAttribute("links", links); + } + model.addAttribute("isModerator", visitor.getUid() == 3694); + return "views/index"; + } + + @GetMapping("/{uname}/") + protected String doGetBlog( + @RequestParam(required = false, name = "show") String paramShow, + @RequestParam(required = false, name = "tag") String paramTagStr, + @RequestParam(required = false, name = "search") String paramSearch, + @PathVariable String uname, + @RequestParam(required = false, defaultValue = "0") Integer before, + @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, + ModelMap model) throws IOException { + com.juick.User user = userService.getUserByName(uname); + com.juick.User visitor = UserUtils.getCurrentUser(); + if (user.isBanned()) { + throw new HttpNotFoundException(); + } + + List<Integer> mids; + + com.juick.Tag paramTag = null; + if (paramTagStr != null) { + if (paramTagStr.length() < 64) { + paramTag = tagService.getTag(paramTagStr, false); + } + if (paramTag == null) { + throw new HttpNotFoundException(); + } else if (!paramTag.getName().equals(paramTagStr)) { + String url = user.getName() + "/?tag=" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8); + return "redirect:/" + url; + } + } + if (paramSearch != null && paramSearch.length() > 64) { + paramSearch = null; + } + + int privacy = 0; + if (visitor.getUid() > 0) { + if (user.getUid() == visitor.getUid() || visitor.getUid() == 1) { + privacy = -3; + } else if (userService.isInWL(user.getUid(), visitor.getUid())) { + privacy = -2; + } + } + + String title; + if (paramShow == null) { + if (paramTag != null) { + title = "Блог " + user.getName() + ": *" + StringEscapeUtils.escapeHtml4(paramTag.getName()); + mids = messagesService.getUserTag(user.getUid(), paramTag.TID, privacy, before); + } else if (paramSearch != null) { + title = "Блог " + user.getName() + ": " + StringEscapeUtils.escapeHtml4(paramSearch); + mids = messagesService.getUserSearch(user.getUid(), Utils.encodeSphinx(paramSearch), privacy, before); + } else { + title = "Блог " + user.getName(); + mids = messagesService.getUserBlog(user.getUid(), privacy, before); + } + } else if (paramShow.equals("recomm")) { + title = "Рекомендации " + user.getName(); + mids = messagesService.getUserRecommendations(user.getUid(), before); + } else if (paramShow.equals("photos")) { + title = "Фотографии " + user.getName(); + mids = messagesService.getUserPhotos(user.getUid(), privacy, before); + } else { + throw new HttpNotFoundException(); + } + + String head = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"@" + + user.getName() + "\" href=\"//rss.juick.com/" + user.getName() + "/blog\"/>"; + if (paramTag != null && tagService.getTagNoIndex(paramTag.TID)) { + head += "<meta name=\"robots\" content=\"noindex,nofollow\"/>"; + } else if (before > 0 || paramShow != null) { + head += "<meta name=\"robots\" content=\"noindex\"/>"; + } + model.addAttribute("pageUrl", "http://juick.com/" + user.getName()); + model.addAttribute("title", title); + model.addAttribute("headers", head); + model.addAttribute("visitor", visitor); + model.addAttribute("noindex", paramShow == null && before == 0); + fillUserModel(model, user, visitor); + model.addAttribute("paramTag", paramTag); + List<com.juick.Message> msgs = messagesService.getMessages(mids); + + if (visitor.getUid() != 0) { + List<Integer> blUIDs = userService.checkBL(visitor.getUid(), + msgs.stream().map(m -> m.getUser().getUid()).collect(Collectors.toList())); + msgs.forEach(m -> m.ReadOnly |= blUIDs.contains(m.getUser().getUid())); + } + model.addAttribute("msgs", msgs); + model.addAttribute("headers", head); + model.addAttribute("showAdv", + paramShow == null && before == 0 && paramSearch == null && visitor.getUid() == 0); + if (mids.size() >= 20) { + String nextpage = "?before=" + mids.get(mids.size() - 1); + if (paramShow != null) { + nextpage += "&show=" + paramShow; + } + if (paramSearch != null) { + nextpage += "&search=" + URLEncoder.encode(paramSearch, CharEncoding.UTF_8); + } + if (paramTag != null) { + nextpage += "&tag=" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8); + } + model.addAttribute("nextpage", nextpage); + } + UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); + String queryString = builder.getQuery(); + String requestURI = builder.toUri().getPath(); + if (sape != null && visitor.getUid() == 0 && queryString == null) { + String links = sape.getPageLinks(requestURI, sapeCookie).render(); + model.addAttribute("links", links); + } + model.addAttribute("isModerator", visitor.getUid() == 3694); + return "views/blog"; + } + + @GetMapping("/{uname}/tags") + protected String doGetTags(@PathVariable String uname, ModelMap model) throws IOException { + com.juick.User user = userService.getUserByName(uname); + com.juick.User visitor = UserUtils.getCurrentUser(); + if (visitor.isBanned()) { + throw new HttpNotFoundException(); + } + + model.addAttribute("title", "Теги " + user.getName()); + model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex,nofollow\"/>"); + model.addAttribute("visitor", visitor); + fillUserModel(model, user, visitor); + model.addAttribute("tags", tagService.getUserTagStats(user.getUid()).stream() + .sorted((e1, e2) -> Integer.compare(e2.getUsageCount(), e1.getUsageCount())).map(t -> t.getTag().getName()).collect(Collectors.toList())); + + return "views/blog_tags"; + } + + @GetMapping("/{uname}/friends") + protected String doGetFriends(@PathVariable String uname, ModelMap model) throws IOException { + com.juick.User user = userService.getUserByName(uname); + com.juick.User visitor = UserUtils.getCurrentUser(); + if (visitor.isBanned()) { + throw new HttpNotFoundException(); + } + model.addAttribute("title", "Подписки " + user.getName()); + model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); + model.addAttribute("visitor", visitor); + fillUserModel(model, user, visitor); + model.addAttribute("users", userService.getUserFriends(user.getUid())); + + return "views/users"; + } + + @GetMapping("/{uname}/readers") + protected String doGetReaders(@PathVariable String uname, ModelMap model) throws IOException { + com.juick.User user = userService.getUserByName(uname); + com.juick.User visitor = UserUtils.getCurrentUser(); + if (visitor.isBanned()) { + throw new HttpForbiddenException(); + } + model.addAttribute("title", "Читатели " + user.getName()); + model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); + model.addAttribute("visitor", visitor); + fillUserModel(model, user, visitor); + model.addAttribute("users", userService.getUserReaders(user.getUid())); + + return "views/users"; + } + + @GetMapping("/{uname}/bl") + protected String doGetBL(@PathVariable String uname, ModelMap model) throws IOException { + com.juick.User user = userService.getUserByName(uname); + com.juick.User visitor = UserUtils.getCurrentUser(); + if (visitor.isBanned() || visitor.getUid() != user.getUid()) { + throw new HttpForbiddenException(); + } + model.addAttribute("title", "Черный список " + user.getName()); + model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); + model.addAttribute("visitor", visitor); + fillUserModel(model, user, visitor); + model.addAttribute("users", userService.getUserBLUsers(user.getUid())); + + return "views/users"; + } + @GetMapping("/tag/{tagName}") + protected String tagAction(HttpServletRequest request, + @PathVariable String tagName, + @RequestParam(required = false, defaultValue = "0") int before, + ModelMap model) throws IOException { + com.juick.User visitor = UserUtils.getCurrentUser(); + + String paramTagStr = StringEscapeUtils.unescapeHtml4(tagName); + com.juick.Tag paramTag = tagService.getTag(paramTagStr, false); + if (paramTag == null) { + throw new HttpNotFoundException(); + } else if (paramTag.SynonymID > 0 && paramTag.TID != paramTag.SynonymID) { + com.juick.Tag synTag = tagService.getTag(paramTag.SynonymID); + String url = "/tag/" + URLEncoder.encode(StringEscapeUtils.escapeHtml4(synTag.getName()), CharEncoding.UTF_8); + if (request.getQueryString() != null) { + url += "?" + request.getQueryString(); + } + return "redirect:" + url; + } else if (!paramTag.getName().equals(paramTagStr)) { + String url = "/tag/" + URLEncoder.encode(StringEscapeUtils.escapeHtml4(paramTag.getName()), CharEncoding.UTF_8); + if (request.getQueryString() != null) { + url += "?" + request.getQueryString(); + } + return "redirect:" + url; + } + + int visitor_uid = visitor.getUid(); + + String title = "*" + StringEscapeUtils.escapeHtml4(paramTag.getName()); + model.addAttribute("title", title); + List<Integer> mids = messagesService.getTag(paramTag.TID, visitor_uid, before, (visitor_uid == 0) ? 40 : 20); + + String head = StringUtils.EMPTY; + if (tagService.getTagNoIndex(paramTag.TID)) { + head = "<meta name=\"robots\" content=\"noindex,nofollow\"/>"; + } else if (before > 0 || mids.size() < 5) { + head = "<meta name=\"robots\" content=\"noindex\"/>"; + } + model.addAttribute("headers", head); + model.addAttribute("visitor", visitor); + + List<com.juick.Message> msgs = messagesService.getMessages(mids); + + if (visitor.getUid() != 0) { + List<Integer> blUIDs = userService.checkBL(visitor.getUid(), + msgs.stream().map(m -> m.getUser().getUid()).collect(Collectors.toList())); + msgs.forEach(m -> m.ReadOnly |= blUIDs.contains(m.getUser().getUid())); + fillUserModel(model, visitor, visitor); + } + model.addAttribute("msgs", msgs); + model.addAttribute("tags", tagService.getPopularTags()); + model.addAttribute("headers", head); + model.addAttribute("noindex", before > 0); + model.addAttribute("showAdv",before == 0 && visitor.getUid() == 0); + model.addAttribute("isModerator", visitor.getUid() == 3694); + if (mids.size() >= 20) { + String nextpage = "/tag/" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8) + "?before=" + mids.get(mids.size() - 1); + model.addAttribute("nextpage", nextpage); + } + return "views/index"; + } + @GetMapping("/pm/inbox") + protected String doGetInbox(ModelMap model) { + com.juick.User visitor = UserUtils.getCurrentUser(); + if (visitor.getUid() == 0) { + return "redirect:/login"; + } + String title = "PM: Inbox"; + List<com.juick.Message> msgs = pmQueriesService.getLastPMInbox(visitor.getUid()); + fillUserModel(model, visitor, visitor); + model.addAttribute("title", title); + model.addAttribute("visitor", visitor); + model.addAttribute("msgs", msgs); + model.addAttribute("tags", tagService.getPopularTags()); + return "views/pm_inbox"; + } + + @GetMapping("/pm/sent") + protected String doGetSent(@RequestParam(required = false) String uname, + ModelMap model) { + com.juick.User visitor = UserUtils.getCurrentUser(); + if (visitor.getUid() == 0) { + return "redirect:/login"; + } + String title = "PM: Sent"; + List<com.juick.Message> msgs = pmQueriesService.getLastPMSent(visitor.getUid()); + + if (WebUtils.isNotUserName(uname)) { + uname = StringUtils.EMPTY; + } + fillUserModel(model, visitor, visitor); + model.addAttribute("title", title); + model.addAttribute("visitor", visitor); + model.addAttribute("msgs", msgs); + model.addAttribute("tags", tagService.getPopularTags()); + model.addAttribute("uname", uname); + return "views/pm_sent"; + } + @GetMapping("/{uname}/{mid}") + protected String threadAction(ModelMap model, + @PathVariable String uname, + @PathVariable int mid, + @RequestParam(required = false, value = "view") String paramView) throws IOException { + com.juick.User visitor = UserUtils.getCurrentUser(); + + if (!messagesService.canViewThread(mid, visitor.getUid())) { + throw new HttpForbiddenException(); + } + + com.juick.Message msg = messagesService.getMessage(mid); + + if (msg == null || msg.getUser().isBanned()) { + throw new HttpNotFoundException(); + } + + com.juick.User user = userService.getUserByName(uname); + if (user.getUid() == 0 || !msg.getUser().equals(user)) { + return String.format("redirect:/%s/%d", msg.getUser().getName(), mid); + } + msg.VisitorCanComment = visitor.getUid() > 0; + if (visitor.getUid() > 0) { + fillUserModel(model, user, visitor); + boolean isMsgAuthor = visitor.getUid() == msg.getUser().getUid(); + boolean isInBL = userService.isInBL(msg.getUser().getUid(), visitor.getUid()); + msg.VisitorCanComment = isMsgAuthor || !(msg.ReadOnly || isInBL); + } + model.addAttribute("msg", msg); + + boolean listview = false; + if (paramView != null) { + if (paramView.equals("list")) { + listview = true; + if (visitor.getUid() > 0) { + userService.setUserOptionInt(visitor.getUid(), "repliesview", 1); + } + } else if (paramView.equals("tree") && visitor.getUid() > 0) { + userService.setUserOptionInt(visitor.getUid(), "repliesview", 0); + } + } else if (visitor.getUid() > 0 && userService.getUserOptionInt(visitor.getUid(), "repliesview", 0) == 1) { + listview = true; + } + model.addAttribute("listview", listview); + String title = msg.getUser().getName() + ": " + msg.getTagsString(); + + model.addAttribute("title", title); + model.addAttribute("visitor", visitor); + String headers = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"@" + msg.getUser().getName() + "\" href=\"//rss.juick.com/" + msg.getUser().getName() + "/blog\"/>"; + String pageUrl = "https://juick.com/" + msg.getUser().getName() + "/" + msg.getMid(); + if (paramView != null) { + headers += "<link rel=\"canonical\" href=\"" + pageUrl + "\"/>"; + } + if (msg.Hidden) { + headers += "<meta name=\"robots\" content=\"noindex\"/>"; + } + String cardType = StringUtils.isNotEmpty(msg.getAttachmentType()) ? "summary_large_image" : "summary"; + String msgImage = StringUtils.isNotEmpty(msg.getAttachmentType()) ? msg.getAttachment().getMedium().getUrl() + : "https://i.juick.com/a/" + msg.getUser().getUid() + ".png"; + model.addAttribute("ogtype", "article"); + String cardDescription = StringEscapeUtils.escapeHtml4(PlainTextFormatter.formatTwitterCard(msg)); + headers += "<meta name=\"twitter:card\" content=\"" + cardType + "\" />\n" + + "<meta name=\"twitter:site\" content=\"@juick\" />\n" + + "<meta property=\"og:url\" content=\"" + pageUrl + "\" />\n" + + "<meta property=\"og:title\" content=\"" + msg.getUser().getName() + " at Juick\" />\n" + + "<meta property=\"og:description\" content=\"" + cardDescription + "\" />\n" + + "<meta name=\"Description\" content=\"" + cardDescription + "\" />\n" + + "<meta property=\"og:image\" content=\"" + msgImage + "\" />"; + String twitterName = crosspostService.getTwitterName(msg.getUser().getUid()); + if (StringUtils.isNotEmpty(twitterName)) { + headers += "<meta name=\"twitter:creator\" content=\"@" + twitterName + "\" />\n"; + } + if (msg.getTags().size() > 0) { + headers += "<meta name=\"Keywords\" content=\"" + msg.getTags().stream().map(Tag::getName) + .collect(Collectors.joining(", ")) + "\" />\n"; + } + model.addAttribute("headers", headers); + model.addAttribute("isModerator", visitor.getUid() == 3694); + model.addAttribute("visitorSubscribed", messagesService.isSubscribed(visitor.getUid(), msg.getMid())); + model.addAttribute("visitorInBL", userService.isInBL(msg.getUser().getUid(), visitor.getUid())); + model.addAttribute("recomm", messagesService.getMessageRecommendations(msg.getMid())); + List<com.juick.Message> replies = messagesService.getReplies(msg.getMid()); + + List<Integer> blUIDs = new ArrayList<>(); + for (int i = 0; i < replies.size(); i++) { + com.juick.Message reply = replies.get(i); + if (reply.getUser().getUid() != msg.getUser().getUid() + && !blUIDs.contains(reply.getUser().getUid())) { + blUIDs.add(reply.getUser().getUid()); + } + if (reply.getReplyto() > 0) { + boolean added = false; + for (int n = 0; n < replies.size(); n++) { + if (replies.get(n).getRid() == reply.getReplyto()) { + replies.get(n).childs.add(reply); + added = true; + break; + } + } + if (!added) { + reply.setReplyto(0); + } + } + + reply.VisitorCanComment = visitor.getUid() > 0; + if (visitor.getUid() > 0) { + boolean isMsgAuthor = visitor.getUid() == msg.getUser().getUid(); + boolean isReplyAuthor = visitor.getUid() == reply.getUser().getUid(); + BooleanSupplier isInBL2 = () -> userService.checkBL(visitor.getUid(), blUIDs).contains(reply.getUser().getUid()); + reply.VisitorCanComment = isMsgAuthor || (!msg.ReadOnly && (isReplyAuthor || !isInBL2.getAsBoolean())); + } + } + + boolean foldable = false; + if (replies.size() > 10) { + for (int i = 0; i < replies.size() - 1; i++) { + if (replies.get(i).getChildsCount() > 1) { + foldable = true; + break; + } + } + } + model.addAttribute("replies", replies); + model.addAttribute("foldable", foldable); + return "views/thread"; + } + + // when message id is not fit to int + @ExceptionHandler(NumberFormatException.class) + public ResponseEntity<String> notFoundAction() { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } +} diff --git a/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java b/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java index b0a2b4af..a87c7136 100644 --- a/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java +++ b/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java @@ -62,6 +62,8 @@ public class NewMessage { @Inject private CrosspostService crosspostService; @Inject + private PMQueriesService pmQueriesService; + @Inject private WebApp webApp; private static final Logger logger = LoggerFactory.getLogger(NewMessage.class); @@ -332,4 +334,107 @@ public class NewMessage { throw new HttpBadRequestException(); } } + @PostMapping("/pm/send") + public String doPostPM(@RequestParam(name = "uname", required = false) String unameParam, + @RequestParam String body) throws IOException { + com.juick.User visitor = UserUtils.getCurrentUser(); + if (visitor.getUid() == 0 || visitor.isBanned()) { + throw new HttpForbiddenException(); + } + String uname = unameParam; + if (uname.startsWith("@")) { + uname = uname.substring(1); + } + int uid = 0; + if (WebUtils.isUserName(uname)) { + uid = userService.getUIDbyName(uname); + } + + if (uid == 0 || body.length() > 10240) { + throw new HttpBadRequestException(); + } + + if (userService.isInBLAny(uid, visitor.getUid())) { + throw new HttpForbiddenException(); + } + + if (pmQueriesService.createPM(visitor.getUid(), uid, body)) { + if (webApp.getXmpp() != null) { + Message msg = new Message(); + msg.setFrom(Jid.of("juick@juick.com")); + msg.setTo(Jid.of(String.format("%d@push.juick.com", uid))); + com.juick.Message jmsg = new com.juick.Message(); + jmsg.setUser(visitor); + jmsg.setText(body); + msg.addExtension(jmsg); + webApp.getXmpp().send(msg); + + msg.setTo(Jid.of(String.format("%d@ws.juick.com", uid))); + webApp.getXmpp().send(msg); + + List<String> jids = userService.getJIDsbyUID(uid); + for (String jid : jids) { + Message mm = new Message(); + mm.setTo(Jid.of(jid)); + mm.setType(Message.Type.CHAT); + if (pmQueriesService.havePMinRoster(visitor.getUid(), jid)) { + mm.setFrom(Jid.of(jmsg.getUser().getName(), "juick.com", "Juick")); + mm.setBody(body); + } else { + mm.setFrom(Jid.of("juick", "juick.com", "Juick")); + mm.setBody("Private message from @" + jmsg.getUser().getName() + ":\n" + body); + } + webApp.getXmpp().send(mm); + } + } else { + logger.warn("XMPP unavailable"); + } + return "redirect:/pm/sent"; + } else { + throw new HttpBadRequestException(); + } + } + @PostMapping("/post2") + public String doPostMessage(@RequestParam(name = "body") String bodyParam, + @RequestParam(required = false) String img, + @RequestParam(required = false) String referer, + @RequestParam(required = false) MultipartFile attach) throws IOException { + + com.juick.User visitor = UserUtils.getCurrentUser(); + if (visitor.getUid() == 0 || visitor.isBanned()) { + throw new HttpForbiddenException(); + } + String body = bodyParam.replace("\r", StringUtils.EMPTY); + + String attachmentFName = HttpUtils.receiveMultiPartFile(attach, webApp.getTmpDir()); + + if (StringUtils.isBlank(attachmentFName) && img != null && img.length() > 10) { + try { + URL imgUrl = new URL(img); + attachmentFName = HttpUtils.downloadImage(imgUrl, webApp.getTmpDir()); + } catch (Exception e) { + logger.error("DOWNLOAD ERROR", e); + throw new HttpBadRequestException(); + } + } + Message msg = new Message(); + msg.setType(Message.Type.CHAT); + msg.setFrom(Jid.of(String.valueOf(visitor.getUid()), "uid.juick.com", "perl")); + msg.setTo(Jid.of("juick@juick.com/Juick")); + msg.setBody(body); + try { + if (StringUtils.isNotEmpty(attachmentFName)) { + String attachmentUrl = String.format("juick://%s", attachmentFName); + msg.addExtension(new OobX(new URI(attachmentUrl), "!!!!Juick!!")); + } + webApp.getXmpp().sendMessage(msg); + } catch (URISyntaxException e1) { + logger.warn("attachment error", e1); + } + if (StringUtils.isBlank(referer) || referer.substring(0, 21).equals("http://juick.com/post") + || referer.substring(0, 22).equals("https://juick.com/post")) { + return "redirect:/?show=my"; + } + return "redirect:" + referer; + } } diff --git a/juick-www/src/main/java/com/juick/www/controllers/PM.java b/juick-www/src/main/java/com/juick/www/controllers/PM.java deleted file mode 100644 index fda3501b..00000000 --- a/juick-www/src/main/java/com/juick/www/controllers/PM.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -package com.juick.www.controllers; - -import com.juick.server.util.HttpBadRequestException; -import com.juick.server.util.HttpForbiddenException; -import com.juick.server.util.UserUtils; -import com.juick.server.util.WebUtils; -import com.juick.service.PMQueriesService; -import com.juick.service.TagService; -import com.juick.service.UserService; -import com.juick.www.WebApp; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import rocks.xmpp.addr.Jid; -import rocks.xmpp.core.stanza.model.Message; - -import javax.inject.Inject; -import java.io.IOException; -import java.util.List; - -/** - * - * @author Ugnich Anton - */ -@Controller -public class PM { - private static final Logger logger = LoggerFactory.getLogger(PM.class); - - @Inject - private PMQueriesService pmQueriesService; - @Inject - private TagService tagService; - @Inject - private UserService userService; - @Inject - private WebApp webApp; - - @GetMapping("/pm/inbox") - protected String doGetInbox(ModelMap model) { - com.juick.User visitor = UserUtils.getCurrentUser(); - if (visitor.getUid() == 0) { - return "redirect:/login"; - } - String title = "PM: Inbox"; - List<com.juick.Message> msgs = pmQueriesService.getLastPMInbox(visitor.getUid()); - model.addAttribute("title", title); - model.addAttribute("visitor", visitor); - model.addAttribute("msgs", msgs); - model.addAttribute("tags", tagService.getPopularTags()); - return "views/pm_inbox"; - } - - @GetMapping("/pm/sent") - protected String doGetSent(@RequestParam(required = false) String uname, - ModelMap model) { - com.juick.User visitor = UserUtils.getCurrentUser(); - if (visitor.getUid() == 0) { - return "redirect:/login"; - } - String title = "PM: Sent"; - List<com.juick.Message> msgs = pmQueriesService.getLastPMSent(visitor.getUid()); - - if (WebUtils.isNotUserName(uname)) { - uname = StringUtils.EMPTY; - } - - model.addAttribute("title", title); - model.addAttribute("visitor", visitor); - model.addAttribute("msgs", msgs); - model.addAttribute("tags", tagService.getPopularTags()); - model.addAttribute("uname", uname); - return "views/pm_sent"; - } - - @PostMapping("/pm/send") - public String doPostPM(@RequestParam(name = "uname", required = false) String unameParam, - @RequestParam String body) throws IOException { - com.juick.User visitor = UserUtils.getCurrentUser(); - if (visitor.getUid() == 0 || visitor.isBanned()) { - throw new HttpForbiddenException(); - } - String uname = unameParam; - if (uname.startsWith("@")) { - uname = uname.substring(1); - } - int uid = 0; - if (WebUtils.isUserName(uname)) { - uid = userService.getUIDbyName(uname); - } - - if (uid == 0 || body.length() > 10240) { - throw new HttpBadRequestException(); - } - - if (userService.isInBLAny(uid, visitor.getUid())) { - throw new HttpForbiddenException(); - } - - if (pmQueriesService.createPM(visitor.getUid(), uid, body)) { - if (webApp.getXmpp() != null) { - Message msg = new Message(); - msg.setFrom(Jid.of("juick@juick.com")); - msg.setTo(Jid.of(String.format("%d@push.juick.com", uid))); - com.juick.Message jmsg = new com.juick.Message(); - jmsg.setUser(visitor); - jmsg.setText(body); - msg.addExtension(jmsg); - webApp.getXmpp().send(msg); - - msg.setTo(Jid.of(String.format("%d@ws.juick.com", uid))); - webApp.getXmpp().send(msg); - - List<String> jids = userService.getJIDsbyUID(uid); - for (String jid : jids) { - Message mm = new Message(); - mm.setTo(Jid.of(jid)); - mm.setType(Message.Type.CHAT); - if (pmQueriesService.havePMinRoster(visitor.getUid(), jid)) { - mm.setFrom(Jid.of(jmsg.getUser().getName(), "juick.com", "Juick")); - mm.setBody(body); - } else { - mm.setFrom(Jid.of("juick", "juick.com", "Juick")); - mm.setBody("Private message from @" + jmsg.getUser().getName() + ":\n" + body); - } - webApp.getXmpp().send(mm); - } - } else { - logger.warn("XMPP unavailable"); - } - return "redirect:/pm/sent"; - } else { - throw new HttpBadRequestException(); - } - } -} diff --git a/juick-www/src/main/java/com/juick/www/controllers/Tags.java b/juick-www/src/main/java/com/juick/www/controllers/Tags.java deleted file mode 100644 index 6a22216f..00000000 --- a/juick-www/src/main/java/com/juick/www/controllers/Tags.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -package com.juick.www.controllers; - -import com.juick.server.util.HttpNotFoundException; -import com.juick.server.util.UserUtils; -import com.juick.service.MessagesService; -import com.juick.service.TagService; -import com.juick.service.UserService; -import org.apache.commons.codec.CharEncoding; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.text.StringEscapeUtils; -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; - -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.net.URLEncoder; -import java.util.List; -import java.util.stream.Collectors; - -/** - * @author Ugnich Anton - */ -@Controller -public class Tags { - @Inject - private MessagesService messagesService; - @Inject - private TagService tagService; - @Inject - private UserService userService; - - @GetMapping("/tag/{tagName}") - protected String tagAction(HttpServletRequest request, - @PathVariable String tagName, - @RequestParam(required = false, defaultValue = "0") int before, - ModelMap model) throws IOException { - com.juick.User visitor = UserUtils.getCurrentUser(); - - String paramTagStr = StringEscapeUtils.unescapeHtml4(tagName); - com.juick.Tag paramTag = tagService.getTag(paramTagStr, false); - if (paramTag == null) { - throw new HttpNotFoundException(); - } else if (paramTag.SynonymID > 0 && paramTag.TID != paramTag.SynonymID) { - com.juick.Tag synTag = tagService.getTag(paramTag.SynonymID); - String url = "/tag/" + URLEncoder.encode(StringEscapeUtils.escapeHtml4(synTag.getName()), CharEncoding.UTF_8); - if (request.getQueryString() != null) { - url += "?" + request.getQueryString(); - } - return "redirect:" + url; - } else if (!paramTag.getName().equals(paramTagStr)) { - String url = "/tag/" + URLEncoder.encode(StringEscapeUtils.escapeHtml4(paramTag.getName()), CharEncoding.UTF_8); - if (request.getQueryString() != null) { - url += "?" + request.getQueryString(); - } - return "redirect:" + url; - } - - int visitor_uid = visitor.getUid(); - - String title = "*" + StringEscapeUtils.escapeHtml4(paramTag.getName()); - model.addAttribute("title", title); - List<Integer> mids = messagesService.getTag(paramTag.TID, visitor_uid, before, (visitor_uid == 0) ? 40 : 20); - - String head = StringUtils.EMPTY; - if (tagService.getTagNoIndex(paramTag.TID)) { - head = "<meta name=\"robots\" content=\"noindex,nofollow\"/>"; - } else if (before > 0 || mids.size() < 5) { - head = "<meta name=\"robots\" content=\"noindex\"/>"; - } - model.addAttribute("headers", head); - model.addAttribute("visitor", visitor); - - List<com.juick.Message> msgs = messagesService.getMessages(mids); - - if (visitor.getUid() != 0) { - List<Integer> blUIDs = userService.checkBL(visitor.getUid(), - msgs.stream().map(m -> m.getUser().getUid()).collect(Collectors.toList())); - msgs.forEach(m -> m.ReadOnly |= blUIDs.contains(m.getUser().getUid())); - } - model.addAttribute("msgs", msgs); - model.addAttribute("tags", tagService.getPopularTags()); - model.addAttribute("headers", head); - model.addAttribute("noindex", before > 0); - model.addAttribute("showAdv",before == 0 && visitor.getUid() == 0); - model.addAttribute("isModerator", visitor.getUid() == 3694); - if (mids.size() >= 20) { - String nextpage = "/tag/" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8) + "?before=" + mids.get(mids.size() - 1); - model.addAttribute("nextpage", nextpage); - } - return "views/index"; - } -} diff --git a/juick-www/src/main/java/com/juick/www/controllers/User.java b/juick-www/src/main/java/com/juick/www/controllers/User.java deleted file mode 100644 index d42d3ed3..00000000 --- a/juick-www/src/main/java/com/juick/www/controllers/User.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -package com.juick.www.controllers; - -import com.juick.server.util.HttpForbiddenException; -import com.juick.server.util.HttpNotFoundException; -import com.juick.server.util.UserUtils; -import com.juick.service.MessagesService; -import com.juick.service.TagService; -import com.juick.service.UserService; -import com.juick.www.Utils; -import org.apache.commons.codec.CharEncoding; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.text.StringEscapeUtils; -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.CookieValue; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import org.springframework.web.util.UriComponents; -import ru.sape.Sape; - -import javax.inject.Inject; -import java.io.IOException; -import java.net.URLEncoder; -import java.util.List; -import java.util.stream.Collectors; - -/** - * - * @author Ugnich Anton - */ -@Controller -public class User { - @Inject - private UserService userService; - @Inject - private TagService tagService; - @Inject - private MessagesService messagesService; - @Inject - private Sape sape; - - void fillUserModel(ModelMap model, com.juick.User user, com.juick.User visitor) { - model.addAttribute("isSubscribed", userService.isSubscribed(visitor.getUid(), user.getUid())); - model.addAttribute("isInBL", userService.isInBL(visitor.getUid(), user.getUid())); - model.addAttribute("isInBLAny", userService.isInBLAny(user.getUid(), visitor.getUid())); - model.addAttribute("statsIRead", userService.getStatsIRead(user.getUid())); - model.addAttribute("statsMyReaders", userService.getStatsMyReaders(user.getUid())); - model.addAttribute("statsMyBL", userService.getUserBLUsers(user.getUid()).size()); - model.addAttribute("statsMessages", userService.getStatsMessages(user.getUid())); - model.addAttribute("statsReplies", userService.getStatsReplies(user.getUid())); - model.addAttribute("iread", userService.getUserReadLeastPopular(user.getUid(), 8)); - model.addAttribute("tagStats", tagService.getUserTagStats(user.getUid()) - .stream().sorted((e1, e2) -> Integer.compare(e2.getUsageCount(), e1.getUsageCount())).limit(20).map(t -> t.getTag().getName()).collect(Collectors.toList())); - } - - @GetMapping("/{uname}/") - protected String doGetBlog( - @RequestParam(required = false, name = "show") String paramShow, - @RequestParam(required = false, name = "tag") String paramTagStr, - @RequestParam(required = false, name = "search") String paramSearch, - @PathVariable String uname, - @RequestParam(required = false, defaultValue = "0") Integer before, - @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, - ModelMap model) throws IOException { - com.juick.User user = userService.getUserByName(uname); - com.juick.User visitor = UserUtils.getCurrentUser(); - if (user.isBanned()) { - throw new HttpNotFoundException(); - } - - List<Integer> mids; - - com.juick.Tag paramTag = null; - if (paramTagStr != null) { - if (paramTagStr.length() < 64) { - paramTag = tagService.getTag(paramTagStr, false); - } - if (paramTag == null) { - throw new HttpNotFoundException(); - } else if (!paramTag.getName().equals(paramTagStr)) { - String url = user.getName() + "/?tag=" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8); - return "redirect:/" + url; - } - } - if (paramSearch != null && paramSearch.length() > 64) { - paramSearch = null; - } - - int privacy = 0; - if (visitor.getUid() > 0) { - if (user.getUid() == visitor.getUid() || visitor.getUid() == 1) { - privacy = -3; - } else if (userService.isInWL(user.getUid(), visitor.getUid())) { - privacy = -2; - } - } - - String title; - if (paramShow == null) { - if (paramTag != null) { - title = "Блог " + user.getName() + ": *" + StringEscapeUtils.escapeHtml4(paramTag.getName()); - mids = messagesService.getUserTag(user.getUid(), paramTag.TID, privacy, before); - } else if (paramSearch != null) { - title = "Блог " + user.getName() + ": " + StringEscapeUtils.escapeHtml4(paramSearch); - mids = messagesService.getUserSearch(user.getUid(), Utils.encodeSphinx(paramSearch), privacy, before); - } else { - title = "Блог " + user.getName(); - mids = messagesService.getUserBlog(user.getUid(), privacy, before); - } - } else if (paramShow.equals("recomm")) { - title = "Рекомендации " + user.getName(); - mids = messagesService.getUserRecommendations(user.getUid(), before); - } else if (paramShow.equals("photos")) { - title = "Фотографии " + user.getName(); - mids = messagesService.getUserPhotos(user.getUid(), privacy, before); - } else { - throw new HttpNotFoundException(); - } - - String head = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"@" + - user.getName() + "\" href=\"//rss.juick.com/" + user.getName() + "/blog\"/>"; - if (paramTag != null && tagService.getTagNoIndex(paramTag.TID)) { - head += "<meta name=\"robots\" content=\"noindex,nofollow\"/>"; - } else if (before > 0 || paramShow != null) { - head += "<meta name=\"robots\" content=\"noindex\"/>"; - } - model.addAttribute("pageUrl", "http://juick.com/" + user.getName()); - model.addAttribute("title", title); - model.addAttribute("headers", head); - model.addAttribute("visitor", visitor); - model.addAttribute("user", user); - model.addAttribute("noindex", paramShow == null && before == 0); - fillUserModel(model, user, visitor); - model.addAttribute("paramTag", paramTag); - List<com.juick.Message> msgs = messagesService.getMessages(mids); - - if (visitor.getUid() != 0) { - List<Integer> blUIDs = userService.checkBL(visitor.getUid(), - msgs.stream().map(m -> m.getUser().getUid()).collect(Collectors.toList())); - msgs.forEach(m -> m.ReadOnly |= blUIDs.contains(m.getUser().getUid())); - } - model.addAttribute("msgs", msgs); - model.addAttribute("headers", head); - model.addAttribute("showAdv", - paramShow == null && before == 0 && paramSearch == null && visitor.getUid() == 0); - if (mids.size() >= 20) { - String nextpage = "?before=" + mids.get(mids.size() - 1); - if (paramShow != null) { - nextpage += "&show=" + paramShow; - } - if (paramSearch != null) { - nextpage += "&search=" + URLEncoder.encode(paramSearch, CharEncoding.UTF_8); - } - if (paramTag != null) { - nextpage += "&tag=" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8); - } - model.addAttribute("nextpage", nextpage); - } - UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); - String queryString = builder.getQuery(); - String requestURI = builder.toUri().getPath(); - if (sape != null && visitor.getUid() == 0 && queryString == null) { - String links = sape.getPageLinks(requestURI, sapeCookie).render(); - model.addAttribute("links", links); - } - model.addAttribute("isModerator", visitor.getUid() == 3694); - return "views/blog"; - } - - @GetMapping("/{uname}/tags") - protected String doGetTags(@PathVariable String uname, ModelMap model) throws IOException { - com.juick.User user = userService.getUserByName(uname); - com.juick.User visitor = UserUtils.getCurrentUser(); - if (visitor.isBanned()) { - throw new HttpNotFoundException(); - } - - model.addAttribute("title", "Теги " + user.getName()); - model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex,nofollow\"/>"); - model.addAttribute("visitor", visitor); - model.addAttribute("user", user); - fillUserModel(model, user, visitor); - model.addAttribute("tags", tagService.getUserTagStats(user.getUid()).stream() - .sorted((e1, e2) -> Integer.compare(e2.getUsageCount(), e1.getUsageCount())).map(t -> t.getTag().getName()).collect(Collectors.toList())); - - return "views/blog_tags"; - } - - @GetMapping("/{uname}/friends") - protected String doGetFriends(@PathVariable String uname, ModelMap model) throws IOException { - com.juick.User user = userService.getUserByName(uname); - com.juick.User visitor = UserUtils.getCurrentUser(); - if (visitor.isBanned()) { - throw new HttpNotFoundException(); - } - model.addAttribute("title", "Подписки " + user.getName()); - model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); - model.addAttribute("visitor", visitor); - model.addAttribute("user", user); - fillUserModel(model, user, visitor); - model.addAttribute("users", userService.getUserFriends(user.getUid())); - - return "views/users"; - } - - @GetMapping("/{uname}/readers") - protected String doGetReaders(@PathVariable String uname, ModelMap model) throws IOException { - com.juick.User user = userService.getUserByName(uname); - com.juick.User visitor = UserUtils.getCurrentUser(); - if (visitor.isBanned()) { - throw new HttpForbiddenException(); - } - model.addAttribute("title", "Читатели " + user.getName()); - model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); - model.addAttribute("visitor", visitor); - model.addAttribute("user", user); - fillUserModel(model, user, visitor); - model.addAttribute("users", userService.getUserReaders(user.getUid())); - - return "views/users"; - } - - @GetMapping("/{uname}/bl") - protected String doGetBL(@PathVariable String uname, ModelMap model) throws IOException { - com.juick.User user = userService.getUserByName(uname); - com.juick.User visitor = UserUtils.getCurrentUser(); - if (visitor.isBanned() || visitor.getUid() != user.getUid()) { - throw new HttpForbiddenException(); - } - model.addAttribute("title", "Черный список " + user.getName()); - model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); - model.addAttribute("visitor", visitor); - model.addAttribute("user", user); - fillUserModel(model, user, visitor); - model.addAttribute("users", userService.getUserBLUsers(user.getUid())); - - return "views/users"; - } -} diff --git a/juick-www/src/main/java/com/juick/www/controllers/UserThread.java b/juick-www/src/main/java/com/juick/www/controllers/UserThread.java deleted file mode 100644 index 88217b9d..00000000 --- a/juick-www/src/main/java/com/juick/www/controllers/UserThread.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -package com.juick.www.controllers; - -import com.juick.Tag; -import com.juick.formatters.PlainTextFormatter; -import com.juick.server.util.HttpForbiddenException; -import com.juick.server.util.HttpNotFoundException; -import com.juick.server.util.UserUtils; -import com.juick.service.CrosspostService; -import com.juick.service.MessagesService; -import com.juick.service.UserService; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.text.StringEscapeUtils; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; - -import javax.inject.Inject; -import java.io.IOException; -import java.util.ArrayList; -import java.util.function.BooleanSupplier; -import java.util.List; -import java.util.stream.Collectors; - -/** - * - * @author Ugnich Anton - */ -@Controller -public class UserThread { - - @Inject - private MessagesService messagesService; - @Inject - private UserService userService; - @Inject - private CrosspostService crosspostService; - - @GetMapping("/{uname}/{mid}") - protected String threadAction(ModelMap model, - @PathVariable String uname, - @PathVariable int mid, - @RequestParam(required = false, value = "view") String paramView) throws IOException { - com.juick.User visitor = UserUtils.getCurrentUser(); - - if (!messagesService.canViewThread(mid, visitor.getUid())) { - throw new HttpForbiddenException(); - } - - com.juick.Message msg = messagesService.getMessage(mid); - - if (msg == null || msg.getUser().isBanned()) { - throw new HttpNotFoundException(); - } - - com.juick.User user = userService.getUserByName(uname); - if (user.getUid() == 0 || !msg.getUser().equals(user)) { - return String.format("redirect:/%s/%d", msg.getUser().getName(), mid); - } - msg.VisitorCanComment = visitor.getUid() > 0; - if (visitor.getUid() > 0) { - boolean isMsgAuthor = visitor.getUid() == msg.getUser().getUid(); - boolean isInBL = userService.isInBL(msg.getUser().getUid(), visitor.getUid()); - msg.VisitorCanComment = isMsgAuthor || !(msg.ReadOnly || isInBL); - } - model.addAttribute("msg", msg); - - boolean listview = false; - if (paramView != null) { - if (paramView.equals("list")) { - listview = true; - if (visitor.getUid() > 0) { - userService.setUserOptionInt(visitor.getUid(), "repliesview", 1); - } - } else if (paramView.equals("tree") && visitor.getUid() > 0) { - userService.setUserOptionInt(visitor.getUid(), "repliesview", 0); - } - } else if (visitor.getUid() > 0 && userService.getUserOptionInt(visitor.getUid(), "repliesview", 0) == 1) { - listview = true; - } - model.addAttribute("listview", listview); - String title = msg.getUser().getName() + ": " + msg.getTagsString(); - - model.addAttribute("title", title); - model.addAttribute("visitor", visitor); - String headers = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"@" + msg.getUser().getName() + "\" href=\"//rss.juick.com/" + msg.getUser().getName() + "/blog\"/>"; - String pageUrl = "https://juick.com/" + msg.getUser().getName() + "/" + msg.getMid(); - if (paramView != null) { - headers += "<link rel=\"canonical\" href=\"" + pageUrl + "\"/>"; - } - if (msg.Hidden) { - headers += "<meta name=\"robots\" content=\"noindex\"/>"; - } - String cardType = StringUtils.isNotEmpty(msg.getAttachmentType()) ? "summary_large_image" : "summary"; - String msgImage = StringUtils.isNotEmpty(msg.getAttachmentType()) ? msg.getAttachment().getMedium().getUrl() - : "https://i.juick.com/a/" + msg.getUser().getUid() + ".png"; - model.addAttribute("ogtype", "article"); - String cardDescription = StringEscapeUtils.escapeHtml4(PlainTextFormatter.formatTwitterCard(msg)); - headers += "<meta name=\"twitter:card\" content=\"" + cardType + "\" />\n" + - "<meta name=\"twitter:site\" content=\"@juick\" />\n" + - "<meta property=\"og:url\" content=\"" + pageUrl + "\" />\n" + - "<meta property=\"og:title\" content=\"" + msg.getUser().getName() + " at Juick\" />\n" + - "<meta property=\"og:description\" content=\"" + cardDescription + "\" />\n" + - "<meta name=\"Description\" content=\"" + cardDescription + "\" />\n" + - "<meta property=\"og:image\" content=\"" + msgImage + "\" />"; - String twitterName = crosspostService.getTwitterName(msg.getUser().getUid()); - if (StringUtils.isNotEmpty(twitterName)) { - headers += "<meta name=\"twitter:creator\" content=\"@" + twitterName + "\" />\n"; - } - if (msg.getTags().size() > 0) { - headers += "<meta name=\"Keywords\" content=\"" + msg.getTags().stream().map(Tag::getName) - .collect(Collectors.joining(", ")) + "\" />\n"; - } - model.addAttribute("headers", headers); - model.addAttribute("contentStyle", "margin-left: 0; width: 100%"); - model.addAttribute("isModerator", visitor.getUid() == 3694); - model.addAttribute("visitorSubscribed", messagesService.isSubscribed(visitor.getUid(), msg.getMid())); - model.addAttribute("visitorInBL", userService.isInBL(msg.getUser().getUid(), visitor.getUid())); - model.addAttribute("recomm", messagesService.getMessageRecommendations(msg.getMid())); - List<com.juick.Message> replies = messagesService.getReplies(msg.getMid()); - - List<Integer> blUIDs = new ArrayList<>(); - for (int i = 0; i < replies.size(); i++) { - com.juick.Message reply = replies.get(i); - if (reply.getUser().getUid() != msg.getUser().getUid() - && !blUIDs.contains(reply.getUser().getUid())) { - blUIDs.add(reply.getUser().getUid()); - } - if (reply.getReplyto() > 0) { - boolean added = false; - for (int n = 0; n < replies.size(); n++) { - if (replies.get(n).getRid() == reply.getReplyto()) { - replies.get(n).childs.add(reply); - added = true; - break; - } - } - if (!added) { - reply.setReplyto(0); - } - } - - reply.VisitorCanComment = visitor.getUid() > 0; - if (visitor.getUid() > 0) { - boolean isMsgAuthor = visitor.getUid() == msg.getUser().getUid(); - boolean isReplyAuthor = visitor.getUid() == reply.getUser().getUid(); - BooleanSupplier isInBL2 = () -> userService.checkBL(visitor.getUid(), blUIDs).contains(reply.getUser().getUid()); - reply.VisitorCanComment = isMsgAuthor || (!msg.ReadOnly && (isReplyAuthor || !isInBL2.getAsBoolean())); - } - } - - boolean foldable = false; - if (replies.size() > 10) { - for (int i = 0; i < replies.size() - 1; i++) { - if (replies.get(i).getChildsCount() > 1) { - foldable = true; - break; - } - } - } - model.addAttribute("replies", replies); - model.addAttribute("foldable", foldable); - return "views/thread"; - } - - // when message id is not fit to int - @ExceptionHandler(NumberFormatException.class) - public ResponseEntity<String> notFoundAction() { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } -} diff --git a/juick-www/src/main/java/com/juick/www/controllers/XMPPPost.java b/juick-www/src/main/java/com/juick/www/controllers/XMPPPost.java deleted file mode 100644 index 51b9d964..00000000 --- a/juick-www/src/main/java/com/juick/www/controllers/XMPPPost.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2008-2017, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.juick.www.controllers; - -import com.juick.server.util.HttpBadRequestException; -import com.juick.server.util.HttpForbiddenException; -import com.juick.server.util.HttpUtils; -import com.juick.server.util.UserUtils; -import com.juick.www.WebApp; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.multipart.MultipartFile; -import rocks.xmpp.addr.Jid; -import rocks.xmpp.core.stanza.model.Message; -import rocks.xmpp.extensions.oob.model.x.OobX; - -import javax.inject.Inject; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; - -/** - * Created by vitalyster on 08.12.2016. - */ -@Controller -public class XMPPPost { - private final static Logger logger = LoggerFactory.getLogger(XMPPPost.class); - - @Inject - private WebApp webApp; - - @PostMapping("/post2") - public String doPostMessage(@RequestParam(name = "body") String bodyParam, - @RequestParam(required = false) String img, - @RequestParam(required = false) String referer, - @RequestParam(required = false) MultipartFile attach) throws IOException { - - com.juick.User visitor = UserUtils.getCurrentUser(); - if (visitor.getUid() == 0 || visitor.isBanned()) { - throw new HttpForbiddenException(); - } - String body = bodyParam.replace("\r", StringUtils.EMPTY); - - String attachmentFName = HttpUtils.receiveMultiPartFile(attach, webApp.getTmpDir()); - - if (StringUtils.isBlank(attachmentFName) && img != null && img.length() > 10) { - try { - URL imgUrl = new URL(img); - attachmentFName = HttpUtils.downloadImage(imgUrl, webApp.getTmpDir()); - } catch (Exception e) { - logger.error("DOWNLOAD ERROR", e); - throw new HttpBadRequestException(); - } - } - Message msg = new Message(); - msg.setType(Message.Type.CHAT); - msg.setFrom(Jid.of(String.valueOf(visitor.getUid()), "uid.juick.com", "perl")); - msg.setTo(Jid.of("juick@juick.com/Juick")); - msg.setBody(body); - try { - if (StringUtils.isNotEmpty(attachmentFName)) { - String attachmentUrl = String.format("juick://%s", attachmentFName); - msg.addExtension(new OobX(new URI(attachmentUrl), "!!!!Juick!!")); - } - webApp.getXmpp().sendMessage(msg); - } catch (URISyntaxException e1) { - logger.warn("attachment error", e1); - } - if (StringUtils.isBlank(referer) || referer.substring(0, 21).equals("http://juick.com/post") - || referer.substring(0, 22).equals("https://juick.com/post")) { - return "redirect:/?show=my"; - } - return "redirect:" + referer; - } -} diff --git a/juick-www/src/main/resources/messages.properties b/juick-www/src/main/resources/messages.properties index aa1e2c05..d5421774 100644 --- a/juick-www/src/main/resources/messages.properties +++ b/juick-www/src/main/resources/messages.properties @@ -7,7 +7,7 @@ link.help=Help link.adv=Advertisement link.popular=Popular -link.allMessages=All messages +link.allMessages=Discover link.withPhotos=Photos link.my=My feed link.privateMessages=PM diff --git a/juick-www/src/main/resources/messages_ru.properties b/juick-www/src/main/resources/messages_ru.properties index 8dd8f4b0..3de7ff92 100644 --- a/juick-www/src/main/resources/messages_ru.properties +++ b/juick-www/src/main/resources/messages_ru.properties @@ -7,8 +7,8 @@ link.help=Помощь link.adv=Реклама link.popular=Популярные -link.allMessages=Все сообщения -link.withPhotos=Фотографии +link.allMessages=Обзор +link.withPhotos=Фото link.my=Моя лента link.privateMessages=Приватные link.discuss=Обсуждения diff --git a/juick-www/src/main/static/scripts.js b/juick-www/src/main/static/scripts.js index bad1d415..321de92d 100644 --- a/juick-www/src/main/static/scripts.js +++ b/juick-www/src/main/static/scripts.js @@ -214,7 +214,7 @@ function wsIncomingReply(msg) { li.innerHTML = msgContHtml; li.addEventListener('click', newReply); li.addEventListener('mouseover', newReply); - li.querySelector('.msg-menu > a').addEventListener('click', function (e) { + li.querySelector('.msg-menu').addEventListener('click', function (e) { showMessageLinksDialog(msg.mid, msg.rid); e.preventDefault(); }); @@ -715,7 +715,7 @@ ready(function () { } } - document.querySelectorAll('.msg-menu a').forEach(function (el) { + document.querySelectorAll('.msg-menu').forEach(function (el) { el.addEventListener('click', function (e) { var reply = e.target.closest('li'); var rid = reply ? parseInt(reply.id) : 0; diff --git a/juick-www/src/main/static/style.css b/juick-www/src/main/static/style.css index cf90bae9..acc0eaf0 100644 --- a/juick-www/src/main/static/style.css +++ b/juick-www/src/main/static/style.css @@ -21,12 +21,18 @@ textarea { -webkit-font-smoothing: subpixel-antialiased; } html { - background: #EEEEE5; + background: #eaeadf; color: #222; } #wrapper { margin: 0 auto; width: 1000px; + margin-top: 50px; +} +#header_wrapper { + margin: 0 auto; + width: 1000px; + display: flex; } h1, h2 { @@ -68,20 +74,25 @@ pre::-moz-selection { } #content { float: right; - margin: 25px 0 0 0; + margin: 12px 0 0 0; width: 728px; } #topwrapper { clear: both; position: relative; } +body > header { + box-shadow: 0 0 3px rgba(0, 0, 0, 0.28); + background: #f2f2ec; + position: fixed; + top: 0; + width: 100%; +} body > header a { - border-bottom: 1px dotted #666; - color: #222; + color: #069; font-size: 13pt; } #logo { - float: left; height: 36px; margin: 7px 25px 0 20px; width: 110px; @@ -103,14 +114,13 @@ body > header a { } } #global { - float: left; + flex-grow: 1; } #global li { display: inline-block; margin: 14px 12px 0 0; } #search { - float: right; margin: 12px 20px 12px 0; } #search input { @@ -119,17 +129,12 @@ body > header a { padding: 4px; } #headdiv { - background: #DDDDD5; border-bottom: 1px solid #D5D5D0; - border-top: 1px solid #D5D5D0; - clear: both; - margin: 0 0 5px 0; padding: 0 20px; - position: relative; } -#headdiv li { - display: inline-block; - margin: 12px 12px 12px 0; +#headdiv p { + padding: 12px; + text-align: center; } #actions { position: absolute; @@ -138,7 +143,7 @@ body > header a { } body > header nav li:after { color: #AAA; - content: "/"; + content: "|"; display: inline-block; margin-left: 12px; } @@ -187,8 +192,9 @@ body > header p { } article { background: #fff; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.16); line-height: 140%; - margin-bottom: 20px; + margin-bottom: 10px; padding: 20px; } article aside { @@ -202,7 +208,6 @@ article aside img { width: 48px; } article time { - border-bottom: 1px dotted #999; color: #999; font-size: 10pt; } @@ -226,12 +231,13 @@ article .ir a { article .ir img { max-width: 100%; } -article > nav.l { - display: inline-block; +article > nav.l, +.msg-cont > nav.l { + display: flex; font-size: 10pt; } -article > nav.l a { - border-bottom: 1px dotted #AAA; +article > nav.l a, +.msg-cont > nav.l a { color: #888; margin-right: 15px; } @@ -278,7 +284,7 @@ article .tags { } article .tags > a, .msg-tags > a { - border: 1px dotted #ccc; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.16); color: #888; display: inline-block; font-size: 10pt; @@ -313,24 +319,12 @@ article .tags > a, } .msg-cont { background: #FFF; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.16); line-height: 140%; - margin-bottom: 20px; + margin-bottom: 12px; padding: 20px; width: 640px; } -.msg-menu { - float: right; - height: 16px; - margin-left: 10px; - margin-top: 2.4px; - width: 16px; -} -.msg-menu > a { - color: #666; - display: block; - height: 16px; - width: 16px; -} .msg-ts, article .t { float: right; @@ -434,24 +428,7 @@ q { padding-left: 10px; } .toolbar { - background: #E5E5DD; border-top: 1px solid #CCC; - width: 680px; -} -.toolbar ul, -.toolbar a { - padding: 5px; -} -.toolbar li { - display: inline; -} -.toolbar div { - background: url("toolbar-icons.png") no-repeat; - display: inline-block; - height: 16px; - margin: 5px; - vertical-align: middle; - width: 16px; } #replies .msg-txt, #private-messages .msg-txt { @@ -538,8 +515,15 @@ q { #column hr { margin: 10px 0; } -#column li { - margin: 6px 0; +#column li > a { + display: block; + height: 100%; + padding: 6px; +} +#column li > a:hover { + background-color: #f2f2ec; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.16); + transition: background-color 0.2s ease-in; } #column .margtop { margin-top: 15px; @@ -564,6 +548,7 @@ q { #ctitle img { margin-right: 5px; vertical-align: middle; + width: 48px; } #ustats li { font-size: 10pt; @@ -837,6 +822,9 @@ article.nsfw .ir img:hover { min-width: 310px; width: auto; } + #wrapper { + margin-top: 50px; + } body > header { margin-bottom: 15px; } @@ -884,11 +872,13 @@ article.nsfw .ir img:hover { article p { margin: 10px 0 8px 0; } - article > nav.l { - display: block; - float: left; - line-height: 15pt; - width: 80%; + article > nav.l, + .msg-cont > nav.l { + flex-direction: column; + } + article > nav.l a, + .msg-cont > nav.l a { + padding: 6px; } article > nav.s { display: block; diff --git a/juick-www/src/main/webapp/WEB-INF/layouts/content.html b/juick-www/src/main/webapp/WEB-INF/layouts/content.html index 9ced2d76..73d140f1 100644 --- a/juick-www/src/main/webapp/WEB-INF/layouts/content.html +++ b/juick-www/src/main/webapp/WEB-INF/layouts/content.html @@ -46,8 +46,13 @@ <body id="body" {% if visitor.uid > 0 %}data-hash="{{visitor.authHash}}"{% endif %}> {% include "views/partial/navigation" %} <div id="wrapper"> -<section id="content" style="{{ contentStyle | default('') }}" - {% if msg | default('') is not empty %}data-mid="{{ msg.mid }}"{% endif %}> + {% if visitor.uid == 0 %} + <div id="headdiv"> + <p>{{ i18n("messages","message.loginForSending", "/login") | raw }}.</p> + </div> + {% endif %} +<section id="content" + {% if msg | default('') is not empty %}data-mid="{{ msg.mid }}"{% endif %}> {% block content %} {% endblock %} </section> diff --git a/juick-www/src/main/webapp/WEB-INF/views/index.html b/juick-www/src/main/webapp/WEB-INF/views/index.html index 51590ce0..23d208b6 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/index.html +++ b/juick-www/src/main/webapp/WEB-INF/views/index.html @@ -12,7 +12,11 @@ {% endif %} {% endblock %} {% block "column" %} +{% if visitor.uid > 0 %} +{% include "views/partial/usercolumn" %} +{% else %} {% include "views/partial/homecolumn" %} +{% endif %} {% if noindex %} <!--/noindex--> {% endif %} diff --git a/juick-www/src/main/webapp/WEB-INF/views/macros/tree.html b/juick-www/src/main/webapp/WEB-INF/views/macros/tree.html index f0c283e9..bb2507aa 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/macros/tree.html +++ b/juick-www/src/main/webapp/WEB-INF/views/macros/tree.html @@ -15,9 +15,6 @@ <img src="//i.juick.com/av-96.png"/> </div> {% endif %} - <div class="msg-menu"> - <a href="#" class="a-thread-links"><i data-icon="ei-link" data-size="s"></i></a> - </div> <div class="msg-ts"> <a href="/{{ msg.mid }}#{{ msg.rid }}"> <time datetime="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z" diff --git a/juick-www/src/main/webapp/WEB-INF/views/partial/footer.html b/juick-www/src/main/webapp/WEB-INF/views/partial/footer.html index 114e4304..784011a9 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/partial/footer.html +++ b/juick-www/src/main/webapp/WEB-INF/views/partial/footer.html @@ -1,6 +1,5 @@ <div id="footer"> - <div id="footer-right"> - <a href="/settings" rel="nofollow">{{ i18n("messages","link.settings") }}</a> · + <div id="footer-right"> · <a href="/help/ru/contacts" rel="nofollow">{{ i18n("messages","link.contacts") }}</a> · <a href="/help/" rel="nofollow">{{ i18n("messages","link.help") }}</a> · <a href="/help/ru/adv" rel="nofollow">{{ i18n("messages","link.adv") }}</a> diff --git a/juick-www/src/main/webapp/WEB-INF/views/partial/message.html b/juick-www/src/main/webapp/WEB-INF/views/partial/message.html index c0d345ba..72e5cce0 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/partial/message.html +++ b/juick-www/src/main/webapp/WEB-INF/views/partial/message.html @@ -6,7 +6,6 @@ <div class="msg-avatar"><a href="/{{ msg.user.name }}/"> <img src="//i.juick.com/a/{{ msg.user.uid }}.png" alt="{{ msg.user.name }}"/></a> </div> - <div class="msg-menu"><a href="#"><i data-icon="ei-link" data-size="s"></i></a></div> <div class="msg-ts"> <a href="/{{ msg.user.name }}/{{ msg.mid }}"> <time itemprop="datePublished dateModified" itemtype="http://schema.org/Date" datetime="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z" @@ -28,15 +27,30 @@ <nav class="l"> {% if visitor.uid != msg.user.uid %} {% if visitor.uid > 0 %} - <a href="/post?body=!+%23{{ msg.mid }}" class="a-like">{{ i18n("messages","message.recommend") }}</a> + <a href="/post?body=!+%23{{ msg.mid }}" class="a-like"> + <i data-icon="ei-heart" data-size="s"></i> {{ i18n("messages","message.recommend") }} + {% if msg.Likes > 0 %} ({{ msg.Likes }}){% endif %} + </a> {% else %} - <a href="/login" class="a-login">{{ i18n("messages","message.recommend") }}</a> + <a href="/login" class="a-login"> + <i data-icon="ei-heart" data-size="s"></i> {{ i18n("messages","message.recommend") }} + {% if msg.Likes > 0 %} ({{ msg.Likes }}){% endif %} + </a> {% endif %} {% endif %} {% if (visitor.uid > 0 and not msg.ReadOnly) or (visitor.uid == msg.user.uid) %} - <a href="/{{ msg.mid }}" class="a-comment">{{ i18n("messages","message.comment") }}</a> + <a href="/{{ msg.mid }}" class="a-comment"> + <i data-icon="ei-comment" data-size="s"></i> {{ i18n("messages","message.comment") }} + {% if msg.Replies > 0 %} ({{ msg.Replies }}){% endif %} + </a> + <a href="#" class="msg-menu"> + <i data-icon="ei-link" data-size="s"></i> Share + </a> {% elseif visitor.uid == 0 and not msg.ReadOnly %} - <a href="/login" class="a-login">{{ i18n("messages","message.comment") }}</a> + <a href="/login" class="a-login"> + <i data-icon="ei-comment" data-size="s"></i> {{ i18n("messages","message.comment") }} + {% if msg.Replies > 0 %} ({{ msg.Replies }}){% endif %} + </a> {% endif %} {% if msg.FriendsOnly %} <a href="#" class="a-privacy">Открыть доступ</a> @@ -47,14 +61,4 @@ <a href="#" class="a-popular-delete">x</a> {% endif %} </nav> - <nav class="s"> - {% if msg.Likes > 0 %} - <a href="/{{ msg.user.name }}/{{ msg.mid }}" class="likes"> - <i data-icon="ei-heart" data-size="s"></i> {{ msg.Likes }}</a> - {% endif %} - {% if msg.Replies > 0 %} - <a href="/{{ msg.user.name }}/{{ msg.mid }}" class="replies"> - <i data-icon="ei-comment" data-size="s"></i> <span itemprop="commentCount">{{ msg.Replies }}</span></a> - {% endif %} - </nav> </article>
\ No newline at end of file diff --git a/juick-www/src/main/webapp/WEB-INF/views/partial/navigation.html b/juick-www/src/main/webapp/WEB-INF/views/partial/navigation.html index 79f1985d..cabdca02 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/partial/navigation.html +++ b/juick-www/src/main/webapp/WEB-INF/views/partial/navigation.html @@ -1,35 +1,20 @@ <header> - <div id="logo"><a href="/">Juick</a></div> - <nav id="global"> - <ul> - <li><a href="/?show=all" rel="nofollow">{{ i18n("messages","link.allMessages") }}</a></li> - <li><a href="/?show=photos" rel="nofollow">{{ i18n("messages","link.withPhotos") }}</a></li> - </ul> - </nav> - <div id="search"> - <form action="/"> - <input name="search" class="text" - placeholder="{{ i18n('messages','label.search') }}" value="{{ search | default('') }}"/> - </form> - </div> - <div id="headdiv"> - {% if visitor.uid > 0 %} - <nav id="user"> - <ul> - <li><a href="/?show=my">{{ i18n("messages","link.my") }}</a></li> - <li><a href="/pm/inbox">{{ i18n("messages","link.privateMessages") }}</a></li> - <li><a href="/?show=discuss">{{ i18n("messages","link.discuss") }}</a></li> - </ul> - </nav> - <nav id="actions"> + <div id="header_wrapper"> + <div id="logo"><a href="/">Juick</a></div> + <nav id="global"> <ul> - <li><a id="post" href="/post"><i data-icon="ei-pencil" data-size="s"></i>{{ i18n("messages","link.postMessage") }}</a></li> - <li><a href="/{{ visitor.getName() }}">@{{ visitor.getName() }}</a></li> - <li><a href="/logout">{{ i18n("messages","link.logout") }}</a></li> + <li><a href="/?show=all" rel="nofollow"><i data-icon="ei-search" data-size="s"></i>{{ i18n("messages","link.allMessages") }}</a></li> + <li><a href="/?show=photos" rel="nofollow"><i data-icon="ei-camera" data-size="s"></i>{{ i18n("messages","link.withPhotos") }}</a></li> + <li><a id="post" href="/post"><i data-icon="ei-pencil" data-size="s"></i>{{ + i18n("messages","link.postMessage") }}</a> + </li> </ul> </nav> - {% else %} - <p>{{ i18n("messages","message.loginForSending", "/login") | raw }}.</p> - {% endif %} + <div id="search"> + <form action="/"> + <input name="search" class="text" + placeholder="{{ i18n('messages','label.search') }}" value="{{ search | default('') }}"/> + </form> + </div> </div> </header> diff --git a/juick-www/src/main/webapp/WEB-INF/views/partial/thread_list.html b/juick-www/src/main/webapp/WEB-INF/views/partial/thread_list.html index 918c4fb0..30bbc44c 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/partial/thread_list.html +++ b/juick-www/src/main/webapp/WEB-INF/views/partial/thread_list.html @@ -13,9 +13,6 @@ <img src="//i.juick.com/av-96.png"/> </div> {% endif %} - <div class="msg-menu"> - <a href="#" class="a-thread-links"><i data-icon="ei-link" data-size="s"></i></a> - </div> <div class="msg-ts"> <a href="/{{ msg.mid }}#{{ msg.rid }}"> <time datetime="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z" diff --git a/juick-www/src/main/webapp/WEB-INF/views/partial/usercolumn.html b/juick-www/src/main/webapp/WEB-INF/views/partial/usercolumn.html index 463ad2ca..edf218a1 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/partial/usercolumn.html +++ b/juick-www/src/main/webapp/WEB-INF/views/partial/usercolumn.html @@ -1,37 +1,40 @@ -<div id="ctitle"><a href="./"> - <img src="//i.juick.com/as/{{ user.uid }}.png" alt=""/>{{ user.name }}</a></div> +<div id="ctitle"> + <a href="/{{ user.name }}"> + <img src="//i.juick.com/a/{{ user.uid }}.png" alt=""/>{{ user.name }} + </a> +</div> {% if visitor is not empty and visitor.uid > 0 and visitor.uid != user.uid %} <ul class="toolbar"> {% if isSubscribed %} <li> <a href="/post?body=U+%40{{ user.name }}" title="Подписан"> - <div style="background-position: -48px 0"></div> + <i data-icon="ei-check" data-size="s"></i>Subscribed </a> </li> {% else %} <li> <a href="/post?body=S+%40{{ user.name }}" title="Подписаться"> - <div style="background-position: -16px 0"></div> + <i data-icon="ei-plus" data-size="s"></i>Subscribe </a> </li> {% endif %} {% if isInBL %} <li> <a href="/post?body=BL+%40{{ user.name }}" title="Разблокировать"> - <div style="background-position: -96px 0"></div> + <i data-icon="ei-close-o" data-size="s"></i>Unblock </a> </li> {% else %} <li> <a href="/post?body=BL+%40{{ user.name }}" title="Заблокировать"> - <div style="background-position: -80px 0"></div> + <i data-icon="ei-close" data-size="s"></i>Block </a> </li> {% endif %} {% if not isInBLAny %} <li> <a href="/pm/sent?uname={{ user.name }}" title="Написать приватное сообщение"> - <div style="background-position: -112px 0"></div> + <i data-icon="ei-envelope" data-size="s"></i>PM </a> </li> {% endif %} @@ -40,17 +43,25 @@ <hr/> {% endif %} <ul> - <li><a href="/{{ user.name }}/">{{ i18n("messages","blog.blog") }}</a></li> - <li><a href="/{{ user.name }}/?show=recomm" rel="nofollow">{{ i18n("messages","blog.recommendations") }}</a></li> - <li><a href="/{{ user.name }}/?show=photos" rel="nofollow">{{ i18n("messages","blog.photos") }}</a></li> + {% if visitor is not empty and visitor.uid == user.uid %} + <li><a href="/?show=my"><i data-icon="ei-clock" data-size="s"></i>{{ i18n("messages","link.my") }}</a></li> + <li><a href="/pm/inbox"><i data-icon="ei-envelope" data-size="s"></i>{{ i18n("messages","link.privateMessages") }}</a></li> + <li><a href="/?show=discuss"><i data-icon="ei-comment" data-size="s"></i>{{ i18n("messages","link.discuss") }}</a></li> + {% endif %} + <li><a href="/{{ user.name }}/?show=recomm" rel="nofollow"><i data-icon="ei-heart" data-size="s"></i>{{ i18n("messages","blog.recommendations") }}</a></li> + <li><a href="/{{ user.name }}/?show=photos" rel="nofollow"><i data-icon="ei-camera" data-size="s"></i>{{ i18n("messages","blog.photos") }}</a></li> {% if visitor is not empty and visitor.uid == user.uid and false %} <li><a href="/?show=mycomments" rel="nofollow">{{ i18n("messages","blog.comments") }}</a></li> <li><a href="/?show=unanswered" rel="nofollow">Неотвеченные</a></li> {% endif %} + {% if visitor is not empty and visitor.uid == user.uid %} + <li><a href="/settings" rel="nofollow"><i data-icon="ei-gear" data-size="s"></i>{{ i18n("messages","link.settings") }}</a></li> + <li><a href="/logout"><i data-icon="ei-user" data-size="s"></i>{{ i18n("messages","link.logout") }}</a></li> + {% endif %} </ul> <hr/> <form action="/{{ user.name }}/"> - <p><input type="text" name="search" class="inp" placeholder="Поиск"/></p> + <p><input type="text" name="search" class="inp" placeholder="{{ i18n('messages','label.search') }}"/></p> </form> {% include "views/partial/usertags" %} <hr/> diff --git a/juick-www/src/main/webapp/WEB-INF/views/pm_inbox.html b/juick-www/src/main/webapp/WEB-INF/views/pm_inbox.html index 156877c0..b2f9abda 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/pm_inbox.html +++ b/juick-www/src/main/webapp/WEB-INF/views/pm_inbox.html @@ -31,5 +31,5 @@ {% endif %} {% endblock %} {% block "column" %} -{% include "views/partial/homecolumn" %} +{% include "views/partial/usercolumn" %} {% endblock %} diff --git a/juick-www/src/main/webapp/WEB-INF/views/pm_sent.html b/juick-www/src/main/webapp/WEB-INF/views/pm_sent.html index 08b9585a..ee9c5b1c 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/pm_sent.html +++ b/juick-www/src/main/webapp/WEB-INF/views/pm_sent.html @@ -29,5 +29,5 @@ {% endif %} {% endblock %} {% block "column" %} -{% include "views/partial/homecolumn" %} +{% include "views/partial/usercolumn" %} {% endblock %} diff --git a/juick-www/src/main/webapp/WEB-INF/views/thread.html b/juick-www/src/main/webapp/WEB-INF/views/thread.html index 679550d5..f7093e0a 100644 --- a/juick-www/src/main/webapp/WEB-INF/views/thread.html +++ b/juick-www/src/main/webapp/WEB-INF/views/thread.html @@ -4,7 +4,6 @@ <ul id="0"> <li id="msg-{{ msg.mid }}" class="msg msgthread"> <div class="msg-cont" itemscope="" itemtype="http://schema.org/BlogPosting" itemref="org"> - <div class="msg-menu"><a href="#"><i data-icon="ei-link" data-size="s"></i></a></div> <div class="msg-ts"><a href="/{{ msg.user.name }}/{{ msg.mid }}"> <time itemprop="datePublished dateModified" datetime="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z" title="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT"> @@ -32,6 +31,32 @@ </a> </div> {% endif %} + <nav class="l"> + {% if visitor.uid != msg.user.uid %} + {% if visitor.uid > 0 %} + <a href="/post?body=!+%23{{ msg.mid }}" class="a-like"> + <i data-icon="ei-heart" data-size="s"></i> {{ i18n("messages","message.recommend") }} + {% if msg.Likes > 0 %} ({{ msg.Likes }}){% endif %} + </a> + {% else %} + <a href="/login" class="a-login"> + <i data-icon="ei-heart" data-size="s"></i> {{ i18n("messages","message.recommend") }} + {% if msg.Likes > 0 %} ({{ msg.Likes }}){% endif %} + </a> + {% endif %} + {% endif %} + {% if (visitor.uid > 0 and not msg.ReadOnly) or (visitor.uid == msg.user.uid) %} + <a href="#" class="msg-menu"><i data-icon="ei-link" data-size="s"></i> Share</a> + {% endif %} + {% if msg.FriendsOnly %} + <a href="#" class="a-privacy">Открыть доступ</a> + {% endif %} + {% if isModerator %} + <a href="#" class="a-popular-plus">+</a> + <a href="#" class="a-popular-minus">-</a> + <a href="#" class="a-popular-delete">x</a> + {% endif %} + </nav> {% if msg.VisitorCanComment %} <form action="/comment" method="POST" enctype="multipart/form-data"> <input type="hidden" name="mid" value="{{ msg.mid }}"/> @@ -51,37 +76,6 @@ {% endif %} </div> </li> - - <li class="toolbar"> - <ul> - <li><a href="/{{ msg.mid }}"> - <div style="background-position: -64px 0"></div> - {{ msg.mid }}</a> - </li> - {% if visitor.uid > 0 %} - {% if visitor.uid != msg.user.uid %} - {% if visitorSubscribed %} - <li><a href="/post?body=U+%23{{ msg.mid }}"> - <div style="background-position: -48px 0"></div> - {{ i18n("messages","message.subscribed") }}</a></li> - {% else %} - <li><a href="/post?body=S+%23{{ msg.mid }}"> - <div style="background-position: -16px 0"></div> - {{ i18n("messages","message.subscribe") }}</a></li> - {% endif %} - {% if not visitorInBL %} - <li><a href="/post?body=%21+%23{{ msg.mid }}"> - <div style="background-position: -32px 0"></div> - {{ i18n("messages","message.recommend") }}</a></li> - {% endif %} - {% else %} - <li><a href="/post?body=D+%23{{ msg.mid }}"> - <div style="background-position: 0"></div> - {{ i18n("messages","message.delete") }}</a></li> - {% endif %} - {% endif %} - </ul> - </li> </ul> <div class="title2"> <div class="title2-right"> @@ -106,4 +100,5 @@ </ul> {% endblock %} {% block "column" %} +{% include "views/partial/usercolumn" %} {% endblock %}
\ No newline at end of file |