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