feat: support generate static pages.

pull/471/head^2
ruibaby 2019-12-25 22:51:54 +08:00
parent f99daee263
commit f3763e80fc
6 changed files with 799 additions and 0 deletions

View File

@ -0,0 +1,26 @@
package run.halo.app.controller.admin.api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.service.StaticPageService;
/**
* @author ryan0up
* @date 2019/12/25
*/
@RestController
@RequestMapping("/api/admin/static_page")
public class StaticPageController {
private final StaticPageService staticPageService;
public StaticPageController(StaticPageService staticPageService) {
this.staticPageService = staticPageService;
}
@GetMapping("generate")
public void generate() {
staticPageService.generate();
}
}

View File

@ -0,0 +1,20 @@
package run.halo.app.service;
/**
* Static Page service interface.
*
* @author ryanwang
* @date 2019-12-25
*/
public interface StaticPageService {
/**
* Static page folder location.
*/
String PAGES_FOLDER = "static_pages";
/**
* Generate pages.
*/
void generate();
}

View File

@ -64,6 +64,11 @@ public interface ThemeService {
*/
String RENDER_TEMPLATE = "themes/%s/%s";
/**
* Render template with suffix.
*/
String RENDER_TEMPLATE_SUFFIX = "themes/%s/%s.ftl";
/**
* Theme cache key.
*/
@ -231,6 +236,15 @@ public interface ThemeService {
@NonNull
String render(@NonNull String pageName);
/**
* Renders a theme page.
*
* @param pageName must not be blank
* @return full path of the theme page
*/
@NonNull
String renderWithSuffix(@NonNull String pageName);
/**
* Gets current theme id.
*

View File

@ -0,0 +1,699 @@
package run.halo.app.service.impl;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.PageUtil;
import freemarker.template.Template;
import freemarker.template.TemplateException;
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.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.ui.ModelMap;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.ServiceException;
import run.halo.app.handler.theme.config.support.ThemeProperty;
import run.halo.app.model.dto.PhotoDTO;
import run.halo.app.model.entity.*;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.properties.PostProperties;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.vo.PostDetailVO;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.model.vo.SheetDetailVO;
import run.halo.app.service.*;
import run.halo.app.utils.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* Static Page service implementation.
*
* @author ryanwang
* @date 2019-12-25
*/
@Slf4j
@Service
public class StaticPageServiceImpl implements StaticPageService {
private final Path pagesDir;
private final PostService postService;
private final PostCategoryService postCategoryService;
private final PostTagService postTagService;
private final PostMetaService postMetaService;
private final SheetService sheetService;
private final CategoryService categoryService;
private final TagService tagService;
private final LinkService linkService;
private final PhotoService photoService;
private final JournalService journalService;
private final ThemeService themeService;
private final HaloProperties haloProperties;
private final OptionService optionService;
private final FreeMarkerConfigurer freeMarkerConfigurer;
public StaticPageServiceImpl(PostService postService,
PostCategoryService postCategoryService,
PostTagService postTagService,
PostMetaService postMetaService,
SheetService sheetService,
CategoryService categoryService,
TagService tagService,
LinkService linkService,
PhotoService photoService,
JournalService journalService,
ThemeService themeService,
HaloProperties haloProperties,
OptionService optionService,
FreeMarkerConfigurer freeMarkerConfigurer) throws IOException {
this.postService = postService;
this.postCategoryService = postCategoryService;
this.postTagService = postTagService;
this.postMetaService = postMetaService;
this.sheetService = sheetService;
this.categoryService = categoryService;
this.tagService = tagService;
this.linkService = linkService;
this.photoService = photoService;
this.journalService = journalService;
this.themeService = themeService;
this.haloProperties = haloProperties;
this.optionService = optionService;
this.freeMarkerConfigurer = freeMarkerConfigurer;
pagesDir = Paths.get(haloProperties.getWorkDir(), PAGES_FOLDER);
FileUtils.createIfAbsent(pagesDir);
}
@Override
public void generate() {
try {
this.generateIndex(1);
this.generatePost();
this.generateArchives(1);
this.generateSheet();
this.generateLink();
this.generatePhoto(1);
this.generateCategories();
this.generateTags();
this.generateRss();
this.generateAtom();
this.generateSiteMapHtml();
this.generateSiteMapXml();
this.generateRobots();
this.copyThemeFolder();
this.copyUpload();
this.generateReadme();
} catch (Exception e) {
throw new ServiceException("生成静态页面失败!", e);
}
}
/**
* Generate index.html and page/{page}/index.html.
*
* @param page current page.
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateIndex(int page) throws IOException, TemplateException {
if (!themeService.templateExists("index.ftl")) {
log.warn("index.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
String indexSort = optionService.getByPropertyOfNonNull(PostProperties.INDEX_SORT).toString();
int pageSize = optionService.getPostPageSize();
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(DESC, "topPriority").and(Sort.by(DESC, indexSort)));
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostListVO> posts = postService.convertToListVo(postPage);
int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
model.addAttribute("is_index", true);
model.addAttribute("posts", posts);
model.addAttribute("rainbow", rainbow);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("index"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("page/" + page + "/index.html"), "UTF-8");
}
fileWriter.write(html);
if (postPage.hasNext()) {
generateIndex(postPage.getNumber() + 2);
log.info("Generate page/{}/index.html", postPage.getNumber() + 2);
}
}
/**
* Generate archives/index.html and archives/page/{page}/index.html.
*
* @param page current page
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateArchives(int page) throws IOException, TemplateException {
if (!themeService.templateExists("archives.ftl")) {
log.warn("archives.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), Sort.by(DESC, "topPriority"));
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostListVO> postListVos = postService.convertToListVo(postPage);
int[] pageRainbow = PageUtil.rainbow(page, postListVos.getTotalPages(), 3);
model.addAttribute("is_archives", true);
model.addAttribute("pageRainbow", pageRainbow);
model.addAttribute("posts", postListVos);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("archives"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("archives/index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("archives/page/" + page + "/index.html"), "UTF-8");
}
fileWriter.write(html);
if (postPage.hasNext()) {
generateArchives(postPage.getNumber() + 2);
log.info("Generate page/{}/index.html", postPage.getNumber() + 2);
}
}
/**
* Generate archives/{url}/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generatePost() throws IOException, TemplateException {
if (!themeService.templateExists("post.ftl")) {
log.warn("post.ftl not found,skip!");
return;
}
List<Post> posts = postService.listAllBy(PostStatus.PUBLISHED);
for (Post post : posts) {
log.info("Generate archives/{}/index.html", post.getUrl());
ModelMap model = new ModelMap();
postService.getNextPost(post.getCreateTime()).ifPresent(nextPost -> model.addAttribute("nextPost", nextPost));
postService.getPrePost(post.getCreateTime()).ifPresent(prePost -> model.addAttribute("prePost", prePost));
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
List<Tag> tags = postTagService.listTagsBy(post.getId());
List<PostMeta> metas = postMetaService.listBy(post.getId());
model.addAttribute("is_post", true);
model.addAttribute("post", postService.convertToDetailVo(post));
model.addAttribute("categories", categories);
model.addAttribute("tags", tags);
model.addAttribute("metas", postMetaService.convertToMap(metas));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("post"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("archives/" + post.getUrl() + "/index.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate archives/{}/index.html succeed.", post.getUrl());
}
}
/**
* Generate s/{url}/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateSheet() throws IOException, TemplateException {
if (!themeService.templateExists("sheet.ftl")) {
log.warn("sheet.ftl not found,skip!");
return;
}
List<Sheet> sheets = sheetService.listAllBy(PostStatus.PUBLISHED);
for (Sheet sheet : sheets) {
log.info("Generate s/{}/index.html", sheet.getUrl());
ModelMap model = new ModelMap();
SheetDetailVO sheetDetailVO = sheetService.convertToDetailVo(sheet);
model.addAttribute("sheet", sheetDetailVO);
model.addAttribute("post", sheetDetailVO);
model.addAttribute("is_sheet", true);
String templateName = "sheet";
if (themeService.templateExists(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate() + HaloConst.SUFFIX_FTL)) {
templateName = ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate();
}
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix(templateName));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("s/" + sheet.getUrl() + "/index.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate s/{}/index.html succeed.", sheet.getUrl());
}
}
/**
* Generate links/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateLink() throws IOException, TemplateException {
log.info("Generate links.html");
if (!themeService.templateExists("links.ftl")) {
log.warn("links.ftl not found,skip!");
return;
}
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("links"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, null);
FileWriter fileWriter = new FileWriter(getPageFile("links/index.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate links.html succeed.");
}
/**
* Generate photos/index.html and photos/page/{page}/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generatePhoto(int page) throws IOException, TemplateException {
log.info("Generate photos.html");
if (!themeService.templateExists("photos.ftl")) {
log.warn("photos.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, 10, Sort.by(DESC, "createTime"));
Page<PhotoDTO> photos = photoService.pageBy(pageable);
model.addAttribute("photos", photos);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("photos/index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("photos/page/" + page + "/photos.html"), "UTF-8");
}
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("photos"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
fileWriter.write(html);
log.info("Generate photos.html succeed.");
if (photos.hasNext()) {
generatePhoto(photos.getNumber() + 2);
log.info("Generate page/{}/index.html", photos.getNumber() + 2);
}
}
/**
* Generate categories/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateCategories() throws IOException, TemplateException {
log.info("Generate categories.html");
ModelMap model = new ModelMap();
if (!themeService.templateExists("categories.ftl")) {
log.warn("categories.ftl not found,skip!");
return;
}
model.addAttribute("is_categories", true);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("categories"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("categories/index.html"), "UTF-8");
fileWriter.write(html);
List<Category> categories = categoryService.listAll();
for (Category category : categories) {
generateCategory(1, category);
}
log.info("Generate categories.html succeed.");
}
/**
* Generate categories/{slugName}/index.html and categories/{slugName}/{page}/index.html.
*
* @param page current page
* @param category current category
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateCategory(int page, Category category) throws IOException, TemplateException {
if (!themeService.templateExists("category.ftl")) {
log.warn("category.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
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);
final int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
model.addAttribute("is_category", true);
model.addAttribute("posts", posts);
model.addAttribute("rainbow", rainbow);
model.addAttribute("category", category);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("category"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("categories/" + category.getSlugName() + "/index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("categories/" + category.getSlugName() + "/page/" + page + "/index.html"), "UTF-8");
}
fileWriter.write(html);
if (postPage.hasNext()) {
generateCategory(postPage.getNumber() + 2, category);
log.info("Generate categories/{}/page/{}/index.html", category.getSlugName(), postPage.getNumber() + 2);
}
}
/**
* Generate tags/{slugName}/index.html and tags/{slugName}/{page}/index.html.
*
* @param page current page
* @param category current category
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateTag(int page, Tag tag) throws IOException, TemplateException {
if (!themeService.templateExists("tag.ftl")) {
log.warn("tag.ftl not found,skip!");
return;
}
ModelMap model = new ModelMap();
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);
final int[] rainbow = PageUtil.rainbow(page, posts.getTotalPages(), 3);
model.addAttribute("is_tag", true);
model.addAttribute("posts", posts);
model.addAttribute("rainbow", rainbow);
model.addAttribute("tag", tag);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("tag"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter;
if (page == 1) {
fileWriter = new FileWriter(getPageFile("tags/" + tag.getSlugName() + "/index.html"), "UTF-8");
} else {
fileWriter = new FileWriter(getPageFile("tags/" + tag.getSlugName() + "/page/" + page + "/index.html"), "UTF-8");
}
fileWriter.write(html);
if (postPage.hasNext()) {
generateTag(postPage.getNumber() + 2, tag);
log.info("Generate tags/{}/page/{}/index.html", tag.getSlugName(), postPage.getNumber() + 2);
}
}
/**
* Generate tags/index.html.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateTags() throws IOException, TemplateException {
log.info("Generate tags.html");
ModelMap model = new ModelMap();
if (!themeService.templateExists("tags.ftl")) {
log.warn("tags.ftl not found,skip!");
return;
}
model.addAttribute("is_tags", true);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(themeService.renderWithSuffix("tags"));
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("tags/index.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate tags.html succeed.");
List<Tag> tags = tagService.listAll();
for (Tag tag : tags) {
generateTag(1, tag);
}
}
/**
* Generate rss.xml and feed.xml
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateRss() throws IOException, TemplateException {
log.info("Generate rss.xml/feed.xml");
ModelMap model = new ModelMap();
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getRssPageSize())));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/rss.ftl");
String xml = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter rssWriter = new FileWriter(getPageFile("rss.xml"), "UTF-8");
rssWriter.write(xml);
FileWriter feedWriter = new FileWriter(getPageFile("feed.xml"), "UTF-8");
feedWriter.write(xml);
log.info("Generate rss.xml/feed.xml succeed.");
}
/**
* Generate atom.xml
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateAtom() throws IOException, TemplateException {
log.info("Generate atom.xml");
ModelMap model = new ModelMap();
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getRssPageSize())));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/atom.ftl");
String xml = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("atom.xml"), "UTF-8");
fileWriter.write(xml);
log.info("Generate atom.xml succeed.");
}
/**
* Generate sitemap.html
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateSiteMapHtml() throws IOException, TemplateException {
log.info("Generate sitemap.html");
ModelMap model = new ModelMap();
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getRssPageSize())));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/sitemap_html.ftl");
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("sitemap.html"), "UTF-8");
fileWriter.write(html);
log.info("Generate sitemap.html succeed.");
}
/**
* Generate sitemap.xml
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateSiteMapXml() throws IOException, TemplateException {
log.info("Generate sitemap.xml");
ModelMap model = new ModelMap();
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getRssPageSize())));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/sitemap_xml.ftl");
String xml = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
FileWriter fileWriter = new FileWriter(getPageFile("sitemap.xml"), "UTF-8");
fileWriter.write(xml);
log.info("Generate sitemap.xml succeed.");
}
/**
* Generate robots.txt
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateRobots() throws IOException, TemplateException {
log.info("Generate robots.txt");
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/robots.ftl");
String txt = FreeMarkerTemplateUtils.processTemplateIntoString(template, null);
FileWriter fileWriter = new FileWriter(getPageFile("robots.txt"), "UTF-8");
fileWriter.write(txt);
log.info("Generate robots.txt succeed.");
}
/**
* Generate README.md.
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
private void generateReadme() throws IOException, TemplateException {
log.info("Generate readme.md");
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("common/web/readme.ftl");
String txt = FreeMarkerTemplateUtils.processTemplateIntoString(template, null);
FileWriter fileWriter = new FileWriter(getPageFile("README.md"), "UTF-8");
fileWriter.write(txt);
log.info("Generate readme.md succeed.");
}
/**
* Copy current theme folder.
*
* @throws IOException IOException
*/
private void copyThemeFolder() throws IOException {
ThemeProperty activatedTheme = themeService.getActivatedTheme();
Path path = Paths.get(pagesDir.toString(), activatedTheme.getId());
FileUtils.createIfAbsent(path);
FileUtils.copyFolder(Paths.get(activatedTheme.getThemePath()), path);
}
/**
* Copy upload folder.
*
* @throws IOException IOException
*/
private void copyUpload() throws IOException {
Path path = Paths.get(pagesDir.toString(), "upload");
FileUtils.createIfAbsent(path);
FileUtils.copyFolder(Paths.get(haloProperties.getWorkDir(), "upload"), path);
}
/**
* Build posts for feed
*
* @param pageable pageable
* @return List<Post>
*/
private List<PostDetailVO> buildPosts(@NonNull Pageable pageable) {
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostDetailVO> posts = postService.convertToDetailVo(postPage);
posts.getContent().forEach(postListVO -> {
try {
// Encode post url
postListVO.setUrl(URLEncoder.encode(postListVO.getUrl(), StandardCharsets.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
log.warn("Failed to encode url: " + postListVO.getUrl(), e);
}
});
return posts.getContent();
}
/**
* Builds page info for post.
*
* @param size page size
* @return page info
*/
@NonNull
private Pageable buildPostPageable(int size) {
return PageRequest.of(0, size, Sort.by(DESC, "createTime"));
}
private File getPageFile(String subPath) {
Path path = Paths.get(pagesDir.toString(), subPath);
return path.toFile();
}
}

View File

@ -352,6 +352,14 @@ public class ThemeServiceImpl implements ThemeService {
return String.format(RENDER_TEMPLATE, activatedTheme.getFolderName(), pageName);
}
@Override
public String renderWithSuffix(String pageName) {
// Get activated theme
ThemeProperty activatedTheme = getActivatedTheme();
// Build render url
return String.format(RENDER_TEMPLATE_SUFFIX, activatedTheme.getFolderName(), pageName);
}
@Override
public String getActivatedThemeId() {
if (activatedThemeId == null) {

View File

@ -0,0 +1,32 @@
<div style="text-align:center">
<img src="${user.avatar!}" width="100" height="100" alt="${user.nickname!}">
<h3>${options.blog_title!}</h3>
<h4>
<a href="${context!}" target="_blank">${context!}</a>
</h4>
</div>
---
<@postTag method="archiveYear">
<#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>
</#list>
</#list>
</@postTag>
## 分类目录
<@categoryTag method="list">
<#list categories as category>
- <a href="${context!}/categories/${category.slugName!}" 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>
</#list>
</@tagTag>