From bf1be64959950260b0b16ac5c78a08586e5f7d10 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Mon, 24 Jul 2023 17:38:14 +0800 Subject: [PATCH] refactor: conditionally render comment for theme (#4271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core /milestone 2.8.x /area theme #### What this PR does / why we need it: 按条件渲染评论组件以简化主题端对评论组件是否显示的条件控制 使用了评论标签的模板页面都能直接使用 `${haloCommentEnabled}` 取值能得到评论组件是否可见的结果为`true/false` 用于在需要级联条件渲染的组件上使用,如: ```html

评论

``` how to test it? 在主题端未加渲染条件时: 1. 测试全局评论组件是否开启的设置是否有效 2. 测试文章和自定义页面是否开启评论的设置是否有效 3. 测试评论组件启用和停止时评论组件的渲染是否正确 4. 测试 `${haloCommentEnabled}` 结果是否正确 #### Which issue(s) this PR fixes: Fixes #4137 #### Does this PR introduce a user-facing change? ```release-note 按条件渲染评论组件以简化主题端对评论组件是否显示的条件控制 ``` --- .../halo/app/theme/dialect/CommentWidget.java | 2 + .../dialect/CommentElementTagProcessor.java | 60 +++++++++++-- .../halo/app/theme/router/ModelMapUtils.java | 3 + .../CommentElementTagProcessorTest.java | 88 ++++++++++++++++++- 4 files changed, 143 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/run/halo/app/theme/dialect/CommentWidget.java b/api/src/main/java/run/halo/app/theme/dialect/CommentWidget.java index 21214115e..25c4ae34c 100644 --- a/api/src/main/java/run/halo/app/theme/dialect/CommentWidget.java +++ b/api/src/main/java/run/halo/app/theme/dialect/CommentWidget.java @@ -13,6 +13,8 @@ import org.thymeleaf.processor.element.IElementTagStructureHandler; */ public interface CommentWidget extends ExtensionPoint { + String ENABLE_COMMENT_ATTRIBUTE = CommentWidget.class.getName() + ".ENABLE"; + void render(ITemplateContext context, IProcessableElementTag tag, IElementTagStructureHandler structureHandler); } diff --git a/application/src/main/java/run/halo/app/theme/dialect/CommentElementTagProcessor.java b/application/src/main/java/run/halo/app/theme/dialect/CommentElementTagProcessor.java index b4db33e1d..e27c15608 100644 --- a/application/src/main/java/run/halo/app/theme/dialect/CommentElementTagProcessor.java +++ b/application/src/main/java/run/halo/app/theme/dialect/CommentElementTagProcessor.java @@ -1,12 +1,20 @@ package run.halo.app.theme.dialect; +import static org.apache.commons.lang3.BooleanUtils.isFalse; +import static org.apache.commons.lang3.BooleanUtils.isTrue; + +import java.util.Optional; import org.springframework.context.ApplicationContext; +import org.springframework.core.convert.support.DefaultConversionService; +import org.thymeleaf.context.Contexts; import org.thymeleaf.context.ITemplateContext; +import org.thymeleaf.context.IWebContext; import org.thymeleaf.model.IProcessableElementTag; import org.thymeleaf.processor.element.AbstractElementTagProcessor; import org.thymeleaf.processor.element.IElementTagStructureHandler; import org.thymeleaf.spring6.context.SpringContextUtils; import org.thymeleaf.templatemode.TemplateMode; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.plugin.extensionpoint.ExtensionGetter; @@ -19,6 +27,7 @@ import run.halo.app.plugin.extensionpoint.ExtensionGetter; */ public class CommentElementTagProcessor extends AbstractElementTagProcessor { + public static final String COMMENT_ENABLED_MODEL_ATTRIBUTE = "haloCommentEnabled"; private static final String TAG_NAME = "comment"; private static final int PRECEDENCE = 1000; @@ -42,16 +51,49 @@ public class CommentElementTagProcessor extends AbstractElementTagProcessor { @Override protected void doProcess(ITemplateContext context, IProcessableElementTag tag, IElementTagStructureHandler structureHandler) { - final ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context); - ExtensionGetter extensionGetter = appCtx.getBean(ExtensionGetter.class); - CommentWidget commentWidget = - extensionGetter.getEnabledExtensionByDefinition(CommentWidget.class) - .next() - .block(); - if (commentWidget == null) { + getCommentWidget(context).ifPresentOrElse(commentWidget -> { + populateAllowCommentAttribute(context, true); + commentWidget.render(context, tag, structureHandler); + }, () -> { + populateAllowCommentAttribute(context, false); structureHandler.replaceWith("", false); - return; + }); + } + + static void populateAllowCommentAttribute(ITemplateContext context, boolean allowComment) { + if (Contexts.isWebContext(context)) { + IWebContext webContext = Contexts.asWebContext(context); + webContext.getExchange() + .setAttributeValue(COMMENT_ENABLED_MODEL_ATTRIBUTE, allowComment); } - commentWidget.render(context, tag, structureHandler); + } + + static Optional getCommentWidget(ITemplateContext context) { + final ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context); + SystemConfigurableEnvironmentFetcher environmentFetcher = + appCtx.getBean(SystemConfigurableEnvironmentFetcher.class); + var commentSetting = environmentFetcher.fetchComment() + .blockOptional() + .orElseThrow(); + var globalEnabled = isTrue(commentSetting.getEnable()); + if (!globalEnabled) { + return Optional.empty(); + } + + if (Contexts.isWebContext(context)) { + IWebContext webContext = Contexts.asWebContext(context); + Object attributeValue = webContext.getExchange() + .getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE); + Boolean enabled = DefaultConversionService.getSharedInstance() + .convert(attributeValue, Boolean.class); + if (isFalse(enabled)) { + return Optional.empty(); + } + } + + ExtensionGetter extensionGetter = appCtx.getBean(ExtensionGetter.class); + return extensionGetter.getEnabledExtensionByDefinition(CommentWidget.class) + .next() + .blockOptional(); } } diff --git a/application/src/main/java/run/halo/app/theme/router/ModelMapUtils.java b/application/src/main/java/run/halo/app/theme/router/ModelMapUtils.java index 1cd3cbb9d..892006c78 100644 --- a/application/src/main/java/run/halo/app/theme/router/ModelMapUtils.java +++ b/application/src/main/java/run/halo/app/theme/router/ModelMapUtils.java @@ -6,6 +6,7 @@ import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.SinglePage; import run.halo.app.extension.Scheme; import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.dialect.CommentWidget; import run.halo.app.theme.finders.vo.PostVo; import run.halo.app.theme.finders.vo.SinglePageVo; @@ -32,6 +33,7 @@ public abstract class ModelMapUtils { model.put("groupVersionKind", POST_SCHEME.groupVersionKind()); model.put("plural", POST_SCHEME.plural()); model.put("post", postVo); + model.put(CommentWidget.ENABLE_COMMENT_ATTRIBUTE, postVo.getSpec().getAllowComment()); return model; } @@ -48,6 +50,7 @@ public abstract class ModelMapUtils { model.put("plural", SINGLE_PAGE_SCHEME.plural()); model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.SINGLE_PAGE.getValue()); model.put("singlePage", pageVo); + model.put(CommentWidget.ENABLE_COMMENT_ATTRIBUTE, pageVo.getSpec().getAllowComment()); return model; } } diff --git a/application/src/test/java/run/halo/app/theme/dialect/CommentElementTagProcessorTest.java b/application/src/test/java/run/halo/app/theme/dialect/CommentElementTagProcessorTest.java index c93bdf373..d8245cc46 100644 --- a/application/src/test/java/run/halo/app/theme/dialect/CommentElementTagProcessorTest.java +++ b/application/src/test/java/run/halo/app/theme/dialect/CommentElementTagProcessorTest.java @@ -2,6 +2,9 @@ package run.halo.app.theme.dialect; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Map; @@ -16,6 +19,7 @@ import org.thymeleaf.IEngineConfiguration; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import org.thymeleaf.context.ITemplateContext; +import org.thymeleaf.context.WebEngineContext; import org.thymeleaf.model.IProcessableElementTag; import org.thymeleaf.processor.element.IElementTagStructureHandler; import org.thymeleaf.spring6.dialect.SpringStandardDialect; @@ -23,7 +27,11 @@ import org.thymeleaf.spring6.expression.ThymeleafEvaluationContext; import org.thymeleaf.templateresolver.StringTemplateResolver; import org.thymeleaf.templateresource.ITemplateResource; import org.thymeleaf.templateresource.StringTemplateResource; +import org.thymeleaf.web.IWebExchange; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.SystemSetting; import run.halo.app.plugin.ExtensionComponentsFinder; import run.halo.app.plugin.extensionpoint.ExtensionGetter; @@ -44,6 +52,9 @@ class CommentElementTagProcessorTest { @Mock private ExtensionGetter extensionGetter; + @Mock + private SystemConfigurableEnvironmentFetcher environmentFetcher; + private TemplateEngine templateEngine; @BeforeEach @@ -52,7 +63,7 @@ class CommentElementTagProcessorTest { templateEngine = new TemplateEngine(); templateEngine.setDialects(Set.of(haloProcessorDialect, new SpringStandardDialect())); templateEngine.addTemplateResolver(new TestTemplateResolver()); - when(applicationContext.getBean(eq(ExtensionGetter.class))) + lenient().when(applicationContext.getBean(eq(ExtensionGetter.class))) .thenReturn(extensionGetter); } @@ -60,6 +71,13 @@ class CommentElementTagProcessorTest { void doProcess() { Context context = getContext(); + when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class))) + .thenReturn(environmentFetcher); + var commentSetting = mock(SystemSetting.Comment.class); + when(environmentFetcher.fetchComment()) + .thenReturn(Mono.just(commentSetting)); + when(commentSetting.getEnable()).thenReturn(true); + when(extensionGetter.getEnabledExtensionByDefinition(eq(CommentWidget.class))) .thenReturn(Flux.empty()); String result = templateEngine.process("commentWidget", context); @@ -87,6 +105,74 @@ class CommentElementTagProcessorTest { """); } + @Test + void getCommentWidget() { + when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class))) + .thenReturn(environmentFetcher); + SystemSetting.Comment commentSetting = mock(SystemSetting.Comment.class); + when(environmentFetcher.fetchComment()) + .thenReturn(Mono.just(commentSetting)); + + CommentWidget commentWidget = mock(CommentWidget.class); + when(extensionGetter.getEnabledExtensionByDefinition(CommentWidget.class)) + .thenReturn(Flux.just(commentWidget)); + WebEngineContext webContext = mock(WebEngineContext.class); + var evaluationContext = mock(ThymeleafEvaluationContext.class); + when(webContext.getVariable( + eq(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME))) + .thenReturn(evaluationContext); + when(evaluationContext.getApplicationContext()).thenReturn(applicationContext); + IWebExchange webExchange = mock(IWebExchange.class); + when(webContext.getExchange()).thenReturn(webExchange); + + // comment disabled + when(commentSetting.getEnable()).thenReturn(true); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isTrue(); + + // comment enabled + when(commentSetting.getEnable()).thenReturn(false); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isFalse(); + + // comment enabled and ENABLE_COMMENT_ATTRIBUTE is true + when(commentSetting.getEnable()).thenReturn(true); + when(webExchange.getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE)) + .thenReturn(true); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isTrue(); + + // comment enabled and ENABLE_COMMENT_ATTRIBUTE is false + when(commentSetting.getEnable()).thenReturn(true); + when(webExchange.getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE)) + .thenReturn(false); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isFalse(); + + // comment enabled and ENABLE_COMMENT_ATTRIBUTE is null + when(commentSetting.getEnable()).thenReturn(true); + when(webExchange.getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE)) + .thenReturn(null); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isTrue(); + + // comment enabled and ENABLE_COMMENT_ATTRIBUTE is 'false' + when(commentSetting.getEnable()).thenReturn(true); + when(webExchange.getAttributeValue(CommentWidget.ENABLE_COMMENT_ATTRIBUTE)) + .thenReturn("false"); + assertThat(CommentElementTagProcessor.getCommentWidget(webContext).isPresent()).isFalse(); + } + + @Test + void populateAllowCommentAttribute() { + WebEngineContext webContext = mock(WebEngineContext.class); + IWebExchange webExchange = mock(IWebExchange.class); + when(webContext.getExchange()).thenReturn(webExchange); + + CommentElementTagProcessor.populateAllowCommentAttribute(webContext, true); + verify(webExchange).setAttributeValue( + eq(CommentElementTagProcessor.COMMENT_ENABLED_MODEL_ATTRIBUTE), eq(true)); + + CommentElementTagProcessor.populateAllowCommentAttribute(webContext, false); + verify(webExchange).setAttributeValue( + eq(CommentElementTagProcessor.COMMENT_ENABLED_MODEL_ATTRIBUTE), eq(false)); + } + static class DefaultCommentWidget implements CommentWidget { @Override