feat: supports setting the post path type and path suffix. (#563)

* feat: build full path for category and tag.

* feat: build full path for post.

* feat: build full path for sheet.

* feat: build full path for post.

* feat: change post url for rss sitemap pages.

* feat: support set links/photos/journal page prefix.

* feat: support set internal sheet's title.

* refactor: build full path method.

* refactor: archives post full path.

* feat: support nextPageFullPath and prePageFullPath variable in pageable pages.

* Update CommentEventListener.java

* feat: make rss formats more standard.

* feat: add some useful freemarker variable.

* feat: add some useful freemarker variable.

* feat: add some useful freemarker variable.

* refactor: preview post and password post.
pull/590/head^2
Ryan Wang 2020-02-24 23:42:54 +08:00 committed by GitHub
parent 8f36a5d124
commit c224b68fbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1109 additions and 479 deletions

View File

@ -11,6 +11,7 @@ import run.halo.app.model.dto.post.BasePostDetailDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.dto.post.BasePostSimpleDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostPermalinkType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.PostContentParam;
import run.halo.app.model.params.PostParam;
@ -166,12 +167,32 @@ public class PostController {
public String preview(@PathVariable("postId") Integer postId) throws UnsupportedEncodingException {
Post post = postService.getById(postId);
post.setUrl(URLEncoder.encode(post.getUrl(), StandardCharsets.UTF_8.name()));
BasePostMinimalDTO postMinimalDTO = postService.convertToMinimal(post);
String token = IdUtil.simpleUUID();
// cache preview token
cacheStore.putAny(token, token, 10, TimeUnit.MINUTES);
StringBuilder previewUrl = new StringBuilder();
if (!optionService.isEnabledAbsolutePath()) {
previewUrl.append(optionService.getBlogBaseUrl());
}
previewUrl.append(postMinimalDTO.getFullPath());
if (optionService.getPostPermalinkType().equals(PostPermalinkType.ID)) {
previewUrl.append("&token=")
.append(token);
} else {
previewUrl.append("?token=")
.append(token);
}
// build preview post url and return
return String.format("%s/archives/%s?token=%s", optionService.getBlogBaseUrl(), URLEncoder.encode(post.getUrl(), StandardCharsets.UTF_8.name()), token);
return previewUrl.toString();
}
}

View File

@ -8,6 +8,7 @@ import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.model.dto.InternalSheetDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.SheetParam;
@ -119,12 +120,26 @@ public class SheetController {
public String preview(@PathVariable("sheetId") Integer sheetId) throws UnsupportedEncodingException {
Sheet sheet = sheetService.getById(sheetId);
sheet.setUrl(URLEncoder.encode(sheet.getUrl(), StandardCharsets.UTF_8.name()));
BasePostMinimalDTO sheetMinimalDTO = sheetService.convertToMinimal(sheet);
String token = IdUtil.simpleUUID();
// cache preview token
cacheStore.putAny(token, token, 10, TimeUnit.MINUTES);
StringBuilder previewUrl = new StringBuilder();
if (!optionService.isEnabledAbsolutePath()) {
previewUrl.append(optionService.getBlogBaseUrl());
}
previewUrl.append(sheetMinimalDTO.getFullPath())
.append("?token=")
.append(token);
// build preview post url and return
return String.format("%s/s/%s?token=%s", optionService.getBlogBaseUrl(), URLEncoder.encode(sheet.getUrl(), StandardCharsets.UTF_8.name()), token);
return previewUrl.toString();
}
}

View File

@ -1,69 +0,0 @@
package run.halo.app.controller.content;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
import java.util.concurrent.TimeUnit;
/**
* Blog archive page controller
*
* @author ryanwang
* @author guqing
* @author evanwang
* @date 2019-03-17
*/
@Slf4j
@Controller
@RequestMapping(value = "/archives")
public class ContentArchiveController {
private final PostService postService;
private final OptionService optionService;
private final StringCacheStore cacheStore;
public ContentArchiveController(PostService postService,
OptionService optionService,
StringCacheStore cacheStore) {
this.postService = postService;
this.optionService = optionService;
this.cacheStore = cacheStore;
}
@GetMapping(value = "{url:.*}/password")
public String password(@PathVariable("url") String url,
Model model) {
model.addAttribute("url", url);
return "common/template/post_password";
}
@PostMapping(value = "{url:.*}/password")
@CacheLock(traceRequest = true, expired = 2)
public String password(@PathVariable("url") String url,
@RequestParam(value = "password") String password) {
Post post = postService.getBy(PostStatus.INTIMATE, url);
if (password.equals(post.getPassword())) {
String token = IdUtil.simpleUUID();
cacheStore.putAny(token, token, 10, TimeUnit.SECONDS);
String redirect = String.format("%s/archives/%s?token=%s", optionService.getBlogBaseUrl(), post.getUrl(), token);
return "redirect:" + redirect;
} else {
String redirect = String.format("%s/archives/%s/password", optionService.getBlogBaseUrl(), post.getUrl());
return "redirect:" + redirect;
}
}
}

View File

@ -1,24 +1,28 @@
package run.halo.app.controller.content;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import run.halo.app.controller.content.model.CategoryModel;
import run.halo.app.controller.content.model.PostModel;
import run.halo.app.controller.content.model.SheetModel;
import run.halo.app.controller.content.model.TagModel;
import org.springframework.web.bind.annotation.*;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.controller.content.model.*;
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.enums.PostPermalinkType;
import run.halo.app.model.properties.PermalinkProperties;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
import run.halo.app.service.SheetService;
import run.halo.app.service.ThemeService;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
/**
* @author ryanwang
@ -37,41 +41,60 @@ public class ContentContentController {
private final TagModel tagModel;
private final JournalModel journalModel;
private final PhotoModel photoModel;
private final OptionService optionService;
private final PostService postService;
private final SheetService sheetService;
private final ThemeService themeService;
private final StringCacheStore cacheStore;
public ContentContentController(PostModel postModel,
SheetModel sheetModel,
CategoryModel categoryModel,
TagModel tagModel,
JournalModel journalModel,
PhotoModel photoModel,
OptionService optionService,
PostService postService,
SheetService sheetService) {
SheetService sheetService,
ThemeService themeService,
StringCacheStore cacheStore) {
this.postModel = postModel;
this.sheetModel = sheetModel;
this.categoryModel = categoryModel;
this.tagModel = tagModel;
this.journalModel = journalModel;
this.photoModel = photoModel;
this.optionService = optionService;
this.postService = postService;
this.sheetService = sheetService;
this.themeService = themeService;
this.cacheStore = cacheStore;
}
@GetMapping("{prefix}")
public String content(@PathVariable("prefix") String prefix,
Model model) {
String archivesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.ARCHIVES_PREFIX, String.class, PermalinkProperties.ARCHIVES_PREFIX.defaultValue());
String categoriesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.CATEGORIES_PREFIX, String.class, PermalinkProperties.CATEGORIES_PREFIX.defaultValue());
String tagsPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.TAGS_PREFIX, String.class, PermalinkProperties.TAGS_PREFIX.defaultValue());
if (archivesPrefix.equals(prefix)) {
if (optionService.getArchivesPrefix().equals(prefix)) {
return postModel.list(1, model, "is_archives", "archives");
} else if (categoriesPrefix.equals(prefix)) {
} else if (optionService.getCategoriesPrefix().equals(prefix)) {
return categoryModel.list(model);
} else if (tagsPrefix.equals(prefix)) {
} else if (optionService.getTagsPrefix().equals(prefix)) {
return tagModel.list(model);
} else if (optionService.getJournalsPrefix().equals(prefix)) {
return journalModel.list(1, model);
} else if (optionService.getPhotosPrefix().equals(prefix)) {
return photoModel.list(1, model);
} else if (optionService.getLinksPrefix().equals(prefix)) {
model.addAttribute("is_links", true);
return themeService.render("links");
} else {
throw new NotFoundException("Not Found");
}
@ -81,9 +104,12 @@ public class ContentContentController {
public String content(@PathVariable("prefix") String prefix,
@PathVariable(value = "page") Integer page,
Model model) {
String archivesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.ARCHIVES_PREFIX, String.class, PermalinkProperties.ARCHIVES_PREFIX.defaultValue());
if (archivesPrefix.equals(prefix)) {
if (optionService.getArchivesPrefix().equals(prefix)) {
return postModel.list(page, model, "is_archives", "archives");
} else if (optionService.getJournalsPrefix().equals(prefix)) {
return journalModel.list(page, model);
} else if (optionService.getPhotosPrefix().equals(prefix)) {
return photoModel.list(page, model);
} else {
throw new NotFoundException("Not Found");
}
@ -95,20 +121,16 @@ public class ContentContentController {
@RequestParam(value = "token", required = false) String token,
Model model) {
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
String archivesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.ARCHIVES_PREFIX, String.class, PermalinkProperties.ARCHIVES_PREFIX.defaultValue());
String sheetPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.SHEET_PREFIX, String.class, PermalinkProperties.SHEET_PREFIX.defaultValue());
String categoriesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.CATEGORIES_PREFIX, String.class, PermalinkProperties.CATEGORIES_PREFIX.defaultValue());
String tagsPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.TAGS_PREFIX, String.class, PermalinkProperties.TAGS_PREFIX.defaultValue());
if (postPermalinkType.equals(PostPermalinkType.DEFAULT) && archivesPrefix.equals(prefix)) {
if (postPermalinkType.equals(PostPermalinkType.DEFAULT) && optionService.getArchivesPrefix().equals(prefix)) {
Post post = postService.getByUrl(url);
return postModel.content(post, token, model);
} else if (sheetPrefix.equals(prefix)) {
} else if (optionService.getSheetPrefix().equals(prefix)) {
Sheet sheet = sheetService.getByUrl(url);
return sheetModel.content(sheet, token, model);
} else if (categoriesPrefix.equals(prefix)) {
} else if (optionService.getCategoriesPrefix().equals(prefix)) {
return categoryModel.listPost(model, url, 1);
} else if (tagsPrefix.equals(prefix)) {
} else if (optionService.getTagsPrefix().equals(prefix)) {
return tagModel.listPost(model, url, 1);
} else {
throw new NotFoundException("Not Found");
@ -120,12 +142,9 @@ public class ContentContentController {
@PathVariable("url") String url,
@PathVariable("page") Integer page,
Model model) {
String categoriesPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.CATEGORIES_PREFIX, String.class, PermalinkProperties.CATEGORIES_PREFIX.defaultValue());
String tagsPrefix = optionService.getByPropertyOrDefault(PermalinkProperties.TAGS_PREFIX, String.class, PermalinkProperties.TAGS_PREFIX.defaultValue());
if (categoriesPrefix.equals(prefix)) {
if (optionService.getCategoriesPrefix().equals(prefix)) {
return categoryModel.listPost(model, url, page);
} else if (tagsPrefix.equals(prefix)) {
} else if (optionService.getTagsPrefix().equals(prefix)) {
return tagModel.listPost(model, url, page);
} else {
throw new NotFoundException("Not Found");
@ -162,4 +181,38 @@ public class ContentContentController {
throw new NotFoundException("Not Found");
}
}
@PostMapping(value = "archives/{url:.*}/password")
@CacheLock(traceRequest = true, expired = 2)
public String password(@PathVariable("url") String url,
@RequestParam(value = "password") String password) throws UnsupportedEncodingException {
Post post = postService.getBy(PostStatus.INTIMATE, url);
post.setUrl(URLEncoder.encode(post.getUrl(), StandardCharsets.UTF_8.name()));
BasePostMinimalDTO postMinimalDTO = postService.convertToMinimal(post);
StringBuilder redirectUrl = new StringBuilder();
if (!optionService.isEnabledAbsolutePath()) {
redirectUrl.append(optionService.getBlogBaseUrl());
}
redirectUrl.append(postMinimalDTO.getFullPath());
if (password.equals(post.getPassword())) {
String token = IdUtil.simpleUUID();
cacheStore.putAny(token, token, 10, TimeUnit.SECONDS);
if (optionService.getPostPermalinkType().equals(PostPermalinkType.ID)) {
redirectUrl.append("&token=")
.append(token);
} else {
redirectUrl.append("?token=")
.append(token);
}
}
return "redirect:" + redirectUrl;
}
}

View File

@ -1,86 +0,0 @@
package run.halo.app.controller.content;
import cn.hutool.core.util.PageUtil;
import lombok.extern.slf4j.Slf4j;
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 org.springframework.data.web.SortDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import run.halo.app.model.entity.Journal;
import run.halo.app.model.enums.JournalType;
import run.halo.app.service.JournalService;
import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* Blog journal page controller
*
* @author ryanwang
* @date 2019-05-04
*/
@Slf4j
@Controller
@RequestMapping(value = "/journals")
public class ContentJournalController {
private final JournalService journalService;
private final OptionService optionService;
private final ThemeService themeService;
public ContentJournalController(JournalService journalService,
OptionService optionService,
ThemeService themeService) {
this.journalService = journalService;
this.optionService = optionService;
this.themeService = themeService;
}
/**
* Render journal page.
*
* @param model model
* @return template path: themes/{theme}/journals.ftl
*/
@GetMapping
public String journals(Model model) {
return this.journals(model, 1, Sort.by(DESC, "createTime"));
}
/**
* Render journal page.
*
* @param model model
* @param page current page number
* @return template path: themes/{theme}/journals.ftl
*/
@GetMapping(value = "page/{page}")
public String journals(Model model,
@PathVariable(value = "page") Integer page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
log.debug("Requested journal page, sort info: [{}]", sort);
int pageSize = optionService.getPostPageSize();
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, pageSize, sort);
Page<Journal> journals = journalService.pageBy(JournalType.PUBLIC, pageable);
int[] rainbow = PageUtil.rainbow(page, journals.getTotalPages(), 3);
model.addAttribute("is_journals", true);
model.addAttribute("journals", journalService.convertToCmtCountDto(journals));
model.addAttribute("rainbow", rainbow);
return themeService.render("journals");
}
}

