mirror of https://github.com/halo-dev/halo
👽 1.支持归档信息根据年份和月份或年份归档,2.初步防御xss
parent
602059fb2a
commit
d7d0d991db
12
pom.xml
12
pom.xml
|
@ -120,6 +120,18 @@
|
|||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.9.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>18.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
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;
|
||||
|
@ -12,6 +16,8 @@ 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
|
||||
|
@ -63,4 +69,21 @@ public class MvcConfiguration 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,12 +90,19 @@ public interface PostRepository extends JpaRepository<Post,Long>{
|
|||
List<Post> findByPostDateBeforeAndPostStatusOrderByPostDateAsc(Date postDate,Integer postStatus);
|
||||
|
||||
/**
|
||||
* 查询文章归档信息
|
||||
* 查询文章归档信息 根据年份和月份
|
||||
*
|
||||
* @return list
|
||||
*/
|
||||
@Query(value = "select year(post_date) as year,month(post_date) as month,count(*) as count from halo_post where post_status=0 group by year(post_date),month(post_date)",nativeQuery = true)
|
||||
List<Object[]> findPostGroupByDate();
|
||||
@Query(value = "select year(post_date) as year,month(post_date) as month,count(*) as count from halo_post where post_status=0 group by year(post_date),month(post_date) order by year desc,month desc",nativeQuery = true)
|
||||
List<Object[]> findPostGroupByYearAndMonth();
|
||||
|
||||
/**
|
||||
* 查询文章归档信息 根据年份
|
||||
* @return
|
||||
*/
|
||||
@Query(value = "select year(post_date) as year,count(*) as count from halo_post where post_status=0 group by year(post_date) order by year desc",nativeQuery = true)
|
||||
List<Object[]> findPostGroupByYear();
|
||||
|
||||
/**
|
||||
* 根据年份和月份查询文章
|
||||
|
@ -107,6 +114,15 @@ public interface PostRepository extends JpaRepository<Post,Long>{
|
|||
@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)
|
||||
List<Post> findPostByYearAndMonth(@Param("year") String year,@Param("month") String month);
|
||||
|
||||
/**
|
||||
* 根据年份查询文章
|
||||
*
|
||||
* @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)
|
||||
List<Post> findPostByYear(@Param("year") String year);
|
||||
|
||||
/**
|
||||
* 根据年份和月份查询文章 分页
|
||||
*
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
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
|
||||
* 以及a标签的href,img标签的src,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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
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() {}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/>
|
||||
* 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
|
||||
* getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 覆盖getHeader方法,将参数名和参数值都做xss过滤。<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;
|
||||
}
|
||||
}
|
|
@ -139,11 +139,18 @@ public interface PostService {
|
|||
List<Post> findByPostDateBefore(Date postDate);
|
||||
|
||||
/**
|
||||
* 查询归档信息
|
||||
* 查询归档信息 根据年份和月份
|
||||
*
|
||||
* @return List
|
||||
*/
|
||||
List<Archive> findPostGroupByPostDate();
|
||||
List<Archive> findPostGroupByYearAndMonth();
|
||||
|
||||
/**
|
||||
* 查询归档信息 根据年份
|
||||
*
|
||||
* @return list
|
||||
*/
|
||||
List<Archive> findPostGroupByYear();
|
||||
|
||||
/**
|
||||
* 根据年份和月份查询文章
|
||||
|
@ -162,7 +169,15 @@ public interface PostService {
|
|||
* @param pageable pageable
|
||||
* @return page
|
||||
*/
|
||||
Page<Post> findPostByYearAndMonth(@Param("year") String year, @Param("month") String month, Pageable pageable);
|
||||
Page<Post> findPostByYearAndMonth(String year,String month, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 根据年份查询文章
|
||||
*
|
||||
* @param year year
|
||||
* @return list
|
||||
*/
|
||||
List<Post> findPostByYear(String year);
|
||||
|
||||
/**
|
||||
* 生成rss
|
||||
|
|
|
@ -205,13 +205,13 @@ public class PostServiceImpl implements PostService {
|
|||
|
||||
|
||||
/**
|
||||
* 查询归档信息
|
||||
* 查询归档信息 根据年份和月份
|
||||
*
|
||||
* @return List
|
||||
*/
|
||||
@Override
|
||||
public List<Archive> findPostGroupByPostDate() {
|
||||
List<Object[]> objects = postRepository.findPostGroupByDate();
|
||||
public List<Archive> findPostGroupByYearAndMonth() {
|
||||
List<Object[]> objects = postRepository.findPostGroupByYearAndMonth();
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
Archive archive = null;
|
||||
for(Object[] obj : objects){
|
||||
|
@ -225,6 +225,26 @@ public class PostServiceImpl implements PostService {
|
|||
return archives;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询归档信息 根据年份
|
||||
*
|
||||
* @return list
|
||||
*/
|
||||
@Override
|
||||
public List<Archive> findPostGroupByYear() {
|
||||
List<Object[]> objects = postRepository.findPostGroupByYear();
|
||||
List<Archive> archives = new ArrayList<>();
|
||||
Archive archive = null;
|
||||
for(Object[] obj : objects){
|
||||
archive = new Archive();
|
||||
archive.setYear(obj[0].toString());
|
||||
archive.setCount(obj[1].toString());
|
||||
archive.setPosts(this.findPostByYear(obj[0].toString()));
|
||||
archives.add(archive);
|
||||
}
|
||||
return archives;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据年份和月份查询文章
|
||||
*
|
||||
|
@ -237,6 +257,17 @@ public class PostServiceImpl implements PostService {
|
|||
return postRepository.findPostByYearAndMonth(year,month);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据年份查询文章
|
||||
*
|
||||
* @param year year
|
||||
* @return list
|
||||
*/
|
||||
@Override
|
||||
public List<Post> findPostByYear(String year) {
|
||||
return postRepository.findPostByYear(year);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据年份和月份索引文章
|
||||
* @param year year year
|
||||
|
|
|
@ -113,7 +113,7 @@ public class IndexController extends BaseController{
|
|||
model.addAttribute("menus",menus);
|
||||
|
||||
//归档数据,包含[year,month,count,List<Post>]
|
||||
List<Archive> archives = postService.findPostGroupByPostDate();
|
||||
List<Archive> archives = postService.findPostGroupByYearAndMonth();
|
||||
model.addAttribute("archives",archives);
|
||||
|
||||
//设置选项
|
||||
|
@ -186,7 +186,7 @@ public class IndexController extends BaseController{
|
|||
model.addAttribute("categories",categories);
|
||||
|
||||
//归档数据,包含[year,month,count,List<Post>]
|
||||
List<Archive> archives = postService.findPostGroupByPostDate();
|
||||
List<Archive> archives = postService.findPostGroupByYearAndMonth();
|
||||
|
||||
//菜单列表
|
||||
List<Menu> menus = menuService.findAllMenus();
|
||||
|
@ -287,7 +287,7 @@ public class IndexController extends BaseController{
|
|||
model.addAttribute("categories",categories);
|
||||
|
||||
//归档数据,包含[year,month,count,List<Post>]
|
||||
List<Archive> archives = postService.findPostGroupByPostDate();
|
||||
List<Archive> archives = postService.findPostGroupByYearAndMonth();
|
||||
model.addAttribute("archives",archives);
|
||||
|
||||
//设置选项
|
||||
|
@ -322,7 +322,7 @@ public class IndexController extends BaseController{
|
|||
model.addAttribute("menus",menus);
|
||||
|
||||
//归档数据,包含[year,month,count,List<Post>]
|
||||
List<Archive> archives = postService.findPostGroupByPostDate();
|
||||
List<Archive> archives = postService.findPostGroupByYearAndMonth();
|
||||
model.addAttribute("archives",archives);
|
||||
|
||||
|
||||
|
@ -379,9 +379,13 @@ public class IndexController extends BaseController{
|
|||
model.addAttribute("is_archives",true);
|
||||
|
||||
//包含[List<Post>,year,month,count]
|
||||
List<Archive> archives = postService.findPostGroupByPostDate();
|
||||
List<Archive> archives = postService.findPostGroupByYearAndMonth();
|
||||
model.addAttribute("archives",archives);
|
||||
|
||||
//包含[List<Post>,year,count]
|
||||
List<Archive> archivesLess = postService.findPostGroupByYear();
|
||||
model.addAttribute("archivesLess",archivesLess);
|
||||
|
||||
//用户信息
|
||||
User user = userService.findUser();
|
||||
model.addAttribute("user",user);
|
||||
|
@ -439,7 +443,7 @@ public class IndexController extends BaseController{
|
|||
model.addAttribute("menus",menus);
|
||||
|
||||
//归档数据,包含[year,month,count,List<Post>]
|
||||
List<Archive> archives = postService.findPostGroupByPostDate();
|
||||
List<Archive> archives = postService.findPostGroupByYearAndMonth();
|
||||
model.addAttribute("archives",archives);
|
||||
|
||||
//是否是归档页,用于判断输出链接
|
||||
|
|
|
@ -131,7 +131,11 @@
|
|||
</div>
|
||||
<div class="box-body">
|
||||
<div>
|
||||
<img src="/static/images/thumbnail.png" class="img-responsive img-thumbnail" id="selectImg" onclick="openAttach('selectImg')" style="cursor: pointer;">
|
||||
<#if post??>
|
||||
<img src="${post.postThumbnail?default("/static/images/thumbnail.png")}" class="img-responsive img-thumbnail" id="selectImg" onclick="openAttach('selectImg')" style="cursor: pointer;">
|
||||
<#else >
|
||||
<img src="/static/images/thumbnail.png" class="img-responsive img-thumbnail" id="selectImg" onclick="openAttach('selectImg')" style="cursor: pointer;">
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<a href="#pages" data-toggle="tab">自定义页面</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-content" style="padding: 0;">
|
||||
<div class="tab-pane active" id="internal">
|
||||
<div class="box-body table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
|
@ -79,36 +79,37 @@
|
|||
</div>
|
||||
<div class="tab-pane" id="pages">
|
||||
<div class="box-body table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>标题</th>
|
||||
<th>路径</th>
|
||||
<th>日期</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>友情链接</td>
|
||||
<td>/link</td>
|
||||
<th>日期</th>
|
||||
<td>
|
||||
<a href="/links" class="btn btn-info btn-sm " target="_blank">预览</a>
|
||||
<a href="/admin/page/links" class="btn btn-primary btn-sm ">配置</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>关于页面</td>
|
||||
<td>/about</td>
|
||||
<th>日期</th>
|
||||
<td>
|
||||
<a href="#" class="btn btn-info btn-sm " target="_blank">预览</a>
|
||||
<a href="/admin/page/about" class="btn btn-primary btn-sm ">配置</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
开发中...
|
||||
<#--<table class="table table-bordered table-hover">-->
|
||||
<#--<thead>-->
|
||||
<#--<tr>-->
|
||||
<#--<th>标题</th>-->
|
||||
<#--<th>路径</th>-->
|
||||
<#--<th>日期</th>-->
|
||||
<#--<th>操作</th>-->
|
||||
<#--</tr>-->
|
||||
<#--</thead>-->
|
||||
<#--<tbody>-->
|
||||
<#--<tr>-->
|
||||
<#--<td>友情链接</td>-->
|
||||
<#--<td>/link</td>-->
|
||||
<#--<th>日期</th>-->
|
||||
<#--<td>-->
|
||||
<#--<a href="/links" class="btn btn-info btn-xs " target="_blank">预览</a>-->
|
||||
<#--<a href="/admin/page/links" class="btn btn-primary btn-xs ">配置</a>-->
|
||||
<#--</td>-->
|
||||
<#--</tr>-->
|
||||
<#--<tr>-->
|
||||
<#--<td>关于页面</td>-->
|
||||
<#--<td>/about</td>-->
|
||||
<#--<th>日期</th>-->
|
||||
<#--<td>-->
|
||||
<#--<a href="#" class="btn btn-info btn-xs " target="_blank">预览</a>-->
|
||||
<#--<a href="/admin/page/about" class="btn btn-primary btn-xs ">配置</a>-->
|
||||
<#--</td>-->
|
||||
<#--</tr>-->
|
||||
<#--</tbody>-->
|
||||
<#--</table>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -57,53 +57,59 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#list posts.content as post>
|
||||
<#if posts.content?size gt 0>
|
||||
<#list posts.content as post>
|
||||
<tr>
|
||||
<td>${post.postTitle}</td>
|
||||
<td>
|
||||
<#if post.categories?size gt 0>
|
||||
<#list post.categories as cate>
|
||||
<label>${cate.cateName}</label>
|
||||
</#list>
|
||||
<#else >
|
||||
<label>无分类</label>
|
||||
</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#if post.tags?size gt 0>
|
||||
<#list post.tags as tag>
|
||||
<label>${tag.tagName}</label>
|
||||
</#list>
|
||||
<#else >
|
||||
<label>无标签</label>
|
||||
</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#if post.getComments()??>
|
||||
${post.getComments()?size}
|
||||
</#if>
|
||||
</td>
|
||||
<td>${post.postDate?if_exists?string("yyyy-MM-dd HH:mm")}</td>
|
||||
<td>
|
||||
<#switch post.postStatus>
|
||||
<#case 0>
|
||||
<a href="/archives/${post.postUrl}" class="btn btn-primary btn-xs " target="_blank">查看</a>
|
||||
<a href="/admin/posts/edit?postId=${post.postId}" class="btn btn-info btn-xs ">修改</a>
|
||||
<button class="btn btn-danger btn-xs " onclick="modelShow('/admin/posts/throw?postId=${post.postId}','确定移到回收站?')">丢弃</button>
|
||||
<#break >
|
||||
<#case 1>
|
||||
<a href="/admin/posts/view?postId=${post.postId}" class="btn btn-primary btn-xs " target="_blank">预览</a>
|
||||
<a href="/admin/posts/edit?postId=${post.postId}" class="btn btn-info btn-xs ">修改</a>
|
||||
<button class="btn btn-danger btn-xs " onclick="modelShow('/admin/posts/revert?postId=${post.postId}&status=1','确定发布该文章?')">发布</button>
|
||||
<#break >
|
||||
<#case 2>
|
||||
<a href="/admin/posts/revert?postId=${post.postId}&status=2" class="btn btn-primary btn-xs ">还原</a>
|
||||
<button class="btn btn-danger btn-xs " onclick="modelShow('/admin/posts/remove?postId=${post.postId}','确定永久删除?(不可逆)')">永久删除</button>
|
||||
<#break >
|
||||
</#switch>
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
<#else>
|
||||
<tr>
|
||||
<td>${post.postTitle}</td>
|
||||
<td>
|
||||
<#if post.categories?size gt 0>
|
||||
<#list post.categories as cate>
|
||||
${cate.cateName}
|
||||
</#list>
|
||||
<#else >
|
||||
无分类
|
||||
</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#if post.tags?size gt 0>
|
||||
<#list post.tags as tag>
|
||||
${tag.tagName}
|
||||
</#list>
|
||||
<#else >
|
||||
无标签
|
||||
</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#if post.getComments()??>
|
||||
${post.getComments()?size}
|
||||
</#if>
|
||||
</td>
|
||||
<td>${post.postDate?if_exists?string("yyyy-MM-dd HH:mm")}</td>
|
||||
<td>
|
||||
<#switch post.postStatus>
|
||||
<#case 0>
|
||||
<a href="/archives/${post.postUrl}" class="btn btn-primary btn-xs " target="_blank">查看</a>
|
||||
<a href="/admin/posts/edit?postId=${post.postId}" class="btn btn-info btn-xs ">修改</a>
|
||||
<button class="btn btn-danger btn-xs " onclick="modelShow('/admin/posts/throw?postId=${post.postId}','确定移到回收站?')">丢弃</button>
|
||||
<#break >
|
||||
<#case 1>
|
||||
<a href="/admin/posts/view?postId=${post.postId}" class="btn btn-primary btn-xs " target="_blank">预览</a>
|
||||
<a href="/admin/posts/edit?postId=${post.postId}" class="btn btn-info btn-xs ">修改</a>
|
||||
<button class="btn btn-danger btn-xs " onclick="modelShow('/admin/posts/revert?postId=${post.postId}&status=1','确定发布该文章?')">发布</button>
|
||||
<#break >
|
||||
<#case 2>
|
||||
<a href="/admin/posts/revert?postId=${post.postId}&status=2" class="btn btn-primary btn-xs ">还原</a>
|
||||
<button class="btn btn-danger btn-xs " onclick="modelShow('/admin/posts/remove?postId=${post.postId}','确定永久删除?(不可逆)')">永久删除</button>
|
||||
<#break >
|
||||
</#switch>
|
||||
</td>
|
||||
<th colspan="6" style="text-align: center">暂无文章</th>
|
||||
</tr>
|
||||
</#list>
|
||||
</#if>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -10,61 +10,131 @@
|
|||
<div class="content-wrapper">
|
||||
<link rel="stylesheet" href="/static/plugins/toast/css/jquery.toast.min.css">
|
||||
<style>
|
||||
.tag-cloud .label{
|
||||
padding: .3em .9em .45em;
|
||||
font-size: 100%;
|
||||
font-weight: 400;
|
||||
.tags {
|
||||
zoom: 1;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.tag-cloud{
|
||||
margin-bottom: 11px;
|
||||
margin-right: 3px;
|
||||
display: inline-block;
|
||||
.tags:before, .tags:after {
|
||||
content: '';
|
||||
display: table;
|
||||
}
|
||||
.tags:after {
|
||||
clear: both;
|
||||
}
|
||||
.tags li {
|
||||
position: relative;
|
||||
float: left;
|
||||
margin: 0 0 8px 12px;
|
||||
list-style: none;
|
||||
}
|
||||
.bg-color-1{
|
||||
background-color: #3c8dbc;
|
||||
.tags li:active {
|
||||
margin-top: 1px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.bg-color-2{
|
||||
background-color: #00a65a;
|
||||
.tags li:after {
|
||||
content: '';
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: -2px;
|
||||
width: 5px;
|
||||
height: 6px;
|
||||
opacity: .95;
|
||||
background: #eb6b22;
|
||||
border-radius: 3px 0 0 3px;
|
||||
-webkit-box-shadow: inset 1px 0 #99400e;
|
||||
box-shadow: inset 1px 0 #99400e;
|
||||
}
|
||||
.bg-color-3{
|
||||
background-color: #001f3f;
|
||||
.tags a, .tags span {
|
||||
display: block;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bg-color-4{
|
||||
background-color: #39cccc;
|
||||
.tags a {
|
||||
height: 26px;
|
||||
line-height: 23px;
|
||||
padding: 0 9px 0 8px;
|
||||
font-size: 12px;
|
||||
color: #555;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px white;
|
||||
background: #fafafa;
|
||||
border-width: 1px 0 1px 1px;
|
||||
border-style: solid;
|
||||
border-color: #dadada #d2d2d2 #c5c5c5;
|
||||
border-radius: 3px 0 0 3px;
|
||||
background-image: -webkit-linear-gradient(top, #fcfcfc, #f0f0f0);
|
||||
background-image: -moz-linear-gradient(top, #fcfcfc, #f0f0f0);
|
||||
background-image: -o-linear-gradient(top, #fcfcfc, #f0f0f0);
|
||||
background-image: linear-gradient(to bottom, #fcfcfc, #f0f0f0);
|
||||
-webkit-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.7), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.7), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.bg-color-5{
|
||||
background-color: #3d9970;
|
||||
.tags a:hover span {
|
||||
padding: 0 7px 0 6px;
|
||||
max-width: 40px;
|
||||
-webkit-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15), 1px 1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15), 1px 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.bg-color-6{
|
||||
background-color: #01ff70;
|
||||
.tags span {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 100%;
|
||||
z-index: 2;
|
||||
overflow: hidden;
|
||||
max-width: 0;
|
||||
height: 24px;
|
||||
line-height: 21px;
|
||||
padding: 0 0 0 2px;
|
||||
color: white;
|
||||
text-shadow: 0 -1px rgba(0, 0, 0, 0.3);
|
||||
background: #eb6b22;
|
||||
border: 1px solid;
|
||||
border-color: #d15813 #c85412 #bf5011;
|
||||
border-radius: 0 2px 2px 0;
|
||||
opacity: .95;
|
||||
background-image: -webkit-linear-gradient(top, #ed7b39, #df5e14);
|
||||
background-image: -moz-linear-gradient(top, #ed7b39, #df5e14);
|
||||
background-image: -o-linear-gradient(top, #ed7b39, #df5e14);
|
||||
background-image: linear-gradient(to bottom, #ed7b39, #df5e14);
|
||||
-webkit-transition: 0.3s ease-out;
|
||||
-moz-transition: 0.3s ease-out;
|
||||
-o-transition: 0.3s ease-out;
|
||||
transition: 0.3s ease-out;
|
||||
-webkit-transition-property: padding, max-width;
|
||||
-moz-transition-property: padding, max-width;
|
||||
-o-transition-property: padding, max-width;
|
||||
transition-property: padding, max-width;
|
||||
}
|
||||
.bg-color-7{
|
||||
background-color: #ff851b;
|
||||
|
||||
.green li:after {
|
||||
background: #65bb34;
|
||||
-webkit-box-shadow: inset 1px 0 #3a6b1e;
|
||||
box-shadow: inset 1px 0 #3a6b1e;
|
||||
}
|
||||
.bg-color-8{
|
||||
background-color: #f012be;
|
||||
.green span {
|
||||
background: #65bb34;
|
||||
border-color: #549b2b #4f9329 #4b8b27;
|
||||
background-image: -webkit-linear-gradient(top, #71ca3f, #5aa72e);
|
||||
background-image: -moz-linear-gradient(top, #71ca3f, #5aa72e);
|
||||
background-image: -o-linear-gradient(top, #71ca3f, #5aa72e);
|
||||
background-image: linear-gradient(to bottom, #71ca3f, #5aa72e);
|
||||
}
|
||||
.bg-color-9{
|
||||
background-color: #605ca8;
|
||||
|
||||
.blue li:after {
|
||||
background: #56a3d5;
|
||||
-webkit-box-shadow: inset 1px 0 #276f9e;
|
||||
box-shadow: inset 1px 0 #276f9e;
|
||||
}
|
||||
.bg-color-10{
|
||||
background-color: #d81b60;
|
||||
}
|
||||
.bg-color-11{
|
||||
background-color: #dd4b39;
|
||||
}
|
||||
.bg-color-12{
|
||||
background-color: #f39c12;
|
||||
}
|
||||
.bg-color-13{
|
||||
background-color: #00c0ef;
|
||||
}
|
||||
.bg-color-14{
|
||||
background-color: #0073b7;
|
||||
}
|
||||
.bg-color-15{
|
||||
background-color: #111111;
|
||||
.blue span {
|
||||
background: #56a3d5;
|
||||
border-color: #3591cd #318cc7 #2f86be;
|
||||
background-image: -webkit-linear-gradient(top, #6aaeda, #4298d0);
|
||||
background-image: -moz-linear-gradient(top, #6aaeda, #4298d0);
|
||||
background-image: -o-linear-gradient(top, #6aaeda, #4298d0);
|
||||
background-image: linear-gradient(to bottom, #6aaeda, #4298d0);
|
||||
}
|
||||
</style>
|
||||
<section class="content-header">
|
||||
|
@ -106,7 +176,7 @@
|
|||
<div class="box-footer">
|
||||
<button type="submit" class="btn btn-primary btn-sm ">确定${statusName}</button>
|
||||
<#if updateTag.posts?size = 0>
|
||||
<button type="submit" class="btn btn-danger btn-sm pull-right">删除</button>
|
||||
<a data-pjax="true" href="/admin/tag/remove?tagId=${updateTag.tagId}" class="btn btn-danger btn-sm pull-right">删除</a>
|
||||
</#if>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -137,54 +207,16 @@
|
|||
<h3 class="box-title">所有标签</h3>
|
||||
</div>
|
||||
<div class="box-body table-responsive">
|
||||
<#--<table class="table table-hover">-->
|
||||
<#--<thead>-->
|
||||
<#--<tr>-->
|
||||
<#--<th>名称</th>-->
|
||||
<#--<th>路径</th>-->
|
||||
<#--<th>总数</th>-->
|
||||
<#--<th>操作</th>-->
|
||||
<#--</tr>-->
|
||||
<#--</thead>-->
|
||||
<#--<tbody>-->
|
||||
<#--<#list tags as tag>-->
|
||||
<#--<tr>-->
|
||||
<#--<td>${tag.tagName}</td>-->
|
||||
<#--<td>${tag.tagUrl}</td>-->
|
||||
<#--<td>2</td>-->
|
||||
<#--<td>-->
|
||||
<#--<#if updateTag ?? && tag.tagId==updateTag.tagId>-->
|
||||
<#--<a class="btn btn-primary btn-xs " href="#" disabled>正在修改</a>-->
|
||||
<#--<#else >-->
|
||||
<#--<a data-pjax="true" class="btn btn-primary btn-xs " href="/admin/tag/edit?tagId=${tag.tagId}">修改</a>-->
|
||||
<#--</#if>-->
|
||||
<#--<button class="btn btn-danger btn-xs " onclick="modelShow('/admin/tag/remove?tagId=${tag.tagId}')">删除</button>-->
|
||||
<#--</td>-->
|
||||
<#--</tr>-->
|
||||
<#--</#list>-->
|
||||
<#--</tbody>-->
|
||||
<#--</table>-->
|
||||
<#list tags as tag>
|
||||
<div class="tag-cloud">
|
||||
<a class="tag-link" data-pjax="true" href="/admin/tag/edit?tagId=${tag.tagId}">
|
||||
<span class="label">${tag.tagName}( ${tag.posts?size} )</span>
|
||||
</a>
|
||||
</div>
|
||||
</#list>
|
||||
<script>
|
||||
var randomNum;
|
||||
var tagLabel = $('.tag-link');
|
||||
<#--for(var i = 0; i < ${tags?size}; i++) {-->
|
||||
<#--randomNum = Math.floor(Math. random() * 15 + 1);-->
|
||||
<#--tagLabel.children('.label').addClass("bg-color-"+randomNum);-->
|
||||
<#--tagLabel = tagLabel.next();-->
|
||||
<#--}-->
|
||||
|
||||
$(".label").each(function () {
|
||||
randomNum = Math.floor(Math. random() * 15 + 1);
|
||||
$(this).addClass("bg-color-"+randomNum);
|
||||
});
|
||||
</script>
|
||||
<ul class="tags blue">
|
||||
<#list tags as tag>
|
||||
<li>
|
||||
<a data-pjax="true" href="/admin/tag/edit?tagId=${tag.tagId}">
|
||||
${tag.tagName}
|
||||
<span>${tag.posts?size}</span>
|
||||
</a>
|
||||
</li>
|
||||
</#list>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
<div class="content">
|
||||
<div class="archive animated fadeInDown">
|
||||
<ul class="list-with-title">
|
||||
<#list archives as archive>
|
||||
<div class="listing-title">${archive.year}.${archive.month}</div>
|
||||
<#list archivesLess as archive>
|
||||
<div class="listing-title">${archive.year}</div>
|
||||
<ul class="listing">
|
||||
<#list archive.posts as post>
|
||||
<#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>
|
||||
|
|
Loading…
Reference in New Issue