From 4cbd53bf4b8eccb4458a4ed3fe47719136aaf62d Mon Sep 17 00:00:00 2001 From: CaiHaosen <63104253+CaiHaosen@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:40:55 +0800 Subject: [PATCH] Implement global HTML injection for head and footer (#9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind feature #### What this PR does / why we need it: 此 PR 实现了代码全局的HEAD和FOOTER注入,暂时没有实现路径匹配的逻辑 #### Which issue(s) this PR fixes: Fixes #8 #### Does this PR introduce a user-facing change? ```release-note None ``` --- build.gradle | 2 +- .../halo/injection/AbstractHtmlProcessor.java | 18 +++++++++ .../halo/injection/HtmlFooterProcessor.java | 36 ++++++++++++++++++ .../run/halo/injection/HtmlHeadProcessor.java | 35 +++++++++++++++++ .../java/run/halo/injection/HtmlService.java | 8 ++++ .../run/halo/injection/HtmlServiceImpl.java | 38 +++++++++++++++++++ .../run/halo/injection/InjectionPlugin.java | 23 ++++++++++- 7 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 src/main/java/run/halo/injection/AbstractHtmlProcessor.java create mode 100644 src/main/java/run/halo/injection/HtmlFooterProcessor.java create mode 100644 src/main/java/run/halo/injection/HtmlHeadProcessor.java create mode 100644 src/main/java/run/halo/injection/HtmlService.java create mode 100644 src/main/java/run/halo/injection/HtmlServiceImpl.java diff --git a/build.gradle b/build.gradle index 8268e510..d73bba4f 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ repositories { } dependencies { - implementation platform('run.halo.tools.platform:plugin:2.11.0-SNAPSHOT') + implementation platform('run.halo.tools.platform:plugin:2.17.0-SNAPSHOT') compileOnly 'run.halo.app:api' testImplementation 'run.halo.app:api' diff --git a/src/main/java/run/halo/injection/AbstractHtmlProcessor.java b/src/main/java/run/halo/injection/AbstractHtmlProcessor.java new file mode 100644 index 00000000..73a1814c --- /dev/null +++ b/src/main/java/run/halo/injection/AbstractHtmlProcessor.java @@ -0,0 +1,18 @@ +package run.halo.injection; + +import java.util.Set; +import org.thymeleaf.context.ITemplateContext; + +public abstract class AbstractHtmlProcessor { + protected static final String TEMPLATE_ID_VARIABLE = "_templateId"; + + protected boolean isContentTemplate(ITemplateContext context) { + return "post".equals(context.getVariable(TEMPLATE_ID_VARIABLE)) + || "page".equals(context.getVariable(TEMPLATE_ID_VARIABLE)); + } + + // 匹配路径接口 + protected boolean isRequestPathMatchingRoute(String requestRoute, Set pageRules) { + return true; + } +} diff --git a/src/main/java/run/halo/injection/HtmlFooterProcessor.java b/src/main/java/run/halo/injection/HtmlFooterProcessor.java new file mode 100644 index 00000000..b21fac45 --- /dev/null +++ b/src/main/java/run/halo/injection/HtmlFooterProcessor.java @@ -0,0 +1,36 @@ +package run.halo.injection; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thymeleaf.context.ITemplateContext; +import org.thymeleaf.model.IModel; +import org.thymeleaf.model.IProcessableElementTag; +import org.thymeleaf.processor.element.IElementTagStructureHandler; +import reactor.core.publisher.Mono; +import run.halo.app.theme.dialect.TemplateFooterProcessor; + +@Slf4j +@Component +@RequiredArgsConstructor +public class HtmlFooterProcessor extends AbstractHtmlProcessor implements TemplateFooterProcessor { + private final HtmlService htmlService; + + @Override + public Mono process(ITemplateContext context, IProcessableElementTag tag, + IElementTagStructureHandler structureHandler, IModel model) { + return htmlService.listEnabledInjectionsByPoint(HtmlInjection.InjectionPoint.FOOTER) + .doOnNext(htmlInjection -> { + if (isContentTemplate(context)) { + model.add( + context.getModelFactory().createText( + htmlInjection.getSpec().getFragment())); + } + }) + .onErrorResume(e -> { + log.error("HtmlFooterProcessor process failed", e); + return Mono.empty(); + }) + .then(); + } +} diff --git a/src/main/java/run/halo/injection/HtmlHeadProcessor.java b/src/main/java/run/halo/injection/HtmlHeadProcessor.java new file mode 100644 index 00000000..49d2252f --- /dev/null +++ b/src/main/java/run/halo/injection/HtmlHeadProcessor.java @@ -0,0 +1,35 @@ +package run.halo.injection; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thymeleaf.context.ITemplateContext; +import org.thymeleaf.model.IModel; +import org.thymeleaf.processor.element.IElementModelStructureHandler; +import reactor.core.publisher.Mono; +import run.halo.app.theme.dialect.TemplateHeadProcessor; + +@Slf4j +@Component +@RequiredArgsConstructor +public class HtmlHeadProcessor extends AbstractHtmlProcessor implements TemplateHeadProcessor { + private final HtmlService htmlService; + + @Override + public Mono process(ITemplateContext context, IModel model, + IElementModelStructureHandler structureHandler) { + return htmlService.listEnabledInjectionsByPoint(HtmlInjection.InjectionPoint.HEADER) + .doOnNext(htmlInjection -> { + if (isContentTemplate(context)) { + model.add( + context.getModelFactory().createText( + htmlInjection.getSpec().getFragment())); + } + }) + .onErrorResume(e -> { + log.error("HtmlHeadProcessor process failed", e); + return Mono.empty(); + }) + .then(); + } +} diff --git a/src/main/java/run/halo/injection/HtmlService.java b/src/main/java/run/halo/injection/HtmlService.java new file mode 100644 index 00000000..85fcbd96 --- /dev/null +++ b/src/main/java/run/halo/injection/HtmlService.java @@ -0,0 +1,8 @@ +package run.halo.injection; + +import reactor.core.publisher.Flux; + +public interface HtmlService { + + Flux listEnabledInjectionsByPoint(HtmlInjection.InjectionPoint injectionPoint); +} diff --git a/src/main/java/run/halo/injection/HtmlServiceImpl.java b/src/main/java/run/halo/injection/HtmlServiceImpl.java new file mode 100644 index 00000000..32ac4722 --- /dev/null +++ b/src/main/java/run/halo/injection/HtmlServiceImpl.java @@ -0,0 +1,38 @@ +package run.halo.injection; + +import static run.halo.app.extension.index.query.QueryFactory.and; +import static run.halo.app.extension.index.query.QueryFactory.equal; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import run.halo.app.extension.ListOptions; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.extension.index.query.Query; + +@Service +@RequiredArgsConstructor +public class HtmlServiceImpl implements HtmlService { + private final ReactiveExtensionClient client; + + /** + * 根据注入点获取正在启用的HtmlInjection 对象. + */ + @Override + public Flux listEnabledInjectionsByPoint( + HtmlInjection.InjectionPoint injectionPoint) { + Query query = and( + equal("spec.enabled", "true"), + equal("spec.injectionPoint", injectionPoint.name()) + ); + + ListOptions options = ListOptions.builder() + .fieldQuery(query) + .build(); + + return client.listAll(HtmlInjection.class, options, + Sort.by(Sort.Order.asc("metadata.name"))); + } +} + diff --git a/src/main/java/run/halo/injection/InjectionPlugin.java b/src/main/java/run/halo/injection/InjectionPlugin.java index 029b35e6..937b4ded 100644 --- a/src/main/java/run/halo/injection/InjectionPlugin.java +++ b/src/main/java/run/halo/injection/InjectionPlugin.java @@ -1,12 +1,14 @@ package run.halo.injection; +import static run.halo.app.extension.index.IndexAttributeFactory.simpleAttribute; + import org.springframework.stereotype.Component; import run.halo.app.extension.Scheme; import run.halo.app.extension.SchemeManager; +import run.halo.app.extension.index.IndexSpec; import run.halo.app.plugin.BasePlugin; import run.halo.app.plugin.PluginContext; - @Component public class InjectionPlugin extends BasePlugin { private final SchemeManager schemeManager; @@ -18,7 +20,24 @@ public InjectionPlugin(PluginContext pluginContext, SchemeManager schemeManager) @Override public void start() { - schemeManager.register(HtmlInjection.class); + schemeManager.register(HtmlInjection.class, indexSpecs -> { + // 为 enabled 添加索引方便根据启用状态查询 + indexSpecs.add(new IndexSpec() + .setName("spec.enabled") + .setIndexFunc(simpleAttribute(HtmlInjection.class, htmlInjection -> { + return Boolean.toString(htmlInjection.getSpec().isEnabled()); + })) + ); + // 为 injectionPoint 添加索引 + indexSpecs.add(new IndexSpec() + .setName("spec.injectionPoint") + .setIndexFunc(simpleAttribute(HtmlInjection.class, htmlInjection -> { + HtmlInjection.InjectionPoint injectionPoint = + htmlInjection.getSpec().getInjectionPoint(); + return injectionPoint != null ? injectionPoint.name() : null; + })) + ); + }); } @Override