View File

@ -73,11 +73,49 @@ public class ContentSearchController {
final Page<PostListVO> posts = postService.convertToListVo(postPage);
// TODO remove this variable
final int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
// Next page and previous page url.
StringBuilder nextPageFullPath = new StringBuilder();
StringBuilder prePageFullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
nextPageFullPath.append(optionService.getBlogBaseUrl())
.append("/");
prePageFullPath.append(optionService.getBlogBaseUrl())
.append("/");
} else {
nextPageFullPath.append("/");
prePageFullPath.append("/");
}
nextPageFullPath.append("search");
prePageFullPath.append("search");
nextPageFullPath.append("/page/")
.append(posts.getNumber() + 2)
.append(optionService.getPathSuffix())
.append("?keyword=")
.append(keyword);
if (posts.getNumber() == 1) {
prePageFullPath.append("?keyword=")
.append(keyword);
} else {
prePageFullPath.append("/page/")
.append(posts.getNumber())
.append(optionService.getPathSuffix())
.append("?keyword=")
.append(keyword);
}
model.addAttribute("is_search", true);
model.addAttribute("keyword", keyword);
model.addAttribute("posts", posts);
model.addAttribute("rainbow", rainbow);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString());
return themeService.render("search");
}
}

View File

@ -1,77 +0,0 @@
package run.halo.app.controller.content;
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 org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import run.halo.app.model.dto.PhotoDTO;
import run.halo.app.service.PhotoService;
import run.halo.app.service.ThemeService;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* Content sheet controller.
*
* @author ryanwang
* @author evanwang
* @date 2019-03-21
*/
@Controller
public class ContentSheetController {
private final ThemeService themeService;
private final PhotoService photoService;
public ContentSheetController(ThemeService themeService,
PhotoService photoService) {
this.themeService = themeService;
this.photoService = photoService;
}
/**
* Render photo page
*
* @return template path: themes/{theme}/photos.ftl
*/
@GetMapping(value = "/photos")
public String photos(Model model,
@RequestParam(value = "size", required = false, defaultValue = "10") Integer size) {
return photos(model, 1, size);
}
/**
* Render photo page
*
* @param model model
* @param page current page
* @param size current page size
* @return template path: themes/{theme}/photos.ftl
*/
@GetMapping(value = "/photos/page/{page}")
public String photos(Model model,
@PathVariable(value = "page") Integer page,
@RequestParam(value = "size", required = false, defaultValue = "10") Integer size) {
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, size, Sort.by(DESC, "createTime"));
Page<PhotoDTO> photos = photoService.pageBy(pageable);
model.addAttribute("photos", photos);
return themeService.render("photos");
}
/**
* Render links page
*
* @return template path: themes/{theme}/links.ftl
*/
@GetMapping(value = "/links")
public String links() {
return themeService.render("links");
}
}

View File

@ -7,6 +7,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.model.dto.CategoryDTO;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostStatus;
@ -50,16 +51,37 @@ public class CategoryModel {
public String listPost(Model model, String slugName, Integer page) {
// Get category by slug name
final Category category = categoryService.getBySlugNameOfNonNull(slugName);
CategoryDTO categoryDTO = categoryService.convertTo(category);
final Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), Sort.by(DESC, "createTime"));
Page<Post> postPage = postCategoryService.pagePostBy(category.getId(), PostStatus.PUBLISHED, pageable);
Page<PostListVO> posts = postService.convertToListVo(postPage);
// TODO remove this variable
final int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
// Next page and previous page url.
StringBuilder nextPageFullPath = new StringBuilder(categoryDTO.getFullPath());
StringBuilder prePageFullPath = new StringBuilder(categoryDTO.getFullPath());
nextPageFullPath.append("/page/")
.append(posts.getNumber() + 2)
.append(optionService.getPathSuffix());
if (posts.getNumber() == 1) {
prePageFullPath.append("/");
} else {
prePageFullPath.append("/page/")
.append(posts.getNumber())
.append(optionService.getPathSuffix());
}
model.addAttribute("is_category", true);
model.addAttribute("posts", posts);
model.addAttribute("rainbow", rainbow);
model.addAttribute("category", category);
model.addAttribute("category", categoryDTO);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString());
return themeService.render("category");
}
}

View File

@ -0,0 +1,87 @@
package run.halo.app.controller.content.model;
import cn.hutool.core.util.PageUtil;
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 org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.model.entity.Journal;
import run.halo.app.model.enums.JournalType;
import run.halo.app.model.properties.SheetProperties;
import run.halo.app.service.JournalService;
import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* @author ryanwang
* @date 2020-02-11
*/
@Component
public class JournalModel {
private final JournalService journalService;
private final OptionService optionService;
private final ThemeService themeService;
public JournalModel(JournalService journalService,
OptionService optionService,
ThemeService themeService) {
this.journalService = journalService;
this.optionService = optionService;
this.themeService = themeService;
}
public String list(Integer page, Model model) {
int pageSize = optionService.getByPropertyOrDefault(SheetProperties.JOURNALS_PAGE_SIZE, Integer.class, Integer.parseInt(SheetProperties.JOURNALS_PAGE_SIZE.defaultValue()));
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(DESC, "createTime"));
Page<Journal> journals = journalService.pageBy(JournalType.PUBLIC, pageable);
// TODO remove this variable
int[] rainbow = PageUtil.rainbow(page, journals.getTotalPages(), 3);
// Next page and previous page url.
StringBuilder nextPageFullPath = new StringBuilder();
StringBuilder prePageFullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
nextPageFullPath.append(optionService.getBlogBaseUrl())
.append("/");
prePageFullPath.append(optionService.getBlogBaseUrl())
.append("/");
} else {
nextPageFullPath.append("/");
prePageFullPath.append("/");
}
nextPageFullPath.append(optionService.getJournalsPrefix());
prePageFullPath.append(optionService.getJournalsPrefix());
nextPageFullPath.append("/page/")
.append(journals.getNumber() + 2)
.append(optionService.getPathSuffix());
if (journals.getNumber() == 1) {
prePageFullPath.append("/");
} else {
prePageFullPath.append("/page/")
.append(journals.getNumber())
.append(optionService.getPathSuffix());
}
model.addAttribute("is_journals", true);
model.addAttribute("journals", journalService.convertToCmtCountDto(journals));
model.addAttribute("rainbow", rainbow);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString());
return themeService.render("journals");
}
}

View File

@ -0,0 +1,81 @@
package run.halo.app.controller.content.model;
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 org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.model.dto.PhotoDTO;
import run.halo.app.model.properties.SheetProperties;
import run.halo.app.service.OptionService;
import run.halo.app.service.PhotoService;
import run.halo.app.service.ThemeService;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* @author ryanwang
* @date 2020-02-11
*/
@Component
public class PhotoModel {
private final PhotoService photoService;
private final ThemeService themeService;
private final OptionService optionService;
public PhotoModel(PhotoService photoService,
ThemeService themeService,
OptionService optionService) {
this.photoService = photoService;
this.themeService = themeService;
this.optionService = optionService;
}
public String list(Integer page, Model model) {
int pageSize = optionService.getByPropertyOrDefault(SheetProperties.PHOTOS_PAGE_SIZE, Integer.class, Integer.parseInt(SheetProperties.PHOTOS_PAGE_SIZE.defaultValue()));
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(DESC, "createTime"));
Page<PhotoDTO> photos = photoService.pageBy(pageable);
// Next page and previous page url.
StringBuilder nextPageFullPath = new StringBuilder();
StringBuilder prePageFullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
nextPageFullPath.append(optionService.getBlogBaseUrl())
.append("/");
prePageFullPath.append(optionService.getBlogBaseUrl())
.append("/");
} else {
nextPageFullPath.append("/");
prePageFullPath.append("/");
}
nextPageFullPath.append(optionService.getPhotosPrefix());
prePageFullPath.append(optionService.getPhotosPrefix());
nextPageFullPath.append("/page/")
.append(photos.getNumber() + 2)
.append(optionService.getPathSuffix());
if (photos.getNumber() == 1) {
prePageFullPath.append("/");
} else {
prePageFullPath.append("/page/")
.append(photos.getNumber())
.append(optionService.getPathSuffix());
}
model.addAttribute("is_photos", true);
model.addAttribute("photos", photos);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString());
return themeService.render("photos");
}
}

View File

@ -72,26 +72,26 @@ public class PostModel {
public String content(Post post, String token, Model model) {
if (post.getStatus().equals(PostStatus.INTIMATE) && StringUtils.isEmpty(token)) {
String redirect = String
.format("%s/archives/%s/password", optionService.getBlogBaseUrl(),
post.getUrl());
return "redirect:" + redirect;
model.addAttribute("url", post.getUrl());
return "common/template/post_password";
}
if (!StringUtils.isEmpty(token)) {
if (StringUtils.isEmpty(token)) {
post = postService.getBy(PostStatus.PUBLISHED, post.getUrl());
} else {
// verify token
String cachedToken = cacheStore.getAny(token, String.class)
.orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限"));
String cachedToken = cacheStore.getAny(token, String.class).orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限"));
if (!cachedToken.equals(token)) {
throw new ForbiddenException("您没有该文章的访问权限");
}
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
}
postService.publishVisitEvent(post.getId());
AdjacentPostVO adjacentPostVO = postService.getAdjacentPosts(post);
adjacentPostVO.getOptionalPrePost().ifPresent(prePost -> model.addAttribute("prePost", prePost));
adjacentPostVO.getOptionalNextPost().ifPresent(nextPost -> model.addAttribute("nextPost", nextPost));
adjacentPostVO.getOptionalPrePost().ifPresent(prePost -> model.addAttribute("prePost", postService.convertToDetailVo(prePost)));
adjacentPostVO.getOptionalNextPost().ifPresent(nextPost -> model.addAttribute("nextPost", postService.convertToDetailVo(nextPost)));
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
List<Tag> tags = postTagService.listTagsBy(post.getId());
@ -122,12 +122,36 @@ public class PostModel {
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostListVO> posts = postService.convertToListVo(postPage);
// TODO remove this variable
int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
// Next page and previous page url.
StringBuilder nextPageFullPath = new StringBuilder();
StringBuilder prePageFullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
nextPageFullPath.append(optionService.getBlogBaseUrl());
prePageFullPath.append(optionService.getBlogBaseUrl());
}
nextPageFullPath.append("/page/")
.append(posts.getNumber() + 2)
.append(optionService.getPathSuffix());
if (posts.getNumber() == 1) {
prePageFullPath.append("/");
} else {
prePageFullPath.append("/page/")
.append(posts.getNumber())
.append(optionService.getPathSuffix());
}
model.addAttribute(decide, true);
model.addAttribute("posts", posts);
model.addAttribute("rainbow", rainbow);
model.addAttribute("pageRainbow", rainbow);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString());
return themeService.render(template);
}
}

View File

@ -7,6 +7,7 @@ import org.springframework.ui.Model;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.vo.SheetDetailVO;
import run.halo.app.service.SheetService;
@ -35,17 +36,19 @@ public class SheetModel {
}
public String content(Sheet sheet, String token, Model model) {
if (!StringUtils.isEmpty(token)) {
// render markdown to html when preview sheet
sheet.setFormatContent(MarkdownUtils.renderHtml(sheet.getOriginalContent()));
if (StringUtils.isEmpty(token)) {
sheet = sheetService.getBy(PostStatus.PUBLISHED, sheet.getUrl());
} else {
// verify token
String cachedToken = cacheStore.getAny(token, String.class).orElseThrow(() -> new ForbiddenException("您没有该页面的访问权限"));
if (!cachedToken.equals(token)) {
throw new ForbiddenException("您没有该页面的访问权限");
}
// render markdown to html when preview sheet
sheet.setFormatContent(MarkdownUtils.renderHtml(sheet.getOriginalContent()));
}
sheetService.publishVisitEvent(sheet.getId());
SheetDetailVO sheetDetailVO = sheetService.convertToDetailVo(sheet);

View File

@ -7,6 +7,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.model.dto.TagDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.enums.PostStatus;
@ -50,16 +51,37 @@ public class TagModel {
public String listPost(Model model, String slugName, Integer page) {
// Get tag by slug name
final Tag tag = tagService.getBySlugNameOfNonNull(slugName);
TagDTO tagDTO = tagService.convertTo(tag);
final Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), Sort.by(DESC, "createTime"));
Page<Post> postPage = postTagService.pagePostsBy(tag.getId(), PostStatus.PUBLISHED, pageable);
Page<PostListVO> posts = postService.convertToListVo(postPage);
// TODO remove this variable
final int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
// Next page and previous page url.
StringBuilder nextPageFullPath = new StringBuilder(tagDTO.getFullPath());
StringBuilder prePageFullPath = new StringBuilder(tagDTO.getFullPath());
nextPageFullPath.append("/page/")
.append(posts.getNumber() + 2)
.append(optionService.getPathSuffix());
if (posts.getNumber() == 1) {
prePageFullPath.append("/");
} else {
prePageFullPath.append("/page/")
.append(posts.getNumber())
.append(optionService.getPathSuffix());
}
model.addAttribute("is_tag", true);
model.addAttribute("posts", posts);
model.addAttribute("rainbow", rainbow);
model.addAttribute("tag", tag);
model.addAttribute("tag", tagDTO);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString());
return themeService.render("tag");
}
}

View File

