From 3487132154a4b6b0fe059d45802599022a215a19 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Tue, 19 Aug 2025 14:47:37 +0800 Subject: [PATCH] Add support for hidden comments (#7679) * Add 'hidden' field to comment and reply requests Signed-off-by: Ryan Wang * Add support for filtering comments with hidden * Specify hidden=false and approved=true for anonymous users * Set default hidden flag only if null in comments * Add 'private reply' option to comment modals * Add private tag for hidden comments and replies * Allow hiding comments only * Enhance comment visibility logic to allow owners to view hidden comments * Remove hidden input for reply form Signed-off-by: Ryan Wang * Refine i18n Signed-off-by: Ryan Wang --------- Signed-off-by: Ryan Wang Co-authored-by: John Niang --- api-docs/openapi/v3_0/aggregated.json | 10 +- .../v3_0/apis_console.api_v1alpha1.json | 10 +- .../v3_0/apis_public.api_v1alpha1.json | 8 + .../app/core/user/service/UserService.java | 8 + .../app/content/comment/CommentRequest.java | 4 + .../content/comment/CommentServiceImpl.java | 5 +- .../app/content/comment/ReplyRequest.java | 4 + .../app/content/comment/ReplyServiceImpl.java | 10 +- .../endpoint/theme/CommentFinderEndpoint.java | 7 +- .../user/service/impl/UserServiceImpl.java | 12 ++ .../impl/CommentPublicQueryServiceImpl.java | 155 ++++++++++-------- ...mentPublicQueryServiceIntegrationTest.java | 9 + .../components/CommentDetailModal.vue | 6 + .../comments/components/CommentListItem.vue | 4 + .../comments/components/ReplyDetailModal.vue | 21 ++- .../comments/components/ReplyListItem.vue | 4 + .../src/api/post-v1alpha1-console-api.ts | 8 +- .../api-client/src/models/comment-request.ts | 6 + .../api-client/src/models/reply-request.ts | 6 + ui/src/locales/_missing_translations_es.yaml | 7 + ui/src/locales/en.yaml | 1 + ui/src/locales/zh-CN.yaml | 1 + ui/src/locales/zh-TW.yaml | 1 + 23 files changed, 223 insertions(+), 84 deletions(-) diff --git a/api-docs/openapi/v3_0/aggregated.json b/api-docs/openapi/v3_0/aggregated.json index 22cbdcfe8..932dfc761 100644 --- a/api-docs/openapi/v3_0/aggregated.json +++ b/api-docs/openapi/v3_0/aggregated.json @@ -3597,7 +3597,7 @@ }, "/apis/api.console.halo.run/v1alpha1/posts/{name}/unpublish": { "put": { - "description": "Publish a post.", + "description": "UnPublish a post.", "operationId": "UnpublishPost", "parameters": [ { @@ -16462,6 +16462,10 @@ "minLength": 1, "type": "string" }, + "hidden": { + "type": "boolean", + "default": false + }, "owner": { "$ref": "#/components/schemas/CommentEmailOwner" }, @@ -21328,6 +21332,10 @@ "minLength": 1, "type": "string" }, + "hidden": { + "type": "boolean", + "default": false + }, "owner": { "$ref": "#/components/schemas/CommentEmailOwner" }, diff --git a/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json b/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json index 4de16175c..55348d656 100644 --- a/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json +++ b/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json @@ -1464,7 +1464,7 @@ }, "/apis/api.console.halo.run/v1alpha1/posts/{name}/unpublish": { "put": { - "description": "Publish a post.", + "description": "UnPublish a post.", "operationId": "UnpublishPost", "parameters": [ { @@ -3868,6 +3868,10 @@ "minLength": 1, "type": "string" }, + "hidden": { + "type": "boolean", + "default": false + }, "owner": { "$ref": "#/components/schemas/CommentEmailOwner" }, @@ -5643,6 +5647,10 @@ "minLength": 1, "type": "string" }, + "hidden": { + "type": "boolean", + "default": false + }, "owner": { "$ref": "#/components/schemas/CommentEmailOwner" }, diff --git a/api-docs/openapi/v3_0/apis_public.api_v1alpha1.json b/api-docs/openapi/v3_0/apis_public.api_v1alpha1.json index d1dc75ba8..2cb9d294f 100644 --- a/api-docs/openapi/v3_0/apis_public.api_v1alpha1.json +++ b/api-docs/openapi/v3_0/apis_public.api_v1alpha1.json @@ -1455,6 +1455,10 @@ "minLength": 1, "type": "string" }, + "hidden": { + "type": "boolean", + "default": false + }, "owner": { "$ref": "#/components/schemas/CommentEmailOwner" }, @@ -2826,6 +2830,10 @@ "minLength": 1, "type": "string" }, + "hidden": { + "type": "boolean", + "default": false + }, "owner": { "$ref": "#/components/schemas/CommentEmailOwner" }, diff --git a/api/src/main/java/run/halo/app/core/user/service/UserService.java b/api/src/main/java/run/halo/app/core/user/service/UserService.java index ce1c049b3..56018d669 100644 --- a/api/src/main/java/run/halo/app/core/user/service/UserService.java +++ b/api/src/main/java/run/halo/app/core/user/service/UserService.java @@ -22,6 +22,14 @@ public interface UserService { Mono grantRoles(String username, Set roles); + /** + * Check if the user has sufficient roles. + * + * @param roles roles to check + * @return a Mono that emits true if the user has all the roles, false otherwise + */ + Mono hasSufficientRoles(Collection roles); + Mono signUp(SignUpData signUpData); Mono createUser(User user, Set roles); diff --git a/application/src/main/java/run/halo/app/content/comment/CommentRequest.java b/application/src/main/java/run/halo/app/content/comment/CommentRequest.java index 599cfca6d..f07e5419a 100644 --- a/application/src/main/java/run/halo/app/content/comment/CommentRequest.java +++ b/application/src/main/java/run/halo/app/content/comment/CommentRequest.java @@ -32,6 +32,9 @@ public class CommentRequest { @Schema(defaultValue = "false") private Boolean allowNotification; + @Schema(defaultValue = "false") + private Boolean hidden; + /** * Converts {@link CommentRequest} to {@link Comment}. * @@ -48,6 +51,7 @@ public class CommentRequest { spec.setRaw(raw); spec.setContent(content); spec.setAllowNotification(allowNotification); + spec.setHidden(hidden); if (owner != null) { spec.setOwner(owner.toCommentOwner()); diff --git a/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java b/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java index d107d4077..5ae33ac92 100644 --- a/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java @@ -107,7 +107,10 @@ public class CommentServiceImpl extends AbstractCommentService implements Commen comment.getSpec().setCreationTime(Instant.now()); } - comment.getSpec().setHidden(false); + if (comment.getSpec().getHidden() == null) { + comment.getSpec().setHidden(false); + } + return Mono.just(comment); }) .flatMap(populatedComment -> Mono.when(populateOwner(populatedComment), diff --git a/application/src/main/java/run/halo/app/content/comment/ReplyRequest.java b/application/src/main/java/run/halo/app/content/comment/ReplyRequest.java index d7ad2c327..2774f9624 100644 --- a/application/src/main/java/run/halo/app/content/comment/ReplyRequest.java +++ b/application/src/main/java/run/halo/app/content/comment/ReplyRequest.java @@ -26,6 +26,9 @@ public class ReplyRequest { @Schema(defaultValue = "false") private Boolean allowNotification; + @Schema(defaultValue = "false") + private Boolean hidden; + private CommentEmailOwner owner; private String quoteReply; @@ -45,6 +48,7 @@ public class ReplyRequest { spec.setRaw(raw); spec.setContent(content); spec.setAllowNotification(allowNotification); + spec.setHidden(hidden); spec.setQuoteReply(quoteReply); if (owner != null) { diff --git a/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java b/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java index e1214943e..464fa9eca 100644 --- a/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java @@ -64,7 +64,7 @@ public class ReplyServiceImpl extends AbstractCommentService implements ReplySer .flatMap(this::approveComment) .filter(comment -> isTrue(comment.getSpec().getApproved())) .switchIfEmpty(Mono.error(requestRestrictedExceptionSupplier)) - .flatMap(comment -> prepareReply(commentName, reply)) + .flatMap(comment -> prepareReply(comment, reply)) .flatMap(this::doCreateReply); } @@ -76,6 +76,9 @@ public class ReplyServiceImpl extends AbstractCommentService implements ReplySer return approveReply(quotedReply) .filter(reply -> isTrue(reply.getSpec().getApproved())) .switchIfEmpty(Mono.error(requestRestrictedExceptionSupplier)) + .doOnNext(approvedQuoteReply -> prepared.getSpec() + .setHidden(approvedQuoteReply.getSpec().getHidden()) + ) .flatMap(approvedQuoteReply -> client.create(prepared)); } @@ -131,8 +134,9 @@ public class ReplyServiceImpl extends AbstractCommentService implements ReplySer .filter(OptimisticLockingFailureException.class::isInstance)); } - private Mono prepareReply(String commentName, Reply reply) { - reply.getSpec().setCommentName(commentName); + private Mono prepareReply(Comment comment, Reply reply) { + reply.getSpec().setCommentName(comment.getMetadata().getName()); + reply.getSpec().setHidden(comment.getSpec().getHidden()); if (reply.getSpec().getTop() == null) { reply.getSpec().setTop(false); } diff --git a/application/src/main/java/run/halo/app/core/endpoint/theme/CommentFinderEndpoint.java b/application/src/main/java/run/halo/app/core/endpoint/theme/CommentFinderEndpoint.java index 30d831acf..479dba0da 100644 --- a/application/src/main/java/run/halo/app/core/endpoint/theme/CommentFinderEndpoint.java +++ b/application/src/main/java/run/halo/app/core/endpoint/theme/CommentFinderEndpoint.java @@ -176,8 +176,6 @@ public class CommentFinderEndpoint implements CustomEndpoint { Reply reply = replyRequest.toReply(); reply.getSpec().setIpAddress(IpAddressUtils.getIpAddress(request)); reply.getSpec().setUserAgent(HaloUtils.userAgentFrom(request)); - // fix gh-2951 - reply.getSpec().setHidden(false); return environmentFetcher.fetchComment() .map(commentSetting -> { if (isFalse(commentSetting.getEnable())) { @@ -191,6 +189,11 @@ public class CommentFinderEndpoint implements CustomEndpoint { } reply.getSpec() .setApproved(isFalse(commentSetting.getRequireReviewForNew())); + + if (reply.getSpec().getHidden() == null) { + reply.getSpec().setHidden(false); + } + return reply; }) .defaultIfEmpty(reply); diff --git a/application/src/main/java/run/halo/app/core/user/service/impl/UserServiceImpl.java b/application/src/main/java/run/halo/app/core/user/service/impl/UserServiceImpl.java index 905713a8c..72deab22c 100644 --- a/application/src/main/java/run/halo/app/core/user/service/impl/UserServiceImpl.java +++ b/application/src/main/java/run/halo/app/core/user/service/impl/UserServiceImpl.java @@ -14,6 +14,8 @@ import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.ReactiveTransactionManager; @@ -50,6 +52,7 @@ import run.halo.app.infra.exception.EmailVerificationFailed; import run.halo.app.infra.exception.UnsatisfiedAttributeValueException; import run.halo.app.infra.exception.UserNotFoundException; import run.halo.app.plugin.extensionpoint.ExtensionGetter; +import run.halo.app.security.authorization.AuthorityUtils; import run.halo.app.security.device.DeviceService; @Service @@ -183,6 +186,15 @@ public class UserServiceImpl implements UserService { }); } + @Override + public Mono hasSufficientRoles(Collection roles) { + return ReactiveSecurityContextHolder.getContext() + .map(SecurityContext::getAuthentication) + .map(a -> AuthorityUtils.authoritiesToRoles(a.getAuthorities())) + .flatMap(userRoles -> roleService.contains(userRoles, roles)) + .defaultIfEmpty(false); + } + @Override public Mono signUp(SignUpData signUpData) { return environmentFetcher.fetch(SystemSetting.User.GROUP, SystemSetting.User.class) diff --git a/application/src/main/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImpl.java b/application/src/main/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImpl.java index e6a157967..acc084b71 100644 --- a/application/src/main/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImpl.java +++ b/application/src/main/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImpl.java @@ -3,25 +3,28 @@ package run.halo.app.theme.finders.impl; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; +import static run.halo.app.core.extension.content.Comment.CommentOwner.ownerIdentity; import static run.halo.app.extension.index.query.QueryFactory.and; import static run.halo.app.extension.index.query.QueryFactory.equal; import static run.halo.app.extension.index.query.QueryFactory.isNull; import static run.halo.app.extension.index.query.QueryFactory.or; import com.google.common.hash.Hashing; -import java.security.Principal; import java.util.HashMap; +import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Sort; import org.springframework.lang.Nullable; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.SecurityContext; import org.springframework.stereotype.Component; -import org.springframework.util.Assert; +import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import run.halo.app.content.comment.OwnerInfo; @@ -32,14 +35,13 @@ import run.halo.app.core.extension.content.Comment; import run.halo.app.core.extension.content.Reply; import run.halo.app.core.user.service.UserService; import run.halo.app.extension.AbstractExtension; +import run.halo.app.extension.ExtensionUtil; import run.halo.app.extension.ListOptions; import run.halo.app.extension.ListResult; import run.halo.app.extension.PageRequest; import run.halo.app.extension.PageRequestImpl; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.Ref; -import run.halo.app.extension.index.query.Query; -import run.halo.app.extension.router.selector.FieldSelector; import run.halo.app.infra.AnonymousUserConst; import run.halo.app.theme.finders.CommentPublicQueryService; import run.halo.app.theme.finders.vo.CommentStatsVo; @@ -59,6 +61,8 @@ import run.halo.app.theme.finders.vo.ReplyVo; public class CommentPublicQueryServiceImpl implements CommentPublicQueryService { private static final int DEFAULT_SIZE = 10; + private static final String COMMENT_VIEW_PERMISSION = "role-template-view-comments"; + private final ReactiveExtensionClient client; private final UserService userService; private final CounterService counterService; @@ -80,22 +84,18 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService var pageRequest = Optional.ofNullable(pageParam) .map(page -> page.withSort(page.getSort().and(defaultCommentSort()))) .orElse(PageRequestImpl.ofSize(0)); - return fixedCommentFieldSelector(ref) - .flatMap(fieldSelector -> { - var listOptions = new ListOptions(); - listOptions.setFieldSelector(fieldSelector); - return client.listBy(Comment.class, listOptions, pageRequest) - .flatMap(listResult -> Flux.fromStream(listResult.get()) - .map(this::toCommentVo) - .flatMapSequential(Function.identity()) - .collectList() - .map(commentVos -> new ListResult<>(listResult.getPage(), - listResult.getSize(), - listResult.getTotal(), - commentVos) - ) - ); - }) + return populateCommentListOptions(ref) + .flatMap(listOptions -> client.listBy(Comment.class, listOptions, pageRequest)) + .flatMap(listResult -> Flux.fromStream(listResult.get()) + .map(this::toCommentVo) + .flatMapSequential(Function.identity()) + .collectList() + .map(commentVos -> new ListResult<>(listResult.getPage(), + listResult.getSize(), + listResult.getTotal(), + commentVos) + ) + ) .defaultIfEmpty(ListResult.emptyResult()); } @@ -127,10 +127,10 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService @Override public Mono> listReply(String commentName, PageRequest pageParam) { - return fixedReplyFieldSelector(commentName) - .flatMap(fieldSelector -> { - var listOptions = new ListOptions(); - listOptions.setFieldSelector(fieldSelector); + // check comment + return client.get(Comment.class, commentName) + .flatMap(this::populateReplyListOptions) + .flatMap(listOptions -> { var pageRequest = Optional.ofNullable(pageParam) .map(page -> page.withSort(page.getSort().and(defaultReplySort()))) .orElse(PageRequestImpl.ofSize(0)); @@ -250,53 +250,76 @@ public class CommentPublicQueryServiceImpl implements CommentPublicQueryService .map(OwnerInfo::from); } - private Mono fixedCommentFieldSelector(@Nullable Ref ref) { - return Mono.fromSupplier( - () -> { - var baseQuery = isNull("metadata.deletionTimestamp"); - if (ref != null) { - baseQuery = - and(baseQuery, - equal("spec.subjectRef", Comment.toSubjectRefKey(ref))); + private Mono populateCommentListOptions(@Nullable Ref ref) { + return populateVisibleListOptions(null) + .doOnNext(builder -> { + if (ref != null) { + builder.andQuery( + equal("spec.subjectRef", Comment.toSubjectRefKey(ref))); + } + }) + .map(ListOptions.ListOptionsBuilder::build); + } + + private Mono populateVisibleListOptions( + @Nullable Comment comment) { + return ReactiveSecurityContextHolder.getContext() + .map(SecurityContext::getAuthentication) + .map(Authentication::getName) + .defaultIfEmpty(AnonymousUserConst.PRINCIPAL) + .zipWith(userService.hasSufficientRoles(Set.of(COMMENT_VIEW_PERMISSION)) + .defaultIfEmpty(false)) + .flatMap(tuple2 -> { + var username = tuple2.getT1(); + var hasViewPermission = tuple2.getT2(); + var commentHidden = false; + var isCommentOwner = false; + if (comment != null) { + commentHidden = Boolean.TRUE.equals(comment.getSpec().getHidden()); + var owner = comment.getSpec().getOwner(); + isCommentOwner = owner != null && Objects.equals( + ownerIdentity(owner.getKind(), owner.getName()), + ownerIdentity(User.KIND, username) + ); + boolean hasPermission = + (!commentHidden) || (hasViewPermission || isCommentOwner); + if (ExtensionUtil.isDeleted(comment) || !hasPermission) { + return Mono.error(new ServerWebInputException( + "The comment was not found, hidden or deleted." + )); } - return baseQuery; - }) - .flatMap(this::concatVisibleQuery) - .map(FieldSelector::of); + } + + var builder = ListOptions.builder(); + builder.andQuery(isNull("metadata.deletionTimestamp")); + var visibleQuery = and( + equal("spec.hidden", BooleanUtils.FALSE), + equal("spec.approved", BooleanUtils.TRUE) + ); + + var isAnonymous = AnonymousUserConst.isAnonymousUser(username); + if (isAnonymous) { + builder.andQuery(visibleQuery); + } else if (!(hasViewPermission || (commentHidden && isCommentOwner))) { + builder.andQuery(or( + equal("spec.owner", ownerIdentity(User.KIND, username)), + visibleQuery + )); + } + // View all replies if the user is not an anonymous user, has view permission + // or is the comment owner. + return Mono.just(builder); + }); } - private Mono concatVisibleQuery(Query query) { - Assert.notNull(query, "The query must not be null"); - var approvedQuery = and( - equal("spec.approved", BooleanUtils.TRUE), - equal("spec.hidden", BooleanUtils.FALSE) - ); - // we should list all comments that the user owns - return getCurrentUserWithoutAnonymous() - .map(username -> or(approvedQuery, equal("spec.owner", - Comment.CommentOwner.ownerIdentity(User.KIND, username))) - ) - .defaultIfEmpty(approvedQuery) - .map(compositeQuery -> and(query, compositeQuery)); - } - - private Mono fixedReplyFieldSelector(String commentName) { - Assert.notNull(commentName, "The commentName must not be null"); + private Mono populateReplyListOptions(Comment comment) { // The comment name must be equal to the comment name of the reply // is approved and not hidden - return Mono.fromSupplier(() -> and( - equal("spec.commentName", commentName), - isNull("metadata.deletionTimestamp") - )) - .flatMap(this::concatVisibleQuery) - .map(FieldSelector::of); - } - - Mono getCurrentUserWithoutAnonymous() { - return ReactiveSecurityContextHolder.getContext() - .mapNotNull(SecurityContext::getAuthentication) - .map(Principal::getName) - .filter(username -> !AnonymousUserConst.PRINCIPAL.equals(username)); + return populateVisibleListOptions(comment) + .doOnNext(builder -> + builder.andQuery(equal("spec.commentName", comment.getMetadata().getName())) + ) + .map(ListOptions.ListOptionsBuilder::build); } static Sort defaultCommentSort() { diff --git a/application/src/test/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceIntegrationTest.java b/application/src/test/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceIntegrationTest.java index 5413e62b4..e585330f7 100644 --- a/application/src/test/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceIntegrationTest.java +++ b/application/src/test/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceIntegrationTest.java @@ -35,6 +35,7 @@ import run.halo.app.extension.SchemeManager; import run.halo.app.extension.index.IndexerFactory; import run.halo.app.extension.store.ReactiveExtensionStoreClient; import run.halo.app.infra.AnonymousUserConst; +import run.halo.app.infra.exception.DuplicateNameException; import run.halo.app.infra.utils.JsonUtils; @DirtiesContext @@ -288,11 +289,19 @@ class CommentPublicQueryServiceIntegrationTest { @Nested class ListReplyTest { private final List storedReplies = mockRelies(); + @Autowired private CommentPublicQueryServiceImpl commentPublicQueryService; @BeforeEach void setUp() { + // create comment + var comment = createComment(); + client.create(comment) + .onErrorResume(DuplicateNameException.class, e -> Mono.just(comment)) + .as(StepVerifier::create) + .expectNextCount(1) + .verifyComplete(); Flux.fromIterable(storedReplies) .flatMap(reply -> client.create(reply)) .as(StepVerifier::create) diff --git a/ui/console-src/modules/contents/comments/components/CommentDetailModal.vue b/ui/console-src/modules/contents/comments/components/CommentDetailModal.vue index db3cae160..829a90599 100644 --- a/ui/console-src/modules/contents/comments/components/CommentDetailModal.vue +++ b/ui/console-src/modules/contents/comments/components/CommentDetailModal.vue @@ -14,6 +14,7 @@ import { VDescriptionItem, VModal, VSpace, + VTag, } from "@halo-dev/components"; import { useQueryClient } from "@tanstack/vue-query"; import { useUserAgent } from "@uc/modules/profile/tabs/composables/use-user-agent"; @@ -177,6 +178,11 @@ const { data: contentProvider } = useContentProviderExtensionPoint(); +
+ + {{ $t("core.comment.list.fields.private") }} + +
+ + {{ $t("core.comment.list.fields.private") }} + {{ $t("core.comment.text.commented_on") }} diff --git a/ui/console-src/modules/contents/comments/components/ReplyDetailModal.vue b/ui/console-src/modules/contents/comments/components/ReplyDetailModal.vue index e2fd4ab83..e3442063a 100644 --- a/ui/console-src/modules/contents/comments/components/ReplyDetailModal.vue +++ b/ui/console-src/modules/contents/comments/components/ReplyDetailModal.vue @@ -15,6 +15,7 @@ import { VDescriptionItem, VModal, VSpace, + VTag, } from "@halo-dev/components"; import { useQueryClient } from "@tanstack/vue-query"; import { useUserAgent } from "@uc/modules/profile/tabs/composables/use-user-agent"; @@ -185,17 +186,25 @@ const { data: contentProvider } = useContentProviderExtensionPoint(); - -
- +
+ + + {{ $t("core.comment.list.fields.private") }} +
+ +
+ + {{ $t("core.comment.list.fields.private") }} + +
+ + {{ $t("core.comment.list.fields.private") }} + {{ $t("core.comment.text.replied_below") }} diff --git a/ui/packages/api-client/src/api/post-v1alpha1-console-api.ts b/ui/packages/api-client/src/api/post-v1alpha1-console-api.ts index 120bd46c7..37c4a565b 100644 --- a/ui/packages/api-client/src/api/post-v1alpha1-console-api.ts +++ b/ui/packages/api-client/src/api/post-v1alpha1-console-api.ts @@ -520,7 +520,7 @@ export const PostV1alpha1ConsoleApiAxiosParamCreator = function (configuration?: }; }, /** - * Publish a post. + * UnPublish a post. * @param {string} name * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -797,7 +797,7 @@ export const PostV1alpha1ConsoleApiFp = function(configuration?: Configuration) return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, /** - * Publish a post. + * UnPublish a post. * @param {string} name * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -935,7 +935,7 @@ export const PostV1alpha1ConsoleApiFactory = function (configuration?: Configura return localVarFp.revertToSpecifiedSnapshotForPost(requestParameters.name, requestParameters.revertSnapshotForPostParam, options).then((request) => request(axios, basePath)); }, /** - * Publish a post. + * UnPublish a post. * @param {PostV1alpha1ConsoleApiUnpublishPostRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1362,7 +1362,7 @@ export class PostV1alpha1ConsoleApi extends BaseAPI { } /** - * Publish a post. + * UnPublish a post. * @param {PostV1alpha1ConsoleApiUnpublishPostRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} diff --git a/ui/packages/api-client/src/models/comment-request.ts b/ui/packages/api-client/src/models/comment-request.ts index 3e0e9ed76..096e1cd37 100644 --- a/ui/packages/api-client/src/models/comment-request.ts +++ b/ui/packages/api-client/src/models/comment-request.ts @@ -38,6 +38,12 @@ export interface CommentRequest { * @memberof CommentRequest */ 'content': string; + /** + * + * @type {boolean} + * @memberof CommentRequest + */ + 'hidden'?: boolean; /** * * @type {CommentEmailOwner} diff --git a/ui/packages/api-client/src/models/reply-request.ts b/ui/packages/api-client/src/models/reply-request.ts index 74e561b08..debf53743 100644 --- a/ui/packages/api-client/src/models/reply-request.ts +++ b/ui/packages/api-client/src/models/reply-request.ts @@ -35,6 +35,12 @@ export interface ReplyRequest { * @memberof ReplyRequest */ 'content': string; + /** + * + * @type {boolean} + * @memberof ReplyRequest + */ + 'hidden'?: boolean; /** * * @type {CommentEmailOwner} diff --git a/ui/src/locales/_missing_translations_es.yaml b/ui/src/locales/_missing_translations_es.yaml index b5a6b07d8..69fb9de14 100644 --- a/ui/src/locales/_missing_translations_es.yaml +++ b/ui/src/locales/_missing_translations_es.yaml @@ -197,6 +197,13 @@ core: button: Reply and approve cancel_approve: button: Cancel approve + list: + fields: + private: Private + reply_modal: + fields: + hidden: + label: Private reply detail_modal: fields: owner: Commentator diff --git a/ui/src/locales/en.yaml b/ui/src/locales/en.yaml index 232816c7a..b2dcf8490 100644 --- a/ui/src/locales/en.yaml +++ b/ui/src/locales/en.yaml @@ -595,6 +595,7 @@ core: reply_count: "{count} Replies" has_new_replies: New replies pending_review: Pending review + private: Private subject_refs: post: Post page: Page diff --git a/ui/src/locales/zh-CN.yaml b/ui/src/locales/zh-CN.yaml index 9da3e4f95..a8ab8580d 100644 --- a/ui/src/locales/zh-CN.yaml +++ b/ui/src/locales/zh-CN.yaml @@ -561,6 +561,7 @@ core: reply_count: "{count} 条回复" has_new_replies: 有新的回复 pending_review: 待审核 + private: 私密 subject_refs: post: 文章 page: 页面 diff --git a/ui/src/locales/zh-TW.yaml b/ui/src/locales/zh-TW.yaml index 1fd5dbb4a..4ebbd2ed4 100644 --- a/ui/src/locales/zh-TW.yaml +++ b/ui/src/locales/zh-TW.yaml @@ -546,6 +546,7 @@ core: reply_count: "{count} 條回覆" has_new_replies: 有新的回覆 pending_review: 待審核 + private: 私密 subject_refs: post: 文章 page: 頁面