From 696a9ad2ee5807ffa5637061cd059b28d835f9f2 Mon Sep 17 00:00:00 2001 From: Li <1103069291@qq.com> Date: Fri, 5 Mar 2021 23:37:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=A9=E5=B1=95=20freemarker=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20block=20(#1295)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix #950 附件不存在时删除报错的问题 * pref: 扩展 freemarker 增加 block 功能 close #1292 * checkStyle * add unit test * update test --- build.gradle | 2 + .../halo/app/config/HaloMvcConfiguration.java | 21 ++++ .../inheritance/ThemeExtendsDirective.java | 46 ++++++++ .../halo/app/freemarker/FreeMarkerTest.java | 102 ++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 src/main/java/run/halo/app/core/freemarker/inheritance/ThemeExtendsDirective.java create mode 100644 src/test/java/run/halo/app/freemarker/FreeMarkerTest.java diff --git a/build.gradle b/build.gradle index 0b651d181..78db65fee 100644 --- a/build.gradle +++ b/build.gradle @@ -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" diff --git a/src/main/java/run/halo/app/config/HaloMvcConfiguration.java b/src/main/java/run/halo/app/config/HaloMvcConfiguration.java index d36d93c12..aee099a83 100644 --- a/src/main/java/run/halo/app/config/HaloMvcConfiguration.java +++ b/src/main/java/run/halo/app/config/HaloMvcConfiguration.java @@ -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 freemarkerLayoutDirectives() { + Map 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); diff --git a/src/main/java/run/halo/app/core/freemarker/inheritance/ThemeExtendsDirective.java b/src/main/java/run/halo/app/core/freemarker/inheritance/ThemeExtendsDirective.java new file mode 100644 index 000000000..7ff9fef08 --- /dev/null +++ b/src/main/java/run/halo/app/core/freemarker/inheritance/ThemeExtendsDirective.java @@ -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() + : ""; + } +} diff --git a/src/test/java/run/halo/app/freemarker/FreeMarkerTest.java b/src/test/java/run/halo/app/freemarker/FreeMarkerTest.java new file mode 100644 index 000000000..423fbce3a --- /dev/null +++ b/src/test/java/run/halo/app/freemarker/FreeMarkerTest.java @@ -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" + + " 自定义标题 - 替换模板内容\n" + + " \n" + + " <@layout.put block=\"header\">\n" + + "

第二级页头 - 默认放置在模板内容之后

\n" + + " \n" + + " <@layout.put block=\"class\">sheet test-sheet\n" + + " <@layout.put block=\"contents\">\n" + + "

这是自定义页面内容

\n" + + " \n" + + " <@layout.put block=\"footer\" type=\"prepend\">\n" + + "
\n" + + "
页脚内容 - 放置在模板内容之前
\n" + + " \n" + + ""); + templateLoader.putTemplate("layout/base.ftl", + "\n" + + "\n" + + "\n" + + " <@layout.block name=\"title\">\n" + + " 标题\n" + + " \n" + + "\n" + + "\n" + + "<@layout.block name=\"header\">\n" + + "

页头

\n" + + "\n" + + "
\">\n" + + " <@layout.block name=\"content\">\n" + + " \n" + + "
\n" + + "<@layout.block name=\"footer\">\n" + + "
页脚
\n" + + "\n" + + "\n" + + ""); + + cfg.setTemplateLoader(templateLoader); + + freeMarkerConfigurer.setConfiguration(cfg); + + Template template = cfg.getTemplate("index.ftl"); + StringWriter out = new StringWriter(); + + template.process(null, out); + + assertEquals( + "\n" + + "\n" + + "\n" + + " 自定义标题 - 替换模板内容\n" + + "\n" + + "\n" + + "

页头

\n" + + "

第二级页头 - 默认放置在模板内容之后

\n" + + "
\n" + + "
\n" + + "
\n" + + "
页脚内容 - 放置在模板内容之前
\n" + + "
页脚
\n" + + "\n" + + "", + out.toString()); + } +}