@ -4,11 +4,13 @@ import freemarker.core.Environment;
import freemarker.template.*;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import run.halo.app.model.entity.Category;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.CategoryService;
import run.halo.app.service.PostCategoryService;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.springframework.data.domain.Sort.Direction.DESC;
@ -44,7 +46,8 @@ public class CategoryTagDirective implements TemplateDirectiveModel {
break;
case "listByPostId":
Integer postId = Integer.parseInt(params.get("postId").toString());
env.setVariable("categories", builder.build().wrap(postCategoryService.listCategoriesBy(postId)));
List<Category> categories = postCategoryService.listCategoriesBy(postId);
env.setVariable("categories", builder.build().wrap(categoryService.convertTo(categories)));
break;
case "count":
env.setVariable("count", builder.build().wrap(categoryService.count()));

View File

@ -3,6 +3,7 @@ package run.halo.app.core.freemarker.tag;
import freemarker.core.Environment;
import freemarker.template.*;
import org.springframework.stereotype.Component;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.PostCategoryService;
@ -10,6 +11,7 @@ import run.halo.app.service.PostService;
import run.halo.app.service.PostTagService;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
@ -45,7 +47,8 @@ public class PostTagDirective implements TemplateDirectiveModel {
switch (method) {
case "latest":
int top = Integer.parseInt(params.get("top").toString());
env.setVariable("posts", builder.build().wrap(postService.listLatest(top)));
List<Post> posts = postService.listLatest(top);
env.setVariable("posts", builder.build().wrap(postService.convertToListVo(posts)));
break;
case "count":
env.setVariable("count", builder.build().wrap(postService.countByStatus(PostStatus.PUBLISHED)));
@ -62,19 +65,19 @@ public class PostTagDirective implements TemplateDirectiveModel {
break;
case "listByCategoryId":
Integer categoryId = Integer.parseInt(params.get("categoryId").toString());
env.setVariable("posts", builder.build().wrap(postCategoryService.listPostBy(categoryId, PostStatus.PUBLISHED)));
env.setVariable("posts", builder.build().wrap(postService.convertToListVo(postCategoryService.listPostBy(categoryId, PostStatus.PUBLISHED))));
break;
case "listByCategorySlug":
String categorySlug = params.get("categorySlug").toString();
env.setVariable("posts", builder.build().wrap(postCategoryService.listPostBy(categorySlug, PostStatus.PUBLISHED)));
env.setVariable("posts", builder.build().wrap(postService.convertToListVo(postCategoryService.listPostBy(categorySlug, PostStatus.PUBLISHED))));
break;
case "listByTagId":
Integer tagId = Integer.parseInt(params.get("tagId").toString());
env.setVariable("posts", builder.build().wrap(postTagService.listPostsBy(tagId, PostStatus.PUBLISHED)));
env.setVariable("posts", builder.build().wrap(postService.convertToListVo(postTagService.listPostsBy(tagId, PostStatus.PUBLISHED))));
break;
case "listByTagSlug":
String tagSlug = params.get("tagSlug").toString();
env.setVariable("posts", builder.build().wrap(postTagService.listPostsBy(tagSlug, PostStatus.PUBLISHED)));
env.setVariable("posts", builder.build().wrap(postService.convertToListVo(postTagService.listPostsBy(tagSlug, PostStatus.PUBLISHED))));
break;
default:
break;

View File

@ -4,11 +4,13 @@ import freemarker.core.Environment;
import freemarker.template.*;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.PostTagService;
import run.halo.app.service.TagService;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.springframework.data.domain.Sort.Direction.DESC;
@ -44,7 +46,8 @@ public class TagTagDirective implements TemplateDirectiveModel {
break;
case "listByPostId":
Integer postId = Integer.parseInt(params.get("postId").toString());
env.setVariable("tags", builder.build().wrap(postTagService.listTagsBy(postId)));
List<Tag> tags = postTagService.listTagsBy(postId);
env.setVariable("tags", builder.build().wrap(tagService.convertTo(tags)));
break;
case "count":
env.setVariable("count", builder.build().wrap(tagService.count()));

View File

@ -10,6 +10,7 @@ import org.springframework.stereotype.Component;
import run.halo.app.event.comment.CommentNewEvent;
import run.halo.app.event.comment.CommentReplyEvent;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.mail.MailService;
import run.halo.app.model.entity.*;
import run.halo.app.model.properties.CommentProperties;
@ -87,12 +88,9 @@ public class CommentEventListener {
log.debug("Got post comment: [{}]", postComment);
Post post = postService.getById(postComment.getPostId());
BasePostMinimalDTO post = postService.convertToMinimal(postService.getById(postComment.getPostId()));
StrBuilder url = new StrBuilder(optionService.getBlogBaseUrl())
.append("/archives/")
.append(post.getUrl());
data.put("url", url.toString());
data.put("url", post.getFullPath());
data.put("page", post.getTitle());
data.put("author", postComment.getAuthor());
data.put("content", postComment.getContent());
@ -106,12 +104,9 @@ public class CommentEventListener {
log.debug("Got sheet comment: [{}]", sheetComment);
Sheet sheet = sheetService.getById(sheetComment.getPostId());
BasePostMinimalDTO sheet = sheetService.convertToMinimal(sheetService.getById(sheetComment.getPostId()));
StrBuilder url = new StrBuilder(optionService.getBlogBaseUrl())
.append("/s/")
.append(sheet.getUrl());
data.put("url", url.toString());
data.put("url", sheet.getFullPath());
data.put("page", sheet.getTitle());
data.put("author", sheetComment.getAuthor());
data.put("content", sheetComment.getContent());
@ -127,7 +122,8 @@ public class CommentEventListener {
Journal journal = journalService.getById(journalComment.getPostId());
StrBuilder url = new StrBuilder(optionService.getBlogBaseUrl())
.append("/journals");
.append("/")
.append(optionService.getJournalsPrefix());
data.put("url", url.toString());
data.put("page", journal.getCreateTime());
data.put("author", journalComment.getAuthor());
@ -180,13 +176,9 @@ public class CommentEventListener {
baseAuthorEmail = baseComment.getEmail();
Post post = postService.getById(postComment.getPostId());
BasePostMinimalDTO post = postService.convertToMinimal(postService.getById(postComment.getPostId()));
StrBuilder url = new StrBuilder(optionService.getBlogBaseUrl())
.append("/archives/")
.append(post.getUrl());
data.put("url", url);
data.put("url", post.getFullPath());
data.put("page", post.getTitle());
data.put("baseAuthor", baseComment.getAuthor());
data.put("baseContent", baseComment.getContent());
@ -214,13 +206,9 @@ public class CommentEventListener {
baseAuthorEmail = baseComment.getEmail();
Sheet sheet = sheetService.getById(sheetComment.getPostId());
BasePostMinimalDTO sheet = sheetService.convertToMinimal(sheetService.getById(sheetComment.getPostId()));
StrBuilder url = new StrBuilder(optionService.getBlogBaseUrl())
.append("/s/")
.append(sheet.getUrl());
data.put("url", url);
data.put("url", sheet.getFullPath());
data.put("page", sheet.getTitle());
data.put("baseAuthor", baseComment.getAuthor());
data.put("baseContent", baseComment.getContent());
@ -250,7 +238,8 @@ public class CommentEventListener {
Journal journal = journalService.getById(journalComment.getPostId());
StrBuilder url = new StrBuilder(optionService.getBlogBaseUrl())
.append("/journals");
.append("/")
.append(optionService.getJournalsPrefix());
data.put("url", url);
data.put("page", journal.getContent());
data.put("baseAuthor", baseComment.getAuthor());

View File

@ -13,7 +13,8 @@ import run.halo.app.event.theme.ThemeActivatedEvent;
import run.halo.app.event.theme.ThemeUpdatedEvent;
import run.halo.app.event.user.UserUpdatedEvent;
import run.halo.app.handler.theme.config.support.ThemeProperty;
import run.halo.app.model.properties.OtherProperties;
import run.halo.app.model.properties.BlogProperties;
import run.halo.app.model.properties.SeoProperties;
import run.halo.app.model.support.HaloConst;
import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService;
@ -99,9 +100,30 @@ public class FreemarkerConfigAwareListener {
}
private void loadOptionsConfig() throws TemplateModelException {
String context = optionService.isEnabledAbsolutePath() ? optionService.getBlogBaseUrl() + "/" : "/";
configuration.setSharedVariable("options", optionService.listOptions());
configuration.setSharedVariable("context", optionService.getBlogBaseUrl());
configuration.setSharedVariable("context", context);
configuration.setSharedVariable("version", HaloConst.HALO_VERSION);
configuration.setSharedVariable("blog_title", optionService.getBlogTitle());
configuration.setSharedVariable("blog_url", optionService.getBlogBaseUrl());
configuration.setSharedVariable("blog_logo", optionService.getByPropertyOrDefault(BlogProperties.BLOG_LOGO, String.class, BlogProperties.BLOG_LOGO.defaultValue()));
configuration.setSharedVariable("seo_keywords", optionService.getByPropertyOrDefault(SeoProperties.KEYWORDS, String.class, SeoProperties.KEYWORDS.defaultValue()));
configuration.setSharedVariable("seo_description", optionService.getByPropertyOrDefault(SeoProperties.DESCRIPTION, String.class, SeoProperties.DESCRIPTION.defaultValue()));
configuration.setSharedVariable("rss_url", optionService.getBlogBaseUrl() + "/rss.xml");
configuration.setSharedVariable("atom_url", optionService.getBlogBaseUrl() + "/atom.xml");
configuration.setSharedVariable("sitemap_xml_url", optionService.getBlogBaseUrl() + "/sitemap.xml");
configuration.setSharedVariable("sitemap_html_url", optionService.getBlogBaseUrl() + "/sitemap.html");
configuration.setSharedVariable("links_url", context + optionService.getLinksPrefix());
configuration.setSharedVariable("photos_url", context + optionService.getPhotosPrefix());
configuration.setSharedVariable("journals_url", context + optionService.getJournalsPrefix());
configuration.setSharedVariable("archives_url", context + optionService.getArchivesPrefix());
configuration.setSharedVariable("categories_url", context + optionService.getCategoriesPrefix());
configuration.setSharedVariable("tags_url", context + optionService.getTagsPrefix());
log.debug("Loaded options");
}
@ -110,9 +132,7 @@ public class FreemarkerConfigAwareListener {
// Get current activated theme.
ThemeProperty activatedTheme = themeService.getActivatedTheme();
Boolean enabledAbsolutePath = optionService.getByPropertyOrDefault(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.class, true);
String themeBasePath = (enabledAbsolutePath ? optionService.getBlogBaseUrl() : "") + "/themes/" + activatedTheme.getFolderName();
String themeBasePath = (optionService.isEnabledAbsolutePath() ? optionService.getBlogBaseUrl() : "") + "/themes/" + activatedTheme.getFolderName();
configuration.setSharedVariable("theme", activatedTheme);

View File

@ -39,6 +39,24 @@ public enum PermalinkProperties implements PropertyEnum {
*/
SHEET_PREFIX("sheet_prefix", String.class, "s"),
/**
* Links page prefix
* default is links
*/
LINKS_PREFIX("links_prefix", String.class, "links"),
/**
* Photos page prefix
* default is photos
*/
PHOTOS_PREFIX("photos_prefix", String.class, "photos"),
/**
* Journals page prefix
* default is journals
*/
JOURNALS_PREFIX("journals_prefix", String.class, "journals"),
/**
* Path suffix
* such as: .html or .jsp

View File

@ -185,6 +185,7 @@ public interface PropertyEnum extends ValueEnum<String> {
propertyEnumClasses.add(EmailProperties.class);
propertyEnumClasses.add(OtherProperties.class);
propertyEnumClasses.add(PostProperties.class);
propertyEnumClasses.add(SheetProperties.class);
propertyEnumClasses.add(PrimaryProperties.class);
propertyEnumClasses.add(QiniuOssProperties.class);
propertyEnumClasses.add(SeoProperties.class);

View File

@ -0,0 +1,62 @@
package run.halo.app.model.properties;
/**
* Sheet properties.
*
* @author ryanwang
* @date 2020-02-11
*/
public enum SheetProperties implements PropertyEnum {
/**
* Links page title.
*/
LINKS_TITLE("links_title", String.class, "友情链接"),
/**
* Photos page title.
*/
PHOTOS_TITLE("photos_title", String.class, "图库"),
/**
* Photos page size.
*/
PHOTOS_PAGE_SIZE("photos_page_size", Integer.class, "10"),
/**
* Journals page title.
*/
JOURNALS_TITLE("journals_title", String.class, "日志"),
/**
* Journals page size.
*/
JOURNALS_PAGE_SIZE("journals_page_size", Integer.class, "10");
private final String value;
private final Class<?> type;
private final String defaultValue;
SheetProperties(String value, Class<?> type, String defaultValue) {
this.value = value;
this.type = type;
this.defaultValue = defaultValue;
}
@Override
public String getValue() {
return value;
}
@Override
public Class<?> getType() {
return type;
}
@Override
public String defaultValue() {
return defaultValue;
}
}

View File

@ -26,6 +26,7 @@ import java.util.Optional;
* Option service interface.
*
* @author johnniang
* @author ryanwang
* @date 2019-03-14
*/
public interface OptionService extends CrudService<Option, Integer> {
@ -358,6 +359,69 @@ public interface OptionService extends CrudService<Option, Integer> {
*/
PostPermalinkType getPostPermalinkType();
/**
* Get sheet custom prefix.
*
* @return sheet prefix.
*/
String getSheetPrefix();
/**
* Get links page custom prefix.
*
* @return links page prefix.
*/
String getLinksPrefix();
/**
* Get photos page custom prefix.
*
* @return photos page prefix.
*/
String getPhotosPrefix();
/**
* Get journals page custom prefix.
*
* @return journals page prefix.
*/
String getJournalsPrefix();
/**
* Get archives custom prefix.
*
* @return archives prefix.
*/
String getArchivesPrefix();
/**
* Get categories custom prefix.
*
* @return categories prefix.
*/
String getCategoriesPrefix();
/**
* Get tags custom prefix.
*
* @return tags prefix.
*/
String getTagsPrefix();
/**
* Get custom path suffix.
*
* @return path suffix.
*/
String getPathSuffix();
/**
* Is enabled absolute path.
*
* @return true or false.
*/
Boolean isEnabledAbsolutePath();
/**
* Replace option url in batch.
*

View File

@ -1,11 +1,9 @@
package run.halo.app.service;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import run.halo.app.model.entity.PostComment;
import run.halo.app.model.params.CommentQuery;
import run.halo.app.model.vo.PostCommentWithPostVO;
import run.halo.app.service.base.BaseCommentService;
@ -47,9 +45,6 @@ public interface PostCommentService extends BaseCommentService<PostComment> {
@NonNull
List<PostCommentWithPostVO> convertToWithPostVo(@Nullable List<PostComment> postComments);
@NonNull
Page<PostCommentWithPostVO> pageTreeBy(@NonNull CommentQuery commentQuery, @NonNull Pageable pageable);
/**
* Validate CommentBlackList Status
*/

View File

@ -213,6 +213,15 @@ public interface PostService extends BasePostService<Post> {
@NonNull
Page<PostListVO> convertToListVo(@NonNull Page<Post> postPage);
/**
* Converts to a list of post list vo.
*
* @param posts post must not be null
* @return a list of post list vo
*/
@NonNull
List<PostListVO> convertToListVo(@NonNull List<Post> posts);
/**
* Converts to a page of detail vo.
*

View File

@ -131,6 +131,7 @@ public class AdminServiceImpl implements AdminService {
this.mode = mode;
}
@Override
public AuthToken authenticate(LoginParam loginParam) {
Assert.notNull(loginParam, "Login param must not be null");

View File

@ -18,7 +18,6 @@ import run.halo.app.model.entity.Attachment;
import run.halo.app.model.enums.AttachmentType;
import run.halo.app.model.params.AttachmentQuery;
import run.halo.app.model.properties.AttachmentProperties;
import run.halo.app.model.properties.OtherProperties;
import run.halo.app.model.support.UploadResult;
import run.halo.app.repository.AttachmentRepository;
import run.halo.app.service.AttachmentService;
@ -158,7 +157,7 @@ public class AttachmentServiceImpl extends AbstractCrudService<Attachment, Integ
// Get blog base url
String blogBaseUrl = optionService.getBlogBaseUrl();
Boolean enabledAbsolutePath = optionService.getByPropertyOrDefault(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.class, true);
Boolean enabledAbsolutePath = optionService.isEnabledAbsolutePath();
// Convert to output dto
AttachmentDTO attachmentDTO = new AttachmentDTO().convertFrom(attachment);

View File

@ -15,6 +15,7 @@ import run.halo.app.model.entity.Category;
import run.halo.app.model.vo.CategoryVO;
import run.halo.app.repository.CategoryRepository;
import run.halo.app.service.CategoryService;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.base.AbstractCrudService;
import run.halo.app.utils.ServiceUtils;
@ -39,11 +40,15 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
private final PostCategoryService postCategoryService;
private final OptionService optionService;
public CategoryServiceImpl(CategoryRepository categoryRepository,
PostCategoryService postCategoryService) {
PostCategoryService postCategoryService,
OptionService optionService) {
super(categoryRepository);
this.categoryRepository = categoryRepository;
this.postCategoryService = postCategoryService;
this.optionService = optionService;
}
@Override
@ -188,7 +193,23 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
public CategoryDTO convertTo(Category category) {
Assert.notNull(category, "Category must not be null");
return new CategoryDTO().convertFrom(category);
CategoryDTO categoryDTO = new CategoryDTO().convertFrom(category);
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append("/")
.append(optionService.getCategoriesPrefix())
.append("/")
.append(category.getSlugName())
.append(optionService.getPathSuffix());
categoryDTO.setFullPath(fullPath.toString());
return categoryDTO;
}
@Override

View File

@ -472,6 +472,51 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
return getEnumByPropertyOrDefault(PermalinkProperties.POST_PERMALINK_TYPE, PostPermalinkType.class, PostPermalinkType.DEFAULT);
}
@Override
public String getSheetPrefix() {
return getByPropertyOrDefault(PermalinkProperties.SHEET_PREFIX, String.class, PermalinkProperties.SHEET_PREFIX.defaultValue());
}
@Override
public String getLinksPrefix() {
return getByPropertyOrDefault(PermalinkProperties.LINKS_PREFIX, String.class, PermalinkProperties.LINKS_PREFIX.defaultValue());
}
@Override
public String getPhotosPrefix() {
return getByPropertyOrDefault(PermalinkProperties.PHOTOS_PREFIX, String.class, PermalinkProperties.PHOTOS_PREFIX.defaultValue());
}
@Override
public String getJournalsPrefix() {
return getByPropertyOrDefault(PermalinkProperties.JOURNALS_PREFIX, String.class, PermalinkProperties.JOURNALS_PREFIX.defaultValue());
}
@Override
public String getArchivesPrefix() {
return getByPropertyOrDefault(PermalinkProperties.ARCHIVES_PREFIX, String.class, PermalinkProperties.ARCHIVES_PREFIX.defaultValue());
}
@Override
public String getCategoriesPrefix() {
return getByPropertyOrDefault(PermalinkProperties.CATEGORIES_PREFIX, String.class, PermalinkProperties.CATEGORIES_PREFIX.defaultValue());
}
@Override
public String getTagsPrefix() {
return getByPropertyOrDefault(PermalinkProperties.TAGS_PREFIX, String.class, PermalinkProperties.TAGS_PREFIX.defaultValue());
}
@Override
public String getPathSuffix() {
return getByPropertyOrDefault(PermalinkProperties.PATH_SUFFIX, String.class, PermalinkProperties.PATH_SUFFIX.defaultValue());
}
@Override
public Boolean isEnabledAbsolutePath() {
return getByPropertyOrDefault(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.class, true);
}
@Override
public List<OptionDTO> replaceUrl(String oldUrl, String newUrl) {
List<Option> options = listAll();

View File

@ -16,6 +16,7 @@ import run.halo.app.model.projection.CategoryPostCountProjection;
import run.halo.app.repository.CategoryRepository;
import run.halo.app.repository.PostCategoryRepository;
import run.halo.app.repository.PostRepository;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.base.AbstractCrudService;
import run.halo.app.utils.ServiceUtils;
@ -40,13 +41,17 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
private final CategoryRepository categoryRepository;
private final OptionService optionService;
public PostCategoryServiceImpl(PostCategoryRepository postCategoryRepository,
PostRepository postRepository,
CategoryRepository categoryRepository) {
CategoryRepository categoryRepository,
OptionService optionService) {
super(postCategoryRepository);
this.postCategoryRepository = postCategoryRepository;
this.postRepository = postRepository;
this.categoryRepository = categoryRepository;
this.optionService = optionService;
}
@Override
@ -241,6 +246,21 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
CategoryWithPostCountDTO categoryWithPostCountDTO = new CategoryWithPostCountDTO().convertFrom(category);
// Set post count
categoryWithPostCountDTO.setPostCount(categoryPostCountMap.getOrDefault(category.getId(), 0L));
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append("/")
.append(optionService.getCategoriesPrefix())
.append("/")
.append(category.getSlugName())
.append(optionService.getPathSuffix());
categoryWithPostCountDTO.setFullPath(fullPath.toString());
return categoryWithPostCountDTO;
})
.collect(Collectors.toList());

View File

@ -1,10 +1,10 @@
package run.halo.app.service.impl;
import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@ -15,7 +15,7 @@ import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.PostComment;
import run.halo.app.model.enums.CommentViolationTypeEnum;
import run.halo.app.model.params.CommentQuery;
import run.halo.app.model.enums.PostPermalinkType;
import run.halo.app.model.properties.CommentProperties;
import run.halo.app.model.vo.PostCommentWithPostVO;
import run.halo.app.repository.PostCommentRepository;
@ -44,8 +44,6 @@ import java.util.stream.Collectors;
@Service
public class PostCommentServiceImpl extends BaseCommentServiceImpl<PostComment> implements PostCommentService {
private final PostCommentRepository postCommentRepository;
private final PostRepository postRepository;
private final CommentBlackListService commentBlackListService;
@ -57,7 +55,6 @@ public class PostCommentServiceImpl extends BaseCommentServiceImpl<PostComment>
CommentBlackListService commentBlackListService,
ApplicationEventPublisher eventPublisher) {
super(postCommentRepository, optionService, userService, eventPublisher);
this.postCommentRepository = postCommentRepository;
this.postRepository = postRepository;
this.commentBlackListService = commentBlackListService;
}
@ -74,7 +71,10 @@ public class PostCommentServiceImpl extends BaseCommentServiceImpl<PostComment>
public PostCommentWithPostVO convertToWithPostVo(PostComment comment) {
Assert.notNull(comment, "PostComment must not be null");
PostCommentWithPostVO postCommentWithPostVO = new PostCommentWithPostVO().convertFrom(comment);
postCommentWithPostVO.setPost(new BasePostMinimalDTO().convertFrom(postRepository.getOne(comment.getPostId())));
BasePostMinimalDTO basePostMinimalDTO = new BasePostMinimalDTO().convertFrom(postRepository.getOne(comment.getPostId()));
postCommentWithPostVO.setPost(buildPostFullPath(basePostMinimalDTO));
return postCommentWithPostVO;
}
@ -96,18 +96,58 @@ public class PostCommentServiceImpl extends BaseCommentServiceImpl<PostComment>
// Convert to vo
PostCommentWithPostVO postCommentWithPostVO = new PostCommentWithPostVO().convertFrom(comment);
// Get post and set to the vo
postCommentWithPostVO.setPost(new BasePostMinimalDTO().convertFrom(postMap.get(comment.getPostId())));
BasePostMinimalDTO basePostMinimalDTO = new BasePostMinimalDTO().convertFrom(postMap.get(comment.getPostId()));
postCommentWithPostVO.setPost(buildPostFullPath(basePostMinimalDTO));
return postCommentWithPostVO;
}).collect(Collectors.toList());
}
@Override
public Page<PostCommentWithPostVO> pageTreeBy(CommentQuery commentQuery, Pageable pageable) {
Page<PostComment> postCommentPage = pageBy(commentQuery, pageable);
private BasePostMinimalDTO buildPostFullPath(BasePostMinimalDTO basePostMinimalDTO) {
PostPermalinkType permalinkType = optionService.getPostPermalinkType();
return null;
String pathSuffix = optionService.getPathSuffix();
String archivesPrefix = optionService.getArchivesPrefix();
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append("/");
if (permalinkType.equals(PostPermalinkType.DEFAULT)) {
fullPath.append(archivesPrefix)
.append("/")
.append(basePostMinimalDTO.getUrl())
.append(pathSuffix);
} else if (permalinkType.equals(PostPermalinkType.ID)) {
fullPath.append("?p=")
.append(basePostMinimalDTO.getId());
} else if (permalinkType.equals(PostPermalinkType.DATE)) {
fullPath.append(DateUtil.year(basePostMinimalDTO.getCreateTime()))
.append("/")
.append(DateUtil.month(basePostMinimalDTO.getCreateTime()) + 1)
.append("/")
.append(basePostMinimalDTO.getUrl())
.append(pathSuffix);
} else if (permalinkType.equals(PostPermalinkType.DAY)) {
fullPath.append(DateUtil.year(basePostMinimalDTO.getCreateTime()))
.append("/")
.append(DateUtil.month(basePostMinimalDTO.getCreateTime()) + 1)
.append("/")
.append(DateUtil.dayOfMonth(basePostMinimalDTO.getCreateTime()))
.append("/")
.append(basePostMinimalDTO.getUrl())
.append(pathSuffix);
}
basePostMinimalDTO.setFullPath(fullPath.toString());
return basePostMinimalDTO;
}
@Override

View File

@ -21,15 +21,17 @@ import run.halo.app.event.logger.LogEvent;
import run.halo.app.event.post.PostVisitEvent;
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.dto.BaseMetaDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.dto.post.BasePostSimpleDTO;
import run.halo.app.model.entity.*;
import run.halo.app.model.enums.LogType;
import run.halo.app.model.enums.PostPermalinkType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.PostQuery;
import run.halo.app.model.properties.PermalinkProperties;
import run.halo.app.model.properties.PostProperties;
import run.halo.app.model.vo.*;
import run.halo.app.repository.PostRepository;
import run.halo.app.repository.base.BasePostRepository;
import run.halo.app.service.*;
import run.halo.app.utils.DateUtils;
import run.halo.app.utils.MarkdownUtils;
@ -76,7 +78,9 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
private final OptionService optionService;
public PostServiceImpl(PostRepository postRepository,
public PostServiceImpl(BasePostRepository<Post> basePostRepository,
OptionService optionService,
PostRepository postRepository,
TagService tagService,
CategoryService categoryService,
PostTagService postTagService,
@ -84,8 +88,8 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
PostCommentService postCommentService,
ApplicationEventPublisher eventPublisher,
PostMetaService postMetaService,
OptionService optionService) {
super(postRepository, optionService);
OptionService optionService1) {
super(basePostRepository, optionService);
this.postRepository = postRepository;
this.tagService = tagService;
this.categoryService = categoryService;
@ -94,7 +98,7 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
this.postCommentService = postCommentService;
this.eventPublisher = eventPublisher;
this.postMetaService = postMetaService;
this.optionService = optionService;
this.optionService = optionService1;
}
@Override
@ -506,16 +510,6 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
// Get post meta list map
Map<Integer, List<PostMeta>> postMetaListMap = postMetaService.listPostMetaAsMap(postIds);
String blogUrl = optionService.getBlogBaseUrl();
PostPermalinkType permalinkType = optionService.getPostPermalinkType();
String pathSuffix = optionService
.getByPropertyOrDefault(PermalinkProperties.PATH_SUFFIX, String.class, "");
String archivesPrefix = optionService
.getByPropertyOrDefault(PermalinkProperties.ARCHIVES_PREFIX, String.class, "");
return postPage.map(post -> {
PostListVO postListVO = new PostListVO().convertFrom(post);
@ -552,46 +546,116 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
// Set comment count
postListVO.setCommentCount(commentCountMap.getOrDefault(post.getId(), 0L));
StringBuilder fullPath = new StringBuilder(blogUrl)
.append("/");
if (permalinkType.equals(PostPermalinkType.DEFAULT)) {
fullPath.append(archivesPrefix)
.append("/")
.append(postListVO.getUrl())
.append(pathSuffix);
} else if (permalinkType.equals(PostPermalinkType.ID)) {
fullPath.append("?p=")
.append(postListVO.getId());
} else if (permalinkType.equals(PostPermalinkType.DATE)) {
fullPath.append(DateUtil.year(postListVO.getCreateTime()))
.append("/")
.append(DateUtil.month(postListVO.getCreateTime()) + 1)
.append("/")
.append(postListVO.getUrl())
.append(pathSuffix);
} else if (permalinkType.equals(PostPermalinkType.DAY)) {
fullPath.append(DateUtil.year(postListVO.getCreateTime()))
.append("/")
.append(DateUtil.month(postListVO.getCreateTime()) + 1)
.append("/")
.append(DateUtil.dayOfMonth(postListVO.getCreateTime()))
.append("/")
.append(postListVO.getUrl())
.append(pathSuffix);
}
postListVO.setFullPath(fullPath.toString());
postListVO.setFullPath(buildFullPath(post));
return postListVO;
});
}
@Override
public List<PostListVO> convertToListVo(List<Post> posts) {
Assert.notNull(posts, "Post page must not be null");
Set<Integer> postIds = ServiceUtils.fetchProperty(posts, Post::getId);
// Get tag list map
Map<Integer, List<Tag>> tagListMap = postTagService.listTagListMapBy(postIds);
// Get category list map
Map<Integer, List<Category>> categoryListMap = postCategoryService
.listCategoryListMap(postIds);
// Get comment count
Map<Integer, Long> commentCountMap = postCommentService.countByPostIds(postIds);
// Get post meta list map
Map<Integer, List<PostMeta>> postMetaListMap = postMetaService.listPostMetaAsMap(postIds);
return posts.stream().map(post -> {
PostListVO postListVO = new PostListVO().convertFrom(post);
if (StringUtils.isBlank(postListVO.getSummary())) {
postListVO.setSummary(generateSummary(post.getFormatContent()));
}
Optional.ofNullable(tagListMap.get(post.getId())).orElseGet(LinkedList::new);
// Set tags
postListVO.setTags(Optional.ofNullable(tagListMap.get(post.getId()))
.orElseGet(LinkedList::new)
.stream()
.filter(Objects::nonNull)
.map(tagService::convertTo)
.collect(Collectors.toList()));
// Set categories
postListVO.setCategories(Optional.ofNullable(categoryListMap.get(post.getId()))
.orElseGet(LinkedList::new)
.stream()
.filter(Objects::nonNull)
.map(categoryService::convertTo)
.collect(Collectors.toList()));
// Set post metas
postListVO.setPostMetas(Optional.ofNullable(postMetaListMap.get(post.getId()))
.orElseGet(LinkedList::new)
.stream()
.filter(Objects::nonNull)
.map(postMeta -> (BaseMetaDTO) new BaseMetaDTO().convertFrom(postMeta))
.collect(Collectors.toList()));
// Set comment count
postListVO.setCommentCount(commentCountMap.getOrDefault(post.getId(), 0L));
postListVO.setFullPath(buildFullPath(post));
return postListVO;
}).collect(Collectors.toList());
}
@Override
public Page<PostDetailVO> convertToDetailVo(Page<Post> postPage) {
Assert.notNull(postPage, "Post page must not be null");
return postPage.map(this::convertToDetailVo);
}
@Override
public BasePostMinimalDTO convertToMinimal(Post post) {
Assert.notNull(post, "Post must not be null");
BasePostMinimalDTO basePostMinimalDTO = new BasePostMinimalDTO().convertFrom(post);
basePostMinimalDTO.setFullPath(buildFullPath(post));
return basePostMinimalDTO;
}
@Override
public List<BasePostMinimalDTO> convertToMinimal(List<Post> posts) {
if (CollectionUtils.isEmpty(posts)) {
return Collections.emptyList();
}
return posts.stream()
.map(this::convertToMinimal)
.collect(Collectors.toList());
}
@Override
public BasePostSimpleDTO convertToSimple(Post post) {
Assert.notNull(post, "Post must not be null");
BasePostSimpleDTO basePostSimpleDTO = new BasePostSimpleDTO().convertFrom(post);
// Set summary
if (StringUtils.isBlank(basePostSimpleDTO.getSummary())) {
basePostSimpleDTO.setSummary(generateSummary(post.getFormatContent()));
}
basePostSimpleDTO.setFullPath(buildFullPath(post));
return basePostSimpleDTO;
}
/**
* Converts to post detail vo.
*
@ -632,6 +696,8 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
postDetailVO.setCommentCount(postCommentService.countByPostId(post.getId()));
postDetailVO.setFullPath(buildFullPath(post));
return postDetailVO;
}
@ -731,8 +797,8 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
// get pageable post list
List<Post> postList = new ArrayList<>();
// init fist page && default page size
Integer page = 1;
Integer defaultPageSize = 500;
int page = 1;
int defaultPageSize = 500;
boolean needNext = true;
// get custom sort type
@ -768,7 +834,7 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
// get current post index in post list
List<Integer> idList = postList.stream().map(Post::getId).collect(Collectors.toList());
Integer index = idList.indexOf(currentPost.getId());
int index = idList.indexOf(currentPost.getId());
if (index == -1) {
// if not found, return empty object
@ -777,8 +843,7 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
AdjacentPostVO adjacentPostVO = new AdjacentPostVO();
//setup pre
//TODO convert POST to PostVO (with fullPath)
// setup pre
if (index > 0) {
adjacentPostVO.setPrePost(postList.get(index - 1));
}
@ -796,4 +861,48 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
.toString();
return Sort.by(DESC, "topPriority").and(Sort.by(DESC, indexSort)).and(Sort.by(DESC, "id"));
}
private String buildFullPath(Post post) {
PostPermalinkType permalinkType = optionService.getPostPermalinkType();
String pathSuffix = optionService.getPathSuffix();
String archivesPrefix = optionService.getArchivesPrefix();
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append("/");
if (permalinkType.equals(PostPermalinkType.DEFAULT)) {
fullPath.append(archivesPrefix)
.append("/")
.append(post.getUrl())
.append(pathSuffix);
} else if (permalinkType.equals(PostPermalinkType.ID)) {
fullPath.append("?p=")
.append(post.getId());
} else if (permalinkType.equals(PostPermalinkType.DATE)) {
fullPath.append(DateUtil.year(post.getCreateTime()))
.append("/")
.append(DateUtil.month(post.getCreateTime()) + 1)
.append("/")
.append(post.getUrl())
.append(pathSuffix);
} else if (permalinkType.equals(PostPermalinkType.DAY)) {
fullPath.append(DateUtil.year(post.getCreateTime()))
.append("/")
.append(DateUtil.month(post.getCreateTime()) + 1)
.append("/")
.append(DateUtil.dayOfMonth(post.getCreateTime()))
.append("/")
.append(post.getUrl())
.append(pathSuffix);
}
return fullPath.toString();
}
}

View File

@ -16,6 +16,7 @@ import run.halo.app.model.projection.TagPostPostCountProjection;
import run.halo.app.repository.PostRepository;
import run.halo.app.repository.PostTagRepository;
import run.halo.app.repository.TagRepository;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostTagService;
import run.halo.app.service.base.AbstractCrudService;
import run.halo.app.utils.ServiceUtils;
@ -39,13 +40,17 @@ public class PostTagServiceImpl extends AbstractCrudService<PostTag, Integer> im
private final TagRepository tagRepository;
private final OptionService optionService;
public PostTagServiceImpl(PostTagRepository postTagRepository,
PostRepository postRepository,
TagRepository tagRepository) {
TagRepository tagRepository,
OptionService optionService) {
super(postTagRepository);
this.postTagRepository = postTagRepository;
this.postRepository = postRepository;
this.tagRepository = tagRepository;
this.optionService = optionService;
}
@Override
@ -73,6 +78,21 @@ public class PostTagServiceImpl extends AbstractCrudService<PostTag, Integer> im
tag -> {
TagWithPostCountDTO tagWithCountOutputDTO = new TagWithPostCountDTO().convertFrom(tag);
tagWithCountOutputDTO.setPostCount(tagPostCountMap.getOrDefault(tag.getId(), 0L));
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append("/")
.append(optionService.getTagsPrefix())
.append("/")
.append(tag.getSlugName())
.append(optionService.getPathSuffix());
tagWithCountOutputDTO.setFullPath(fullPath.toString());
return tagWithCountOutputDTO;
}
).collect(Collectors.toList());

View File

@ -35,8 +35,6 @@ import java.util.stream.Collectors;
@Service
public class SheetCommentServiceImpl extends BaseCommentServiceImpl<SheetComment> implements SheetCommentService {
private final SheetCommentRepository sheetCommentRepository;
private final SheetRepository sheetRepository;
public SheetCommentServiceImpl(SheetCommentRepository sheetCommentRepository,
@ -45,7 +43,6 @@ public class SheetCommentServiceImpl extends BaseCommentServiceImpl<SheetComment
ApplicationEventPublisher eventPublisher,
SheetRepository sheetRepository) {
super(sheetCommentRepository, optionService, userService, eventPublisher);
this.sheetCommentRepository = sheetCommentRepository;
this.sheetRepository = sheetRepository;
}
@ -63,7 +60,10 @@ public class SheetCommentServiceImpl extends BaseCommentServiceImpl<SheetComment
public SheetCommentWithSheetVO convertToWithSheetVo(SheetComment comment) {
Assert.notNull(comment, "SheetComment must not be null");
SheetCommentWithSheetVO sheetCommentWithSheetVO = new SheetCommentWithSheetVO().convertFrom(comment);
sheetCommentWithSheetVO.setSheet(new BasePostMinimalDTO().convertFrom(sheetRepository.getOne(comment.getPostId())));
BasePostMinimalDTO basePostMinimalDTO = new BasePostMinimalDTO().convertFrom(sheetRepository.getOne(comment.getPostId()));
sheetCommentWithSheetVO.setSheet(buildSheetFullPath(basePostMinimalDTO));
return sheetCommentWithSheetVO;
}
@ -81,12 +81,32 @@ public class SheetCommentServiceImpl extends BaseCommentServiceImpl<SheetComment
.filter(comment -> sheetMap.containsKey(comment.getPostId()))
.map(comment -> {
SheetCommentWithSheetVO sheetCmtWithPostVO = new SheetCommentWithSheetVO().convertFrom(comment);
sheetCmtWithPostVO.setSheet(new BasePostMinimalDTO().convertFrom(sheetMap.get(comment.getPostId())));
BasePostMinimalDTO postMinimalDTO = new BasePostMinimalDTO().convertFrom(sheetMap.get(comment.getPostId()));
sheetCmtWithPostVO.setSheet(buildSheetFullPath(postMinimalDTO));
return sheetCmtWithPostVO;
})
.collect(Collectors.toList());
}
private BasePostMinimalDTO buildSheetFullPath(BasePostMinimalDTO basePostMinimalDTO) {
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append("/")
.append(optionService.getSheetPrefix())
.append("/")
.append(basePostMinimalDTO.getUrl())
.append(optionService.getPathSuffix());
basePostMinimalDTO.setFullPath(fullPath.toString());
return basePostMinimalDTO;
}
@Override
public Page<SheetCommentWithSheetVO> convertToWithSheetVo(Page<SheetComment> sheetCommentPage) {
Assert.notNull(sheetCommentPage, "Sheet comment page must not be null");

View File

@ -9,11 +9,13 @@ import org.springframework.data.domain.Pageable;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import run.halo.app.event.logger.LogEvent;
import run.halo.app.event.post.SheetVisitEvent;
import run.halo.app.exception.AlreadyExistsException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.dto.InternalSheetDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.entity.SheetComment;
import run.halo.app.model.entity.SheetMeta;
@ -27,6 +29,7 @@ import run.halo.app.utils.MarkdownUtils;
import run.halo.app.utils.ServiceUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* Sheet service implementation.
@ -50,18 +53,21 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
private final ThemeService themeService;
private final OptionService optionService;
public SheetServiceImpl(SheetRepository sheetRepository,
ApplicationEventPublisher eventPublisher,
SheetCommentService sheetCommentService,
OptionService optionService,
SheetMetaService sheetMetaService,
ThemeService themeService) {
ThemeService themeService,
OptionService optionService) {
super(sheetRepository, optionService);
this.sheetRepository = sheetRepository;
this.eventPublisher = eventPublisher;
this.sheetCommentService = sheetCommentService;
this.sheetMetaService = sheetMetaService;
this.themeService = themeService;
this.optionService = optionService;
}
@Override
@ -129,9 +135,7 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
public Sheet getByUrl(String url) {
Assert.hasText(url, "Url must not be blank");
Sheet sheet = sheetRepository.getByUrl(url).orElseThrow(() -> new NotFoundException("查询不到该页面的信息").setErrorData(url));
return sheet;
return sheetRepository.getByUrl(url).orElseThrow(() -> new NotFoundException("查询不到该页面的信息").setErrorData(url));
}
@Override
@ -141,9 +145,7 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
Optional<Sheet> postOptional = sheetRepository.getByUrlAndStatus(url, status);
Sheet sheet = postOptional.orElseThrow(() -> new NotFoundException("查询不到该页面的信息").setErrorData(url));
return sheet;
return postOptional.orElseThrow(() -> new NotFoundException("查询不到该页面的信息").setErrorData(url));
}
@Override
@ -196,21 +198,21 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
InternalSheetDTO linkSheet = new InternalSheetDTO();
linkSheet.setId(1);
linkSheet.setTitle("友情链接");
linkSheet.setUrl("/links");
linkSheet.setUrl((optionService.isEnabledAbsolutePath() ? optionService.getBlogBaseUrl() : "") + "/" + optionService.getLinksPrefix());
linkSheet.setStatus(themeService.templateExists("links.ftl"));
// photos sheet
InternalSheetDTO photoSheet = new InternalSheetDTO();
photoSheet.setId(2);
photoSheet.setTitle("图库页面");
photoSheet.setUrl("/photos");
photoSheet.setUrl((optionService.isEnabledAbsolutePath() ? optionService.getBlogBaseUrl() : "") + "/" + optionService.getPhotosPrefix());
photoSheet.setStatus(themeService.templateExists("photos.ftl"));
// journals sheet
InternalSheetDTO journalSheet = new InternalSheetDTO();
journalSheet.setId(3);
journalSheet.setTitle("日志页面");
journalSheet.setUrl("/journals");
journalSheet.setUrl((optionService.isEnabledAbsolutePath() ? optionService.getBlogBaseUrl() : "") + "/" + optionService.getJournalsPrefix());
journalSheet.setStatus(themeService.templateExists("journals.ftl"));
internalSheetDTOS.add(linkSheet);
@ -254,6 +256,9 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
return sheetPage.map(sheet -> {
SheetListVO sheetListVO = new SheetListVO().convertFrom(sheet);
sheetListVO.setCommentCount(sheetCommentCountMap.getOrDefault(sheet.getId(), 0L));
sheetListVO.setFullPath(buildFullPath(sheet));
return sheetListVO;
});
}
@ -271,6 +276,27 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
return convertTo(sheet, sheetMetas);
}
@Override
public BasePostMinimalDTO convertToMinimal(Sheet sheet) {
Assert.notNull(sheet, "Sheet must not be null");
BasePostMinimalDTO basePostMinimalDTO = new BasePostMinimalDTO().convertFrom(sheet);
basePostMinimalDTO.setFullPath(buildFullPath(sheet));
return basePostMinimalDTO;
}
@Override
public List<BasePostMinimalDTO> convertToMinimal(List<Sheet> sheets) {
if (CollectionUtils.isEmpty(sheets)) {
return Collections.emptyList();
}
return sheets.stream()
.map(this::convertToMinimal)
.collect(Collectors.toList());
}
@NonNull
private SheetDetailVO convertTo(@NonNull Sheet sheet, List<SheetMeta> sheetMetas) {
Assert.notNull(sheet, "Sheet must not be null");
@ -289,6 +315,9 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
}
sheetDetailVO.setCommentCount(sheetCommentService.countByPostId(sheet.getId()));
sheetDetailVO.setFullPath(buildFullPath(sheet));
return sheetDetailVO;
}
@ -311,4 +340,20 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
throw new AlreadyExistsException("页面路径 " + sheet.getUrl() + " 已存在");
}
}
private String buildFullPath(Sheet sheet) {
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append("/")
.append(optionService.getSheetPrefix())
.append("/")
.append(sheet.getUrl())
.append(optionService.getPathSuffix());
return fullPath.toString();
}
}

View File

@ -10,6 +10,7 @@ import run.halo.app.exception.NotFoundException;
import run.halo.app.model.dto.TagDTO;
import run.halo.app.model.entity.Tag;
import run.halo.app.repository.TagRepository;
import run.halo.app.service.OptionService;
import run.halo.app.service.TagService;
import run.halo.app.service.base.AbstractCrudService;
@ -30,9 +31,13 @@ public class TagServiceImpl extends AbstractCrudService<Tag, Integer> implements
private final TagRepository tagRepository;
public TagServiceImpl(TagRepository tagRepository) {
private final OptionService optionService;
public TagServiceImpl(TagRepository tagRepository,
OptionService optionService) {
super(tagRepository);
this.tagRepository = tagRepository;
this.optionService = optionService;
}
@Override
@ -71,7 +76,23 @@ public class TagServiceImpl extends AbstractCrudService<Tag, Integer> implements
public TagDTO convertTo(Tag tag) {
Assert.notNull(tag, "Tag must not be null");
return new TagDTO().convertFrom(tag);
TagDTO tagDTO = new TagDTO().convertFrom(tag);
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append("/")
.append(optionService.getTagsPrefix())
.append("/")
.append(tag.getSlugName())
.append(optionService.getPathSuffix());
tagDTO.setFullPath(fullPath.toString());
return tagDTO;
}
@Override

View File

@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="alternate" type="application/rss+xml" title="atom 1.0" href="${context!}/atom.xml">
<link rel="alternate" type="application/rss+xml" title="atom 1.0" href="${atom_url!}">
<title>${error.status!} | ${error.error!}</title>
<style type="text/css">
@ -125,7 +125,7 @@
<h1 class="title">${error.error!}.</h1>
<p>${error.message!}</p>
<div class="back-home">
<button onclick="window.location.href='${context!}'">首页</button>
<button onclick="window.location.href='${blog_url!}'">首页</button>
</div>
</div>
</body>

View File

@ -2,7 +2,7 @@
<div class="emailcontent" style="width:100%;max-width:720px;text-align: left;margin: 0 auto;padding-top: 20px;padding-bottom: 80px">
<div class="emailtitle" style="border-radius: 5px;border:1px solid #eee;overflow: hidden;">
<h1 style="color:#fff;background: #3798e8;line-height:70px;font-size:24px;font-weight:normal;padding-left:40px;margin:0">
您在 ${options.blog_title!} 上的留言有回复啦!
您在 ${blog_title!} 上的留言有回复啦!
</h1>
<div class="emailtext" style="background:#fff;padding:20px 32px 40px;">
@ -17,13 +17,13 @@
<a href="${url!}">查看完整内容</a>
</p>
<p style="color: #6e6e6e;font-size:13px;line-height:24px;">欢迎再度光临
<a href="${context!}">${options.blog_title!}</a>
<a href="${blog_url!}">${blog_title!}</a>
</p>
<p style="color: #6e6e6e;font-size:13px;line-height:24px;">(此邮件由系统自动发出, 请勿回复。如有打扰,请见谅。)</p>
</div>
<p style="color: #6e6e6e;font-size:13px;line-height:24px;text-align:right;padding:0 32px">邮件发自:
<a href="${context!}" style="color:#51a0e3;text-decoration:none">${options.blog_title!}</a>
<a href="${blog_url!}" style="color:#51a0e3;text-decoration:none">${blog_title!}</a>
</p>
</div>
</div>

View File

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
<meta name="robots" content="noindex,nofllow"/>
<title>私密文章访问 - ${options.blog_title!}</title>
<title>私密文章访问 - ${blog_title!}</title>
<style>
body {
background-color: #080821;
@ -152,7 +152,7 @@
</head>
<body>
<div class="container">
<form method="post" action="${context!}/archives/${url}/password">
<form method="post" action="${context!}/archives/${url!}/password">
<div class="password-input">
<input type="password" name="password" placeholder="请输入文章访问密码">
<span class="bottom"></span>

View File

@ -1,41 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
<channel>
<title>${options.blog_title!}</title>
<atom:link href="${context!}/atom.xml" rel="self" type="application/rss+xml"/>
<link>${context!}</link>
<description>${user.description!}</description>
<language>zh-CN</language>
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<generator>https://halo.run</generator>
</channel>
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title type="text">${blog_title!}</title>
<#if user.description??>
<subtitle type="text">${user.description!}</subtitle>
</#if>
<updated>${.now?iso_local}</updated>
<id>${blog_url!}</id>
<link rel="alternate" type="text/html" href="${blog_url!}" />
<link rel="self" type="application/atom+xml" href="${atom_url!}" />
<rights>Copyright © ${.now?string('yyyy')}, ${blog_title!}</rights>
<generator uri="https://halo.run/" version="${version!}">Halo</generator>
<#if posts?? && posts?size gt 0>
<#list posts as post>
<item>
<entry>
<title><![CDATA[${post.title!}]]></title>
<link>${context!}/archives/${post.url!}</link>
<comments>${context!}/archives/${post.url!}#comments</comments>
<pubDate>${post.createTime!}</pubDate>
<dc:creator><![CDATA[${user.nickname!}]]></dc:creator>
<#if post.categories?? && post.categories?size gt 0>
<#list post.categories as category>
<category><![CDATA[${category.name!}]]></category>
</#list>
</#if>
<description>
<![CDATA[
${post.summary!}
]]>
</description>
<content:encoded>
<link rel="alternate" type="text/html" href="${post.fullPath!}" />
<id>tag:${blog_url!},${post.createTime?string('yyyy-MM-dd')}:${post.url!}</id>
<published>${post.createTime?iso_local}</published>
<updated>${post.editTime?iso_local}</updated>
<author>
<name>${user.nickname!}</name>
<uri>${blog_url!}</uri>
</author>
<content type="html" xml:base="${blog_url!}" xml:lang="en">
<![CDATA[
<#if (options.rss_content_type!'full') == 'full'>
${post.formatContent!}
@ -43,8 +30,8 @@
${post.summary!}
</#if>
]]>
</content:encoded>
</item>
</content>
</entry>
</#list>
</#if>
</rss>
</feed>

View File

@ -1,8 +1,8 @@
<div style="text-align:center">
<img src="${user.avatar!}" width="100" height="100" alt="${user.nickname!}">
<h3>${options.blog_title!}</h3>
<h3>${blog_title!}</h3>
<h4>
<a href="${context!}" target="_blank">${context!}</a>
<a href="${blog_url!}" target="_blank">${blog_url!}</a>
</h4>
</div>
@ -12,7 +12,7 @@
<#list archives as archive>
## ${archive.year?c}
<#list archive.posts?sort_by("createTime")?reverse as post>
- <a href="${context!}/archives/${post.url!}" title="${post.title!}" target="_blank">${post.title!}</a>
- <a href="${post.fullPath!}" title="${post.title!}" target="_blank">${post.title!}</a>
</#list>
</#list>
</@postTag>
@ -20,13 +20,13 @@
## 分类目录
<@categoryTag method="list">
<#list categories as category>
- <a href="${context!}/categories/${category.slugName!}" target="_blank">${category.name!}</a>
- <a href="${category.fullPath!}" target="_blank">${category.name!}</a>
</#list>
</@categoryTag>
## 标签
<@tagTag method="list">
<#list tags as tag>
- <a href="${context!}/tags/${tag.slugName!}" target="_blank">${tag.name!}</a>
- <a href="${tag.fullPath!}" target="_blank">${tag.name!}</a>
</#list>
</@tagTag>

View File

@ -3,6 +3,6 @@ User-agent: *
Disallow: /
<#else>
User-agent: *
Sitemap: ${context!}/sitemap.xml
Sitemap: ${context!}/sitemap.html
Sitemap: ${sitemap_xml_url!}
Sitemap: ${sitemap_html_url!}
</#if>

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>${options.blog_title!}</title>
<link>${context!}</link>
<title>${blog_title!}</title>
<link>${blog_url!}</link>
<#if user.description??>
<description>${user.description!}</description>
</#if>
<language>zh-CN</language>
<generator>Halo ${version!}</generator>
<#if posts?? && posts?size gt 0>
<#list posts as post>
<item>
@ -15,8 +16,8 @@
${post.title!}
]]>
</title>
<link>${options.blog_url}/archives/${post.url!}</link>
<content:encoded>
<link>${post.fullPath!}</link>
<description>
<![CDATA[
<#if (options.rss_content_type!'full') == 'full'>
${post.formatContent!}
@ -24,8 +25,8 @@
${post.summary!}
</#if>
]]>
</content:encoded>
<pubDate>${post.createTime}</pubDate>
</description>
<pubDate>${post.createTime?iso_local}</pubDate>
</item>
</#list>
</#if>

View File

@ -7,7 +7,7 @@ see https://gitee.com/yadong.zhang/DBlog/blob/master/blog-web/src/main/java/com/
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>${options.blog_title!} 网站地图</title>
<title>${blog_title!} 网站地图</title>
<meta name="robots" content="index,follow"/>
<style type="text/css">
body {
@ -119,8 +119,8 @@ see https://gitee.com/yadong.zhang/DBlog/blob/master/blog-web/src/main/java/com/
</style>
</head>
<body>
<h2 style="text-align: center; margin-top: 20px">${options.blog_title!} 网站地图 </h2>
<div id="nav"><a href="${context!}"><strong>${options.blog_title!}</strong></a> &raquo; <a href="${context!}/sitemap.html">站点地图</a></div>
<h2 style="text-align: center; margin-top: 20px">${blog_title!} 网站地图 </h2>
<div id="nav"><a href="${blog_url!}"><strong>${blog_title!}</strong></a> &raquo; <a href="${sitemap_html_url!}">站点地图</a></div>
<div id="content">
<h3>最新文章</h3>
<ul id="contentTable">
@ -132,8 +132,8 @@ see https://gitee.com/yadong.zhang/DBlog/blob/master/blog-web/src/main/java/com/
</li>
<div class="clear"></div>
<li>
<div class="T1 pull-left"><a href="${context!}" title="${options.blog_title!}">${options.blog_title!}</a></div>
<div class="T2 pull-right">${options.blog_start!}</div>
<div class="T1 pull-left"><a href="${blog_url!}" title="${blog_title!}">${blog_title!}</a></div>
<div class="T2 pull-right">${(options.birthday)?number_to_date?string("yyyy-MM-dd")} </div>
<div class="T3 pull-right">daily</div>
<div class="T4 pull-right">1</div>
</li>
@ -141,7 +141,7 @@ see https://gitee.com/yadong.zhang/DBlog/blob/master/blog-web/src/main/java/com/
<#if posts?? && posts?size gt 0>
<#list posts as post>
<li>
<div class="T1 pull-left"><a href="${context!}/archives/${post.url!}" title="${post.title!}">${post.title!} | ${options.blog_title!}</a></div>
<div class="T1 pull-left"><a href="${post.fullPath!}" title="${post.title!}">${post.title!} | ${blog_title!}</a></div>
<div class="T2 pull-right">${post.createTime?string('yyyy-MM-dd')}</div>
<div class="T3 pull-right">daily</div>
<div class="T4 pull-right">0.6</div>
@ -156,10 +156,10 @@ see https://gitee.com/yadong.zhang/DBlog/blob/master/blog-web/src/main/java/com/
<ul id="contentTable">
<@categoryTag method="list">
<#if categories?? && categories?size gt 0>
<#list categories as cate>
<#list categories as category>
<li>
<div class="T1 pull-left"><a href="${options.blog_url}/categories/${cate.slugName!}" title="${cate.name}">${cate.name} | ${options.blog_title!}</a></div>
<div class="T2 pull-right">${cate.createTime?string('yyyy-MM-dd')}</div>
<div class="T1 pull-left"><a href="${category.fullPath!}" title="${category.name}">${category.name} | ${blog_title!}</a></div>
<div class="T2 pull-right">${category.createTime?string('yyyy-MM-dd')}</div>
<div class="T3 pull-right">daily</div>
<div class="T4 pull-right">0.6</div>
</li>
@ -176,7 +176,7 @@ see https://gitee.com/yadong.zhang/DBlog/blob/master/blog-web/src/main/java/com/
<#if tags?? && tags?size gt 0>
<#list tags as tag>
<li>
<div class="T1 pull-left"><a href="${options.blog_url}/tags/${tag.slugName!}" title="${tag.name}">${tag.name} | ${options.blog_title!}</a></div>
<div class="T1 pull-left"><a href="${tag.fullPath!}" title="${tag.name}">${tag.name} | ${blog_title!}</a></div>
<div class="T2 pull-right">${tag.createTime?string('yyyy-MM-dd')}</div>
<div class="T3 pull-right">daily</div>
<div class="T4 pull-right">0.6</div>
@ -188,7 +188,7 @@ see https://gitee.com/yadong.zhang/DBlog/blob/master/blog-web/src/main/java/com/
</ul>
</div>
<div id="footer">
该文件由 <a href="${context!}" title="${options.blog_title!}">${options.blog_title!}</a> 网站自动生成。
该文件由 <a href="${blog_url!}" title="${blog_title!}">${blog_title!}</a> 网站自动生成。
</div>
</body>
</html>

View File

@ -3,7 +3,7 @@
<#if posts?? && posts?size gt 0>
<#list posts as post>
<url>
<loc>${context!}/archives/${post.url!}</loc>
<loc>${post.fullPath!}</loc>
<lastmod>${post.createTime?iso_local}</lastmod>
</url>
</#list>