feat: 扩展 freemarker 实现 block (#1295)

* fix #950 附件不存在时删除报错的问题

* pref: 扩展 freemarker 增加 block 功能

close #1292

* checkStyle

* add unit test

* update test
pull/1289/head^2
Li 2021-03-05 23:37:02 +08:00 committed by GitHub
parent 3571b3f0af
commit 696a9ad2ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 171 additions and 0 deletions

View File

@ -103,6 +103,7 @@ ext {
zxingVersion = "3.4.0"
huaweiObsVersion = "3.19.7"
githubApiVersion = "1.84"
templateInheritanceVersion = "0.4.RELEASE"
}
dependencies {
@ -148,6 +149,7 @@ dependencies {
implementation "com.vladsch.flexmark:flexmark-ext-yaml-front-matter:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-ext-gitlab:$flexmarkVersion"
implementation "kr.pe.kwonnam.freemarker:freemarker-template-inheritance:$templateInheritanceVersion"
implementation "net.coobird:thumbnailator:$thumbnailatorVersion"
implementation "net.sf.image4j:image4j:$image4jVersion"
implementation "org.flywaydb:flyway-core:$flywayVersion"

View File

@ -9,12 +9,17 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import freemarker.core.TemplateClassResolver;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.servlet.MultipartConfigElement;
import javax.servlet.http.HttpServletRequest;
import kr.pe.kwonnam.freemarker.inheritance.BlockDirective;
import kr.pe.kwonnam.freemarker.inheritance.PutDirective;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
@ -48,6 +53,7 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.core.PageJacksonSerializer;
import run.halo.app.core.freemarker.inheritance.ThemeExtendsDirective;
import run.halo.app.factory.StringToEnumConverterFactory;
import run.halo.app.model.support.HaloConst;
import run.halo.app.security.resolver.AuthenticationArgumentResolver;
@ -79,6 +85,16 @@ public class HaloMvcConfiguration implements WebMvcConfigurer {
this.haloProperties = haloProperties;
}
@Bean
public Map<String, TemplateModel> freemarkerLayoutDirectives() {
Map<String, TemplateModel> freemarkerLayoutDirectives = new HashMap<>();
freemarkerLayoutDirectives.put("extends", new ThemeExtendsDirective());
freemarkerLayoutDirectives.put("block", new BlockDirective());
freemarkerLayoutDirectives.put("put", new PutDirective());
return freemarkerLayoutDirectives;
}
/**
* Configuring freemarker template file path.
*
@ -108,6 +124,11 @@ public class HaloMvcConfiguration implements WebMvcConfigurer {
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
}
configuration.setSharedVariables(new HashMap<>() {{
put("layout", freemarkerLayoutDirectives());
}
});
// Set predefined freemarker configuration
configurer.setConfiguration(configuration);

View File

@ -0,0 +1,46 @@
package run.halo.app.core.freemarker.inheritance;
import freemarker.core.Environment;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import java.io.IOException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import kr.pe.kwonnam.freemarker.inheritance.ExtendsDirective;
import org.springframework.stereotype.Component;
/**
* @author LIlGG
* @date 2021/3/4
*/
@Component
public class ThemeExtendsDirective extends ExtendsDirective {
private static final Pattern THEME_TEMPLATE_PATH_PATTERN = Pattern.compile("^themes/.*?/");
@Override
@SuppressWarnings("unchecked")
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
String currTemplateName = getTemplateRelativePath(env);
String name = ((SimpleScalar) params.get("name")).getAsString();
String includeTemplateName = env.rootBasedToAbsoluteTemplateName(
env.toFullTemplateName(currTemplateName, name)
);
params.put("name", new SimpleScalar(includeTemplateName));
super.execute(env, params, loopVars, body);
}
private String getTemplateRelativePath(Environment env) {
String templateName = env.getCurrentTemplate().getName();
Matcher matcher = THEME_TEMPLATE_PATH_PATTERN.matcher(templateName);
return matcher.find()
? matcher.group()
: "";
}
}

View File

@ -0,0 +1,102 @@
package run.halo.app.freemarker;
import static org.junit.jupiter.api.Assertions.assertEquals;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.IOException;
import java.io.StringWriter;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
/**
* @author LIlGG
* @date 2021/3/5
*/
@SpringBootTest
@ActiveProfiles("test")
class FreeMarkerTest {
@Autowired
FreeMarkerConfigurer freeMarkerConfigurer;
@Test
public void testBlockRender() throws IOException, TemplateException {
Configuration cfg = freeMarkerConfigurer.getConfiguration();
cfg.setDefaultEncoding("UTF-8");
StringTemplateLoader templateLoader = new StringTemplateLoader();
templateLoader.putTemplate("common/macro/common_macro.ftl", "");
templateLoader.putTemplate("common/macro/global_macro.ftl", "");
templateLoader.putTemplate("index.ftl",
"<@layout.extends name=\"layout/base.ftl\">\n"
+ " <@layout.put block=\"title\" type=\"replace\">\n"
+ " <title>自定义标题 - 替换模板内容</title>\n"
+ " </@layout.put>\n"
+ " <@layout.put block=\"header\">\n"
+ " <h2>第二级页头 - 默认放置在模板内容之后</h2>\n"
+ " </@layout.put>\n"
+ " <@layout.put block=\"class\">sheet test-sheet</@layout.put>\n"
+ " <@layout.put block=\"contents\">\n"
+ " <p>这是自定义页面内容</p>\n"
+ " </@layout.put>\n"
+ " <@layout.put block=\"footer\" type=\"prepend\">\n"
+ " <hr/>\n"
+ " <div class=\"footer\">页脚内容 - 放置在模板内容之前</div>\n"
+ " </@layout.put>\n"
+ "</@layout.extends>");
templateLoader.putTemplate("layout/base.ftl",
"<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <@layout.block name=\"title\">\n"
+ " <title>标题</title>\n"
+ " </@layout.block>\n"
+ "</head>\n"
+ "<body>\n"
+ "<@layout.block name=\"header\">\n"
+ " <h1>页头</h1>\n"
+ "</@layout.block>\n"
+ "<div class=\"content <@layout.block name='class'></@layout.block>\">\n"
+ " <@layout.block name=\"content\">\n"
+ " </@layout.block>\n"
+ "</div>\n"
+ "<@layout.block name=\"footer\">\n"
+ " <div>页脚</div>\n"
+ "</@layout.block>\n"
+ "</body>\n"
+ "</html>");
cfg.setTemplateLoader(templateLoader);
freeMarkerConfigurer.setConfiguration(cfg);
Template template = cfg.getTemplate("index.ftl");
StringWriter out = new StringWriter();
template.process(null, out);
assertEquals(
"<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <title>自定义标题 - 替换模板内容</title>\n"
+ "</head>\n"
+ "<body>\n"
+ " <h1>页头</h1>\n"
+ " <h2>第二级页头 - 默认放置在模板内容之后</h2>\n"
+ "<div class=\"content sheet test-sheet\">\n"
+ "</div>\n"
+ " <hr/>\n"
+ " <div class=\"footer\">页脚内容 - 放置在模板内容之前</div>\n"
+ " <div>页脚</div>\n"
+ "</body>\n"
+ "</html>",
out.toString());
}
}