mirror of https://github.com/halo-dev/halo
refactor: conditionally render comment for theme (#4271)
#### 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 <!-- 评论组件不可见时不渲染标题 --> <p th:if="${haloCommentEnabled}">评论</p> <halo:comment /> ``` 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 按条件渲染评论组件以简化主题端对评论组件是否显示的条件控制 ```pull/4290/head
parent
4505fcfd16
commit
bf1be64959
|
@ -13,6 +13,8 @@ import org.thymeleaf.processor.element.IElementTagStructureHandler;
|
||||||
*/
|
*/
|
||||||
public interface CommentWidget extends ExtensionPoint {
|
public interface CommentWidget extends ExtensionPoint {
|
||||||
|
|
||||||
|
String ENABLE_COMMENT_ATTRIBUTE = CommentWidget.class.getName() + ".ENABLE";
|
||||||
|
|
||||||
void render(ITemplateContext context, IProcessableElementTag tag,
|
void render(ITemplateContext context, IProcessableElementTag tag,
|
||||||
IElementTagStructureHandler structureHandler);
|
IElementTagStructureHandler structureHandler);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
package run.halo.app.theme.dialect;
|
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.context.ApplicationContext;
|
||||||
|
import org.springframework.core.convert.support.DefaultConversionService;
|
||||||
|
import org.thymeleaf.context.Contexts;
|
||||||
import org.thymeleaf.context.ITemplateContext;
|
import org.thymeleaf.context.ITemplateContext;
|
||||||
|
import org.thymeleaf.context.IWebContext;
|
||||||
import org.thymeleaf.model.IProcessableElementTag;
|
import org.thymeleaf.model.IProcessableElementTag;
|
||||||
import org.thymeleaf.processor.element.AbstractElementTagProcessor;
|
import org.thymeleaf.processor.element.AbstractElementTagProcessor;
|
||||||
import org.thymeleaf.processor.element.IElementTagStructureHandler;
|
import org.thymeleaf.processor.element.IElementTagStructureHandler;
|
||||||
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.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +27,7 @@ import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||||
*/
|
*/
|
||||||
public class CommentElementTagProcessor extends AbstractElementTagProcessor {
|
public class CommentElementTagProcessor extends AbstractElementTagProcessor {
|
||||||
|
|
||||||
|
public static final String COMMENT_ENABLED_MODEL_ATTRIBUTE = "haloCommentEnabled";
|
||||||
private static final String TAG_NAME = "comment";
|
private static final String TAG_NAME = "comment";
|
||||||
|
|
||||||
private static final int PRECEDENCE = 1000;
|
private static final int PRECEDENCE = 1000;
|
||||||
|
@ -42,16 +51,49 @@ public class CommentElementTagProcessor extends AbstractElementTagProcessor {
|
||||||
@Override
|
@Override
|
||||||
protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
|
protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
|
||||||
IElementTagStructureHandler structureHandler) {
|
IElementTagStructureHandler structureHandler) {
|
||||||
final ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context);
|
getCommentWidget(context).ifPresentOrElse(commentWidget -> {
|
||||||
ExtensionGetter extensionGetter = appCtx.getBean(ExtensionGetter.class);
|
populateAllowCommentAttribute(context, true);
|
||||||
CommentWidget commentWidget =
|
commentWidget.render(context, tag, structureHandler);
|
||||||
extensionGetter.getEnabledExtensionByDefinition(CommentWidget.class)
|
}, () -> {
|
||||||
.next()
|
populateAllowCommentAttribute(context, false);
|
||||||
.block();
|
|
||||||
if (commentWidget == null) {
|
|
||||||
structureHandler.replaceWith("", 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<CommentWidget> 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.content.SinglePage;
|
import run.halo.app.core.extension.content.SinglePage;
|
||||||
import run.halo.app.extension.Scheme;
|
import run.halo.app.extension.Scheme;
|
||||||
import run.halo.app.theme.DefaultTemplateEnum;
|
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.PostVo;
|
||||||
import run.halo.app.theme.finders.vo.SinglePageVo;
|
import run.halo.app.theme.finders.vo.SinglePageVo;
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ public abstract class ModelMapUtils {
|
||||||
model.put("groupVersionKind", POST_SCHEME.groupVersionKind());
|
model.put("groupVersionKind", POST_SCHEME.groupVersionKind());
|
||||||
model.put("plural", POST_SCHEME.plural());
|
model.put("plural", POST_SCHEME.plural());
|
||||||
model.put("post", postVo);
|
model.put("post", postVo);
|
||||||
|
model.put(CommentWidget.ENABLE_COMMENT_ATTRIBUTE, postVo.getSpec().getAllowComment());
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +50,7 @@ public abstract class ModelMapUtils {
|
||||||
model.put("plural", SINGLE_PAGE_SCHEME.plural());
|
model.put("plural", SINGLE_PAGE_SCHEME.plural());
|
||||||
model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.SINGLE_PAGE.getValue());
|
model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.SINGLE_PAGE.getValue());
|
||||||
model.put("singlePage", pageVo);
|
model.put("singlePage", pageVo);
|
||||||
|
model.put(CommentWidget.ENABLE_COMMENT_ATTRIBUTE, pageVo.getSpec().getAllowComment());
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ package run.halo.app.theme.dialect;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
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 static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -16,6 +19,7 @@ import org.thymeleaf.IEngineConfiguration;
|
||||||
import org.thymeleaf.TemplateEngine;
|
import org.thymeleaf.TemplateEngine;
|
||||||
import org.thymeleaf.context.Context;
|
import org.thymeleaf.context.Context;
|
||||||
import org.thymeleaf.context.ITemplateContext;
|
import org.thymeleaf.context.ITemplateContext;
|
||||||
|
import org.thymeleaf.context.WebEngineContext;
|
||||||
import org.thymeleaf.model.IProcessableElementTag;
|
import org.thymeleaf.model.IProcessableElementTag;
|
||||||
import org.thymeleaf.processor.element.IElementTagStructureHandler;
|
import org.thymeleaf.processor.element.IElementTagStructureHandler;
|
||||||
import org.thymeleaf.spring6.dialect.SpringStandardDialect;
|
import org.thymeleaf.spring6.dialect.SpringStandardDialect;
|
||||||
|
@ -23,7 +27,11 @@ import org.thymeleaf.spring6.expression.ThymeleafEvaluationContext;
|
||||||
import org.thymeleaf.templateresolver.StringTemplateResolver;
|
import org.thymeleaf.templateresolver.StringTemplateResolver;
|
||||||
import org.thymeleaf.templateresource.ITemplateResource;
|
import org.thymeleaf.templateresource.ITemplateResource;
|
||||||
import org.thymeleaf.templateresource.StringTemplateResource;
|
import org.thymeleaf.templateresource.StringTemplateResource;
|
||||||
|
import org.thymeleaf.web.IWebExchange;
|
||||||
import reactor.core.publisher.Flux;
|
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.ExtensionComponentsFinder;
|
||||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||||
|
|
||||||
|
@ -44,6 +52,9 @@ class CommentElementTagProcessorTest {
|
||||||
@Mock
|
@Mock
|
||||||
private ExtensionGetter extensionGetter;
|
private ExtensionGetter extensionGetter;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
|
||||||
private TemplateEngine templateEngine;
|
private TemplateEngine templateEngine;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
@ -52,7 +63,7 @@ class CommentElementTagProcessorTest {
|
||||||
templateEngine = new TemplateEngine();
|
templateEngine = new TemplateEngine();
|
||||||
templateEngine.setDialects(Set.of(haloProcessorDialect, new SpringStandardDialect()));
|
templateEngine.setDialects(Set.of(haloProcessorDialect, new SpringStandardDialect()));
|
||||||
templateEngine.addTemplateResolver(new TestTemplateResolver());
|
templateEngine.addTemplateResolver(new TestTemplateResolver());
|
||||||
when(applicationContext.getBean(eq(ExtensionGetter.class)))
|
lenient().when(applicationContext.getBean(eq(ExtensionGetter.class)))
|
||||||
.thenReturn(extensionGetter);
|
.thenReturn(extensionGetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +71,13 @@ class CommentElementTagProcessorTest {
|
||||||
void doProcess() {
|
void doProcess() {
|
||||||
Context context = getContext();
|
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)))
|
when(extensionGetter.getEnabledExtensionByDefinition(eq(CommentWidget.class)))
|
||||||
.thenReturn(Flux.empty());
|
.thenReturn(Flux.empty());
|
||||||
String result = templateEngine.process("commentWidget", context);
|
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 {
|
static class DefaultCommentWidget implements CommentWidget {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in New Issue