mirror of https://github.com/halo-dev/halo
feat: 扩展 freemarker 实现 block (#1295)
* fix #950 附件不存在时删除报错的问题 * pref: 扩展 freemarker 增加 block 功能 close #1292 * checkStyle * add unit test * update testpull/1289/head^2
parent
3571b3f0af
commit
696a9ad2ee
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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()
|
||||
: "";
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue