mirror of https://github.com/halo-dev/halo
feat: add annotations expression object for thymeleaf (#3076)
#### What type of PR is this? /kind feature /area core /milestone 2.1.x #### What this PR does / why we need it: 新增操作 annotations 的表达式对象 在 thymeleaf 模板中使用示例: ```html <p th:text="${#annotations.get(user, 'background')}"></p> <p th:text="${#annotations.getOrDefault(user, 'background', 'default-value')}"></p> <p th:text="${#annotations.contains(user, 'background')}"></p> ``` #### Which issue(s) this PR fixes: Fixes #3073 #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note 新增 Annotations 表达式对象用于在 thymeleaf 中操作自定义模型的 annotations ```pull/3086/head
parent
77dd5b24dd
commit
cc891d6655
|
@ -0,0 +1,40 @@
|
||||||
|
package run.halo.app.theme.dialect;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import org.thymeleaf.context.IExpressionContext;
|
||||||
|
import org.thymeleaf.expression.IExpressionObjectFactory;
|
||||||
|
import run.halo.app.theme.dialect.expression.Annotations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the expression objects to be used by Halo dialects.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class HaloExpressionObjectFactory implements IExpressionObjectFactory {
|
||||||
|
|
||||||
|
public static final String ANNOTATIONS_EXPRESSION_OBJECT_NAME = "annotations";
|
||||||
|
|
||||||
|
protected static final Set<String> ALL_EXPRESSION_OBJECT_NAMES = Set.of(
|
||||||
|
ANNOTATIONS_EXPRESSION_OBJECT_NAME);
|
||||||
|
|
||||||
|
private static final Annotations ANNOTATIONS = new Annotations();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getAllExpressionObjectNames() {
|
||||||
|
return ALL_EXPRESSION_OBJECT_NAMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object buildObject(IExpressionContext context, String expressionObjectName) {
|
||||||
|
if (ANNOTATIONS_EXPRESSION_OBJECT_NAME.equals(expressionObjectName)) {
|
||||||
|
return ANNOTATIONS;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCacheable(String expressionObjectName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ package run.halo.app.theme.dialect;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.thymeleaf.dialect.AbstractProcessorDialect;
|
import org.thymeleaf.dialect.AbstractProcessorDialect;
|
||||||
|
import org.thymeleaf.dialect.IExpressionObjectDialect;
|
||||||
|
import org.thymeleaf.expression.IExpressionObjectFactory;
|
||||||
import org.thymeleaf.processor.IProcessor;
|
import org.thymeleaf.processor.IProcessor;
|
||||||
import org.thymeleaf.standard.StandardDialect;
|
import org.thymeleaf.standard.StandardDialect;
|
||||||
|
|
||||||
|
@ -12,9 +14,13 @@ import org.thymeleaf.standard.StandardDialect;
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class HaloProcessorDialect extends AbstractProcessorDialect {
|
public class HaloProcessorDialect extends AbstractProcessorDialect implements
|
||||||
|
IExpressionObjectDialect {
|
||||||
private static final String DIALECT_NAME = "haloThemeProcessorDialect";
|
private static final String DIALECT_NAME = "haloThemeProcessorDialect";
|
||||||
|
|
||||||
|
private static final IExpressionObjectFactory HALO_EXPRESSION_OBJECTS_FACTORY =
|
||||||
|
new HaloExpressionObjectFactory();
|
||||||
|
|
||||||
public HaloProcessorDialect() {
|
public HaloProcessorDialect() {
|
||||||
// We will set this dialect the same "dialect processor" precedence as
|
// We will set this dialect the same "dialect processor" precedence as
|
||||||
// the Standard Dialect, so that processor executions can interleave.
|
// the Standard Dialect, so that processor executions can interleave.
|
||||||
|
@ -31,4 +37,9 @@ public class HaloProcessorDialect extends AbstractProcessorDialect {
|
||||||
processors.add(new CommentElementTagProcessor(dialectPrefix));
|
processors.add(new CommentElementTagProcessor(dialectPrefix));
|
||||||
return processors;
|
return processors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IExpressionObjectFactory getExpressionObjectFactory() {
|
||||||
|
return HALO_EXPRESSION_OBJECTS_FACTORY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package run.halo.app.theme.dialect.expression;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import run.halo.app.theme.finders.vo.ExtensionVoOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Expression Object for performing annotations operations inside Halo Extra Expressions.</p>
|
||||||
|
* An object of this class is usually available in variable evaluation expressions with the name
|
||||||
|
* <code>#annotations</code>.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.2
|
||||||
|
*/
|
||||||
|
public class Annotations {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get annotation value from extension vo.
|
||||||
|
*
|
||||||
|
* @param extension extension vo
|
||||||
|
* @param key the key of annotation
|
||||||
|
* @return annotation value if exists, otherwise null
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String get(ExtensionVoOperator extension, String key) {
|
||||||
|
Map<String, String> annotations = extension.getMetadata().getAnnotations();
|
||||||
|
if (annotations == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return annotations.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value to which the specified key is mapped, or defaultValue if
|
||||||
|
* <code>extension</code> contains no mapping for the key.
|
||||||
|
*
|
||||||
|
* @param extension extension vo
|
||||||
|
* @param key the key of annotation
|
||||||
|
* @return annotation value if exists, otherwise defaultValue
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public String getOrDefault(ExtensionVoOperator extension, String key, String defaultValue) {
|
||||||
|
Map<String, String> annotations = extension.getMetadata().getAnnotations();
|
||||||
|
if (annotations == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return annotations.getOrDefault(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the extension has the specified annotation.
|
||||||
|
*
|
||||||
|
* @param extension extension vo
|
||||||
|
* @param key the key of annotation
|
||||||
|
* @return true if the extension has the specified annotation, otherwise false
|
||||||
|
*/
|
||||||
|
public boolean contains(ExtensionVoOperator extension, String key) {
|
||||||
|
Map<String, String> annotations = extension.getMetadata().getAnnotations();
|
||||||
|
if (annotations == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return annotations.containsKey(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@Builder
|
@Builder
|
||||||
@ToString
|
@ToString
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class CategoryTreeVo implements VisualizableTreeNode<CategoryTreeVo> {
|
public class CategoryTreeVo implements VisualizableTreeNode<CategoryTreeVo>, ExtensionVoOperator {
|
||||||
|
|
||||||
private MetadataOperator metadata;
|
private MetadataOperator metadata;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class CategoryVo {
|
public class CategoryVo implements ExtensionVoOperator {
|
||||||
|
|
||||||
MetadataOperator metadata;
|
MetadataOperator metadata;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class CommentVo {
|
public class CommentVo implements ExtensionVoOperator {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(required = true)
|
||||||
MetadataOperator metadata;
|
MetadataOperator metadata;
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package run.halo.app.theme.finders.vo;
|
||||||
|
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import run.halo.app.extension.MetadataOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An operator for extension value object.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public interface ExtensionVoOperator {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
MetadataOperator getMetadata();
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@SuperBuilder
|
@SuperBuilder
|
||||||
@ToString
|
@ToString
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class ListedPostVo {
|
public class ListedPostVo implements ExtensionVoOperator {
|
||||||
|
|
||||||
private MetadataOperator metadata;
|
private MetadataOperator metadata;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@SuperBuilder
|
@SuperBuilder
|
||||||
@ToString
|
@ToString
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class ListedSinglePageVo {
|
public class ListedSinglePageVo implements ExtensionVoOperator {
|
||||||
|
|
||||||
private MetadataOperator metadata;
|
private MetadataOperator metadata;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@Data
|
@Data
|
||||||
@ToString
|
@ToString
|
||||||
@Builder
|
@Builder
|
||||||
public class MenuItemVo implements VisualizableTreeNode<MenuItemVo> {
|
public class MenuItemVo implements VisualizableTreeNode<MenuItemVo>, ExtensionVoOperator {
|
||||||
|
|
||||||
MetadataOperator metadata;
|
MetadataOperator metadata;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@Value
|
@Value
|
||||||
@ToString
|
@ToString
|
||||||
@Builder
|
@Builder
|
||||||
public class MenuVo {
|
public class MenuVo implements ExtensionVoOperator {
|
||||||
|
|
||||||
MetadataOperator metadata;
|
MetadataOperator metadata;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@Builder
|
@Builder
|
||||||
@ToString
|
@ToString
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class ReplyVo {
|
public class ReplyVo implements ExtensionVoOperator {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(required = true)
|
||||||
MetadataOperator metadata;
|
MetadataOperator metadata;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
*/
|
*/
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
public class TagVo {
|
public class TagVo implements ExtensionVoOperator {
|
||||||
|
|
||||||
MetadataOperator metadata;
|
MetadataOperator metadata;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
@ToString
|
@ToString
|
||||||
public class ThemeVo {
|
public class ThemeVo implements ExtensionVoOperator {
|
||||||
|
|
||||||
MetadataOperator metadata;
|
MetadataOperator metadata;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
public class UserVo {
|
public class UserVo implements ExtensionVoOperator {
|
||||||
MetadataOperator metadata;
|
MetadataOperator metadata;
|
||||||
|
|
||||||
User.UserSpec spec;
|
User.UserSpec spec;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
@ -26,6 +27,7 @@ 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 reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import run.halo.app.core.extension.User;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.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;
|
||||||
|
@ -35,6 +37,7 @@ 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.SinglePageFinder;
|
import run.halo.app.theme.finders.SinglePageFinder;
|
||||||
import run.halo.app.theme.finders.vo.PostVo;
|
import run.halo.app.theme.finders.vo.PostVo;
|
||||||
|
import run.halo.app.theme.finders.vo.UserVo;
|
||||||
import run.halo.app.theme.router.strategy.ModelConst;
|
import run.halo.app.theme.router.strategy.ModelConst;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,7 +91,7 @@ class HaloProcessorDialectTest {
|
||||||
codeInjection.setContentHead("<meta name=\"content-head-test\" content=\"test\" />");
|
codeInjection.setContentHead("<meta name=\"content-head-test\" content=\"test\" />");
|
||||||
codeInjection.setGlobalHead("<meta name=\"global-head-test\" content=\"test\" />");
|
codeInjection.setGlobalHead("<meta name=\"global-head-test\" content=\"test\" />");
|
||||||
codeInjection.setFooter("<footer>hello this is global footer.</footer>");
|
codeInjection.setFooter("<footer>hello this is global footer.</footer>");
|
||||||
when(fetcher.fetch(eq(SystemSetting.CodeInjection.GROUP),
|
lenient().when(fetcher.fetch(eq(SystemSetting.CodeInjection.GROUP),
|
||||||
eq(SystemSetting.CodeInjection.class))).thenReturn(Mono.just(codeInjection));
|
eq(SystemSetting.CodeInjection.class))).thenReturn(Mono.just(codeInjection));
|
||||||
|
|
||||||
lenient().when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class)))
|
lenient().when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class)))
|
||||||
|
@ -96,10 +99,10 @@ class HaloProcessorDialectTest {
|
||||||
lenient().when(fetcher.fetch(eq(SystemSetting.Seo.GROUP),
|
lenient().when(fetcher.fetch(eq(SystemSetting.Seo.GROUP),
|
||||||
eq(SystemSetting.Seo.class))).thenReturn(Mono.empty());
|
eq(SystemSetting.Seo.class))).thenReturn(Mono.empty());
|
||||||
|
|
||||||
when(applicationContext.getBean(eq(ExtensionComponentsFinder.class)))
|
lenient().when(applicationContext.getBean(eq(ExtensionComponentsFinder.class)))
|
||||||
.thenReturn(extensionComponentsFinder);
|
.thenReturn(extensionComponentsFinder);
|
||||||
|
|
||||||
when(extensionComponentsFinder.getExtensions(eq(TemplateHeadProcessor.class)))
|
lenient().when(extensionComponentsFinder.getExtensions(eq(TemplateHeadProcessor.class)))
|
||||||
.thenReturn(new ArrayList<>(map.values()));
|
.thenReturn(new ArrayList<>(map.values()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,6 +245,78 @@ class HaloProcessorDialectTest {
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class AnnotationExpressionObjectFactoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getWhenAnnotationsIsNull() {
|
||||||
|
Context context = getContext();
|
||||||
|
context.setVariable("user", createUser());
|
||||||
|
|
||||||
|
String result = templateEngine.process("annotationsGetExpression", context);
|
||||||
|
assertThat(result).isEqualTo("<p></p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getWhenAnnotationsExists() {
|
||||||
|
Context context = getContext();
|
||||||
|
UserVo user = createUser();
|
||||||
|
user.getMetadata().setAnnotations(Map.of("background", "fake-background"));
|
||||||
|
context.setVariable("user", user);
|
||||||
|
|
||||||
|
String result = templateEngine.process("annotationsGetExpression", context);
|
||||||
|
assertThat(result).isEqualTo("<p>fake-background</p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getOrDefaultWhenAnnotationsIsNull() {
|
||||||
|
Context context = getContext();
|
||||||
|
UserVo user = createUser();
|
||||||
|
user.getMetadata().setAnnotations(Map.of("background", "red"));
|
||||||
|
context.setVariable("user", user);
|
||||||
|
|
||||||
|
String result = templateEngine.process("annotationsGetOrDefaultExpression", context);
|
||||||
|
assertThat(result).isEqualTo("<p>red</p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getOrDefaultWhenAnnotationsExists() {
|
||||||
|
Context context = getContext();
|
||||||
|
context.setVariable("user", createUser());
|
||||||
|
|
||||||
|
String result = templateEngine.process("annotationsGetOrDefaultExpression", context);
|
||||||
|
assertThat(result).isEqualTo("<p>default-value</p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void containsWhenAnnotationsIsNull() {
|
||||||
|
Context context = getContext();
|
||||||
|
context.setVariable("user", createUser());
|
||||||
|
|
||||||
|
String result = templateEngine.process("annotationsContainsExpression", context);
|
||||||
|
assertThat(result).isEqualTo("<p>false</p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void containsWhenAnnotationsIsNotNull() {
|
||||||
|
Context context = getContext();
|
||||||
|
UserVo user = createUser();
|
||||||
|
user.getMetadata().setAnnotations(Map.of("background", ""));
|
||||||
|
context.setVariable("user", user);
|
||||||
|
|
||||||
|
String result = templateEngine.process("annotationsContainsExpression", context);
|
||||||
|
assertThat(result).isEqualTo("<p>true</p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
UserVo createUser() {
|
||||||
|
User user = new User();
|
||||||
|
user.setMetadata(new Metadata());
|
||||||
|
user.getMetadata().setName("fake-user");
|
||||||
|
user.setSpec(new User.UserSpec());
|
||||||
|
return UserVo.from(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Context getContext() {
|
private Context getContext() {
|
||||||
Context context = new Context();
|
Context context = new Context();
|
||||||
context.setVariable(
|
context.setVariable(
|
||||||
|
@ -266,6 +341,16 @@ class HaloProcessorDialectTest {
|
||||||
if (template.equals("seo")) {
|
if (template.equals("seo")) {
|
||||||
return new StringTemplateResource(seoTemplate());
|
return new StringTemplateResource(seoTemplate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (template.equals("annotationsGetExpression")) {
|
||||||
|
return new StringTemplateResource(annotationsGetExpression());
|
||||||
|
}
|
||||||
|
if (template.equals("annotationsGetOrDefaultExpression")) {
|
||||||
|
return new StringTemplateResource(annotationsGetOrDefaultExpression());
|
||||||
|
}
|
||||||
|
if (template.equals("annotationsContainsExpression")) {
|
||||||
|
return new StringTemplateResource(annotationsContainsExpression());
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,5 +401,23 @@ class HaloProcessorDialectTest {
|
||||||
</html>
|
</html>
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String annotationsGetExpression() {
|
||||||
|
return """
|
||||||
|
<p th:text="${#annotations.get(user, 'background')}"></p>
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String annotationsGetOrDefaultExpression() {
|
||||||
|
return """
|
||||||
|
<p th:text="${#annotations.getOrDefault(user, 'background', 'default-value')}"></p>
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String annotationsContainsExpression() {
|
||||||
|
return """
|
||||||
|
<p th:text="${#annotations.contains(user, 'background')}"></p>
|
||||||
|
""";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue