mirror of https://github.com/halo-dev/halo
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
parent
3973768a7a
commit
3d79484591
|
@ -9,6 +9,7 @@ import org.thymeleaf.processor.element.AbstractElementModelProcessor;
|
||||||
import org.thymeleaf.processor.element.IElementModelStructureHandler;
|
import org.thymeleaf.processor.element.IElementModelStructureHandler;
|
||||||
import org.thymeleaf.spring6.context.SpringContextUtils;
|
import org.thymeleaf.spring6.context.SpringContextUtils;
|
||||||
import org.thymeleaf.templatemode.TemplateMode;
|
import org.thymeleaf.templatemode.TemplateMode;
|
||||||
|
import run.halo.app.plugin.ExtensionComponentsFinder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global head injection processor.
|
* Global head injection processor.
|
||||||
|
@ -50,10 +51,6 @@ public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor
|
||||||
structureHandler.setLocalVariable(PROCESS_FLAG, true);
|
structureHandler.setLocalVariable(PROCESS_FLAG, true);
|
||||||
|
|
||||||
// handle <head> tag
|
// 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.
|
* Create the DOM structure that will be substituting our custom tag.
|
||||||
|
@ -65,7 +62,8 @@ public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor
|
||||||
|
|
||||||
// apply processors to modelToInsert
|
// apply processors to modelToInsert
|
||||||
Collection<TemplateHeadProcessor> templateHeadProcessors =
|
Collection<TemplateHeadProcessor> templateHeadProcessors =
|
||||||
getTemplateHeadProcessors(appCtx);
|
getTemplateHeadProcessors(context);
|
||||||
|
|
||||||
for (TemplateHeadProcessor processor : templateHeadProcessors) {
|
for (TemplateHeadProcessor processor : templateHeadProcessors) {
|
||||||
processor.process(context, modelToInsert, structureHandler)
|
processor.process(context, modelToInsert, structureHandler)
|
||||||
.block();
|
.block();
|
||||||
|
@ -75,8 +73,10 @@ public class GlobalHeadInjectionProcessor extends AbstractElementModelProcessor
|
||||||
model.insertModel(model.size() - 1, modelToInsert);
|
model.insertModel(model.size() - 1, modelToInsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<TemplateHeadProcessor> getTemplateHeadProcessors(ApplicationContext ctx) {
|
private Collection<TemplateHeadProcessor> getTemplateHeadProcessors(ITemplateContext context) {
|
||||||
return ctx.getBeansOfType(TemplateHeadProcessor.class)
|
ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context);
|
||||||
.values();
|
ExtensionComponentsFinder componentsFinder =
|
||||||
|
appCtx.getBean(ExtensionComponentsFinder.class);
|
||||||
|
return componentsFinder.getExtensions(TemplateHeadProcessor.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.theme.dialect;
|
package run.halo.app.theme.dialect;
|
||||||
|
|
||||||
|
import org.pf4j.ExtensionPoint;
|
||||||
import org.thymeleaf.context.ITemplateContext;
|
import org.thymeleaf.context.ITemplateContext;
|
||||||
import org.thymeleaf.model.IModel;
|
import org.thymeleaf.model.IModel;
|
||||||
import org.thymeleaf.processor.element.IElementModelStructureHandler;
|
import org.thymeleaf.processor.element.IElementModelStructureHandler;
|
||||||
|
@ -12,7 +13,7 @@ import reactor.core.publisher.Mono;
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface TemplateHeadProcessor {
|
public interface TemplateHeadProcessor extends ExtensionPoint {
|
||||||
|
|
||||||
Mono<Void> process(ITemplateContext context, IModel model,
|
Mono<Void> process(ITemplateContext context, IModel model,
|
||||||
IElementModelStructureHandler structureHandler);
|
IElementModelStructureHandler structureHandler);
|
||||||
|
|
|
@ -30,6 +30,7 @@ import run.halo.app.core.extension.Post;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
|
import run.halo.app.plugin.ExtensionComponentsFinder;
|
||||||
import run.halo.app.theme.DefaultTemplateEnum;
|
import run.halo.app.theme.DefaultTemplateEnum;
|
||||||
import run.halo.app.theme.finders.PostFinder;
|
import run.halo.app.theme.finders.PostFinder;
|
||||||
import run.halo.app.theme.finders.vo.PostVo;
|
import run.halo.app.theme.finders.vo.PostVo;
|
||||||
|
@ -57,6 +58,9 @@ class HaloProcessorDialectTest {
|
||||||
@Mock
|
@Mock
|
||||||
private SystemConfigurableEnvironmentFetcher fetcher;
|
private SystemConfigurableEnvironmentFetcher fetcher;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ExtensionComponentsFinder extensionComponentsFinder;
|
||||||
|
|
||||||
private TemplateEngine templateEngine;
|
private TemplateEngine templateEngine;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
@ -70,7 +74,7 @@ class HaloProcessorDialectTest {
|
||||||
map.put("postTemplateHeadProcessor", new PostTemplateHeadProcessor(postFinder));
|
map.put("postTemplateHeadProcessor", new PostTemplateHeadProcessor(postFinder));
|
||||||
map.put("templateGlobalHeadProcessor", new TemplateGlobalHeadProcessor(fetcher));
|
map.put("templateGlobalHeadProcessor", new TemplateGlobalHeadProcessor(fetcher));
|
||||||
map.put("faviconHeadProcessor", new DefaultFaviconHeadProcessor(fetcher));
|
map.put("faviconHeadProcessor", new DefaultFaviconHeadProcessor(fetcher));
|
||||||
lenient().when(applicationContext.getBeansOfType(TemplateHeadProcessor.class))
|
lenient().when(applicationContext.getBeansOfType(eq(TemplateHeadProcessor.class)))
|
||||||
.thenReturn(map);
|
.thenReturn(map);
|
||||||
|
|
||||||
SystemSetting.CodeInjection codeInjection = new SystemSetting.CodeInjection();
|
SystemSetting.CodeInjection codeInjection = new SystemSetting.CodeInjection();
|
||||||
|
@ -80,8 +84,14 @@ class HaloProcessorDialectTest {
|
||||||
when(fetcher.fetch(eq(SystemSetting.CodeInjection.GROUP),
|
when(fetcher.fetch(eq(SystemSetting.CodeInjection.GROUP),
|
||||||
eq(SystemSetting.CodeInjection.class))).thenReturn(Mono.just(codeInjection));
|
eq(SystemSetting.CodeInjection.class))).thenReturn(Mono.just(codeInjection));
|
||||||
|
|
||||||
when(applicationContext.getBean(SystemConfigurableEnvironmentFetcher.class))
|
when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class)))
|
||||||
.thenReturn(fetcher);
|
.thenReturn(fetcher);
|
||||||
|
|
||||||
|
when(applicationContext.getBean(eq(ExtensionComponentsFinder.class)))
|
||||||
|
.thenReturn(extensionComponentsFinder);
|
||||||
|
|
||||||
|
when(extensionComponentsFinder.getExtensions(eq(TemplateHeadProcessor.class)))
|
||||||
|
.thenReturn(new ArrayList<>(map.values()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue