1.修复了使用Freemarker自定义标签后不能保存系统设置的问题,2.优化部分重复代码,3.Anatole主题即将适配完毕

pull/1/head
RYAN0UP_ 2018-04-27 18:25:31 +08:00
parent ec92b35f2f
commit fcdb55f7d8
41 changed files with 218 additions and 404 deletions

View File

@ -1,13 +1,9 @@
package cc.ryanc.halo.config;
import cc.ryanc.halo.security.XssFilter;
import cc.ryanc.halo.web.interceptor.InstallInterceptor;
import cc.ryanc.halo.web.interceptor.LoginInterceptor;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@ -16,8 +12,6 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Map;
/**
* @author : RYAN0UP
* @date : 2018/1/2
@ -69,21 +63,4 @@ public class MvcConfig implements WebMvcConfigurer {
registry.addResourceHandler("/upload/**")
.addResourceLocations("classpath:/upload/");
}
/**
* xss
*/
@Bean
public FilterRegistrationBean xssFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new XssFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns("/*");
Map<String, String> initParameters = Maps.newHashMap();
initParameters.put("excludes", "/static/*,/upload/*");
initParameters.put("isIncludeRichText", "true");
filterRegistrationBean.setInitParameters(initParameters);
return filterRegistrationBean;
}
}

View File

@ -31,6 +31,12 @@ public class ArticleTagDirective implements TemplateDirectiveModel {
case "postsCount":
environment.setVariable("postsCount",builder.build().wrap(postService.findAllPosts().size()));
break;
case "archives":
environment.setVariable("archives",builder.build().wrap(postService.findPostGroupByYearAndMonth()));
break;
case "archivesLess":
environment.setVariable("archivesLess",builder.build().wrap(postService.findPostGroupByYear()));
break;
default:
break;
}

View File

@ -1,6 +1,7 @@
package cc.ryanc.halo.model.tag;
import cc.ryanc.halo.service.CategoryService;
import cc.ryanc.halo.service.CommentService;
import cc.ryanc.halo.service.MenuService;
import freemarker.core.Environment;
import freemarker.template.*;
@ -26,6 +27,9 @@ public class CommonTagDirective implements TemplateDirectiveModel {
@Autowired
private CategoryService categoryService;
@Autowired
private CommentService commentService;
@Override
public void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody templateDirectiveBody) throws TemplateException, IOException {
DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
@ -38,6 +42,9 @@ public class CommonTagDirective implements TemplateDirectiveModel {
case "categories":
environment.setVariable("categories",builder.build().wrap(categoryService.findAllCategories()));
break;
case "newComments":
environment.setVariable("newComments",builder.build().wrap(commentService.findAllComments(1)));
break;
default:
break;
}

View File

@ -18,14 +18,22 @@ import java.util.List;
public interface CommentRepository extends JpaRepository<Comment,Long> {
/**
*
*
*
* @param status Status
* @param pageable pageable
* @param status
* @param pageable
* @return page
*/
Page<Comment> findCommentsByCommentStatus(Integer status, Pageable pageable);
/**
*
*
* @param status
* @return list
*/
List<Comment> findCommentsByCommentStatus(Integer status);
/**
*
*

View File

@ -112,7 +112,7 @@ public interface PostRepository extends JpaRepository<Post,Long>{
* @param month month
* @return list
*/
@Query(value = "select *,year(post_date) as year,month(post_date) as month from halo_post where post_status=0 and year(post_date)=:year and month(post_date)=:month order by post_date",nativeQuery = true)
@Query(value = "select *,year(post_date) as year,month(post_date) as month from halo_post where post_status=0 and year(post_date)=:year and month(post_date)=:month order by post_date desc",nativeQuery = true)
List<Post> findPostByYearAndMonth(@Param("year") String year,@Param("month") String month);
/**
@ -121,7 +121,7 @@ public interface PostRepository extends JpaRepository<Post,Long>{
* @param year year
* @return list
*/
@Query(value = "select *,year(post_date) as year from halo_post where post_status=0 and year(post_date)=:year order by post_date",nativeQuery = true)
@Query(value = "select *,year(post_date) as year from halo_post where post_status=0 and year(post_date)=:year order by post_date desc",nativeQuery = true)
List<Post> findPostByYear(@Param("year") String year);
/**
@ -132,7 +132,7 @@ public interface PostRepository extends JpaRepository<Post,Long>{
* @param pageable pageable
* @return page
*/
@Query(value = "select * from halo_post where post_status=0 and year(post_date)=:year and month(post_date)=:month order by ?#{#pageable}",countQuery = "select count(*) from halo_post where post_status=0 and year(post_date)=:year and month(post_date)=:month",nativeQuery = true)
@Query(value = "select * from halo_post where post_status=0 and year(post_date)=:year and month(post_date)=:month order by post_date desc",countQuery = "select count(*) from halo_post where post_status=0 and year(post_date)=:year and month(post_date)=:month",nativeQuery = true)
Page<Post> findPostByYearAndMonth(@Param("year") String year,@Param("month") String month,Pageable pageable);
List<Post> findPostByCategories(Category category);

View File

@ -1,32 +0,0 @@
package cc.ryanc.halo.security;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
/**
* @author : RYAN0UP
* @version : 1.0
* @date : 2018/4/25
*/
public class JsoupUtil {
/**
* 使basicWithImages
* 便a,b,blockquote,br,cite,code,dd,dl,dt,em,i,li,ol,p,pre,q,small,span,
* strike,strong,sub,sup,u,ul,img
* ahref,imgsrc,align,alt,height,width,title
*/
private static final Whitelist whitelist = Whitelist.basicWithImages();
/** 配置过滤化参数,不对代码进行格式化 */
private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
static {
// 富文本编辑时一些样式是使用style来进行实现的
// 比如红色字体 style="color:red;"
// 所以需要给所有标签添加style属性
whitelist.addAttributes(":all", "style");
}
public static String clean(String content) {
return Jsoup.clean(content, "", whitelist, outputSettings);
}
}

View File

@ -1,86 +0,0 @@
package cc.ryanc.halo.security;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author : RYAN0UP
* @version : 1.0
* @date : 2018/4/25
*/
@Slf4j
public class XssFilter implements Filter {
private static boolean IS_INCLUDE_RICH_TEXT = false;
public List<String> excludes = new ArrayList<>();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,ServletException {
if(log.isDebugEnabled()){
log.debug("xss filter is open");
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if(handleExcludeURL(req, resp)){
filterChain.doFilter(request, response);
return;
}
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request,IS_INCLUDE_RICH_TEXT);
filterChain.doFilter(xssRequest, response);
}
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
if (excludes == null || excludes.isEmpty()) {
return false;
}
String url = request.getServletPath();
for (String pattern : excludes) {
Pattern p = Pattern.compile("^" + pattern);
Matcher m = p.matcher(url);
if (m.find()) {
return true;
}
}
return false;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
if(log.isDebugEnabled()){
log.debug("xss filter init~~~~~~~~~~~~");
}
String isIncludeRichText = filterConfig.getInitParameter("isIncludeRichText");
if(StringUtils.isNotBlank(isIncludeRichText)){
IS_INCLUDE_RICH_TEXT = BooleanUtils.toBoolean(isIncludeRichText);
}
String temp = filterConfig.getInitParameter("excludes");
if (temp != null) {
String[] url = temp.split(",");
for (int i = 0; url != null && i < url.length; i++) {
excludes.add(url[i]);
}
}
}
@Override
public void destroy() {}
}

View File

@ -1,88 +0,0 @@
package cc.ryanc.halo.security;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* @author : RYAN0UP
* @version : 1.0
* @date : 2018/4/25
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
private boolean isIncludeRichText = false;
public XssHttpServletRequestWrapper(HttpServletRequest request, boolean isIncludeRichText) {
super(request);
orgRequest = request;
this.isIncludeRichText = isIncludeRichText;
}
/**
* getParameterxss<br/>
* super.getParameterValues(name)<br/>
* getParameterNames,getParameterValuesgetParameterMap
*/
@Override
public String getParameter(String name) {
if(("content".equals(name) || name.endsWith("WithHtml")) && !isIncludeRichText){
return super.getParameter(name);
}
name = JsoupUtil.clean(name);
String value = super.getParameter(name);
if (StringUtils.isNotBlank(value)) {
value = JsoupUtil.clean(value);
}
return value;
}
@Override
public String[] getParameterValues(String name) {
String[] arr = super.getParameterValues(name);
if(arr != null){
for (int i=0;i<arr.length;i++) {
arr[i] = JsoupUtil.clean(arr[i]);
}
}
return arr;
}
/**
* getHeaderxss<br/>
* super.getHeaders(name)<br/>
* getHeaderNames
*/
@Override
public String getHeader(String name) {
name = JsoupUtil.clean(name);
String value = super.getHeader(name);
if (StringUtils.isNotBlank(value)) {
value = JsoupUtil.clean(value);
}
return value;
}
/**
* request
*
* @return
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* request
*
* @return
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if (req instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) req).getOrgRequest();
}
return req;
}
}

View File

@ -40,6 +40,13 @@ public interface CommentService {
*/
Page<Comment> findAllComments(Integer status, Pageable pageable);
/**
*
*
* @param status
* @return list
*/
List<Comment> findAllComments(Integer status);
/**
*

View File

@ -58,6 +58,17 @@ public class CommentServiceImpl implements CommentService {
return commentRepository.findCommentsByCommentStatus(status,pageable);
}
/**
*
*
* @param status
* @return list
*/
@Override
public List<Comment> findAllComments(Integer status) {
return commentRepository.findCommentsByCommentStatus(status);
}
/**
*
*

View File

@ -5,8 +5,6 @@ import cc.ryanc.halo.repository.OptionsRepository;
import cc.ryanc.halo.service.OptionsService;
import cc.ryanc.halo.util.HaloUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.HashMap;

View File

@ -278,7 +278,7 @@ public class PostServiceImpl implements PostService {
*/
@Override
public Page<Post> findPostByYearAndMonth(String year, String month, Pageable pageable) {
return postRepository.findPostByYearAndMonth(year,month,pageable);
return postRepository.findPostByYearAndMonth(year,month,null);
}
/**

View File

@ -3,8 +3,6 @@ package cc.ryanc.halo.util;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.dto.HaloConst;
import cc.ryanc.halo.model.dto.Theme;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.syndication.feed.rss.Channel;
import com.sun.syndication.feed.rss.Content;
import com.sun.syndication.feed.rss.Item;
@ -12,7 +10,6 @@ import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.WireFeedOutput;
import io.github.biezhi.ome.OhMyEmail;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ResourceUtils;
import javax.imageio.ImageIO;
@ -261,19 +258,15 @@ public class HaloUtil {
File[] moduleFiles = modulePath.listFiles();
if(null!=moduleFiles) {
for (File file : moduleFiles) {
if (file.isFile()) {
if (file.getName().endsWith(".ftl")) {
tpls.add("module/" + file.getName());
}
if (file.isFile() && file.getName().endsWith(".ftl")) {
tpls.add("module/" + file.getName());
}
}
}
if(null!=baseFiles){
for (File file:baseFiles){
if(file.isFile()) {
if (file.getName().endsWith(".ftl")) {
tpls.add(file.getName());
}
if(file.isFile() && file.getName().endsWith(".ftl")) {
tpls.add(file.getName());
}
}
}

View File

@ -6,10 +6,7 @@ import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.domain.User;
import cc.ryanc.halo.model.dto.HaloConst;
import cc.ryanc.halo.model.dto.LogsRecord;
import cc.ryanc.halo.service.CommentService;
import cc.ryanc.halo.service.LogsService;
import cc.ryanc.halo.service.PostService;
import cc.ryanc.halo.service.UserService;
import cc.ryanc.halo.service.*;
import cc.ryanc.halo.util.HaloUtil;
import cc.ryanc.halo.web.controller.core.BaseController;
import lombok.extern.slf4j.Slf4j;
@ -85,8 +82,6 @@ public class AdminController extends BaseController{
model.addAttribute("comments",comments);
model.addAttribute("mediaCount",HaloConst.ATTACHMENTS.size());
this.getNewComments(session);
return "admin/admin_index";
}
@ -164,9 +159,9 @@ public class AdminController extends BaseController{
@GetMapping(value = "/logOut")
public String logOut(HttpSession session){
User user = (User) session.getAttribute(HaloConst.USER_SESSION_KEY);
log.info("用户["+user.getUserName()+"]退出登录");
logsService.saveByLogs(new Logs(LogsRecord.LOGOUT,user.getUserName(),HaloUtil.getIpAddr(request),HaloUtil.getDate()));
session.invalidate();
log.info("用户["+user.getUserName()+"]退出登录");
return "redirect:/admin/login";
}

View File

@ -88,7 +88,6 @@ public class CommentController extends BaseController{
HttpSession session){
try {
commentService.updateCommentStatus(commentId,2);
this.getNewComments(session);
}catch (Exception e){
log.error("未知错误:{0}",e.getMessage());
}
@ -132,7 +131,6 @@ public class CommentController extends BaseController{
log.error("邮件服务器未配置:{0}",e.getMessage());
}
}
this.getNewComments(session);
return "redirect:/admin/comments?status="+status;
}
@ -150,7 +148,6 @@ public class CommentController extends BaseController{
HttpSession session){
try{
commentService.removeByCommentId(commentId);
this.getNewComments(session);
}catch (Exception e){
log.error("删除评论失败:{0}",e.getMessage());
}

View File

@ -1,11 +1,12 @@
package cc.ryanc.halo.web.controller.admin;
import cc.ryanc.halo.model.dto.HaloConst;
import cc.ryanc.halo.service.OptionsService;
import freemarker.template.Configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
@ -22,6 +23,9 @@ public class OptionController {
@Autowired
private OptionsService optionsService;
@Autowired
private Configuration configuration;
/**
* option
*
@ -43,8 +47,8 @@ public class OptionController {
public boolean saveOptions(@RequestParam Map<String,String> options){
try {
optionsService.saveOptions(options);
HaloConst.OPTIONS.clear();
HaloConst.OPTIONS = optionsService.findAllOptions();
//刷新options
configuration.setSharedVariable("options",optionsService.findAllOptions());
log.info("所保存的设置选项列表:"+options);
return true;
}catch (Exception e){

View File

@ -122,15 +122,11 @@ public class PostController extends BaseController{
*/
@GetMapping(value = "/new")
public String newPost(Model model){
try {
List<Category> categories = categoryService.findAllCategories();
List<Tag> tags = tagService.findAllTags();
model.addAttribute("categories",categories);
model.addAttribute("tags",tags);
model.addAttribute("btnPush","发布");
}catch (Exception e){
log.error("未知错误:{0}",e.getMessage());
}
List<Category> categories = categoryService.findAllCategories();
List<Tag> tags = tagService.findAllTags();
model.addAttribute("categories",categories);
model.addAttribute("tags",tags);
model.addAttribute("btnPush","发布");
return "admin/admin_editor";
}
@ -161,7 +157,7 @@ public class PostController extends BaseController{
post.setUser(user);
List<Category> categories = categoryService.strListToCateList(cateList);
post.setCategories(categories);
List<Tag> tags = tagService.strListToTagList(tagList);
List<Tag> tags = tagService.strListToTagList(tagList.replaceAll(" ",""));
post.setTags(tags);
postService.saveByPost(post);
log.info("已发表新文章:"+post.getPostTitle());

View File

@ -67,7 +67,7 @@ public class ThemeController extends BaseController{
public boolean activeTheme(@PathParam("siteTheme") String siteTheme,
HttpServletRequest request){
try {
//保存主题设置项在数据库
//保存主题设置项
optionsService.saveOption("theme",siteTheme);
//设置主题
BaseController.THEME = siteTheme;
@ -77,7 +77,7 @@ public class ThemeController extends BaseController{
);
return true;
}catch (Exception e){
log.error("主题设置失败,当前主题为:"+BaseController.THEME);
log.error("主题设置失败,当前主题为:"+siteTheme);
return false;
}
}

View File

@ -3,10 +3,10 @@ package cc.ryanc.halo.web.controller.admin;
import cc.ryanc.halo.model.domain.User;
import cc.ryanc.halo.service.UserService;
import cc.ryanc.halo.util.HaloUtil;
import freemarker.template.Configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
@ -25,6 +25,9 @@ public class UserController {
@Autowired
private UserService userService;
@Autowired
private Configuration configuration;
/**
*
*
@ -48,6 +51,7 @@ public class UserController {
try{
if(null!=user){
userService.saveByUser(user);
configuration.setSharedVariable("user",userService.findUser());
session.invalidate();
}else{
return false;

View File

@ -1,14 +1,5 @@
package cc.ryanc.halo.web.controller.core;
import cc.ryanc.halo.model.domain.Comment;
import cc.ryanc.halo.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import javax.servlet.http.HttpSession;
/**
* @author : RYAN0UP
* @date : 2017/12/15
@ -22,9 +13,6 @@ public abstract class BaseController {
*/
public static String THEME = "halo";
@Autowired
private CommentService commentService;
/**
*
*
@ -37,17 +25,4 @@ public abstract class BaseController {
themeStr.append("/");
return themeStr.append(pageName).toString();
}
/**
*
*
* @param session session
*/
protected void getNewComments(HttpSession session){
Sort sort = new Sort(Sort.Direction.DESC,"commentDate");
Pageable pageable = new PageRequest(0,999,sort);
Page<Comment> comments = commentService.findAllComments(1,pageable);
session.removeAttribute("newComments");
session.setAttribute("newComments",comments.getContent());
}
}

View File

@ -1,7 +1,6 @@
package cc.ryanc.halo.web.controller.front;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.dto.Archive;
import cc.ryanc.halo.service.PostService;
import cc.ryanc.halo.web.controller.core.BaseController;
import lombok.extern.slf4j.Slf4j;
@ -60,20 +59,6 @@ public class ArchivesController extends BaseController {
Pageable pageable = new PageRequest(page-1,5,sort);
Page<Post> posts = postService.findPostByStatus(0,pageable);
model.addAttribute("posts",posts);
model.addAttribute("is_archives",true);
//包含[List<Post>,year,month,count]
List<Archive> archives = postService.findPostGroupByYearAndMonth();
model.addAttribute("archives",archives);
//包含[List<Post>,year,count]
List<Archive> archivesLess = postService.findPostGroupByYear();
model.addAttribute("archivesLess",archivesLess);
//是否是归档页,用于判断输出链接
model.addAttribute("isArchives","true");
return this.render("archives");
}
@ -92,19 +77,9 @@ public class ArchivesController extends BaseController {
log.info(year);
log.info(month);
//根据年月查出的文章数据,分页
Sort sort = new Sort(Sort.Direction.DESC,"post_date");
Pageable pageable = new PageRequest(0,5,sort);
Page<Post> posts = postService.findPostByYearAndMonth(year,month,pageable);
Page<Post> posts = postService.findPostByYearAndMonth(year,month,null);
model.addAttribute("posts",posts);
//归档数据,包含[year,month,count,List<Post>]
List<Archive> archives = postService.findPostGroupByYearAndMonth();
model.addAttribute("archives",archives);
//是否是归档页,用于判断输出链接
model.addAttribute("isArchives","true");
return this.render("archives");
}
@ -138,11 +113,6 @@ public class ArchivesController extends BaseController {
}
model.addAttribute("post",post);
//归档数据,包含[year,month,count,List<Post>]
List<Archive> archives = postService.findPostGroupByYearAndMonth();
model.addAttribute("archives",archives);
return this.render("post");
}
}

View File

@ -69,12 +69,6 @@ public class IndexController extends BaseController {
Page<Post> posts = postService.findPostByStatus(0,pageable);
model.addAttribute("posts",posts);
model.addAttribute("is_home",true);
//归档数据,包含[year,month,count,List<Post>]
List<Archive> archives = postService.findPostGroupByYearAndMonth();
model.addAttribute("archives",archives);
return this.render("index");
}

View File

@ -53,9 +53,6 @@ public class PagesController extends BaseController {
public String gallery(Model model){
List<Gallery> galleries = galleryService.findAllGalleries();
model.addAttribute("galleries",galleries);
model.addAttribute("is_gallery",true);
return this.render("gallery");
}
@ -67,17 +64,9 @@ public class PagesController extends BaseController {
*/
@GetMapping(value = "/links")
public String links(Model model){
//所有友情链接
List<Link> links = linkService.findAllLinks();
model.addAttribute("links",links);
model.addAttribute("is_links",true);
//归档数据,包含[year,month,count,List<Post>]
List<Archive> archives = postService.findPostGroupByYearAndMonth();
model.addAttribute("archives",archives);
return this.render("links");
}
}

View File

@ -47,11 +47,6 @@ public class TagsController extends BaseController {
//所有标签
List<Tag> tags = tagService.findAllTags();
model.addAttribute("tags",tags);
//归档数据,包含[year,month,count,List<Post>]
List<Archive> archives = postService.findPostGroupByYearAndMonth();
model.addAttribute("archives",archives);
return this.render("tags");
}
@ -88,6 +83,7 @@ public class TagsController extends BaseController {
Pageable pageable = new PageRequest(page-1,size,sort);
Page<Post> posts = postService.findPostsByTags(tag,pageable);
model.addAttribute("posts",posts);
model.addAttribute("tag",tag);
return this.render("index");
}
}

View File

@ -51,6 +51,7 @@
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-sm ">确定${statusName}</button>
<a data-pjax="true" href="/admin/category" class="btn btn-info btn-sm ">返回添加</a>
</div>
</form>
<#else >

View File

@ -72,6 +72,9 @@
</#if>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-sm ">确定${statusName}</button>
<#if updateMenu??>
<a data-pjax="true" href="/admin/menus" class="btn btn-info btn-sm ">返回添加</a>
</#if>
</div>
</form>
</div>

View File

@ -219,13 +219,13 @@
</label>
<div class="col-sm-4">
<select class="form-control" id="nativeCommentAvatar" name="native_comment_avatar">
<option value="default" ${((options.native_comment_avatar?default('default'))=='default')?string('selected','')}>神秘人</option>
<option value="gravatar" ${((options.native_comment_avatar?default('default'))=='gravatar')?string('selected','')}>Gravatar标志</option>
<option value="mm" ${((options.native_comment_avatar?default('default'))=='mm')?string('selected','')}>默认</option>
<option value="identicon" ${((options.native_comment_avatar?default('default'))=='identicon')?string('selected','')}>抽象几何图形</option>
<option value="monsterid" ${((options.native_comment_avatar?default('default'))=='monsterid')?string('selected','')}>小怪物</option>
<option value="wavatar" ${((options.native_comment_avatar?default('default'))=='wavatar')?string('selected','')}>Wavatar</option>
<option value="retro" ${((options.native_comment_avatar?default('default'))=='retro')?string('selected','')}>复古</option>
<option value="hide" ${((options.native_comment_avatar?default('default'))=='hide')?string('selected','')}>不显示头像</option>
<option value="robohash" ${((options.native_comment_avatar?default('default'))=='robohash')?string('selected','')}>机器人</option>
<option value="blank" ${((options.native_comment_avatar?default('default'))=='blank')?string('selected','')}>不显示头像</option>
</select>
</div>
</div>

View File

@ -47,7 +47,10 @@
<small>*网站的描述,部分主题可显示</small>
</div>
</div>
<div class="box-footer"><button type="submit" class="btn btn-primary btn-sm ">确定${statusName}</button></div>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-sm ">确定${statusName}</button>
<a data-pjax="true" href="/admin/page/links" class="btn btn-info btn-sm ">返回添加</a>
</div>
</form>
<#else>
<form action="/admin/page/links/save" method="post" role="form" onsubmit="return isNull()">
@ -73,7 +76,9 @@
<small>*网站的描述,部分主题可显示</small>
</div>
</div>
<div class="box-footer"><button type="submit" class="btn btn-primary btn-sm ">确定${statusName}</button></div>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-sm ">确定${statusName}</button>
</div>
</form>
</#if>
</div>

View File

@ -175,6 +175,7 @@
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary btn-sm ">确定${statusName}</button>
<a data-pjax="true" href="/admin/tag" class="btn btn-info btn-sm ">返回添加</a>
<#if updateTag.posts?size = 0>
<a data-pjax="true" href="/admin/tag/remove?tagId=${updateTag.tagId}" class="btn btn-danger btn-sm pull-right">删除</a>
</#if>

View File

@ -10,33 +10,42 @@
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<li><a href="/" title="跳转到前台" target="_blank"><i class="fa fa-location-arrow"></i></a></li>
<@commonTag method="newComments">
<li class="dropdown messages-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-envelope-o"></i>
<#if newComments?size gt 0>
<span class="label label-success">${newComments?size}</span>
<span class="label label-success">${newComments?size}</span>
</#if>
</a>
<ul class="dropdown-menu">
<li class="header">你有${newComments?size}条新评论</li>
<li>
<ul class="menu">
<#list newComments! as comment>
<#if newComments?size gt 0>
<#assign x=0>
<#list newComments?sort_by("commentDate")?reverse as comment>
<#assign x = x+1>
<li>
<a href="/admin/comments?status=1">
<div class="pull-left">
<img src="http://www.gravatar.com/avatar/${comment.commentAuthorAvatarMd5?if_exists}?s=256&d=gravatar" class="img-circle" alt="User Image">
<img src="http://www.gravatar.com/avatar/${comment.commentAuthorAvatarMd5?default("hash")}?s=256&d=${options.native_comment_avatar?default("mm")}" class="img-circle" alt="User Image">
</div>
<h5>${comment.commentAuthor}<small> ${comment.commentDate}</small></h5>
<p>${comment.commentContent}</p>
</a>
</li>
<#if x==10>
<#break >
</#if>
</#list>
</#if>
</ul>
</li>
<li class="footer"><a href="/admin/comments">查看所有评论</a></li>
<li class="footer"><a href="/admin/comments?status=1">查看所有评论</a></li>
</ul>
</li>
</@commonTag>
<li class="dropdown user user-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<img src="<#if user_session.userAvatar?if_exists!="">${user_session.userAvatar}<#else >/static/images/default.png</#if>" class="user-image" alt="User Image">

View File

@ -7,21 +7,23 @@
<div class="content">
<div class="archive animated fadeInDown">
<ul class="list-with-title">
<#list archivesLess as archive>
<div class="listing-title">${archive.year}</div>
<ul class="listing">
<#list archive.posts?sort_by("postDate")?reverse as post>
<div class="listing-item">
<div class="listing-post">
<a href="/archives/${post.postUrl}" title="${post.postTitle}">${post.postTitle}</a>
<div class="post-time">
<span class="date">${post.postDate?string("yyyy-MM-dd")}</span>
<@articleTag method="archivesLess">
<#list archivesLess as archive>
<div class="listing-title">${archive.year}</div>
<ul class="listing">
<#list archive.posts?sort_by("postDate")?reverse as post>
<div class="listing-item">
<div class="listing-post">
<a href="/archives/${post.postUrl}" title="${post.postTitle}">${post.postTitle}</a>
<div class="post-time">
<span class="date">${post.postDate?string("yyyy-MM-dd")}</span>
</div>
</div>
</div>
</div>
</#list>
</ul>
</#list>
</#list>
</ul>
</#list>
</@articleTag>
</ul>
</div>
</div>

View File

@ -1,5 +1,9 @@
<#include "module/macro.ftl">
<@head title="${options.blog_title?default('Anatole')}" keywords="${options.seo_keywords?default('Anatole')}" description="${options.seo_desc?default('Anatole')}"></@head>
<#if tag??>
<@head title="${tag.tagName} · ${options.blog_title?default('Anatole')}" keywords="${options.seo_keywords?default('Anatole')}" description="${options.seo_desc?default('Anatole')}"></@head>
<#else>
<@head title="${options.blog_title?default('Anatole')}" keywords="${options.seo_keywords?default('Anatole')}" description="${options.seo_desc?default('Anatole')}"></@head>
</#if>
<#include "module/sidebar.ftl">
<div class="main">
<#include "module/page-top.ftl">

View File

@ -29,6 +29,9 @@
<li class="active">
<a href="#sns" data-toggle="tab">社交资料</a>
</li>
<li>
<a href="#style" data-toggle="tab">样式设置</a>
</li>
<li>
<a href="#about" data-toggle="tab">关于</a>
</li>
@ -86,6 +89,38 @@
</div>
</form>
</div>
<!--样式设置-->
<div class="tab-pane" id="style">
<form method="post" class="form-horizontal" id="anatoleStyleOptions">
<div class="box-body">
<div class="form-group">
<label for="anatoleStyleFavicon" class="col-sm-4 control-label">Favicon</label>
<div class="col-sm-8">
<div class="input-group">
<input type="text" class="form-control" id="anatoleStyleFavicon" name="anatole_style_favicon" value="${options.anatole_style_favicon?default("/anatole/source/images/favicon.png")}" >
<span class="input-group-btn">
<button class="btn btn-default btn-flat" type="button" onclick="openAttach('anatoleStyleFavicon')">选择</button>
</span>
</div>
</div>
</div>
<div class="form-group">
<label for="anatoleStyleHitokoto" class="col-sm-4 control-label">博客描述开启一言:</label>
<div class="col-sm-8">
<label class="radio-inline">
<input type="radio" name="anatole_style_hitokoto" id="anatoleStyleHitokoto" value="true" ${((options.anatole_style_hitokoto?if_exists)=='true')?string('checked','')}> 开启
</label>
<label class="radio-inline">
<input type="radio" name="anatole_style_hitokoto" id="anatoleStyleHitokoto" value="false" ${((options.anatole_style_hitokoto?default('false'))=='false')?string('checked','')}> 关闭
</label>
</div>
</div>
</div>
<div class="box-footer">
<button type="button" class="btn btn-primary btn-sm pull-right" onclick="saveThemeOptions('anatoleStyleOptions')">保存设置</button>
</div>
</form>
</div>
<!-- 关于该主题 -->
<div class="tab-pane" id="about">
<div class="box box-widget widget-user-2">
@ -127,5 +162,16 @@
}
});
}
function openAttach(id) {
layer.open({
type: 2,
title: '所有附件',
shadeClose: true,
shade: 0.5,
area: ['90%', '90%'],
content: '/admin/attachments/select?id='+id,
scrollbar: false
});
}
</script>
</html>

View File

@ -20,12 +20,12 @@
<span class="date">${post.postDate?string("yyyy-MM-dd")}</span>
<i class="fa fa-comment-o"></i>
<a href="/archives/${post.postUrl}#comment_widget">Comments</a>
<if post.tags??>
<#if post.tags?size gt 0>
<i class="fa fa-tag"></i>
<#list post.tags as tag>
<a href="/tags/${tag.tagUrl}" class="tag">&nbsp;${tag.tagName}</a>
</#list>
</if>
</#if>
</div>
</div>
</div>

View File

@ -6,7 +6,11 @@
<a href="/">${options.blog_title?default("ANATOLE")}</a>
</h3>
<div class="description">
<p>${user.userDesc?default("A other Halo theme")}</p>
<#if options.anatole_style_hitokoto?default("false")=="true">
<p id="yiyan">获取中...</p>
<#else >
<p>${user.userDesc?default("A other Halo theme")}</p>
</#if>
</div>
</div>
</div>

View File

@ -42,7 +42,7 @@
class="fa fa-weibo"></a>
</div>
<div class="twitter">
<a href="http://twitter.com/home?status=http://anatole.cai-cai.me/post/2015-05-22 ,Anatole,更轻量的Evernote第三方客户端Alternote,;"
<a href="http://twitter.com/home?status=${options.blog_url}/archives/${post.postUrl} ,${options.blog_title?if_exists},${post.postTitle},;"
class="fa fa-twitter"></a>
</div>
</div>

View File

@ -4,18 +4,20 @@
<#include "module/header.ftl">
<div class="main-content">
<section class="container">
<#if archives ??>
<#list archives as archive>
<@articleTag method="archives">
<#if archives ??>
<#list archives as archive>
<h1 class="animated fadeInDown">${archive.year}年${archive.month}月 (${archive.count})</h1>
<ul>
<#list archive.posts as post>
<li class="animated fadeInDown">
<a href="/article/${post.postUrl}">${post.postTitle}</a>
<a href="/archives/${post.postUrl}">${post.postTitle}</a>
</li>
</#list>
</ul>
</#list>
</#if>
</#list>
</#if>
</@articleTag>
</section>
</div>
<@footer></@footer>

View File

@ -53,7 +53,6 @@
</div>
</div>
</div>
<#include "module/comment.ftl">
</section>
</div>
<@footer></@footer>

View File

@ -8,11 +8,13 @@
</div>
<div class="navbar-menu pull-right hidden-xs">
<ul>
<#list menus?sort_by('menuSort') as menu>
<li>
<a href="${menu.menuUrl}">${menu.menuName}</a>
</li>
</#list>
<@commonTag method="menus">
<#list menus?sort_by('menuSort') as menu>
<li>
<a href="${menu.menuUrl}">${menu.menuName}</a>
</li>
</#list>
</@commonTag>
</ul>
</div>
</div>

View File

@ -28,13 +28,15 @@
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<#if archives??>
<#list archives?sort_by("year")?reverse as archive>
<li>
<a class="sidebar_archives-link" href="/archives/${archive.year}/${archive.month}/">${archive.month}月 ${archive.year}<span class="sidebar_archives-count">${archive.count}</span></a>
</li>
</#list>
</#if>
<@articleTag method="archives">
<#if archives??>
<#list archives?sort_by("year")?reverse as archive>
<li>
<a class="sidebar_archives-link" href="/archives/${archive.year}/${archive.month}/">${archive.month}月 ${archive.year}<span class="sidebar_archives-count">${archive.count}</span></a>
</li>
</#list>
</#if>
</@articleTag>
</ul>
</li>
</#if>

View File

@ -3,16 +3,16 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<title>${title?default("Anatole")}</title>
<meta content="yes" name="apple-mobile-web-app-capable" />
<meta content="black" name="apple-mobile-web-app-status-bar-style" />
<meta content="telephone=no" name="format-detection" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="format-detection" content="telephone=no" />
<meta name="renderer" content="webkit">
<meta name="author" content="" />
<meta name="author" content="${user.userDisplayName?if_exists}" />
<meta name="keywords" content="${keywords?default("Anatole")}"/>
<meta name="description" content="${description?default("Anatole")}" />
<link rel="shortcut icon" href="/anatole/source/images/favicon.png" type="image/x-icon" />
<link rel="shortcut icon" href="${options.anatole_style_favicon?default("/anatole/source/images/favicon.png")}" type="image/x-icon" />
<link href="/anatole/source/css/font-awesome.min.css" type="text/css" rel="stylesheet"/>
<link rel="stylesheet" href="/anatole/source/css/blog_basic.css?version=88107691fe">
<link href="/anatole/source/css/style.css" type="text/css" rel="stylesheet" />
@ -38,6 +38,19 @@
if (!urlstatus) {
$(".nav li a").eq(0).addClass('current');
}
<#if options.anatole_style_hitokoto?default("false")=="true">
var xhr = new XMLHttpRequest();
xhr.open('get', 'https://v1.hitokoto.cn');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var data = JSON.parse(xhr.responseText);
var yiyan = document.getElementById('yiyan');
yiyan.innerText = data.hitokoto+" -「"+data.from+"」";
}
};
xhr.send();
</#if>
</script>
</body>
</html>