feat: head tag supports extension for template (#2574)

#### What type of PR is this?
/kind feature
/milestone 2.0
/area core

#### What this PR does / why we need it:
允许插件通过实现 TemplateHeadProcessor 接口来修改主题模板的 head 标签

#### Which issue(s) this PR fixes:
how to test it?
1. 克隆 https://github.com/halo-sigs/plugin-umami
2. build 一个 jar 包作为插件使用
3. 配置 plugin-umami 后能在主题页的 head 标签看到一个用于 umami 统计的 script 标签

Fixes #

#### Special notes for your reviewer:
/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/2537/head
guqing 2022-10-18 12:18:09 +08:00 committed by GitHub
parent 3973768a7a
commit 3d79484591
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 22 additions and 11 deletions

View File

@ -9,6 +9,7 @@ import org.thymeleaf.processor.element.AbstractElementModelProcessor;
import org.thymeleaf.processor.element.IElementModelStructureHandler;
import org.thymeleaf.spring6.context.SpringContextUtils;
import org.thymeleaf.templatemode.TemplateMode;
import run.halo.app.plugin.ExtensionComponentsFinder;
/**
* Global head injection processor.
@ -50,10 +51,6 @@ public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor
structureHandler.setLocalVariable(PROCESS_FLAG, true);
// handle <head> tag
/*
* Obtain the Spring application context.
*/
final ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context);
/*
* Create the DOM structure that will be substituting our custom tag.
@ -65,7 +62,8 @@ public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor
// apply processors to modelToInsert
Collection<TemplateHeadProcessor> templateHeadProcessors =
getTemplateHeadProcessors(appCtx);
getTemplateHeadProcessors(context);
for (TemplateHeadProcessor processor : templateHeadProcessors) {
processor.process(context, modelToInsert, structureHandler)
.block();
@ -75,8 +73,10 @@ public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor
model.insertModel(model.size() - 1, modelToInsert);
}
private Collection<TemplateHeadProcessor> getTemplateHeadProcessors(ApplicationContext ctx) {
return ctx.getBeansOfType(TemplateHeadProcessor.class)
.values();
private Collection<TemplateHeadProcessor> getTemplateHeadProcessors(ITemplateContext context) {
ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context);
ExtensionComponentsFinder componentsFinder =
appCtx.getBean(ExtensionComponentsFinder.class);
return componentsFinder.getExtensions(TemplateHeadProcessor.class);
}
}

View File

@ -1,5 +1,6 @@
package run.halo.app.theme.dialect;
import org.pf4j.ExtensionPoint;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.model.IModel;
import org.thymeleaf.processor.element.IElementModelStructureHandler;
@ -12,7 +13,7 @@ import reactor.core.publisher.Mono;
* @since 2.0.0
*/
@FunctionalInterface
public interface TemplateHeadProcessor {
public interface TemplateHeadProcessor extends ExtensionPoint {
Mono<Void> process(ITemplateContext context, IModel model,
IElementModelStructureHandler structureHandler);

View File

@ -30,6 +30,7 @@ import run.halo.app.core.extension.Post;
import run.halo.app.extension.Metadata;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
import run.halo.app.plugin.ExtensionComponentsFinder;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.PostFinder;
import run.halo.app.theme.finders.vo.PostVo;
@ -57,6 +58,9 @@ class HaloProcessorDialectTest {
@Mock
private SystemConfigurableEnvironmentFetcher fetcher;
@Mock
private ExtensionComponentsFinder extensionComponentsFinder;
private TemplateEngine templateEngine;
@BeforeEach
@ -70,7 +74,7 @@ class HaloProcessorDialectTest {
map.put("postTemplateHeadProcessor", new PostTemplateHeadProcessor(postFinder));
map.put("templateGlobalHeadProcessor", new TemplateGlobalHeadProcessor(fetcher));
map.put("faviconHeadProcessor", new DefaultFaviconHeadProcessor(fetcher));
lenient().when(applicationContext.getBeansOfType(TemplateHeadProcessor.class))
lenient().when(applicationContext.getBeansOfType(eq(TemplateHeadProcessor.class)))
.thenReturn(map);
SystemSetting.CodeInjection codeInjection = new SystemSetting.CodeInjection();
@ -80,8 +84,14 @@ class HaloProcessorDialectTest {
when(fetcher.fetch(eq(SystemSetting.CodeInjection.GROUP),
eq(SystemSetting.CodeInjection.class))).thenReturn(Mono.just(codeInjection));
when(applicationContext.getBean(SystemConfigurableEnvironmentFetcher.class))
when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class)))
.thenReturn(fetcher);
when(applicationContext.getBean(eq(ExtensionComponentsFinder.class)))
.thenReturn(extensionComponentsFinder);
when(extensionComponentsFinder.getExtensions(eq(TemplateHeadProcessor.class)))
.thenReturn(new ArrayList<>(map.values()));
}
@Test